aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Barron <barronpm@gmail.com>2020-05-15 17:20:07 -0400
committerPatrick Barron <barronpm@gmail.com>2020-05-15 17:20:07 -0400
commitaca7e221d811040bdb14da6390bbd16c5f7db785 (patch)
tree87c77521066511890e236a97afb27a475f32c0a3
parentb7621d762c58320b35096838ae8ab10d98ea9bb2 (diff)
parent652e9702fc67784fd11e26ee159f31e6f0ba9dd7 (diff)
Merge branch 'master' into userdb-efcore
# Conflicts: # Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs # Emby.Server.Implementations/ApplicationHost.cs # Emby.Server.Implementations/Devices/DeviceManager.cs # Jellyfin.Server/Jellyfin.Server.csproj # Jellyfin.Server/Migrations/MigrationRunner.cs # MediaBrowser.Controller/Devices/IDeviceManager.cs
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs5
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs2
-rw-r--r--Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs110
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs119
-rw-r--r--Emby.Server.Implementations/Browser/BrowserLauncher.cs8
-rw-r--r--Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs6
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs290
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj3
-rw-r--r--Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs4
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs222
-rw-r--r--Emby.Server.Implementations/HttpServer/IHttpListener.cs39
-rw-r--r--Emby.Server.Implementations/HttpServer/ResponseFilter.cs19
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs279
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/bn.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/he.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/nb.json6
-rw-r--r--Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs39
-rw-r--r--Emby.Server.Implementations/Net/IWebSocket.cs48
-rw-r--r--Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs29
-rw-r--r--Emby.Server.Implementations/Session/HttpSessionController.cs191
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs15
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs38
-rw-r--r--Emby.Server.Implementations/Session/WebSocketController.cs86
-rw-r--r--Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs105
-rw-r--r--Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs135
-rw-r--r--Emby.Server.Implementations/WebSockets/WebSocketHandler.cs10
-rw-r--r--Emby.Server.Implementations/WebSockets/WebSocketManager.cs102
-rw-r--r--Jellyfin.Data/Entities/ActivityLog.cs119
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj6
-rw-r--r--Jellyfin.Server.Implementations/Activity/ActivityManager.cs17
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj8
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDb.cs5
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDbProvider.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs (renamed from Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.Designer.cs)9
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.cs (renamed from Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.cs)10
-rw-r--r--Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs3
-rw-r--r--Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs4
-rw-r--r--Jellyfin.Server/CoreAppHost.cs14
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj8
-rw-r--r--Jellyfin.Server/Migrations/MigrationRunner.cs1
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs87
-rw-r--r--Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs79
-rw-r--r--Jellyfin.Server/Program.cs7
-rw-r--r--Jellyfin.Server/Startup.cs1
-rw-r--r--MediaBrowser.Api/BaseApiService.cs4
-rw-r--r--MediaBrowser.Api/Devices/DeviceService.cs36
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs10
-rw-r--r--MediaBrowser.Api/Movies/MoviesService.cs2
-rw-r--r--MediaBrowser.Api/Movies/TrailersService.cs12
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs2
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs2
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs2
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs2
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs2
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs2
-rw-r--r--MediaBrowser.Api/Playback/UniversalAudioService.cs9
-rw-r--r--MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs43
-rw-r--r--MediaBrowser.Api/System/ActivityLogService.cs7
-rw-r--r--MediaBrowser.Api/System/ActivityLogWebSocketListener.cs16
-rw-r--r--MediaBrowser.Api/UserLibrary/ArtistsService.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs2
-rw-r--r--MediaBrowser.Controller/Devices/IDeviceManager.cs23
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs57
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs12
-rw-r--r--MediaBrowser.Controller/Net/IHttpServer.cs27
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketConnection.cs41
-rw-r--r--MediaBrowser.Controller/Session/ISessionController.cs3
-rw-r--r--MediaBrowser.Controller/Session/SessionInfo.cs74
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs15
-rw-r--r--MediaBrowser.Model/Activity/IActivityManager.cs3
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs32
-rw-r--r--MediaBrowser.Model/Devices/DeviceOptions.cs9
-rw-r--r--MediaBrowser.Model/Devices/DevicesOptions.cs23
-rw-r--r--MediaBrowser.Model/Net/WebSocketMessage.cs8
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs18
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs24
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs15
-rw-r--r--MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs74
-rw-r--r--MediaBrowser.Providers/Tmdb/TmdbUtils.cs35
-rw-r--r--MediaBrowser.sln4
87 files changed, 986 insertions, 1992 deletions
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index ff37407a2..db786833f 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -914,7 +914,8 @@ namespace Emby.Dlna.PlayTo
return 0;
}
- public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
{
if (_disposed)
{
@@ -930,10 +931,12 @@ namespace Emby.Dlna.PlayTo
{
return SendPlayCommand(data as PlayRequest, cancellationToken);
}
+
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
{
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
}
+
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
{
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 7f755fd25..948fe037b 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -227,7 +227,7 @@ namespace Emby.Naming.Video
}
return remainingFiles
- .Where(i => i.ExtraType == null)
+ .Where(i => i.ExtraType != null)
.Where(i => baseNames.Any(b =>
i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
.ToList();
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
index 3d3859078..31d9609a6 100644
--- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
+++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
@@ -8,7 +8,6 @@ using Jellyfin.Data.Entities;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
@@ -30,7 +29,7 @@ namespace Emby.Server.Implementations.Activity
/// </summary>
public sealed class ActivityLogEntryPoint : IServerEntryPoint
{
- private readonly ILogger _logger;
+ private readonly ILogger<ActivityLogEntryPoint> _logger;
private readonly IInstallationManager _installationManager;
private readonly ISessionManager _sessionManager;
private readonly ITaskManager _taskManager;
@@ -38,14 +37,12 @@ namespace Emby.Server.Implementations.Activity
private readonly ILocalizationManager _localization;
private readonly ISubtitleManager _subManager;
private readonly IUserManager _userManager;
- private readonly IDeviceManager _deviceManager;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="sessionManager">The session manager.</param>
- /// <param name="deviceManager">The device manager.</param>
/// <param name="taskManager">The task manager.</param>
/// <param name="activityManager">The activity manager.</param>
/// <param name="localization">The localization manager.</param>
@@ -55,7 +52,6 @@ namespace Emby.Server.Implementations.Activity
public ActivityLogEntryPoint(
ILogger<ActivityLogEntryPoint> logger,
ISessionManager sessionManager,
- IDeviceManager deviceManager,
ITaskManager taskManager,
IActivityManager activityManager,
ILocalizationManager localization,
@@ -65,7 +61,6 @@ namespace Emby.Server.Implementations.Activity
{
_logger = logger;
_sessionManager = sessionManager;
- _deviceManager = deviceManager;
_taskManager = taskManager;
_activityManager = activityManager;
_localization = localization;
@@ -98,36 +93,18 @@ namespace Emby.Server.Implementations.Activity
_userManager.OnUserDeleted += OnUserDeleted;
_userManager.OnUserLockedOut += OnUserLockedOut;
- _deviceManager.CameraImageUploaded += OnCameraImageUploaded;
-
return Task.CompletedTask;
}
- private async void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
- {
- await CreateLogEntry(new ActivityLog(
- string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("CameraImageUploadedFrom"),
- e.Argument.Device.Name),
- NotificationType.CameraImageUploaded.ToString(),
- Guid.Empty,
- DateTime.UtcNow,
- LogLevel.Trace))
- .ConfigureAwait(false);
- }
-
private async void OnUserLockedOut(object sender, GenericEventArgs<User> e)
{
await CreateLogEntry(new ActivityLog(
- string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("UserLockedOutWithName"),
- e.Argument.Username),
- NotificationType.UserLockedOut.ToString(),
- e.Argument.Id,
- DateTime.UtcNow,
- LogLevel.Trace))
+ string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("UserLockedOutWithName"),
+ e.Argument.Username),
+ NotificationType.UserLockedOut.ToString(),
+ e.Argument.Id))
.ConfigureAwait(false);
}
@@ -138,11 +115,9 @@ namespace Emby.Server.Implementations.Activity
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
e.Provider,
- Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)),
+ Notifications.NotificationEntryPoint.GetItemName(e.Item)),
"SubtitleDownloadFailure",
- Guid.Empty,
- DateTime.UtcNow,
- LogLevel.Trace)
+ Guid.Empty)
{
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
ShortOverview = e.Exception.Message
@@ -180,9 +155,7 @@ namespace Emby.Server.Implementations.Activity
GetItemName(item),
e.DeviceName),
GetPlaybackStoppedNotificationType(item.MediaType),
- user.Id,
- DateTime.UtcNow,
- LogLevel.Trace))
+ user.Id))
.ConfigureAwait(false);
}
@@ -217,9 +190,7 @@ namespace Emby.Server.Implementations.Activity
GetItemName(item),
e.DeviceName),
GetPlaybackNotificationType(item.MediaType),
- user.Id,
- DateTime.UtcNow,
- LogLevel.Trace))
+ user.Id))
.ConfigureAwait(false);
}
@@ -286,9 +257,7 @@ namespace Emby.Server.Implementations.Activity
session.UserName,
session.DeviceName),
"SessionEnded",
- session.UserId,
- DateTime.UtcNow,
- LogLevel.Trace)
+ session.UserId)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -307,9 +276,7 @@ namespace Emby.Server.Implementations.Activity
_localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
user.Name),
"AuthenticationSucceeded",
- user.Id,
- DateTime.UtcNow,
- LogLevel.Trace)
+ user.Id)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -326,10 +293,9 @@ namespace Emby.Server.Implementations.Activity
_localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
e.Argument.Username),
"AuthenticationFailed",
- Guid.Empty,
- DateTime.UtcNow,
- LogLevel.Error)
+ Guid.Empty)
{
+ LogSeverity = LogLevel.Error,
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"),
@@ -345,9 +311,7 @@ namespace Emby.Server.Implementations.Activity
_localization.GetLocalizedString("UserPolicyUpdatedWithName"),
e.Argument.Username),
"UserPolicyUpdated",
- e.Argument.Id,
- DateTime.UtcNow,
- LogLevel.Trace))
+ e.Argument.Id))
.ConfigureAwait(false);
}
@@ -359,9 +323,7 @@ namespace Emby.Server.Implementations.Activity
_localization.GetLocalizedString("UserDeletedWithName"),
e.Argument.Username),
"UserDeleted",
- Guid.Empty,
- DateTime.UtcNow,
- LogLevel.Trace))
+ Guid.Empty))
.ConfigureAwait(false);
}
@@ -373,9 +335,8 @@ namespace Emby.Server.Implementations.Activity
_localization.GetLocalizedString("UserPasswordChangedWithName"),
e.Argument.Username),
"UserPasswordChanged",
- e.Argument.Id,
- DateTime.UtcNow,
- LogLevel.Trace)).ConfigureAwait(false);
+ e.Argument.Id))
+ .ConfigureAwait(false);
}
private async void OnUserCreated(object sender, GenericEventArgs<User> e)
@@ -386,9 +347,7 @@ namespace Emby.Server.Implementations.Activity
_localization.GetLocalizedString("UserCreatedWithName"),
e.Argument.Username),
"UserCreated",
- e.Argument.Id,
- DateTime.UtcNow,
- LogLevel.Trace))
+ e.Argument.Id))
.ConfigureAwait(false);
}
@@ -408,9 +367,7 @@ namespace Emby.Server.Implementations.Activity
session.UserName,
session.DeviceName),
"SessionStarted",
- session.UserId,
- DateTime.UtcNow,
- LogLevel.Trace)
+ session.UserId)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -427,9 +384,7 @@ namespace Emby.Server.Implementations.Activity
_localization.GetLocalizedString("PluginUpdatedWithName"),
e.Argument.Item1.Name),
NotificationType.PluginUpdateInstalled.ToString(),
- Guid.Empty,
- DateTime.UtcNow,
- LogLevel.Trace)
+ Guid.Empty)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -447,9 +402,7 @@ namespace Emby.Server.Implementations.Activity
_localization.GetLocalizedString("PluginUninstalledWithName"),
e.Argument.Name),
NotificationType.PluginUninstalled.ToString(),
- Guid.Empty,
- DateTime.UtcNow,
- LogLevel.Trace))
+ Guid.Empty))
.ConfigureAwait(false);
}
@@ -461,9 +414,7 @@ namespace Emby.Server.Implementations.Activity
_localization.GetLocalizedString("PluginInstalledWithName"),
e.Argument.name),
NotificationType.PluginInstalled.ToString(),
- Guid.Empty,
- DateTime.UtcNow,
- LogLevel.Trace)
+ Guid.Empty)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -482,9 +433,7 @@ namespace Emby.Server.Implementations.Activity
_localization.GetLocalizedString("NameInstallFailed"),
installationInfo.Name),
NotificationType.InstallationFailed.ToString(),
- Guid.Empty,
- DateTime.UtcNow,
- LogLevel.Trace)
+ Guid.Empty)
{
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -528,10 +477,9 @@ namespace Emby.Server.Implementations.Activity
await CreateLogEntry(new ActivityLog(
string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
NotificationType.TaskFailed.ToString(),
- Guid.Empty,
- DateTime.UtcNow,
- LogLevel.Error)
+ Guid.Empty)
{
+ LogSeverity = LogLevel.Error,
Overview = string.Join(Environment.NewLine, vals),
ShortOverview = runningTime
}).ConfigureAwait(false);
@@ -565,8 +513,6 @@ namespace Emby.Server.Implementations.Activity
_userManager.OnUserPasswordChanged -= OnUserPasswordChanged;
_userManager.OnUserDeleted -= OnUserDeleted;
_userManager.OnUserLockedOut -= OnUserLockedOut;
-
- _deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
}
/// <summary>
@@ -586,7 +532,7 @@ namespace Emby.Server.Implementations.Activity
{
int years = days / DaysInYear;
values.Add(CreateValueString(years, "year"));
- days = days % DaysInYear;
+ days %= DaysInYear;
}
// Number of months
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 06cc51c42..b2fe6a3ab 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -43,12 +43,8 @@ using Emby.Server.Implementations.Security;
using Emby.Server.Implementations.Serialization;
using Emby.Server.Implementations.Services;
using Emby.Server.Implementations.Session;
-using Emby.Server.Implementations.SocketSharp;
using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates;
-using Jellyfin.Server.Implementations;
-using Jellyfin.Server.Implementations.Activity;
-using Jellyfin.Server.Implementations.User;
using MediaBrowser.Api;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
@@ -84,7 +80,6 @@ using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo;
-using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dlna;
@@ -103,12 +98,10 @@ using MediaBrowser.Providers.Subtitles;
using MediaBrowser.WebDashboard.Api;
using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Http.Extensions;
-using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
using Prometheus.DotNetRuntime;
+using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Server.Implementations
{
@@ -505,32 +498,8 @@ namespace Emby.Server.Implementations
RegisterServices(serviceCollection);
}
- public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
- {
- if (!context.WebSockets.IsWebSocketRequest)
- {
- await next().ConfigureAwait(false);
- return;
- }
-
- await _httpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
- }
-
- public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
- {
- if (context.WebSockets.IsWebSocketRequest)
- {
- await next().ConfigureAwait(false);
- return;
- }
-
- var request = context.Request;
- var response = context.Response;
- var localPath = context.Request.Path.ToString();
-
- var req = new WebSocketSharpRequest(request, response, request.Path, LoggerFactory.CreateLogger<WebSocketSharpRequest>());
- await _httpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
- }
+ public Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
+ => _httpServer.RequestHandler(context);
/// <summary>
/// Registers services/resources with the service collection that will be available via DI.
@@ -548,20 +517,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IJsonSerializer, JsonSerializer>();
- // TODO: Remove support for injecting ILogger completely
- serviceCollection.AddSingleton((provider) =>
- {
- Logger.LogWarning("Injecting ILogger directly is deprecated and should be replaced with ILogger<T>");
- return Logger;
- });
-
- // TODO: properly set up scoping and switch to AddDbContextPool
- serviceCollection.AddDbContext<JellyfinDb>(
- options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
- ServiceLifetime.Transient);
-
- serviceCollection.AddSingleton<JellyfinDbProvider>();
-
serviceCollection.AddSingleton(_fileSystemManager);
serviceCollection.AddSingleton<TvdbClientManager>();
@@ -599,6 +554,8 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
+ serviceCollection.AddSingleton<IDisplayPreferencesRepository, SqliteDisplayPreferencesRepository>();
+
serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
@@ -626,7 +583,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<ISearchEngine, SearchEngine>();
serviceCollection.AddSingleton<ServiceController>();
- serviceCollection.AddSingleton<IHttpListener, WebSocketSharpListener>();
serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>();
serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
@@ -668,8 +624,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
- serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
-
serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
serviceCollection.AddSingleton<ISessionContext, SessionContext>();
@@ -697,6 +651,7 @@ namespace Emby.Server.Implementations
_httpServer = Resolve<IHttpServer>();
_httpClient = Resolve<IHttpClient>();
+ ((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
SetStaticProperties();
@@ -1147,9 +1102,6 @@ namespace Emby.Server.Implementations
ItemsByNamePath = ApplicationPaths.InternalMetadataPath,
InternalMetadataPath = ApplicationPaths.InternalMetadataPath,
CachePath = ApplicationPaths.CachePath,
- HttpServerPortNumber = HttpPort,
- SupportsHttps = SupportsHttps,
- HttpsPortNumber = HttpsPort,
OperatingSystem = OperatingSystem.Id.ToString(),
OperatingSystemDisplayName = OperatingSystem.Name,
CanSelfRestart = CanSelfRestart,
@@ -1185,23 +1137,22 @@ namespace Emby.Server.Implementations
};
}
- public bool EnableHttps => SupportsHttps && ServerConfigurationManager.Configuration.EnableHttps;
-
- public bool SupportsHttps => Certificate != null || ServerConfigurationManager.Configuration.IsBehindProxy;
+ /// <inheritdoc/>
+ public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps;
- public async Task<string> GetLocalApiUrl(CancellationToken cancellationToken, bool forceHttp = false)
+ /// <inheritdoc/>
+ public async Task<string> GetLocalApiUrl(CancellationToken cancellationToken)
{
try
{
// Return the first matched address, if found, or the first known local address
var addresses = await GetLocalIpAddressesInternal(false, 1, cancellationToken).ConfigureAwait(false);
-
- foreach (var address in addresses)
+ if (addresses.Count == 0)
{
- return GetLocalApiUrl(address, forceHttp);
+ return null;
}
- return null;
+ return GetLocalApiUrl(addresses.First());
}
catch (Exception ex)
{
@@ -1228,7 +1179,7 @@ namespace Emby.Server.Implementations
}
/// <inheritdoc />
- public string GetLocalApiUrl(IPAddress ipAddress, bool forceHttp = false)
+ public string GetLocalApiUrl(IPAddress ipAddress)
{
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
{
@@ -1238,29 +1189,30 @@ namespace Emby.Server.Implementations
str.CopyTo(span.Slice(1));
span[^1] = ']';
- return GetLocalApiUrl(span, forceHttp);
+ return GetLocalApiUrl(span);
}
- return GetLocalApiUrl(ipAddress.ToString(), forceHttp);
+ return GetLocalApiUrl(ipAddress.ToString());
}
- /// <inheritdoc />
- public string GetLocalApiUrl(ReadOnlySpan<char> host, bool forceHttp = false)
+ /// <inheritdoc/>
+ public string GetLoopbackHttpApiUrl()
{
- var url = new StringBuilder(64);
- bool useHttps = EnableHttps && !forceHttp;
- url.Append(useHttps ? "https://" : "http://")
- .Append(host)
- .Append(':')
- .Append(useHttps ? HttpsPort : HttpPort);
-
- string baseUrl = ServerConfigurationManager.Configuration.BaseUrl;
- if (baseUrl.Length != 0)
- {
- url.Append(baseUrl);
- }
+ return GetLocalApiUrl("127.0.0.1", Uri.UriSchemeHttp, HttpPort);
+ }
- return url.ToString();
+ /// <inheritdoc/>
+ public string GetLocalApiUrl(ReadOnlySpan<char> host, string scheme = null, int? port = null)
+ {
+ // NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
+ // not. For consistency, always trim the trailing slash.
+ return new UriBuilder
+ {
+ Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
+ Host = host.ToString(),
+ Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort),
+ Path = ServerConfigurationManager.Configuration.BaseUrl
+ }.ToString().TrimEnd('/');
}
public Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken)
@@ -1294,7 +1246,7 @@ namespace Emby.Server.Implementations
}
}
- var valid = await IsIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false);
+ var valid = await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false);
if (valid)
{
resultList.Add(address);
@@ -1328,7 +1280,7 @@ namespace Emby.Server.Implementations
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
- private async Task<bool> IsIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken)
+ private async Task<bool> IsLocalIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken)
{
if (address.Equals(IPAddress.Loopback)
|| address.Equals(IPAddress.IPv6Loopback))
@@ -1336,8 +1288,7 @@ namespace Emby.Server.Implementations
return true;
}
- var apiUrl = GetLocalApiUrl(address);
- apiUrl += "/system/ping";
+ var apiUrl = GetLocalApiUrl(address) + "/system/ping";
if (_validAddressResults.TryGetValue(apiUrl, out var cachedResult))
{
diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs
index 96096e142..7f7c6a0be 100644
--- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs
+++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs
@@ -31,18 +31,18 @@ namespace Emby.Server.Implementations.Browser
/// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored.
/// </summary>
/// <param name="appHost">The application host.</param>
- /// <param name="url">The URL.</param>
- private static void TryOpenUrl(IServerApplicationHost appHost, string url)
+ /// <param name="relativeUrl">The URL to open, relative to the server base URL.</param>
+ private static void TryOpenUrl(IServerApplicationHost appHost, string relativeUrl)
{
try
{
string baseUrl = appHost.GetLocalApiUrl("localhost");
- appHost.LaunchUrl(baseUrl + url);
+ appHost.LaunchUrl(baseUrl + relativeUrl);
}
catch (Exception ex)
{
var logger = appHost.Resolve<ILogger>();
- logger?.LogError(ex, "Failed to open browser window with URL {URL}", url);
+ logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
}
}
}
diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
index a6eaf2d0a..305e67e8c 100644
--- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
+++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -193,12 +193,6 @@ namespace Emby.Server.Implementations.Configuration
changed = true;
}
- if (!config.CameraUploadUpgraded)
- {
- config.CameraUploadUpgraded = true;
- changed = true;
- }
-
if (!config.CollectionsUpgraded)
{
config.CollectionsUpgraded = true;
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
index d6bce93f8..2d44a7c28 100644
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ b/Emby.Server.Implementations/Devices/DeviceManager.cs
@@ -7,26 +7,19 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Configuration;
+using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Security;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Devices;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Session;
using MediaBrowser.Model.Users;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Devices
{
@@ -34,38 +27,23 @@ namespace Emby.Server.Implementations.Devices
{
private readonly IJsonSerializer _json;
private readonly IUserManager _userManager;
- private readonly IFileSystem _fileSystem;
- private readonly ILibraryMonitor _libraryMonitor;
private readonly IServerConfigurationManager _config;
- private readonly ILibraryManager _libraryManager;
- private readonly ILocalizationManager _localizationManager;
private readonly IAuthenticationRepository _authRepo;
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
- public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
-
- private readonly object _cameraUploadSyncLock = new object();
private readonly object _capabilitiesSyncLock = new object();
public DeviceManager(
IAuthenticationRepository authRepo,
IJsonSerializer json,
- ILibraryManager libraryManager,
- ILocalizationManager localizationManager,
IUserManager userManager,
- IFileSystem fileSystem,
- ILibraryMonitor libraryMonitor,
IServerConfigurationManager config)
{
_json = json;
_userManager = userManager;
- _fileSystem = fileSystem;
- _libraryMonitor = libraryMonitor;
_config = config;
- _libraryManager = libraryManager;
- _localizationManager = localizationManager;
_authRepo = authRepo;
_capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
}
@@ -195,173 +173,7 @@ namespace Emby.Server.Implementations.Devices
return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture));
}
- public ContentUploadHistory GetCameraUploadHistory(string deviceId)
- {
- var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
-
- lock (_cameraUploadSyncLock)
- {
- try
- {
- return _json.DeserializeFromFile<ContentUploadHistory>(path);
- }
- catch (IOException)
- {
- return new ContentUploadHistory
- {
- DeviceId = deviceId
- };
- }
- }
- }
-
- public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file)
- {
- var device = GetDevice(deviceId, false);
- var uploadPathInfo = GetUploadPath(device);
-
- var path = uploadPathInfo.Item1;
-
- if (!string.IsNullOrWhiteSpace(file.Album))
- {
- path = Path.Combine(path, _fileSystem.GetValidFilename(file.Album));
- }
-
- path = Path.Combine(path, file.Name);
- path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg");
-
- Directory.CreateDirectory(Path.GetDirectoryName(path));
-
- await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false);
-
- _libraryMonitor.ReportFileSystemChangeBeginning(path);
-
- try
- {
- using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
- {
- await stream.CopyToAsync(fs).ConfigureAwait(false);
- }
-
- AddCameraUpload(deviceId, file);
- }
- finally
- {
- _libraryMonitor.ReportFileSystemChangeComplete(path, true);
- }
-
- if (CameraImageUploaded != null)
- {
- CameraImageUploaded?.Invoke(this, new GenericEventArgs<CameraImageUploadInfo>
- {
- Argument = new CameraImageUploadInfo
- {
- Device = device,
- FileInfo = file
- }
- });
- }
- }
-
- private void AddCameraUpload(string deviceId, LocalFileInfo file)
- {
- var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
- Directory.CreateDirectory(Path.GetDirectoryName(path));
-
- lock (_cameraUploadSyncLock)
- {
- ContentUploadHistory history;
-
- try
- {
- history = _json.DeserializeFromFile<ContentUploadHistory>(path);
- }
- catch (IOException)
- {
- history = new ContentUploadHistory
- {
- DeviceId = deviceId
- };
- }
-
- history.DeviceId = deviceId;
-
- var list = history.FilesUploaded.ToList();
- list.Add(file);
- history.FilesUploaded = list.ToArray();
-
- _json.SerializeToFile(history, path);
- }
- }
-
- internal Task EnsureLibraryFolder(string path, string name)
- {
- var existingFolders = _libraryManager
- .RootFolder
- .Children
- .OfType<Folder>()
- .Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path))
- .ToList();
-
- if (existingFolders.Count > 0)
- {
- return Task.CompletedTask;
- }
-
- Directory.CreateDirectory(path);
-
- var libraryOptions = new LibraryOptions
- {
- PathInfos = new[] { new MediaPathInfo { Path = path } },
- EnablePhotos = true,
- EnableRealtimeMonitor = false,
- SaveLocalMetadata = true
- };
-
- if (string.IsNullOrWhiteSpace(name))
- {
- name = _localizationManager.GetLocalizedString("HeaderCameraUploads");
- }
-
- return _libraryManager.AddVirtualFolder(name, CollectionType.HomeVideos, libraryOptions, true);
- }
-
- private Tuple<string, string, string> GetUploadPath(DeviceInfo device)
- {
- var config = _config.GetUploadOptions();
- var path = config.CameraUploadPath;
-
- if (string.IsNullOrWhiteSpace(path))
- {
- path = DefaultCameraUploadsPath;
- }
-
- var topLibraryPath = path;
-
- if (config.EnableCameraUploadSubfolders)
- {
- path = Path.Combine(path, _fileSystem.GetValidFilename(device.Name));
- }
-
- return new Tuple<string, string, string>(path, topLibraryPath, null);
- }
-
- internal string GetUploadsPath()
- {
- var config = _config.GetUploadOptions();
- var path = config.CameraUploadPath;
-
- if (string.IsNullOrWhiteSpace(path))
- {
- path = DefaultCameraUploadsPath;
- }
-
- return path;
- }
-
- private string DefaultCameraUploadsPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads");
-
- public bool CanAccessDevice(Jellyfin.Data.Entities.User user, string deviceId)
+ public bool CanAccessDevice(User user, string deviceId)
{
if (user == null)
{
@@ -391,102 +203,4 @@ namespace Emby.Server.Implementations.Devices
return true;
}
}
-
- public class DeviceManagerEntryPoint : IServerEntryPoint
- {
- private readonly DeviceManager _deviceManager;
- private readonly IServerConfigurationManager _config;
- private ILogger _logger;
-
- public DeviceManagerEntryPoint(
- IDeviceManager deviceManager,
- IServerConfigurationManager config,
- ILogger<DeviceManagerEntryPoint> logger)
- {
- _deviceManager = (DeviceManager)deviceManager;
- _config = config;
- _logger = logger;
- }
-
- public async Task RunAsync()
- {
- if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted)
- {
- var path = _deviceManager.GetUploadsPath();
-
- if (Directory.Exists(path))
- {
- try
- {
- await _deviceManager.EnsureLibraryFolder(path, null).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error creating camera uploads library");
- }
-
- _config.Configuration.CameraUploadUpgraded = true;
- _config.SaveConfiguration();
- }
- }
- }
-
- #region IDisposable Support
- private bool disposedValue = false; // To detect redundant calls
-
- protected virtual void Dispose(bool disposing)
- {
- if (!disposedValue)
- {
- if (disposing)
- {
- // TODO: dispose managed state (managed objects).
- }
-
- // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
- // TODO: set large fields to null.
-
- disposedValue = true;
- }
- }
-
- // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
- // ~DeviceManagerEntryPoint() {
- // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
- // Dispose(false);
- // }
-
- // This code added to correctly implement the disposable pattern.
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
- Dispose(true);
- // TODO: uncomment the following line if the finalizer is overridden above.
- // GC.SuppressFinalize(this);
- }
- #endregion
- }
-
- public class DevicesConfigStore : IConfigurationFactory
- {
- public IEnumerable<ConfigurationStore> GetConfigurations()
- {
- return new ConfigurationStore[]
- {
- new ConfigurationStore
- {
- Key = "devices",
- ConfigurationType = typeof(DevicesOptions)
- }
- };
- }
- }
-
- public static class UploadConfigExtension
- {
- public static DevicesOptions GetUploadOptions(this IConfigurationManager config)
- {
- return config.GetConfiguration<DevicesOptions>("devices");
- }
- }
}
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index dccbe2a9a..896e4310e 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -9,7 +9,6 @@
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
<ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" />
- <ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@@ -51,7 +50,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netcoreapp3.1</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index 37d7fd479..878cee23c 100644
--- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.EntryPoints
.Append(config.PublicHttpsPort).Append(Separator)
.Append(_appHost.HttpPort).Append(Separator)
.Append(_appHost.HttpsPort).Append(Separator)
- .Append(_appHost.EnableHttps).Append(Separator)
+ .Append(_appHost.ListenWithHttps).Append(Separator)
.Append(config.EnableRemoteAccess).Append(Separator)
.ToString();
}
@@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.EntryPoints
{
yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
- if (_appHost.EnableHttps)
+ if (_appHost.ListenWithHttps)
{
yield return CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
}
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index 211a0c1d9..794d55c04 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -6,11 +6,12 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Sockets;
+using System.Net.WebSockets;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
-using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Services;
+using Emby.Server.Implementations.SocketSharp;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
@@ -22,15 +23,17 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
using ServiceStack.Text.Jsv;
namespace Emby.Server.Implementations.HttpServer
{
- public class HttpListenerHost : IHttpServer, IDisposable
+ public class HttpListenerHost : IHttpServer
{
/// <summary>
/// The key for a setting that specifies the default redirect path
@@ -39,17 +42,17 @@ namespace Emby.Server.Implementations.HttpServer
public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath";
private readonly ILogger _logger;
+ private readonly ILoggerFactory _loggerFactory;
private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager;
private readonly IServerApplicationHost _appHost;
private readonly IJsonSerializer _jsonSerializer;
private readonly IXmlSerializer _xmlSerializer;
- private readonly IHttpListener _socketListener;
private readonly Func<Type, Func<string, object>> _funcParseFn;
private readonly string _defaultRedirectPath;
private readonly string _baseUrlPrefix;
+
private readonly Dictionary<Type, Type> _serviceOperationsMap = new Dictionary<Type, Type>();
- private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
private readonly IHostEnvironment _hostEnvironment;
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
@@ -63,10 +66,10 @@ namespace Emby.Server.Implementations.HttpServer
INetworkManager networkManager,
IJsonSerializer jsonSerializer,
IXmlSerializer xmlSerializer,
- IHttpListener socketListener,
ILocalizationManager localizationManager,
ServiceController serviceController,
- IHostEnvironment hostEnvironment)
+ IHostEnvironment hostEnvironment,
+ ILoggerFactory loggerFactory)
{
_appHost = applicationHost;
_logger = logger;
@@ -76,11 +79,9 @@ namespace Emby.Server.Implementations.HttpServer
_networkManager = networkManager;
_jsonSerializer = jsonSerializer;
_xmlSerializer = xmlSerializer;
- _socketListener = socketListener;
ServiceController = serviceController;
-
- _socketListener.WebSocketConnected = OnWebSocketConnected;
_hostEnvironment = hostEnvironment;
+ _loggerFactory = loggerFactory;
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
@@ -172,38 +173,6 @@ namespace Emby.Server.Implementations.HttpServer
return attributes;
}
- private void OnWebSocketConnected(WebSocketConnectEventArgs e)
- {
- if (_disposed)
- {
- return;
- }
-
- var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
- {
- OnReceive = ProcessWebSocketMessageReceived,
- Url = e.Url,
- QueryString = e.QueryString
- };
-
- connection.Closed += OnConnectionClosed;
-
- lock (_webSocketConnections)
- {
- _webSocketConnections.Add(connection);
- }
-
- WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection));
- }
-
- private void OnConnectionClosed(object sender, EventArgs e)
- {
- lock (_webSocketConnections)
- {
- _webSocketConnections.Remove((IWebSocketConnection)sender);
- }
- }
-
private static Exception GetActualException(Exception ex)
{
if (ex is AggregateException agg)
@@ -289,32 +258,6 @@ namespace Emby.Server.Implementations.HttpServer
.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
}
- /// <summary>
- /// Shut down the Web Service
- /// </summary>
- public void Stop()
- {
- List<IWebSocketConnection> connections;
-
- lock (_webSocketConnections)
- {
- connections = _webSocketConnections.ToList();
- _webSocketConnections.Clear();
- }
-
- foreach (var connection in connections)
- {
- try
- {
- connection.Dispose();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error disposing connection");
- }
- }
- }
-
public static string RemoveQueryStringByKey(string url, string key)
{
var uri = new Uri(url);
@@ -424,33 +367,52 @@ namespace Emby.Server.Implementations.HttpServer
return true;
}
+ /// <summary>
+ /// Validate a connection from a remote IP address to a URL to see if a redirection to HTTPS is required.
+ /// </summary>
+ /// <returns>True if the request is valid, or false if the request is not valid and an HTTPS redirect is required.</returns>
private bool ValidateSsl(string remoteIp, string urlString)
{
- if (_config.Configuration.RequireHttps && _appHost.EnableHttps && !_config.Configuration.IsBehindProxy)
+ if (_config.Configuration.RequireHttps
+ && _appHost.ListenWithHttps
+ && !urlString.Contains("https://", StringComparison.OrdinalIgnoreCase))
{
- if (urlString.IndexOf("https://", StringComparison.OrdinalIgnoreCase) == -1)
+ // These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected
+ if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1
+ || urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1)
{
- // These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected
- if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1
- || urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1)
- {
- return true;
- }
+ return true;
+ }
- if (!_networkManager.IsInLocalNetwork(remoteIp))
- {
- return false;
- }
+ if (!_networkManager.IsInLocalNetwork(remoteIp))
+ {
+ return false;
}
}
return true;
}
+ /// <inheritdoc />
+ public Task RequestHandler(HttpContext context)
+ {
+ if (context.WebSockets.IsWebSocketRequest)
+ {
+ return WebSocketRequestHandler(context);
+ }
+
+ var request = context.Request;
+ var response = context.Response;
+ var localPath = context.Request.Path.ToString();
+
+ var req = new WebSocketSharpRequest(request, response, request.Path, _logger);
+ return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted);
+ }
+
/// <summary>
/// Overridable method that can be used to implement a custom handler.
/// </summary>
- public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
+ private async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
@@ -493,9 +455,10 @@ namespace Emby.Server.Implementations.HttpServer
if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
{
httpRes.StatusCode = 200;
- httpRes.Headers.Add("Access-Control-Allow-Origin", "*");
- httpRes.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
- httpRes.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization");
+ foreach(var (key, value) in GetDefaultCorsHeaders(httpReq))
+ {
+ httpRes.Headers.Add(key, value);
+ }
httpRes.ContentType = "text/plain";
await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
return;
@@ -578,6 +541,68 @@ namespace Emby.Server.Implementations.HttpServer
}
}
+ private async Task WebSocketRequestHandler(HttpContext context)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ try
+ {
+ _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
+
+ WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
+
+ var connection = new WebSocketConnection(
+ _loggerFactory.CreateLogger<WebSocketConnection>(),
+ webSocket,
+ context.Connection.RemoteIpAddress,
+ context.Request.Query)
+ {
+ OnReceive = ProcessWebSocketMessageReceived
+ };
+
+ WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection));
+
+ await connection.ProcessAsync().ConfigureAwait(false);
+ _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress);
+ }
+ catch (Exception ex) // Otherwise ASP.Net will ignore the exception
+ {
+ _logger.LogError(ex, "WS {IP} WebSocketRequestHandler error", context.Connection.RemoteIpAddress);
+ if (!context.Response.HasStarted)
+ {
+ context.Response.StatusCode = 500;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Get the default CORS headers
+ /// </summary>
+ /// <param name="req"></param>
+ /// <returns></returns>
+ public IDictionary<string, string> GetDefaultCorsHeaders(IRequest req)
+ {
+ var origin = req.Headers["Origin"];
+ if (origin == StringValues.Empty)
+ {
+ origin = req.Headers["Host"];
+ if (origin == StringValues.Empty)
+ {
+ origin = "*";
+ }
+ }
+
+ var headers = new Dictionary<string, string>();
+ headers.Add("Access-Control-Allow-Origin", origin);
+ headers.Add("Access-Control-Allow-Credentials", "true");
+ headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
+ headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization, Cookie");
+ return headers;
+ }
+
// Entry point for HttpListener
public ServiceHandler GetServiceHandler(IHttpRequest httpReq)
{
@@ -624,7 +649,7 @@ namespace Emby.Server.Implementations.HttpServer
ResponseFilters = new Action<IRequest, HttpResponse, object>[]
{
- new ResponseFilter(_logger).FilterResponse
+ new ResponseFilter(this, _logger).FilterResponse
};
}
@@ -685,11 +710,6 @@ namespace Emby.Server.Implementations.HttpServer
return _jsonSerializer.DeserializeFromStreamAsync(stream, type);
}
- public Task ProcessWebSocketRequest(HttpContext context)
- {
- return _socketListener.ProcessWebSocketRequest(context);
- }
-
private string NormalizeEmbyRoutePath(string path)
{
_logger.LogDebug("Normalizing /emby route");
@@ -708,28 +728,6 @@ namespace Emby.Server.Implementations.HttpServer
return _baseUrlPrefix + NormalizeUrlPath(path);
}
- /// <inheritdoc />
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
-
- if (disposing)
- {
- Stop();
- }
-
- _disposed = true;
- }
-
/// <summary>
/// Processes the web socket message received.
/// </summary>
@@ -741,8 +739,6 @@ namespace Emby.Server.Implementations.HttpServer
return Task.CompletedTask;
}
- _logger.LogDebug("Websocket message received: {0}", result.MessageType);
-
IEnumerable<Task> GetTasks()
{
foreach (var x in _webSocketListeners)
diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs
deleted file mode 100644
index 501593725..000000000
--- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using Emby.Server.Implementations.Net;
-using MediaBrowser.Model.Services;
-using Microsoft.AspNetCore.Http;
-
-namespace Emby.Server.Implementations.HttpServer
-{
- public interface IHttpListener : IDisposable
- {
- /// <summary>
- /// Gets or sets the error handler.
- /// </summary>
- /// <value>The error handler.</value>
- Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
-
- /// <summary>
- /// Gets or sets the request handler.
- /// </summary>
- /// <value>The request handler.</value>
- Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; }
-
- /// <summary>
- /// Gets or sets the web socket handler.
- /// </summary>
- /// <value>The web socket handler.</value>
- Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
-
- /// <summary>
- /// Stops this instance.
- /// </summary>
- Task Stop();
-
- Task ProcessWebSocketRequest(HttpContext ctx);
- }
-}
diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
index 4089aa578..85c3db9b2 100644
--- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
+++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Generic;
using System.Globalization;
using System.Text;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -13,14 +15,17 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
public class ResponseFilter
{
+ private readonly IHttpServer _server;
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="ResponseFilter"/> class.
/// </summary>
+ /// <param name="server">The HTTP server.</param>
/// <param name="logger">The logger.</param>
- public ResponseFilter(ILogger logger)
+ public ResponseFilter(IHttpServer server, ILogger logger)
{
+ _server = server;
_logger = logger;
}
@@ -32,10 +37,16 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="dto">The dto.</param>
public void FilterResponse(IRequest req, HttpResponse res, object dto)
{
+ foreach(var (key, value) in _server.GetDefaultCorsHeaders(req))
+ {
+ res.Headers.Add(key, value);
+ }
// Try to prevent compatibility view
- res.Headers.Add("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
- res.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
- res.Headers.Add("Access-Control-Allow-Origin", "*");
+ res.Headers["Access-Control-Allow-Headers"] = ("Accept, Accept-Language, Authorization, Cache-Control, " +
+ "Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, " +
+ "Content-Type, Cookie, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, " +
+ "Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, " +
+ "X-Emby-Authorization");
if (dto is Exception exception)
{
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index 2292d86a4..095725c50 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -1,15 +1,18 @@
-using System;
+#nullable enable
+
+using System;
+using System.Buffers;
+using System.IO.Pipelines;
+using System.Net;
using System.Net.WebSockets;
-using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using Emby.Server.Implementations.Net;
+using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
-using UtfUnknown;
namespace Emby.Server.Implementations.HttpServer
{
@@ -24,69 +27,50 @@ namespace Emby.Server.Implementations.HttpServer
private readonly ILogger _logger;
/// <summary>
- /// The json serializer.
+ /// The json serializer options.
/// </summary>
- private readonly IJsonSerializer _jsonSerializer;
+ private readonly JsonSerializerOptions _jsonOptions;
/// <summary>
/// The socket.
/// </summary>
- private readonly IWebSocket _socket;
+ private readonly WebSocket _socket;
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
/// </summary>
+ /// <param name="logger">The logger.</param>
/// <param name="socket">The socket.</param>
/// <param name="remoteEndPoint">The remote end point.</param>
- /// <param name="jsonSerializer">The json serializer.</param>
- /// <param name="logger">The logger.</param>
- /// <exception cref="ArgumentNullException">socket</exception>
- public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger)
+ /// <param name="query">The query.</param>
+ public WebSocketConnection(
+ ILogger<WebSocketConnection> logger,
+ WebSocket socket,
+ IPAddress? remoteEndPoint,
+ IQueryCollection query)
{
- if (socket == null)
- {
- throw new ArgumentNullException(nameof(socket));
- }
-
- if (string.IsNullOrEmpty(remoteEndPoint))
- {
- throw new ArgumentNullException(nameof(remoteEndPoint));
- }
-
- if (jsonSerializer == null)
- {
- throw new ArgumentNullException(nameof(jsonSerializer));
- }
-
- if (logger == null)
- {
- throw new ArgumentNullException(nameof(logger));
- }
-
- Id = Guid.NewGuid();
- _jsonSerializer = jsonSerializer;
+ _logger = logger;
_socket = socket;
- _socket.OnReceiveBytes = OnReceiveInternal;
-
RemoteEndPoint = remoteEndPoint;
- _logger = logger;
+ QueryString = query;
- socket.Closed += OnSocketClosed;
+ _jsonOptions = JsonDefaults.GetOptions();
+ LastActivityDate = DateTime.Now;
}
/// <inheritdoc />
- public event EventHandler<EventArgs> Closed;
+ public event EventHandler<EventArgs>? Closed;
/// <summary>
/// Gets or sets the remote end point.
/// </summary>
- public string RemoteEndPoint { get; private set; }
+ public IPAddress? RemoteEndPoint { get; }
/// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
- public Func<WebSocketMessageInfo, Task> OnReceive { get; set; }
+ public Func<WebSocketMessageInfo, Task>? OnReceive { get; set; }
/// <summary>
/// Gets the last activity date.
@@ -95,22 +79,10 @@ namespace Emby.Server.Implementations.HttpServer
public DateTime LastActivityDate { get; private set; }
/// <summary>
- /// Gets the id.
- /// </summary>
- /// <value>The id.</value>
- public Guid Id { get; private set; }
-
- /// <summary>
- /// Gets or sets the URL.
- /// </summary>
- /// <value>The URL.</value>
- public string Url { get; set; }
-
- /// <summary>
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
- public IQueryCollection QueryString { get; set; }
+ public IQueryCollection QueryString { get; }
/// <summary>
/// Gets the state.
@@ -118,138 +90,135 @@ namespace Emby.Server.Implementations.HttpServer
/// <value>The state.</value>
public WebSocketState State => _socket.State;
- void OnSocketClosed(object sender, EventArgs e)
+ /// <summary>
+ /// Sends a message asynchronously.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="message">The message.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ public Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken)
{
- Closed?.Invoke(this, EventArgs.Empty);
+ var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions);
+ return _socket.SendAsync(json, WebSocketMessageType.Text, true, cancellationToken);
}
- /// <summary>
- /// Called when [receive].
- /// </summary>
- /// <param name="bytes">The bytes.</param>
- private void OnReceiveInternal(byte[] bytes)
+ /// <inheritdoc />
+ public async Task ProcessAsync(CancellationToken cancellationToken = default)
{
- LastActivityDate = DateTime.UtcNow;
+ var pipe = new Pipe();
+ var writer = pipe.Writer;
- if (OnReceive == null)
+ ValueWebSocketReceiveResult receiveresult;
+ do
{
- return;
- }
- var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName;
+ // Allocate at least 512 bytes from the PipeWriter
+ Memory<byte> memory = writer.GetMemory(512);
+ try
+ {
+ receiveresult = await _socket.ReceiveAsync(memory, cancellationToken);
+ }
+ catch (WebSocketException ex)
+ {
+ _logger.LogWarning("WS {IP} error receiving data: {Message}", RemoteEndPoint, ex.Message);
+ break;
+ }
- if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase))
- {
- OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length));
- }
- else
+ int bytesRead = receiveresult.Count;
+ if (bytesRead == 0)
+ {
+ break;
+ }
+
+ // Tell the PipeWriter how much was read from the Socket
+ writer.Advance(bytesRead);
+
+ // Make the data available to the PipeReader
+ FlushResult flushResult = await writer.FlushAsync();
+ if (flushResult.IsCompleted)
+ {
+ // The PipeReader stopped reading
+ break;
+ }
+
+ LastActivityDate = DateTime.UtcNow;
+
+ if (receiveresult.EndOfMessage)
+ {
+ await ProcessInternal(pipe.Reader).ConfigureAwait(false);
+ }
+ } while (
+ (_socket.State == WebSocketState.Open || _socket.State == WebSocketState.Connecting)
+ && receiveresult.MessageType != WebSocketMessageType.Close);
+
+ Closed?.Invoke(this, EventArgs.Empty);
+
+ if (_socket.State == WebSocketState.Open
+ || _socket.State == WebSocketState.CloseReceived
+ || _socket.State == WebSocketState.CloseSent)
{
- OnReceiveInternal(Encoding.ASCII.GetString(bytes, 0, bytes.Length));
+ await _socket.CloseAsync(
+ WebSocketCloseStatus.NormalClosure,
+ string.Empty,
+ cancellationToken).ConfigureAwait(false);
}
}
- private void OnReceiveInternal(string message)
+ private async Task ProcessInternal(PipeReader reader)
{
- LastActivityDate = DateTime.UtcNow;
-
- if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase))
- {
- // This info is useful sometimes but also clogs up the log
- _logger.LogDebug("Received web socket message that is not a json structure: {message}", message);
- return;
- }
+ ReadResult result = await reader.ReadAsync().ConfigureAwait(false);
+ ReadOnlySequence<byte> buffer = result.Buffer;
if (OnReceive == null)
{
+ // Tell the PipeReader how much of the buffer we have consumed
+ reader.AdvanceTo(buffer.End);
return;
}
+ WebSocketMessage<object> stub;
try
{
- var stub = (WebSocketMessage<object>)_jsonSerializer.DeserializeFromString(message, typeof(WebSocketMessage<object>));
- var info = new WebSocketMessageInfo
+ if (buffer.IsSingleSegment)
{
- MessageType = stub.MessageType,
- Data = stub.Data?.ToString(),
- Connection = this
- };
-
- OnReceive(info);
+ stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buffer.FirstSpan, _jsonOptions);
+ }
+ else
+ {
+ var buf = ArrayPool<byte>.Shared.Rent(Convert.ToInt32(buffer.Length));
+ try
+ {
+ buffer.CopyTo(buf);
+ stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buf, _jsonOptions);
+ }
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buf);
+ }
+ }
}
- catch (Exception ex)
+ catch (JsonException ex)
{
+ // Tell the PipeReader how much of the buffer we have consumed
+ reader.AdvanceTo(buffer.End);
_logger.LogError(ex, "Error processing web socket message");
- }
- }
-
- /// <summary>
- /// Sends a message asynchronously.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="message">The message.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- /// <exception cref="ArgumentNullException">message</exception>
- public Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken)
- {
- if (message == null)
- {
- throw new ArgumentNullException(nameof(message));
- }
-
- var json = _jsonSerializer.SerializeToString(message);
-
- return SendAsync(json, cancellationToken);
- }
-
- /// <summary>
- /// Sends a message asynchronously.
- /// </summary>
- /// <param name="buffer">The buffer.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public Task SendAsync(byte[] buffer, CancellationToken cancellationToken)
- {
- if (buffer == null)
- {
- throw new ArgumentNullException(nameof(buffer));
+ return;
}
- cancellationToken.ThrowIfCancellationRequested();
+ // Tell the PipeReader how much of the buffer we have consumed
+ reader.AdvanceTo(buffer.End);
- return _socket.SendAsync(buffer, true, cancellationToken);
- }
+ _logger.LogDebug("WS {IP} received message: {@Message}", RemoteEndPoint, stub);
- /// <inheritdoc />
- public Task SendAsync(string text, CancellationToken cancellationToken)
- {
- if (string.IsNullOrEmpty(text))
+ var info = new WebSocketMessageInfo
{
- throw new ArgumentNullException(nameof(text));
- }
+ MessageType = stub.MessageType,
+ Data = stub.Data?.ToString(), // Data can be null
+ Connection = this
+ };
- cancellationToken.ThrowIfCancellationRequested();
-
- return _socket.SendAsync(text, true, cancellationToken);
- }
-
- /// <inheritdoc />
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// Releases unmanaged and - optionally - managed resources.
- /// </summary>
- /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- protected virtual void Dispose(bool dispose)
- {
- if (dispose)
- {
- _socket.Dispose();
- }
+ await OnReceive(info).ConfigureAwait(false);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index 85b1b6e32..6c9ba7c27 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// </summary>
public class MusicAlbumResolver : ItemResolver<MusicAlbum>
{
- private readonly ILogger _logger;
+ private readonly ILogger<MusicAlbumResolver> _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
@@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <param name="logger">The logger.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param>
- public MusicAlbumResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager)
+ public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, IFileSystem fileSystem, ILibraryManager libraryManager)
{
_logger = logger;
_fileSystem = fileSystem;
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index 681db4896..5f5cd0e92 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// </summary>
public class MusicArtistResolver : ItemResolver<MusicArtist>
{
- private readonly ILogger _logger;
+ private readonly ILogger<MusicAlbumResolver> _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
@@ -23,12 +23,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <summary>
/// Initializes a new instance of the <see cref="MusicArtistResolver"/> class.
/// </summary>
- /// <param name="logger">The logger.</param>
+ /// <param name="logger">The logger for the created <see cref="MusicAlbumResolver"/> instances.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="config">The configuration manager.</param>
public MusicArtistResolver(
- ILogger<MusicArtistResolver> logger,
+ ILogger<MusicAlbumResolver> logger,
IFileSystem fileSystem,
ILibraryManager libraryManager,
IServerConfigurationManager config)
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 33f4ca146..3efe1ee25 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -1059,7 +1059,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var stream = new MediaSourceInfo
{
- EncoderPath = _appHost.GetLocalApiUrl("127.0.0.1", true) + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
+ EncoderPath = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
EncoderProtocol = MediaProtocol.Http,
Path = info.Path,
Protocol = MediaProtocol.File,
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index d89a816b3..82b1f3cf1 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
//OpenedMediaSource.Path = tempFile;
//OpenedMediaSource.ReadAtNativeFramerate = true;
- MediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1", true) + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
+ MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
MediaSource.Protocol = MediaProtocol.Http;
//OpenedMediaSource.SupportsDirectPlay = false;
//OpenedMediaSource.SupportsDirectStream = true;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index f5dda79db..f7c9c736e 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public M3UTunerHost(
IServerConfigurationManager config,
IMediaSourceManager mediaSourceManager,
- ILogger logger,
+ ILogger<M3UTunerHost> logger,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IHttpClient httpClient,
@@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return Task.FromResult(list);
}
- private static readonly string[] _disallowedSharedStreamExtensions = new string[]
+ private static readonly string[] _disallowedSharedStreamExtensions =
{
".mkv",
".mp4",
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index 0e600202a..083fcd029 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -106,7 +106,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
//OpenedMediaSource.Path = tempFile;
//OpenedMediaSource.ReadAtNativeFramerate = true;
- MediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1", true) + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
+ MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
MediaSource.Protocol = MediaProtocol.Http;
//OpenedMediaSource.Path = TempFilePath;
diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json
index ef7792356..4949b10e6 100644
--- a/Emby.Server.Implementations/Localization/Core/bn.json
+++ b/Emby.Server.Implementations/Localization/Core/bn.json
@@ -91,5 +91,7 @@
"HeaderNextUp": "এরপরে আসছে",
"HeaderLiveTV": "লাইভ টিভি",
"HeaderFavoriteSongs": "প্রিয় গানগুলো",
- "HeaderFavoriteShows": "প্রিয় শোগুলো"
+ "HeaderFavoriteShows": "প্রিয় শোগুলো",
+ "TasksLibraryCategory": "গ্রন্থাগার",
+ "TasksMaintenanceCategory": "রক্ষণাবেক্ষণ"
}
diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json
index 8abe31d2a..4e54b9f7a 100644
--- a/Emby.Server.Implementations/Localization/Core/he.json
+++ b/Emby.Server.Implementations/Localization/Core/he.json
@@ -99,5 +99,13 @@
"TaskCleanCache": "נקה תיקיית מטמון",
"TasksApplicationCategory": "יישום",
"TasksLibraryCategory": "ספרייה",
- "TasksMaintenanceCategory": "תחזוקה"
+ "TasksMaintenanceCategory": "תחזוקה",
+ "TaskUpdatePlugins": "עדכן תוספים",
+ "TaskRefreshPeopleDescription": "מעדכן מטא נתונים עבור שחקנים ובמאים בספריית המדיה שלך.",
+ "TaskRefreshPeople": "רענן אנשים",
+ "TaskCleanLogsDescription": "מוחק קבצי יומן בני יותר מ- {0} ימים.",
+ "TaskCleanLogs": "נקה תיקיית יומן",
+ "TaskRefreshLibraryDescription": "סורק את ספריית המדיה שלך אחר קבצים חדשים ומרענן מטא נתונים.",
+ "TaskRefreshChapterImagesDescription": "יוצר תמונות ממוזערות לסרטונים שיש להם פרקים.",
+ "TasksChannelsCategory": "ערוצי אינטרנט"
}
diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json
index 50d0d083c..5637ce346 100644
--- a/Emby.Server.Implementations/Localization/Core/nb.json
+++ b/Emby.Server.Implementations/Localization/Core/nb.json
@@ -97,5 +97,9 @@
"TasksApplicationCategory": "Applikasjon",
"TasksLibraryCategory": "Bibliotek",
"TasksMaintenanceCategory": "Vedlikehold",
- "TaskCleanCache": "Tøm buffer katalog"
+ "TaskCleanCache": "Tøm buffer katalog",
+ "TaskRefreshLibrary": "Skann mediebibliotek",
+ "TaskRefreshChapterImagesDescription": "Lager forhåndsvisningsbilder for videoer som har kapitler.",
+ "TaskRefreshChapterImages": "Trekk ut Kapittelbilder",
+ "TaskCleanCacheDescription": "Sletter mellomlagrede filer som ikke lengre trengs av systemet."
}
diff --git a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs
deleted file mode 100644
index fda32da5e..000000000
--- a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Logging;
-using WebSocketManager = Emby.Server.Implementations.WebSockets.WebSocketManager;
-
-namespace Emby.Server.Implementations.Middleware
-{
- public class WebSocketMiddleware
- {
- private readonly RequestDelegate _next;
- private readonly ILogger<WebSocketMiddleware> _logger;
- private readonly WebSocketManager _webSocketManager;
-
- public WebSocketMiddleware(RequestDelegate next, ILogger<WebSocketMiddleware> logger, WebSocketManager webSocketManager)
- {
- _next = next;
- _logger = logger;
- _webSocketManager = webSocketManager;
- }
-
- public async Task Invoke(HttpContext httpContext)
- {
- _logger.LogInformation("Handling request: " + httpContext.Request.Path);
-
- if (httpContext.WebSockets.IsWebSocketRequest)
- {
- var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
- if (webSocketContext != null)
- {
- await _webSocketManager.OnWebSocketConnected(webSocketContext).ConfigureAwait(false);
- }
- }
- else
- {
- await _next.Invoke(httpContext).ConfigureAwait(false);
- }
- }
- }
-}
diff --git a/Emby.Server.Implementations/Net/IWebSocket.cs b/Emby.Server.Implementations/Net/IWebSocket.cs
deleted file mode 100644
index 4d160aa66..000000000
--- a/Emby.Server.Implementations/Net/IWebSocket.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System;
-using System.Net.WebSockets;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Emby.Server.Implementations.Net
-{
- /// <summary>
- /// Interface IWebSocket
- /// </summary>
- public interface IWebSocket : IDisposable
- {
- /// <summary>
- /// Occurs when [closed].
- /// </summary>
- event EventHandler<EventArgs> Closed;
-
- /// <summary>
- /// Gets or sets the state.
- /// </summary>
- /// <value>The state.</value>
- WebSocketState State { get; }
-
- /// <summary>
- /// Gets or sets the receive action.
- /// </summary>
- /// <value>The receive action.</value>
- Action<byte[]> OnReceiveBytes { get; set; }
-
- /// <summary>
- /// Sends the async.
- /// </summary>
- /// <param name="bytes">The bytes.</param>
- /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken);
-
- /// <summary>
- /// Sends the asynchronous.
- /// </summary>
- /// <param name="text">The text.</param>
- /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken);
- }
-}
diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs
deleted file mode 100644
index 6880766f9..000000000
--- a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System;
-using Microsoft.AspNetCore.Http;
-
-namespace Emby.Server.Implementations.Net
-{
- public class WebSocketConnectEventArgs : EventArgs
- {
- /// <summary>
- /// Gets or sets the URL.
- /// </summary>
- /// <value>The URL.</value>
- public string Url { get; set; }
- /// <summary>
- /// Gets or sets the query string.
- /// </summary>
- /// <value>The query string.</value>
- public IQueryCollection QueryString { get; set; }
- /// <summary>
- /// Gets or sets the web socket.
- /// </summary>
- /// <value>The web socket.</value>
- public IWebSocket WebSocket { get; set; }
- /// <summary>
- /// Gets or sets the endpoint.
- /// </summary>
- /// <value>The endpoint.</value>
- public string Endpoint { get; set; }
- }
-}
diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs
deleted file mode 100644
index dfb81816c..000000000
--- a/Emby.Server.Implementations/Session/HttpSessionController.cs
+++ /dev/null
@@ -1,191 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Session;
-
-namespace Emby.Server.Implementations.Session
-{
- public class HttpSessionController : ISessionController
- {
- private readonly IHttpClient _httpClient;
- private readonly IJsonSerializer _json;
- private readonly ISessionManager _sessionManager;
-
- public SessionInfo Session { get; private set; }
-
- private readonly string _postUrl;
-
- public HttpSessionController(IHttpClient httpClient,
- IJsonSerializer json,
- SessionInfo session,
- string postUrl, ISessionManager sessionManager)
- {
- _httpClient = httpClient;
- _json = json;
- Session = session;
- _postUrl = postUrl;
- _sessionManager = sessionManager;
- }
-
- private string PostUrl => string.Format("http://{0}{1}", Session.RemoteEndPoint, _postUrl);
-
- public bool IsSessionActive => (DateTime.UtcNow - Session.LastActivityDate).TotalMinutes <= 5;
-
- public bool SupportsMediaControl => true;
-
- private Task SendMessage(string name, string messageId, CancellationToken cancellationToken)
- {
- return SendMessage(name, messageId, new Dictionary<string, string>(), cancellationToken);
- }
-
- private Task SendMessage(string name, string messageId, Dictionary<string, string> args, CancellationToken cancellationToken)
- {
- args["messageId"] = messageId;
- var url = PostUrl + "/" + name + ToQueryString(args);
-
- return SendRequest(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- BufferContent = false
- });
- }
-
- private Task SendPlayCommand(PlayRequest command, string messageId, CancellationToken cancellationToken)
- {
- var dict = new Dictionary<string, string>();
-
- dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray());
-
- if (command.StartPositionTicks.HasValue)
- {
- dict["StartPositionTicks"] = command.StartPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
- }
- if (command.AudioStreamIndex.HasValue)
- {
- dict["AudioStreamIndex"] = command.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture);
- }
- if (command.SubtitleStreamIndex.HasValue)
- {
- dict["SubtitleStreamIndex"] = command.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture);
- }
- if (command.StartIndex.HasValue)
- {
- dict["StartIndex"] = command.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
- }
- if (!string.IsNullOrEmpty(command.MediaSourceId))
- {
- dict["MediaSourceId"] = command.MediaSourceId;
- }
-
- return SendMessage(command.PlayCommand.ToString(), messageId, dict, cancellationToken);
- }
-
- private Task SendPlaystateCommand(PlaystateRequest command, string messageId, CancellationToken cancellationToken)
- {
- var args = new Dictionary<string, string>();
-
- if (command.Command == PlaystateCommand.Seek)
- {
- if (!command.SeekPositionTicks.HasValue)
- {
- throw new ArgumentException("SeekPositionTicks cannot be null");
- }
-
- args["SeekPositionTicks"] = command.SeekPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
- }
-
- return SendMessage(command.Command.ToString(), messageId, args, cancellationToken);
- }
-
- private string[] _supportedMessages = Array.Empty<string>();
- public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken)
- {
- if (!IsSessionActive)
- {
- return Task.CompletedTask;
- }
-
- if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase))
- {
- return SendPlayCommand(data as PlayRequest, messageId, cancellationToken);
- }
- if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
- {
- return SendPlaystateCommand(data as PlaystateRequest, messageId, cancellationToken);
- }
- if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
- {
- var command = data as GeneralCommand;
- return SendMessage(command.Name, messageId, command.Arguments, cancellationToken);
- }
-
- if (!_supportedMessages.Contains(name, StringComparer.OrdinalIgnoreCase))
- {
- return Task.CompletedTask;
- }
-
- var url = PostUrl + "/" + name;
-
- url += "?messageId=" + messageId;
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- BufferContent = false
- };
-
- if (data != null)
- {
- if (typeof(T) == typeof(string))
- {
- var str = data as string;
- if (!string.IsNullOrEmpty(str))
- {
- options.RequestContent = str;
- options.RequestContentType = "application/json";
- }
- }
- else
- {
- options.RequestContent = _json.SerializeToString(data);
- options.RequestContentType = "application/json";
- }
- }
-
- return SendRequest(options);
- }
-
- private async Task SendRequest(HttpRequestOptions options)
- {
- using (var response = await _httpClient.Post(options).ConfigureAwait(false))
- {
-
- }
- }
-
- private static string ToQueryString(Dictionary<string, string> nvc)
- {
- var array = (from item in nvc
- select string.Format("{0}={1}", WebUtility.UrlEncode(item.Key), WebUtility.UrlEncode(item.Value)))
- .ToArray();
-
- var args = string.Join("&", array);
-
- if (string.IsNullOrEmpty(args))
- {
- return args;
- }
-
- return "?" + args;
- }
- }
-}
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index d595e96bd..84b16f1c7 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -490,8 +490,7 @@ namespace Emby.Server.Implementations.Session
Client = appName,
DeviceId = deviceId,
ApplicationVersion = appVersion,
- Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture),
- ServerId = _appHost.SystemId
+ Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture)
};
var username = user?.Username;
@@ -1052,12 +1051,12 @@ namespace Emby.Server.Implementations.Session
private static async Task SendMessageToSession<T>(SessionInfo session, string name, T data, CancellationToken cancellationToken)
{
- var controllers = session.SessionControllers.ToArray();
- var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+ var controllers = session.SessionControllers;
+ var messageId = Guid.NewGuid();
foreach (var controller in controllers)
{
- await controller.SendMessage(name, messageId, data, controllers, cancellationToken).ConfigureAwait(false);
+ await controller.SendMessage(name, messageId, data, cancellationToken).ConfigureAwait(false);
}
}
@@ -1065,13 +1064,13 @@ namespace Emby.Server.Implementations.Session
{
IEnumerable<Task> GetTasks()
{
- var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+ var messageId = Guid.NewGuid();
foreach (var session in sessions)
{
var controllers = session.SessionControllers;
foreach (var controller in controllers)
{
- yield return controller.SendMessage(name, messageId, data, controllers, cancellationToken);
+ yield return controller.SendMessage(name, messageId, data, cancellationToken);
}
}
}
@@ -1772,7 +1771,7 @@ namespace Emby.Server.Implementations.Session
throw new ArgumentNullException(nameof(info));
}
- var user = info.UserId.Equals(Guid.Empty)
+ var user = info.UserId == Guid.Empty
? null
: _userManager.GetUserById(info.UserId);
diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
index 930f2d35d..d4e4ba1f2 100644
--- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -3,7 +3,6 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -12,7 +11,7 @@ namespace Emby.Server.Implementations.Session
/// <summary>
/// Class SessionWebSocketListener
/// </summary>
- public class SessionWebSocketListener : IWebSocketListener, IDisposable
+ public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable
{
/// <summary>
/// The _session manager
@@ -23,42 +22,41 @@ namespace Emby.Server.Implementations.Session
/// The _logger
/// </summary>
private readonly ILogger _logger;
-
- /// <summary>
- /// The _dto service
- /// </summary>
- private readonly IJsonSerializer _json;
+ private readonly ILoggerFactory _loggerFactory;
private readonly IHttpServer _httpServer;
-
/// <summary>
/// Initializes a new instance of the <see cref="SessionWebSocketListener" /> class.
/// </summary>
+ /// <param name="logger">The logger.</param>
/// <param name="sessionManager">The session manager.</param>
/// <param name="loggerFactory">The logger factory.</param>
- /// <param name="json">The json.</param>
/// <param name="httpServer">The HTTP server.</param>
- public SessionWebSocketListener(ISessionManager sessionManager, ILoggerFactory loggerFactory, IJsonSerializer json, IHttpServer httpServer)
+ public SessionWebSocketListener(
+ ILogger<SessionWebSocketListener> logger,
+ ISessionManager sessionManager,
+ ILoggerFactory loggerFactory,
+ IHttpServer httpServer)
{
+ _logger = logger;
_sessionManager = sessionManager;
- _logger = loggerFactory.CreateLogger(GetType().Name);
- _json = json;
+ _loggerFactory = loggerFactory;
_httpServer = httpServer;
- httpServer.WebSocketConnected += _serverManager_WebSocketConnected;
+
+ httpServer.WebSocketConnected += OnServerManagerWebSocketConnected;
}
- void _serverManager_WebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e)
+ private void OnServerManagerWebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e)
{
- var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint);
-
+ var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint.ToString());
if (session != null)
{
EnsureController(session, e.Argument);
}
else
{
- _logger.LogWarning("Unable to determine session based on url: {0}", e.Argument.Url);
+ _logger.LogWarning("Unable to determine session based on query string: {0}", e.Argument.QueryString);
}
}
@@ -79,9 +77,10 @@ namespace Emby.Server.Implementations.Session
return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint);
}
+ /// <inheritdoc />
public void Dispose()
{
- _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected;
+ _httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected;
}
/// <summary>
@@ -94,7 +93,8 @@ namespace Emby.Server.Implementations.Session
private void EnsureController(SessionInfo session, IWebSocketConnection connection)
{
- var controllerInfo = session.EnsureController<WebSocketController>(s => new WebSocketController(s, _logger, _sessionManager));
+ var controllerInfo = session.EnsureController<WebSocketController>(
+ s => new WebSocketController(_loggerFactory.CreateLogger<WebSocketController>(), s, _sessionManager));
var controller = (WebSocketController)controllerInfo.Item1;
controller.AddWebSocket(connection);
diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs
index 0d483c55f..a0274acd2 100644
--- a/Emby.Server.Implementations/Session/WebSocketController.cs
+++ b/Emby.Server.Implementations/Session/WebSocketController.cs
@@ -1,3 +1,7 @@
+#pragma warning disable CS1591
+#pragma warning disable SA1600
+#nullable enable
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -11,60 +15,63 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Session
{
- public class WebSocketController : ISessionController, IDisposable
+ public sealed class WebSocketController : ISessionController, IDisposable
{
- public SessionInfo Session { get; private set; }
- public IReadOnlyList<IWebSocketConnection> Sockets { get; private set; }
-
private readonly ILogger _logger;
-
private readonly ISessionManager _sessionManager;
+ private readonly SessionInfo _session;
- public WebSocketController(SessionInfo session, ILogger logger, ISessionManager sessionManager)
+ private readonly List<IWebSocketConnection> _sockets;
+ private bool _disposed = false;
+
+ public WebSocketController(
+ ILogger<WebSocketController> logger,
+ SessionInfo session,
+ ISessionManager sessionManager)
{
- Session = session;
_logger = logger;
+ _session = session;
_sessionManager = sessionManager;
- Sockets = new List<IWebSocketConnection>();
+ _sockets = new List<IWebSocketConnection>();
}
private bool HasOpenSockets => GetActiveSockets().Any();
+ /// <inheritdoc />
public bool SupportsMediaControl => HasOpenSockets;
+ /// <inheritdoc />
public bool IsSessionActive => HasOpenSockets;
private IEnumerable<IWebSocketConnection> GetActiveSockets()
- {
- return Sockets
- .OrderByDescending(i => i.LastActivityDate)
- .Where(i => i.State == WebSocketState.Open);
- }
+ => _sockets.Where(i => i.State == WebSocketState.Open);
public void AddWebSocket(IWebSocketConnection connection)
{
- var sockets = Sockets.ToList();
- sockets.Add(connection);
+ _logger.LogDebug("Adding websocket to session {Session}", _session.Id);
+ _sockets.Add(connection);
- Sockets = sockets;
-
- connection.Closed += connection_Closed;
+ connection.Closed += OnConnectionClosed;
}
- void connection_Closed(object sender, EventArgs e)
+ private void OnConnectionClosed(object sender, EventArgs e)
{
var connection = (IWebSocketConnection)sender;
- var sockets = Sockets.ToList();
- sockets.Remove(connection);
-
- Sockets = sockets;
-
- _sessionManager.CloseIfNeeded(Session);
+ _logger.LogDebug("Removing websocket from session {Session}", _session.Id);
+ _sockets.Remove(connection);
+ connection.Closed -= OnConnectionClosed;
+ _sessionManager.CloseIfNeeded(_session);
}
- public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public Task SendMessage<T>(
+ string name,
+ Guid messageId,
+ T data,
+ CancellationToken cancellationToken)
{
var socket = GetActiveSockets()
+ .OrderByDescending(i => i.LastActivityDate)
.FirstOrDefault();
if (socket == null)
@@ -72,21 +79,30 @@ namespace Emby.Server.Implementations.Session
return Task.CompletedTask;
}
- return socket.SendAsync(new WebSocketMessage<T>
- {
- Data = data,
- MessageType = name,
- MessageId = messageId
-
- }, cancellationToken);
+ return socket.SendAsync(
+ new WebSocketMessage<T>
+ {
+ Data = data,
+ MessageType = name,
+ MessageId = messageId
+ },
+ cancellationToken);
}
+ /// <inheritdoc />
public void Dispose()
{
- foreach (var socket in Sockets.ToList())
+ if (_disposed)
{
- socket.Closed -= connection_Closed;
+ return;
}
+
+ foreach (var socket in _sockets)
+ {
+ socket.Closed -= OnConnectionClosed;
+ }
+
+ _disposed = true;
}
}
}
diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs
deleted file mode 100644
index 67521d6c6..000000000
--- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-using System;
-using System.Net.WebSockets;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Emby.Server.Implementations.Net;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.SocketSharp
-{
- public class SharpWebSocket : IWebSocket
- {
- /// <summary>
- /// The logger
- /// </summary>
- private readonly ILogger _logger;
-
- public event EventHandler<EventArgs> Closed;
-
- /// <summary>
- /// Gets or sets the web socket.
- /// </summary>
- /// <value>The web socket.</value>
- private readonly WebSocket _webSocket;
-
- private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
- private bool _disposed;
-
- public SharpWebSocket(WebSocket socket, ILogger logger)
- {
- _logger = logger ?? throw new ArgumentNullException(nameof(logger));
- _webSocket = socket ?? throw new ArgumentNullException(nameof(socket));
- }
-
- /// <summary>
- /// Gets the state.
- /// </summary>
- /// <value>The state.</value>
- public WebSocketState State => _webSocket.State;
-
- /// <summary>
- /// Sends the async.
- /// </summary>
- /// <param name="bytes">The bytes.</param>
- /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken)
- {
- return _webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken);
- }
-
- /// <summary>
- /// Sends the asynchronous.
- /// </summary>
- /// <param name="text">The text.</param>
- /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken)
- {
- return _webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken);
- }
-
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// Releases unmanaged and - optionally - managed resources.
- /// </summary>
- /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- protected virtual void Dispose(bool dispose)
- {
- if (_disposed)
- {
- return;
- }
-
- if (dispose)
- {
- _cancellationTokenSource.Cancel();
- if (_webSocket.State == WebSocketState.Open)
- {
- _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client",
- CancellationToken.None);
- }
- Closed?.Invoke(this, EventArgs.Empty);
- }
-
- _disposed = true;
- }
-
- /// <summary>
- /// Gets or sets the receive action.
- /// </summary>
- /// <value>The receive action.</value>
- public Action<byte[]> OnReceiveBytes { get; set; }
- }
-}
diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs
deleted file mode 100644
index b85750c9b..000000000
--- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.WebSockets;
-using System.Threading;
-using System.Threading.Tasks;
-using Emby.Server.Implementations.HttpServer;
-using Emby.Server.Implementations.Net;
-using MediaBrowser.Model.Services;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Http.Extensions;
-using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
-
-namespace Emby.Server.Implementations.SocketSharp
-{
- public class WebSocketSharpListener : IHttpListener
- {
- private readonly ILogger _logger;
-
- private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
- private CancellationToken _disposeCancellationToken;
-
- public WebSocketSharpListener(ILogger<WebSocketSharpListener> logger)
- {
- _logger = logger;
- _disposeCancellationToken = _disposeCancellationTokenSource.Token;
- }
-
- public Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
-
- public Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; }
-
- public Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
-
- private static void LogRequest(ILogger logger, HttpRequest request)
- {
- var url = request.GetDisplayUrl();
-
- logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, request.Headers[HeaderNames.UserAgent].ToString());
- }
-
- public async Task ProcessWebSocketRequest(HttpContext ctx)
- {
- try
- {
- LogRequest(_logger, ctx.Request);
- var endpoint = ctx.Connection.RemoteIpAddress.ToString();
- var url = ctx.Request.GetDisplayUrl();
-
- var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
- var socket = new SharpWebSocket(webSocketContext, _logger);
-
- WebSocketConnected(new WebSocketConnectEventArgs
- {
- Url = url,
- QueryString = ctx.Request.Query,
- WebSocket = socket,
- Endpoint = endpoint
- });
-
- WebSocketReceiveResult result;
- var message = new List<byte>();
-
- do
- {
- var buffer = WebSocket.CreateServerBuffer(4096);
- result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken);
- message.AddRange(buffer.Array.Take(result.Count));
-
- if (result.EndOfMessage)
- {
- socket.OnReceiveBytes(message.ToArray());
- message.Clear();
- }
- } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close);
-
-
- if (webSocketContext.State == WebSocketState.Open)
- {
- await webSocketContext.CloseAsync(
- result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
- result.CloseStatusDescription,
- _disposeCancellationToken).ConfigureAwait(false);
- }
-
- socket.Dispose();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "AcceptWebSocketAsync error");
- if (!ctx.Response.HasStarted)
- {
- ctx.Response.StatusCode = 500;
- }
- }
- }
-
- public Task Stop()
- {
- _disposeCancellationTokenSource.Cancel();
- return Task.CompletedTask;
- }
-
- /// <summary>
- /// Releases the unmanaged resources and disposes of the managed resources used.
- /// </summary>
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- private bool _disposed;
-
- /// <summary>
- /// Releases the unmanaged resources and disposes of the managed resources used.
- /// </summary>
- /// <param name="disposing">Whether or not the managed resources should be disposed.</param>
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
-
- if (disposing)
- {
- Stop().GetAwaiter().GetResult();
- }
-
- _disposed = true;
- }
- }
-}
diff --git a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs
deleted file mode 100644
index eb1877440..000000000
--- a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Threading.Tasks;
-using MediaBrowser.Model.Net;
-
-namespace Emby.Server.Implementations.WebSockets
-{
- public interface IWebSocketHandler
- {
- Task ProcessMessage(WebSocketMessage<object> message, TaskCompletionSource<bool> taskCompletionSource);
- }
-}
diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs
deleted file mode 100644
index 31a7468fb..000000000
--- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.WebSockets;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Serialization;
-using Microsoft.Extensions.Logging;
-using UtfUnknown;
-
-namespace Emby.Server.Implementations.WebSockets
-{
- public class WebSocketManager
- {
- private readonly IWebSocketHandler[] _webSocketHandlers;
- private readonly IJsonSerializer _jsonSerializer;
- private readonly ILogger<WebSocketManager> _logger;
- private const int BufferSize = 4096;
-
- public WebSocketManager(IWebSocketHandler[] webSocketHandlers, IJsonSerializer jsonSerializer, ILogger<WebSocketManager> logger)
- {
- _webSocketHandlers = webSocketHandlers;
- _jsonSerializer = jsonSerializer;
- _logger = logger;
- }
-
- public async Task OnWebSocketConnected(WebSocket webSocket)
- {
- var taskCompletionSource = new TaskCompletionSource<bool>();
- var cancellationToken = new CancellationTokenSource().Token;
- WebSocketReceiveResult result;
- var message = new List<byte>();
-
- // Keep listening for incoming messages, otherwise the socket closes automatically
- do
- {
- var buffer = WebSocket.CreateServerBuffer(BufferSize);
- result = await webSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false);
- message.AddRange(buffer.Array.Take(result.Count));
-
- if (result.EndOfMessage)
- {
- await ProcessMessage(message.ToArray(), taskCompletionSource).ConfigureAwait(false);
- message.Clear();
- }
- } while (!taskCompletionSource.Task.IsCompleted &&
- webSocket.State == WebSocketState.Open &&
- result.MessageType != WebSocketMessageType.Close);
-
- if (webSocket.State == WebSocketState.Open)
- {
- await webSocket.CloseAsync(
- result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
- result.CloseStatusDescription,
- cancellationToken).ConfigureAwait(false);
- }
- }
-
- private async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource<bool> taskCompletionSource)
- {
- var charset = CharsetDetector.DetectFromBytes(messageBytes).Detected?.EncodingName;
- var message = string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase)
- ? Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length)
- : Encoding.ASCII.GetString(messageBytes, 0, messageBytes.Length);
-
- // All messages are expected to be valid JSON objects
- if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase))
- {
- _logger.LogDebug("Received web socket message that is not a json structure: {Message}", message);
- return;
- }
-
- try
- {
- var info = _jsonSerializer.DeserializeFromString<WebSocketMessage<object>>(message);
-
- _logger.LogDebug("Websocket message received: {0}", info.MessageType);
-
- var tasks = _webSocketHandlers.Select(handler => Task.Run(() =>
- {
- try
- {
- handler.ProcessMessage(info, taskCompletionSource).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "{HandlerType} failed processing WebSocket message {MessageType}",
- handler.GetType().Name, info.MessageType ?? string.Empty);
- }
- }));
-
- await Task.WhenAll(tasks);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error processing web socket message");
- }
- }
- }
-}
diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs
index 633838991..522c20664 100644
--- a/Jellyfin.Data/Entities/ActivityLog.cs
+++ b/Jellyfin.Data/Entities/ActivityLog.cs
@@ -1,72 +1,62 @@
using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
-using System.Linq;
-using System.Runtime.CompilerServices;
+using Microsoft.Extensions.Logging;
namespace Jellyfin.Data.Entities
{
- [Table("ActivityLog")]
- public partial class ActivityLog
+ /// <summary>
+ /// An entity referencing an activity log entry.
+ /// </summary>
+ public partial class ActivityLog : ISavingChanges
{
- partial void Init();
-
/// <summary>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// Initializes a new instance of the <see cref="ActivityLog"/> class.
+ /// Public constructor with required data.
/// </summary>
- protected ActivityLog()
+ /// <param name="name">The name.</param>
+ /// <param name="type">The type.</param>
+ /// <param name="userId">The user id.</param>
+ public ActivityLog(string name, string type, Guid userId)
{
- Init();
- }
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
- /// <summary>
- /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
- /// </summary>
- public static ActivityLog CreateActivityLogUnsafe()
- {
- return new ActivityLog();
- }
+ if (string.IsNullOrEmpty(type))
+ {
+ throw new ArgumentNullException(nameof(type));
+ }
- /// <summary>
- /// Public constructor with required data
- /// </summary>
- /// <param name="name"></param>
- /// <param name="type"></param>
- /// <param name="userid"></param>
- /// <param name="datecreated"></param>
- /// <param name="logseverity"></param>
- public ActivityLog(string name, string type, Guid userid, DateTime datecreated, Microsoft.Extensions.Logging.LogLevel logseverity)
- {
- if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
this.Name = name;
-
- if (string.IsNullOrEmpty(type)) throw new ArgumentNullException(nameof(type));
this.Type = type;
+ this.UserId = userId;
+ this.DateCreated = DateTime.UtcNow;
+ this.LogSeverity = LogLevel.Trace;
- this.UserId = userid;
-
- this.DateCreated = datecreated;
-
- this.LogSeverity = logseverity;
-
+ Init();
+ }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ActivityLog"/> class.
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected ActivityLog()
+ {
Init();
}
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
- /// <param name="name"></param>
- /// <param name="type"></param>
- /// <param name="userid"></param>
- /// <param name="datecreated"></param>
- /// <param name="logseverity"></param>
- public static ActivityLog Create(string name, string type, Guid userid, DateTime datecreated, Microsoft.Extensions.Logging.LogLevel logseverity)
+ /// <param name="name">The name.</param>
+ /// <param name="type">The type.</param>
+ /// <param name="userId">The user's id.</param>
+ /// <returns>The new <see cref="ActivityLog"/> instance.</returns>
+ public static ActivityLog Create(string name, string type, Guid userId)
{
- return new ActivityLog(name, type, userid, datecreated, logseverity);
+ return new ActivityLog(name, type, userId);
}
/*************************************************************************
@@ -74,7 +64,8 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Identity, Indexed, Required
+ /// Gets or sets the identity of this instance.
+ /// This is the key in the backing database.
/// </summary>
[Key]
[Required]
@@ -82,7 +73,8 @@ namespace Jellyfin.Data.Entities
public int Id { get; protected set; }
/// <summary>
- /// Required, Max length = 512
+ /// Gets or sets the name.
+ /// Required, Max length = 512.
/// </summary>
[Required]
[MaxLength(512)]
@@ -90,21 +82,24 @@ namespace Jellyfin.Data.Entities
public string Name { get; set; }
/// <summary>
- /// Max length = 512
+ /// Gets or sets the overview.
+ /// Max length = 512.
/// </summary>
[MaxLength(512)]
[StringLength(512)]
public string Overview { get; set; }
/// <summary>
- /// Max length = 512
+ /// Gets or sets the short overview.
+ /// Max length = 512.
/// </summary>
[MaxLength(512)]
[StringLength(512)]
public string ShortOverview { get; set; }
/// <summary>
- /// Required, Max length = 256
+ /// Gets or sets the type.
+ /// Required, Max length = 256.
/// </summary>
[Required]
[MaxLength(256)]
@@ -112,42 +107,48 @@ namespace Jellyfin.Data.Entities
public string Type { get; set; }
/// <summary>
- /// Required
+ /// Gets or sets the user id.
+ /// Required.
/// </summary>
[Required]
public Guid UserId { get; set; }
/// <summary>
- /// Max length = 256
+ /// Gets or sets the item id.
+ /// Max length = 256.
/// </summary>
[MaxLength(256)]
[StringLength(256)]
public string ItemId { get; set; }
/// <summary>
- /// Required
+ /// Gets or sets the date created. This should be in UTC.
+ /// Required.
/// </summary>
[Required]
public DateTime DateCreated { get; set; }
/// <summary>
- /// Required
+ /// Gets or sets the log severity. Default is <see cref="LogLevel.Trace"/>.
+ /// Required.
/// </summary>
[Required]
- public Microsoft.Extensions.Logging.LogLevel LogSeverity { get; set; }
+ public LogLevel LogSeverity { get; set; }
/// <summary>
- /// Required, ConcurrenyToken
+ /// Gets or sets the row version.
+ /// Required, ConcurrencyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
public uint RowVersion { get; set; }
+ partial void Init();
+
+ /// <inheritdoc />
public void OnSavingChanges()
{
RowVersion++;
}
-
}
}
-
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index 8eae366ba..b2a3f7eb3 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -17,14 +17,10 @@
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
-
+
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.3" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.3">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
- </PackageReference>
</ItemGroup>
</Project>
diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs
index d7bbf793c..65ceee32b 100644
--- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs
+++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
@@ -14,7 +13,7 @@ namespace Jellyfin.Server.Implementations.Activity
/// </summary>
public class ActivityManager : IActivityManager
{
- private JellyfinDbProvider _provider;
+ private readonly JellyfinDbProvider _provider;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityManager"/> class.
@@ -50,31 +49,31 @@ namespace Jellyfin.Server.Implementations.Activity
/// <inheritdoc/>
public QueryResult<ActivityLogEntry> GetPagedResult(
- Func<IQueryable<ActivityLog>, IEnumerable<ActivityLog>> func,
+ Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>> func,
int? startIndex,
int? limit)
{
using var dbContext = _provider.CreateContext();
- var result = func.Invoke(dbContext.ActivityLogs).AsQueryable();
+ var query = func(dbContext.ActivityLogs.OrderByDescending(entry => entry.DateCreated));
if (startIndex.HasValue)
{
- result = result.Where(entry => entry.Id >= startIndex.Value);
+ query = query.Skip(startIndex.Value);
}
if (limit.HasValue)
{
- result = result.OrderByDescending(entry => entry.DateCreated).Take(limit.Value);
+ query = query.Take(limit.Value);
}
// This converts the objects from the new database model to the old for compatibility with the existing API.
- var list = result.Select(entry => ConvertToOldModel(entry)).ToList();
+ var list = query.Select(ConvertToOldModel).ToList();
- return new QueryResult<ActivityLogEntry>()
+ return new QueryResult<ActivityLogEntry>
{
Items = list,
- TotalRecordCount = list.Count
+ TotalRecordCount = func(dbContext.ActivityLogs).Count()
};
}
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 7ff978698..09310f98c 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -28,6 +28,14 @@
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.3">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ </ItemGroup>
+
+ <ItemGroup>
<ProjectReference Include="..\Jellyfin.Data\Jellyfin.Data.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs
index 35003c1ae..9ac97a131 100644
--- a/Jellyfin.Server.Implementations/JellyfinDb.cs
+++ b/Jellyfin.Server.Implementations/JellyfinDb.cs
@@ -107,9 +107,10 @@ namespace Jellyfin.Server.Implementations
public override int SaveChanges()
{
- foreach (var entity in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified))
+ foreach (var saveEntity in ChangeTracker.Entries()
+ .Where(e => e.State == EntityState.Modified)
+ .OfType<ISavingChanges>())
{
- var saveEntity = entity.Entity as ISavingChanges;
saveEntity.OnSavingChanges();
}
diff --git a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs
index 8fdeab088..eab531d38 100644
--- a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs
+++ b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs
@@ -27,7 +27,7 @@ namespace Jellyfin.Server.Implementations
/// <returns>The newly created context.</returns>
public JellyfinDb CreateContext()
{
- return _serviceProvider.GetService<JellyfinDb>();
+ return _serviceProvider.GetRequiredService<JellyfinDb>();
}
}
}
diff --git a/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs
index e1ee9b34a..98a83b745 100644
--- a/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs
@@ -1,5 +1,4 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1601
+#pragma warning disable CS1591
// <auto-generated />
using System;
@@ -12,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
[DbContext(typeof(JellyfinDb))]
- [Migration("20200502231229_InitialSchema")]
- partial class InitialSchema
+ [Migration("20200514181226_AddActivityLog")]
+ partial class AddActivityLog
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
@@ -65,7 +64,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasKey("Id");
- b.ToTable("ActivityLog");
+ b.ToTable("ActivityLogs");
});
#pragma warning restore 612, 618
}
diff --git a/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.cs b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.cs
index 42fac865c..5e0b454d8 100644
--- a/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
#pragma warning disable SA1601
using System;
@@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace Jellyfin.Server.Implementations.Migrations
{
- public partial class InitialSchema : Migration
+ public partial class AddActivityLog : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
@@ -14,7 +14,7 @@ namespace Jellyfin.Server.Implementations.Migrations
name: "jellyfin");
migrationBuilder.CreateTable(
- name: "ActivityLog",
+ name: "ActivityLogs",
schema: "jellyfin",
columns: table => new
{
@@ -32,14 +32,14 @@ namespace Jellyfin.Server.Implementations.Migrations
},
constraints: table =>
{
- table.PrimaryKey("PK_ActivityLog", x => x.Id);
+ table.PrimaryKey("PK_ActivityLogs", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
- name: "ActivityLog",
+ name: "ActivityLogs",
schema: "jellyfin");
}
}
diff --git a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs
index 23a0fdc78..72a4a8c3b 100644
--- a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs
+++ b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs
@@ -1,6 +1,3 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1601
-
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs
index d3a7ed333..0fb0ba803 100644
--- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs
+++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs
@@ -1,9 +1,7 @@
// <auto-generated />
using System;
-using Jellyfin.Server.Implementations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
@@ -60,7 +58,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasKey("Id");
- b.ToTable("ActivityLog");
+ b.ToTable("ActivityLogs");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index f678e714c..331a32c73 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -1,12 +1,17 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Reflection;
using Emby.Drawing;
using Emby.Server.Implementations;
using Jellyfin.Drawing.Skia;
+using Jellyfin.Server.Implementations;
+using Jellyfin.Server.Implementations.Activity;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -56,6 +61,15 @@ namespace Jellyfin.Server
Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
}
+ // TODO: Set up scoping and use AddDbContextPool
+ serviceCollection.AddDbContext<JellyfinDb>(
+ options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
+ ServiceLifetime.Transient);
+
+ serviceCollection.AddSingleton<JellyfinDbProvider>();
+
+ serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
+
base.RegisterServices(serviceCollection);
}
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 51b2a8cc6..ea16c5573 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -13,9 +13,6 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
-
- <!-- Used for generating migrations for EF Core -->
- <GenerateRuntimeConfigurationFiles>True</GenerateRuntimeConfigurationFiles>
</PropertyGroup>
<ItemGroup>
@@ -45,10 +42,6 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.7.82" />
<PackageReference Include="Json.Net" Version="1.0.22" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
- </PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.3" />
<PackageReference Include="prometheus-net" Version="3.5.0" />
@@ -68,6 +61,7 @@
<ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj" />
<ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" />
<ProjectReference Include="..\Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj" />
+ <ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
</ItemGroup>
</Project>
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs
index fb793a7cf..c98263223 100644
--- a/Jellyfin.Server/Migrations/MigrationRunner.cs
+++ b/Jellyfin.Server/Migrations/MigrationRunner.cs
@@ -19,6 +19,7 @@ namespace Jellyfin.Server.Migrations
typeof(Routines.DisableTranscodingThrottling),
typeof(Routines.CreateUserLoggingConfigFile),
typeof(Routines.MigrateActivityLogDb),
+ typeof(Routines.RemoveDuplicateExtras),
typeof(Routines.MigrateUserDb)
};
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
index fe7ef01ee..b3cc29708 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
@@ -1,7 +1,7 @@
-#pragma warning disable CS1591
-
using System;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
using Emby.Server.Implementations.Data;
using Jellyfin.Data.Entities;
using Jellyfin.Server.Implementations;
@@ -12,6 +12,9 @@ using SQLitePCL.pretty;
namespace Jellyfin.Server.Migrations.Routines
{
+ /// <summary>
+ /// The migration routine for migrating the activity log database to EF Core.
+ /// </summary>
public class MigrateActivityLogDb : IMigrationRoutine
{
private const string DbFilename = "activitylog.db";
@@ -20,6 +23,12 @@ namespace Jellyfin.Server.Migrations.Routines
private readonly JellyfinDbProvider _provider;
private readonly IServerApplicationPaths _paths;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MigrateActivityLogDb"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="paths">The server application paths.</param>
+ /// <param name="provider">The database provider.</param>
public MigrateActivityLogDb(ILogger<MigrateActivityLogDb> logger, IServerApplicationPaths paths, JellyfinDbProvider provider)
{
_logger = logger;
@@ -27,19 +36,35 @@ namespace Jellyfin.Server.Migrations.Routines
_paths = paths;
}
+ /// <inheritdoc/>
public Guid Id => Guid.Parse("3793eb59-bc8c-456c-8b9f-bd5a62a42978");
+ /// <inheritdoc/>
public string Name => "MigrateActivityLogDatabase";
+ /// <inheritdoc/>
public void Perform()
{
+ var logLevelDictionary = new Dictionary<string, LogLevel>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "None", LogLevel.None },
+ { "Trace", LogLevel.Trace },
+ { "Debug", LogLevel.Debug },
+ { "Information", LogLevel.Information },
+ { "Info", LogLevel.Information },
+ { "Warn", LogLevel.Warning },
+ { "Warning", LogLevel.Warning },
+ { "Error", LogLevel.Error },
+ { "Critical", LogLevel.Critical }
+ };
+
var dataPath = _paths.DataPath;
using (var connection = SQLite3.Open(
Path.Combine(dataPath, DbFilename),
ConnectionFlags.ReadOnly,
null))
{
- _logger.LogInformation("Migrating the database may take a while, do not stop Jellyfin.");
+ _logger.LogWarning("Migrating the activity database may take a while, do not stop Jellyfin.");
using var dbContext = _provider.CreateContext();
var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id ASC");
@@ -51,14 +76,21 @@ namespace Jellyfin.Server.Migrations.Routines
dbContext.Database.ExecuteSqlRaw("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'ActivityLog';");
dbContext.SaveChanges();
- foreach (var entry in queryResult)
+ var newEntries = queryResult.Select(entry =>
{
+ if (!logLevelDictionary.TryGetValue(entry[8].ToString(), out var severity))
+ {
+ severity = LogLevel.Trace;
+ }
+
var newEntry = new ActivityLog(
entry[1].ToString(),
entry[4].ToString(),
- entry[6].SQLiteType == SQLiteType.Null ? Guid.Empty : Guid.Parse(entry[6].ToString()),
- entry[7].ReadDateTime(),
- ParseLogLevel(entry[8].ToString()));
+ entry[6].SQLiteType == SQLiteType.Null ? Guid.Empty : Guid.Parse(entry[6].ToString()))
+ {
+ DateCreated = entry[7].ReadDateTime(),
+ LogSeverity = severity
+ };
if (entry[2].SQLiteType != SQLiteType.Null)
{
@@ -75,46 +107,27 @@ namespace Jellyfin.Server.Migrations.Routines
newEntry.ItemId = entry[5].ToString();
}
- dbContext.ActivityLogs.Add(newEntry);
- dbContext.SaveChanges();
- }
+ return newEntry;
+ });
+
+ dbContext.ActivityLogs.AddRange(newEntries);
+ dbContext.SaveChanges();
}
try
{
File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
+
+ var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
+ if (File.Exists(journalPath))
+ {
+ File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
+ }
}
catch (IOException e)
{
_logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'");
}
}
-
- private LogLevel ParseLogLevel(string entry)
- {
- if (string.Equals(entry, "Debug", StringComparison.OrdinalIgnoreCase))
- {
- return LogLevel.Debug;
- }
-
- if (string.Equals(entry, "Information", StringComparison.OrdinalIgnoreCase)
- || string.Equals(entry, "Info", StringComparison.OrdinalIgnoreCase))
- {
- return LogLevel.Information;
- }
-
- if (string.Equals(entry, "Warning", StringComparison.OrdinalIgnoreCase)
- || string.Equals(entry, "Warn", StringComparison.OrdinalIgnoreCase))
- {
- return LogLevel.Warning;
- }
-
- if (string.Equals(entry, "Error", StringComparison.OrdinalIgnoreCase))
- {
- return LogLevel.Error;
- }
-
- return LogLevel.Trace;
- }
}
}
diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs
new file mode 100644
index 000000000..e95536388
--- /dev/null
+++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Globalization;
+using System.IO;
+
+using MediaBrowser.Controller;
+using Microsoft.Extensions.Logging;
+using SQLitePCL.pretty;
+
+namespace Jellyfin.Server.Migrations.Routines
+{
+ /// <summary>
+ /// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself.
+ /// </summary>
+ internal class RemoveDuplicateExtras : IMigrationRoutine
+ {
+ private const string DbFilename = "library.db";
+ private readonly ILogger _logger;
+ private readonly IServerApplicationPaths _paths;
+
+ public RemoveDuplicateExtras(ILogger<RemoveDuplicateExtras> logger, IServerApplicationPaths paths)
+ {
+ _logger = logger;
+ _paths = paths;
+ }
+
+ /// <inheritdoc/>
+ public Guid Id => Guid.Parse("{ACBE17B7-8435-4A83-8B64-6FCF162CB9BD}");
+
+ /// <inheritdoc/>
+ public string Name => "RemoveDuplicateExtras";
+
+ /// <inheritdoc/>
+ public void Perform()
+ {
+ var dataPath = _paths.DataPath;
+ var dbPath = Path.Combine(dataPath, DbFilename);
+ using (var connection = SQLite3.Open(
+ dbPath,
+ ConnectionFlags.ReadWrite,
+ null))
+ {
+ // Query the database for the ids of duplicate extras
+ var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'");
+ var bads = string.Join(", ", queryResult.SelectScalarString());
+
+ // Do nothing if no duplicate extras were detected
+ if (bads.Length == 0)
+ {
+ _logger.LogInformation("No duplicate extras detected, skipping migration.");
+ return;
+ }
+
+ // Back up the database before deleting any entries
+ for (int i = 1; ; i++)
+ {
+ var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
+ if (!File.Exists(bakPath))
+ {
+ try
+ {
+ File.Copy(dbPath, bakPath);
+ _logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
+ break;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
+ throw;
+ }
+ }
+ }
+
+ // Delete all duplicate extras
+ _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads);
+ connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')");
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index ae423532e..b9895386f 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -10,14 +10,11 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
-using Emby.Drawing;
using Emby.Server.Implementations;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.Networking;
-using Jellyfin.Drawing.Skia;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.WebDashboard.Api;
using Microsoft.AspNetCore.Hosting;
@@ -297,7 +294,7 @@ namespace Jellyfin.Server
{
_logger.LogInformation("Kestrel listening on {IpAddress}", address);
options.Listen(address, appHost.HttpPort);
- if (appHost.EnableHttps && appHost.Certificate != null)
+ if (appHost.ListenWithHttps)
{
options.Listen(address, appHost.HttpsPort, listenOptions =>
{
@@ -327,7 +324,7 @@ namespace Jellyfin.Server
_logger.LogInformation("Kestrel listening on all interfaces");
options.ListenAnyIP(appHost.HttpPort);
- if (appHost.EnableHttps && appHost.Certificate != null)
+ if (appHost.ListenWithHttps)
{
options.ListenAnyIP(appHost.HttpsPort, listenOptions =>
{
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 8bcfd1350..5f9a5c161 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -64,7 +64,6 @@ namespace Jellyfin.Server
app.UseResponseCompression();
// TODO app.UseMiddleware<WebSocketMiddleware>();
- app.Use(serverApplicationHost.ExecuteWebsocketHandlerAsync);
// TODO use when old API is removed: app.UseAuthentication();
app.UseJellyfinApiSwagger();
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs
index a5b504dfa..2ece16ee1 100644
--- a/MediaBrowser.Api/BaseApiService.cs
+++ b/MediaBrowser.Api/BaseApiService.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Api
public abstract class BaseApiService : IService, IRequiresRequest
{
public BaseApiService(
- ILogger logger,
+ ILogger<BaseApiService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory)
{
@@ -35,7 +35,7 @@ namespace MediaBrowser.Api
/// Gets the logger.
/// </summary>
/// <value>The logger.</value>
- protected ILogger Logger { get; }
+ protected ILogger<BaseApiService> Logger { get; }
/// <summary>
/// Gets or sets the server configuration manager.
diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs
index 7004a2559..53eb9667d 100644
--- a/MediaBrowser.Api/Devices/DeviceService.cs
+++ b/MediaBrowser.Api/Devices/DeviceService.cs
@@ -1,5 +1,4 @@
using System.IO;
-using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Net;
@@ -116,11 +115,6 @@ namespace MediaBrowser.Api.Devices
return _deviceManager.GetDeviceOptions(request.Id);
}
- public object Get(GetCameraUploads request)
- {
- return ToOptimizedResult(_deviceManager.GetCameraUploadHistory(request.DeviceId));
- }
-
public void Delete(DeleteDevice request)
{
var sessions = _authRepo.Get(new AuthenticationInfoQuery
@@ -134,35 +128,5 @@ namespace MediaBrowser.Api.Devices
_sessionManager.Logout(session);
}
}
-
- public Task Post(PostCameraUpload request)
- {
- var deviceId = Request.QueryString["DeviceId"];
- var album = Request.QueryString["Album"];
- var id = Request.QueryString["Id"];
- var name = Request.QueryString["Name"];
- var req = Request.Response.HttpContext.Request;
-
- if (req.HasFormContentType)
- {
- var file = req.Form.Files.Count == 0 ? null : req.Form.Files[0];
-
- return _deviceManager.AcceptCameraUpload(deviceId, file.OpenReadStream(), new LocalFileInfo
- {
- MimeType = file.ContentType,
- Album = album,
- Name = name,
- Id = id
- });
- }
-
- return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
- {
- MimeType = Request.ContentType,
- Album = album,
- Name = name,
- Id = id
- });
- }
}
}
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index 783fc6073..50977c48f 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -319,11 +319,14 @@ namespace MediaBrowser.Api.Library
private readonly ILocalizationManager _localization;
private readonly ILibraryMonitor _libraryMonitor;
+ private readonly ILogger<MoviesService> _moviesServiceLogger;
+
/// <summary>
/// Initializes a new instance of the <see cref="LibraryService" /> class.
/// </summary>
public LibraryService(
ILogger<LibraryService> logger,
+ ILogger<MoviesService> moviesServiceLogger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IProviderManager providerManager,
@@ -344,6 +347,7 @@ namespace MediaBrowser.Api.Library
_activityManager = activityManager;
_localization = localization;
_libraryMonitor = libraryMonitor;
+ _moviesServiceLogger = moviesServiceLogger;
}
private string[] GetRepresentativeItemTypes(string contentType)
@@ -543,7 +547,7 @@ namespace MediaBrowser.Api.Library
if (item is Movie || (program != null && program.IsMovie) || item is Trailer)
{
return new MoviesService(
- Logger,
+ _moviesServiceLogger,
ServerConfigurationManager,
ResultFactory,
_userManager,
@@ -762,9 +766,7 @@ namespace MediaBrowser.Api.Library
_activityManager.Create(new Jellyfin.Data.Entities.ActivityLog(
string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name),
"UserDownloadingContent",
- auth.UserId,
- DateTime.UtcNow,
- LogLevel.Trace)
+ auth.UserId)
{
ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
});
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs
index b97a0dca1..aaabe07f3 100644
--- a/MediaBrowser.Api/Movies/MoviesService.cs
+++ b/MediaBrowser.Api/Movies/MoviesService.cs
@@ -82,7 +82,7 @@ namespace MediaBrowser.Api.Movies
/// Initializes a new instance of the <see cref="MoviesService" /> class.
/// </summary>
public MoviesService(
- ILogger logger,
+ ILogger<MoviesService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs
index 8adf9c621..0b5334235 100644
--- a/MediaBrowser.Api/Movies/TrailersService.cs
+++ b/MediaBrowser.Api/Movies/TrailersService.cs
@@ -33,13 +33,18 @@ namespace MediaBrowser.Api.Movies
/// </summary>
private readonly ILibraryManager _libraryManager;
+ /// <summary>
+ /// The logger for the created <see cref="ItemsService"/> instances.
+ /// </summary>
+ private readonly ILogger<ItemsService> _logger;
+
private readonly IDtoService _dtoService;
private readonly ILocalizationManager _localizationManager;
private readonly IJsonSerializer _json;
private readonly IAuthorizationContext _authContext;
public TrailersService(
- ILogger<TrailersService> logger,
+ ILoggerFactory loggerFactory,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
@@ -48,7 +53,7 @@ namespace MediaBrowser.Api.Movies
ILocalizationManager localizationManager,
IJsonSerializer json,
IAuthorizationContext authContext)
- : base(logger, serverConfigurationManager, httpResultFactory)
+ : base(loggerFactory.CreateLogger<TrailersService>(), serverConfigurationManager, httpResultFactory)
{
_userManager = userManager;
_libraryManager = libraryManager;
@@ -56,6 +61,7 @@ namespace MediaBrowser.Api.Movies
_localizationManager = localizationManager;
_json = json;
_authContext = authContext;
+ _logger = loggerFactory.CreateLogger<ItemsService>();
}
public object Get(Getrailers request)
@@ -66,7 +72,7 @@ namespace MediaBrowser.Api.Movies
getItems.IncludeItemTypes = "Trailer";
return new ItemsService(
- Logger,
+ _logger,
ServerConfigurationManager,
ResultFactory,
_userManager,
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 65f6ccd4d..a9824a5e7 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -82,7 +82,7 @@ namespace MediaBrowser.Api.Playback
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
protected BaseStreamingService(
- ILogger logger,
+ ILogger<BaseStreamingService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 4213193ba..627421aac 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -25,7 +25,7 @@ namespace MediaBrowser.Api.Playback.Hls
public abstract class BaseHlsService : BaseStreamingService
{
public BaseHlsService(
- ILogger logger,
+ ILogger<BaseHlsService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 7f74e85e9..061316cb8 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -94,7 +94,7 @@ namespace MediaBrowser.Api.Playback.Hls
public class DynamicHlsService : BaseHlsService
{
public DynamicHlsService(
- ILogger logger,
+ ILogger<DynamicHlsService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index 0ef39c2d7..2c6534cc0 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -80,7 +80,7 @@ namespace MediaBrowser.Api.Playback
private readonly IAuthorizationContext _authContext;
public MediaInfoService(
- ILogger logger,
+ ILogger<MediaInfoService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IMediaSourceManager mediaSourceManager,
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
index 8d1e3a3f2..34c7986ca 100644
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
@@ -33,7 +33,7 @@ namespace MediaBrowser.Api.Playback.Progressive
public class AudioService : BaseProgressiveStreamingService
{
public AudioService(
- ILogger logger,
+ ILogger<AudioService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IHttpClient httpClient,
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index ed68219c9..c7bf055fb 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -28,7 +28,7 @@ namespace MediaBrowser.Api.Playback.Progressive
protected IHttpClient HttpClient { get; private set; }
public BaseProgressiveStreamingService(
- ILogger logger,
+ ILogger<BaseProgressiveStreamingService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IHttpClient httpClient,
diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs
index cebd4b49a..a3b319d44 100644
--- a/MediaBrowser.Api/Playback/UniversalAudioService.cs
+++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs
@@ -75,9 +75,11 @@ namespace MediaBrowser.Api.Playback
public class UniversalAudioService : BaseApiService
{
private readonly EncodingHelper _encodingHelper;
+ private readonly ILoggerFactory _loggerFactory;
public UniversalAudioService(
ILogger<UniversalAudioService> logger,
+ ILoggerFactory loggerFactory,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IHttpClient httpClient,
@@ -108,6 +110,7 @@ namespace MediaBrowser.Api.Playback
AuthorizationContext = authorizationContext;
NetworkManager = networkManager;
_encodingHelper = encodingHelper;
+ _loggerFactory = loggerFactory;
}
protected IHttpClient HttpClient { get; private set; }
@@ -233,7 +236,7 @@ namespace MediaBrowser.Api.Playback
AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId;
var mediaInfoService = new MediaInfoService(
- Logger,
+ _loggerFactory.CreateLogger<MediaInfoService>(),
ServerConfigurationManager,
ResultFactory,
MediaSourceManager,
@@ -277,7 +280,7 @@ namespace MediaBrowser.Api.Playback
if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{
var service = new DynamicHlsService(
- Logger,
+ _loggerFactory.CreateLogger<DynamicHlsService>(),
ServerConfigurationManager,
ResultFactory,
UserManager,
@@ -331,7 +334,7 @@ namespace MediaBrowser.Api.Playback
else
{
var service = new AudioService(
- Logger,
+ _loggerFactory.CreateLogger<AudioService>(),
ServerConfigurationManager,
ResultFactory,
HttpClient,
diff --git a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs
index d882aac88..0e74c9267 100644
--- a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs
+++ b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs
@@ -31,46 +31,46 @@ namespace MediaBrowser.Api.Sessions
{
_sessionManager = sessionManager;
- _sessionManager.SessionStarted += _sessionManager_SessionStarted;
- _sessionManager.SessionEnded += _sessionManager_SessionEnded;
- _sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
- _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped;
- _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress;
- _sessionManager.CapabilitiesChanged += _sessionManager_CapabilitiesChanged;
- _sessionManager.SessionActivity += _sessionManager_SessionActivity;
+ _sessionManager.SessionStarted += OnSessionManagerSessionStarted;
+ _sessionManager.SessionEnded += OnSessionManagerSessionEnded;
+ _sessionManager.PlaybackStart += OnSessionManagerPlaybackStart;
+ _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped;
+ _sessionManager.PlaybackProgress += OnSessionManagerPlaybackProgress;
+ _sessionManager.CapabilitiesChanged += OnSessionManagerCapabilitiesChanged;
+ _sessionManager.SessionActivity += OnSessionManagerSessionActivity;
}
- void _sessionManager_SessionActivity(object sender, SessionEventArgs e)
+ private void OnSessionManagerSessionActivity(object sender, SessionEventArgs e)
{
SendData(false);
}
- void _sessionManager_CapabilitiesChanged(object sender, SessionEventArgs e)
+ private void OnSessionManagerCapabilitiesChanged(object sender, SessionEventArgs e)
{
SendData(true);
}
- void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
+ private void OnSessionManagerPlaybackProgress(object sender, PlaybackProgressEventArgs e)
{
SendData(!e.IsAutomated);
}
- void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
+ private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e)
{
SendData(true);
}
- void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
+ private void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e)
{
SendData(true);
}
- void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
+ private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e)
{
SendData(true);
}
- void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
+ private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e)
{
SendData(true);
}
@@ -84,15 +84,16 @@ namespace MediaBrowser.Api.Sessions
return Task.FromResult(_sessionManager.Sessions);
}
+ /// <inheritdoc />
protected override void Dispose(bool dispose)
{
- _sessionManager.SessionStarted -= _sessionManager_SessionStarted;
- _sessionManager.SessionEnded -= _sessionManager_SessionEnded;
- _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart;
- _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped;
- _sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress;
- _sessionManager.CapabilitiesChanged -= _sessionManager_CapabilitiesChanged;
- _sessionManager.SessionActivity -= _sessionManager_SessionActivity;
+ _sessionManager.SessionStarted -= OnSessionManagerSessionStarted;
+ _sessionManager.SessionEnded -= OnSessionManagerSessionEnded;
+ _sessionManager.PlaybackStart -= OnSessionManagerPlaybackStart;
+ _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped;
+ _sessionManager.PlaybackProgress -= OnSessionManagerPlaybackProgress;
+ _sessionManager.CapabilitiesChanged -= OnSessionManagerCapabilitiesChanged;
+ _sessionManager.SessionActivity -= OnSessionManagerSessionActivity;
base.Dispose(dispose);
}
diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs
index 0a5fc9433..a6bacad4f 100644
--- a/MediaBrowser.Api/System/ActivityLogService.cs
+++ b/MediaBrowser.Api/System/ActivityLogService.cs
@@ -1,5 +1,7 @@
using System;
using System.Globalization;
+using System.Linq;
+using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Activity;
@@ -53,7 +55,10 @@ namespace MediaBrowser.Api.System
(DateTime?)null :
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
- var result = _activityManager.GetPagedResult(request.StartIndex, request.Limit);
+ var filterFunc = new Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>>(
+ entries => entries.Where(entry => entry.DateCreated >= minDate));
+
+ var result = _activityManager.GetPagedResult(filterFunc, request.StartIndex, request.Limit);
return ToOptimizedResult(result);
}
diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
index f8b6ee65d..8e4860be4 100644
--- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
+++ b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System;
using System.Threading.Tasks;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Activity;
@@ -10,7 +10,7 @@ namespace MediaBrowser.Api.System
/// <summary>
/// Class SessionInfoWebSocketListener
/// </summary>
- public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<List<ActivityLogEntry>, WebSocketListenerState>
+ public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<ActivityLogEntry[], WebSocketListenerState>
{
/// <summary>
/// Gets the name.
@@ -26,10 +26,10 @@ namespace MediaBrowser.Api.System
public ActivityLogWebSocketListener(ILogger<ActivityLogWebSocketListener> logger, IActivityManager activityManager) : base(logger)
{
_activityManager = activityManager;
- _activityManager.EntryCreated += _activityManager_EntryCreated;
+ _activityManager.EntryCreated += OnEntryCreated;
}
- void _activityManager_EntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
+ private void OnEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
{
SendData(true);
}
@@ -38,15 +38,15 @@ namespace MediaBrowser.Api.System
/// Gets the data to send.
/// </summary>
/// <returns>Task{SystemInfo}.</returns>
- protected override Task<List<ActivityLogEntry>> GetDataToSend()
+ protected override Task<ActivityLogEntry[]> GetDataToSend()
{
- return Task.FromResult(new List<ActivityLogEntry>());
+ return Task.FromResult(Array.Empty<ActivityLogEntry>());
}
-
+ /// <inheritdoc />
protected override void Dispose(bool dispose)
{
- _activityManager.EntryCreated -= _activityManager_EntryCreated;
+ _activityManager.EntryCreated -= OnEntryCreated;
base.Dispose(dispose);
}
diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
index 3d08d5437..bef91d54d 100644
--- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
@@ -51,7 +51,7 @@ namespace MediaBrowser.Api.UserLibrary
public class ArtistsService : BaseItemsByNameService<MusicArtist>
{
public ArtistsService(
- ILogger<GenresService> logger,
+ ILogger<ArtistsService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
index 75a33350e..11d984a9a 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
@@ -28,7 +28,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="userDataRepository">The user data repository.</param>
/// <param name="dtoService">The dto service.</param>
protected BaseItemsByNameService(
- ILogger logger,
+ ILogger<BaseItemsByNameService<TItemType>> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index cbceb3625..ca9f7aa82 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -60,7 +60,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="localization">The localization.</param>
/// <param name="dtoService">The dto service.</param>
public ItemsService(
- ILogger logger,
+ ILogger<ItemsService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs
index 117af110a..7d279230b 100644
--- a/MediaBrowser.Controller/Devices/IDeviceManager.cs
+++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs
@@ -1,6 +1,4 @@
using System;
-using System.IO;
-using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Events;
@@ -12,11 +10,6 @@ namespace MediaBrowser.Controller.Devices
public interface IDeviceManager
{
/// <summary>
- /// Occurs when [camera image uploaded].
- /// </summary>
- event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
-
- /// <summary>
/// Saves the capabilities.
/// </summary>
/// <param name="reportedId">The reported identifier.</param>
@@ -46,22 +39,6 @@ namespace MediaBrowser.Controller.Devices
QueryResult<DeviceInfo> GetDevices(DeviceQuery query);
/// <summary>
- /// Gets the upload history.
- /// </summary>
- /// <param name="deviceId">The device identifier.</param>
- /// <returns>ContentUploadHistory.</returns>
- ContentUploadHistory GetCameraUploadHistory(string deviceId);
-
- /// <summary>
- /// Accepts the upload.
- /// </summary>
- /// <param name="deviceId">The device identifier.</param>
- /// <param name="stream">The stream.</param>
- /// <param name="file">The file.</param>
- /// <returns>Task.</returns>
- Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file);
-
- /// <summary>
/// Determines whether this instance [can access device] the specified user identifier.
/// </summary>
bool CanAccessDevice(User user, string deviceId);
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index 04ba0fabc..d1d6c74b8 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -39,10 +39,9 @@ namespace MediaBrowser.Controller
int HttpsPort { get; }
/// <summary>
- /// Gets a value indicating whether [supports HTTPS].
+ /// Gets a value indicating whether the server should listen on an HTTPS port.
/// </summary>
- /// <value><c>true</c> if [supports HTTPS]; otherwise, <c>false</c>.</value>
- bool EnableHttps { get; }
+ bool ListenWithHttps { get; }
/// <summary>
/// Gets a value indicating whether this instance has update available.
@@ -57,34 +56,50 @@ namespace MediaBrowser.Controller
string FriendlyName { get; }
/// <summary>
- /// Gets the local ip address.
+ /// Gets all the local IP addresses of this API instance. Each address is validated by sending a 'ping' request
+ /// to the API that should exist at the address.
/// </summary>
- /// <value>The local ip address.</value>
+ /// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param>
+ /// <returns>A list containing all the local IP addresses of the server.</returns>
Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken);
/// <summary>
- /// Gets the local API URL.
+ /// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured
+ /// IP address that can be found via <see cref="GetLocalIpAddresses"/>. HTTPS will be preferred when available.
/// </summary>
- /// <param name="cancellationToken">Token to cancel the request if needed.</param>
- /// <param name="forceHttp">Whether to force usage of plain HTTP protocol.</param>
- /// <value>The local API URL.</value>
- Task<string> GetLocalApiUrl(CancellationToken cancellationToken, bool forceHttp = false);
+ /// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param>
+ /// <returns>The server URL.</returns>
+ Task<string> GetLocalApiUrl(CancellationToken cancellationToken);
/// <summary>
- /// Gets the local API URL.
+ /// Gets a localhost URL that can be used to access the API using the loop-back IP address (127.0.0.1)
+ /// over HTTP (not HTTPS).
/// </summary>
- /// <param name="hostname">The hostname.</param>
- /// <param name="forceHttp">Whether to force usage of plain HTTP protocol.</param>
- /// <returns>The local API URL.</returns>
- string GetLocalApiUrl(ReadOnlySpan<char> hostname, bool forceHttp = false);
+ /// <returns>The API URL.</returns>
+ string GetLoopbackHttpApiUrl();
/// <summary>
- /// Gets the local API URL.
+ /// Gets a local (LAN) URL that can be used to access the API. HTTPS will be preferred when available.
/// </summary>
- /// <param name="address">The IP address.</param>
- /// <param name="forceHttp">Whether to force usage of plain HTTP protocol.</param>
- /// <returns>The local API URL.</returns>
- string GetLocalApiUrl(IPAddress address, bool forceHttp = false);
+ /// <param name="address">The IP address to use as the hostname in the URL.</param>
+ /// <returns>The API URL.</returns>
+ string GetLocalApiUrl(IPAddress address);
+
+ /// <summary>
+ /// Gets a local (LAN) URL that can be used to access the API.
+ /// Note: if passing non-null scheme or port it is up to the caller to ensure they form the correct pair.
+ /// </summary>
+ /// <param name="hostname">The hostname to use in the URL.</param>
+ /// <param name="scheme">
+ /// The scheme to use for the URL. If null, the scheme will be selected automatically,
+ /// preferring HTTPS, if available.
+ /// </param>
+ /// <param name="port">
+ /// The port to use for the URL. If null, the port will be selected automatically,
+ /// preferring the HTTPS port, if available.
+ /// </param>
+ /// <returns>The API URL.</returns>
+ string GetLocalApiUrl(ReadOnlySpan<char> hostname, string scheme = null, int? port = null);
/// <summary>
/// Open a URL in an external browser window.
@@ -101,7 +116,5 @@ namespace MediaBrowser.Controller
string ReverseVirtualPath(string path);
Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next);
-
- Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next);
}
}
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index b710318ee..1162bff13 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -77,8 +77,6 @@ namespace MediaBrowser.Controller.Net
return Task.CompletedTask;
}
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
/// <summary>
/// Starts sending messages over a web socket
/// </summary>
@@ -87,12 +85,12 @@ namespace MediaBrowser.Controller.Net
{
var vals = message.Data.Split(',');
- var dueTimeMs = long.Parse(vals[0], UsCulture);
- var periodMs = long.Parse(vals[1], UsCulture);
+ var dueTimeMs = long.Parse(vals[0], CultureInfo.InvariantCulture);
+ var periodMs = long.Parse(vals[1], CultureInfo.InvariantCulture);
var cancellationTokenSource = new CancellationTokenSource();
- Logger.LogDebug("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name);
+ Logger.LogDebug("WS {1} begin transmitting to {0}", message.Connection.RemoteEndPoint, GetType().Name);
var state = new TStateType
{
@@ -154,7 +152,6 @@ namespace MediaBrowser.Controller.Net
{
MessageType = Name,
Data = data
-
}, cancellationToken).ConfigureAwait(false);
state.DateLastSendUtc = DateTime.UtcNow;
@@ -197,7 +194,7 @@ namespace MediaBrowser.Controller.Net
/// <param name="connection">The connection.</param>
private void DisposeConnection(Tuple<IWebSocketConnection, CancellationTokenSource, TStateType> connection)
{
- Logger.LogDebug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name);
+ Logger.LogDebug("WS {1} stop transmitting to {0}", connection.Item1.RemoteEndPoint, GetType().Name);
// TODO disposing the connection seems to break websockets in subtle ways, so what is the purpose of this function really...
// connection.Item1.Dispose();
@@ -242,6 +239,7 @@ namespace MediaBrowser.Controller.Net
public void Dispose()
{
Dispose(true);
+ GC.SuppressFinalize(this);
}
}
diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs
index 806478864..efb5f4ac3 100644
--- a/MediaBrowser.Controller/Net/IHttpServer.cs
+++ b/MediaBrowser.Controller/Net/IHttpServer.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Services;
@@ -9,9 +8,9 @@ using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Net
{
/// <summary>
- /// Interface IHttpServer
+ /// Interface IHttpServer.
/// </summary>
- public interface IHttpServer : IDisposable
+ public interface IHttpServer
{
/// <summary>
/// Gets the URL prefix.
@@ -20,11 +19,6 @@ namespace MediaBrowser.Controller.Net
string[] UrlPrefixes { get; }
/// <summary>
- /// Stops this instance.
- /// </summary>
- void Stop();
-
- /// <summary>
/// Occurs when [web socket connected].
/// </summary>
event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
@@ -40,22 +34,17 @@ namespace MediaBrowser.Controller.Net
string GlobalResponse { get; set; }
/// <summary>
- /// Sends the http context to the socket listener
+ /// The HTTP request handler
/// </summary>
- /// <param name="ctx"></param>
+ /// <param name="context"></param>
/// <returns></returns>
- Task ProcessWebSocketRequest(HttpContext ctx);
+ Task RequestHandler(HttpContext context);
/// <summary>
- /// The HTTP request handler
+ /// Get the default CORS headers
/// </summary>
- /// <param name="httpReq"></param>
- /// <param name="urlString"></param>
- /// <param name="host"></param>
- /// <param name="localPath"></param>
- /// <param name="cancellationToken"></param>
+ /// <param name="req"></param>
/// <returns></returns>
- Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath,
- CancellationToken cancellationToken);
+ IDictionary<string, string> GetDefaultCorsHeaders(IRequest req);
}
}
diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
index 31eb7ccb7..09e43c683 100644
--- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
@@ -1,4 +1,7 @@
+#nullable enable
+
using System;
+using System.Net;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
@@ -7,18 +10,12 @@ using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Net
{
- public interface IWebSocketConnection : IDisposable
+ public interface IWebSocketConnection
{
/// <summary>
/// Occurs when [closed].
/// </summary>
- event EventHandler<EventArgs> Closed;
-
- /// <summary>
- /// Gets the id.
- /// </summary>
- /// <value>The id.</value>
- Guid Id { get; }
+ event EventHandler<EventArgs>? Closed;
/// <summary>
/// Gets the last activity date.
@@ -27,21 +24,16 @@ namespace MediaBrowser.Controller.Net
DateTime LastActivityDate { get; }
/// <summary>
- /// Gets or sets the URL.
- /// </summary>
- /// <value>The URL.</value>
- string Url { get; set; }
- /// <summary>
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
- IQueryCollection QueryString { get; set; }
+ IQueryCollection QueryString { get; }
/// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
- Func<WebSocketMessageInfo, Task> OnReceive { get; set; }
+ Func<WebSocketMessageInfo, Task>? OnReceive { get; set; }
/// <summary>
/// Gets the state.
@@ -53,7 +45,7 @@ namespace MediaBrowser.Controller.Net
/// Gets the remote end point.
/// </summary>
/// <value>The remote end point.</value>
- string RemoteEndPoint { get; }
+ IPAddress? RemoteEndPoint { get; }
/// <summary>
/// Sends a message asynchronously.
@@ -65,21 +57,6 @@ namespace MediaBrowser.Controller.Net
/// <exception cref="ArgumentNullException">message</exception>
Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken);
- /// <summary>
- /// Sends a message asynchronously.
- /// </summary>
- /// <param name="buffer">The buffer.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task SendAsync(byte[] buffer, CancellationToken cancellationToken);
-
- /// <summary>
- /// Sends a message asynchronously.
- /// </summary>
- /// <param name="text">The text.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- /// <exception cref="ArgumentNullException">buffer</exception>
- Task SendAsync(string text, CancellationToken cancellationToken);
+ Task ProcessAsync(CancellationToken cancellationToken = default);
}
}
diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs
index a59c96ac7..04450085b 100644
--- a/MediaBrowser.Controller/Session/ISessionController.cs
+++ b/MediaBrowser.Controller/Session/ISessionController.cs
@@ -1,3 +1,4 @@
+using System;
using System.Threading;
using System.Threading.Tasks;
@@ -20,6 +21,6 @@ namespace MediaBrowser.Controller.Session
/// <summary>
/// Sends the message.
/// </summary>
- Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken);
+ Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs
index f1f10a3a3..2ba7c9fec 100644
--- a/MediaBrowser.Controller/Session/SessionInfo.cs
+++ b/MediaBrowser.Controller/Session/SessionInfo.cs
@@ -10,13 +10,23 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Session
{
/// <summary>
- /// Class SessionInfo
+ /// Class SessionInfo.
/// </summary>
- public class SessionInfo : IDisposable
+ public sealed class SessionInfo : IDisposable
{
- private ISessionManager _sessionManager;
+ // 1 second
+ private const long ProgressIncrement = 10000000;
+
+ private readonly ISessionManager _sessionManager;
private readonly ILogger _logger;
+
+ private readonly object _progressLock = new object();
+ private Timer _progressTimer;
+ private PlaybackProgressInfo _lastProgressInfo;
+
+ private bool _disposed = false;
+
public SessionInfo(ISessionManager sessionManager, ILogger logger)
{
_sessionManager = sessionManager;
@@ -97,8 +107,6 @@ namespace MediaBrowser.Controller.Session
/// <value>The name of the device.</value>
public string DeviceName { get; set; }
- public string DeviceType { get; set; }
-
/// <summary>
/// Gets or sets the now playing item.
/// </summary>
@@ -128,22 +136,6 @@ namespace MediaBrowser.Controller.Session
[JsonIgnore]
public ISessionController[] SessionControllers { get; set; }
- /// <summary>
- /// Gets or sets the supported commands.
- /// </summary>
- /// <value>The supported commands.</value>
- public string[] SupportedCommands
- {
- get
- {
- if (Capabilities == null)
- {
- return new string[] { };
- }
- return Capabilities.SupportedCommands;
- }
- }
-
public TranscodingInfo TranscodingInfo { get; set; }
/// <summary>
@@ -215,6 +207,14 @@ namespace MediaBrowser.Controller.Session
}
}
+ public QueueItem[] NowPlayingQueue { get; set; }
+
+ public bool HasCustomDeviceName { get; set; }
+
+ public string PlaylistItemId { get; set; }
+
+ public string UserPrimaryImageTag { get; set; }
+
public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
{
var controllers = SessionControllers.ToList();
@@ -258,10 +258,6 @@ namespace MediaBrowser.Controller.Session
return false;
}
- private readonly object _progressLock = new object();
- private Timer _progressTimer;
- private PlaybackProgressInfo _lastProgressInfo;
-
public void StartAutomaticProgress(PlaybackProgressInfo progressInfo)
{
if (_disposed)
@@ -284,9 +280,6 @@ namespace MediaBrowser.Controller.Session
}
}
- // 1 second
- private const long ProgressIncrement = 10000000;
-
private async void OnProgressTimerCallback(object state)
{
if (_disposed)
@@ -345,8 +338,7 @@ namespace MediaBrowser.Controller.Session
}
}
- private bool _disposed = false;
-
+ /// <inheritdoc />
public void Dispose()
{
_disposed = true;
@@ -358,30 +350,12 @@ namespace MediaBrowser.Controller.Session
foreach (var controller in controllers)
{
- var disposable = controller as IDisposable;
-
- if (disposable != null)
+ if (controller is IDisposable disposable)
{
_logger.LogDebug("Disposing session controller {0}", disposable.GetType().Name);
-
- try
- {
- disposable.Dispose();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error disposing session controller");
- }
+ disposable.Dispose();
}
}
-
- _sessionManager = null;
}
-
- public QueueItem[] NowPlayingQueue { get; set; }
- public bool HasCustomDeviceName { get; set; }
- public string PlaylistItemId { get; set; }
- public string ServerId { get; set; }
- public string UserPrimaryImageTag { get; set; }
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs
index 2e328ba63..de35acbba 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs
@@ -7,14 +7,25 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles
{
+ /// <summary>
+ /// Subtitle writer for the WebVTT format.
+ /// </summary>
public class VttWriter : ISubtitleWriter
{
+ /// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
{
writer.WriteLine("WEBVTT");
writer.WriteLine(string.Empty);
+ writer.WriteLine("REGION");
+ writer.WriteLine("id:subtitle");
+ writer.WriteLine("width:80%");
+ writer.WriteLine("lines:3");
+ writer.WriteLine("regionanchor:50%,100%");
+ writer.WriteLine("viewportanchor:50%,90%");
+ writer.WriteLine(string.Empty);
foreach (var trackEvent in info.TrackEvents)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -22,13 +33,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks);
var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks);
- // make sure the start and end times are different and seqential
+ // make sure the start and end times are different and sequential
if (endTime.TotalMilliseconds <= startTime.TotalMilliseconds)
{
endTime = startTime.Add(TimeSpan.FromMilliseconds(1));
}
- writer.WriteLine(@"{0:hh\:mm\:ss\.fff} --> {1:hh\:mm\:ss\.fff}", startTime, endTime);
+ writer.WriteLine(@"{0:hh\:mm\:ss\.fff} --> {1:hh\:mm\:ss\.fff} region:subtitle", startTime, endTime);
var text = trackEvent.Text;
diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs
index 6742dc8fc..9dab5e77b 100644
--- a/MediaBrowser.Model/Activity/IActivityManager.cs
+++ b/MediaBrowser.Model/Activity/IActivityManager.cs
@@ -1,7 +1,6 @@
#pragma warning disable CS1591
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
@@ -21,7 +20,7 @@ namespace MediaBrowser.Model.Activity
QueryResult<ActivityLogEntry> GetPagedResult(int? startIndex, int? limit);
QueryResult<ActivityLogEntry> GetPagedResult(
- Func<IQueryable<ActivityLog>, IEnumerable<ActivityLog>> func,
+ Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>> func,
int? startIndex,
int? limit);
}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 063ccd9b9..1f5981f10 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -49,17 +49,24 @@ namespace MediaBrowser.Model.Configuration
public int HttpsPortNumber { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether [use HTTPS].
+ /// Gets or sets a value indicating whether to use HTTPS.
/// </summary>
- /// <value><c>true</c> if [use HTTPS]; otherwise, <c>false</c>.</value>
+ /// <remarks>
+ /// In order for HTTPS to be used, in addition to setting this to true, valid values must also be
+ /// provided for <see cref="CertificatePath"/> and <see cref="CertificatePassword"/>.
+ /// </remarks>
public bool EnableHttps { get; set; }
+
public bool EnableNormalizedItemByNameIds { get; set; }
/// <summary>
- /// Gets or sets the value pointing to the file system where the ssl certificate is located..
+ /// Gets or sets the filesystem path of an X.509 certificate to use for SSL.
/// </summary>
- /// <value>The value pointing to the file system where the ssl certificate is located..</value>
public string CertificatePath { get; set; }
+
+ /// <summary>
+ /// Gets or sets the password required to access the X.509 certificate data in the file specified by <see cref="CertificatePath"/>.
+ /// </summary>
public string CertificatePassword { get; set; }
/// <summary>
@@ -69,8 +76,9 @@ namespace MediaBrowser.Model.Configuration
public bool IsPortAuthorized { get; set; }
public bool AutoRunWebApp { get; set; }
+
public bool EnableRemoteAccess { get; set; }
- public bool CameraUploadUpgraded { get; set; }
+
public bool CollectionsUpgraded { get; set; }
/// <summary>
@@ -86,6 +94,7 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value>The metadata path.</value>
public string MetadataPath { get; set; }
+
public string MetadataNetworkPath { get; set; }
/// <summary>
@@ -208,15 +217,26 @@ namespace MediaBrowser.Model.Configuration
public int RemoteClientBitrateLimit { get; set; }
public bool EnableFolderView { get; set; }
+
public bool EnableGroupingIntoCollections { get; set; }
+
public bool DisplaySpecialsWithinSeasons { get; set; }
+
public string[] LocalNetworkSubnets { get; set; }
+
public string[] LocalNetworkAddresses { get; set; }
+
public string[] CodecsUsed { get; set; }
+
public bool IgnoreVirtualInterfaces { get; set; }
+
public bool EnableExternalContentInSuggestions { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the server should force connections over HTTPS.
+ /// </summary>
public bool RequireHttps { get; set; }
- public bool IsBehindProxy { get; set; }
+
public bool EnableNewOmdbSupport { get; set; }
public string[] RemoteIPFilter { get; set; }
diff --git a/MediaBrowser.Model/Devices/DeviceOptions.cs b/MediaBrowser.Model/Devices/DeviceOptions.cs
new file mode 100644
index 000000000..8b77fd7fc
--- /dev/null
+++ b/MediaBrowser.Model/Devices/DeviceOptions.cs
@@ -0,0 +1,9 @@
+#pragma warning disable CS1591
+
+namespace MediaBrowser.Model.Devices
+{
+ public class DeviceOptions
+ {
+ public string CustomName { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Devices/DevicesOptions.cs b/MediaBrowser.Model/Devices/DevicesOptions.cs
deleted file mode 100644
index 02570650e..000000000
--- a/MediaBrowser.Model/Devices/DevicesOptions.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-
-namespace MediaBrowser.Model.Devices
-{
- public class DevicesOptions
- {
- public string[] EnabledCameraUploadDevices { get; set; }
- public string CameraUploadPath { get; set; }
- public bool EnableCameraUploadSubfolders { get; set; }
-
- public DevicesOptions()
- {
- EnabledCameraUploadDevices = Array.Empty<string>();
- }
- }
-
- public class DeviceOptions
- {
- public string CustomName { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Net/WebSocketMessage.cs b/MediaBrowser.Model/Net/WebSocketMessage.cs
index 7575224d4..03f03e4cc 100644
--- a/MediaBrowser.Model/Net/WebSocketMessage.cs
+++ b/MediaBrowser.Model/Net/WebSocketMessage.cs
@@ -1,5 +1,8 @@
+
#pragma warning disable CS1591
+using System;
+
namespace MediaBrowser.Model.Net
{
/// <summary>
@@ -13,7 +16,9 @@ namespace MediaBrowser.Model.Net
/// </summary>
/// <value>The type of the message.</value>
public string MessageType { get; set; }
- public string MessageId { get; set; }
+
+ public Guid MessageId { get; set; }
+
public string ServerId { get; set; }
/// <summary>
@@ -22,5 +27,4 @@ namespace MediaBrowser.Model.Net
/// <value>The data.</value>
public T Data { get; set; }
}
-
}
diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index 3d543039e..f2c5aa1e3 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -116,24 +116,6 @@ namespace MediaBrowser.Model.System
public string TranscodingTempPath { get; set; }
/// <summary>
- /// Gets or sets the HTTP server port number.
- /// </summary>
- /// <value>The HTTP server port number.</value>
- public int HttpServerPortNumber { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether [enable HTTPS].
- /// </summary>
- /// <value><c>true</c> if [enable HTTPS]; otherwise, <c>false</c>.</value>
- public bool SupportsHttps { get; set; }
-
- /// <summary>
- /// Gets or sets the HTTPS server port number.
- /// </summary>
- /// <value>The HTTPS server port number.</value>
- public int HttpsPortNumber { get; set; }
-
- /// <summary>
/// Gets or sets a value indicating whether this instance has update available.
/// </summary>
/// <value><c>true</c> if this instance has update available; otherwise, <c>false</c>.</value>
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
index 37160dd2c..f0328e8d8 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
@@ -11,13 +11,10 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Plugins.Omdb
{
- public class OmdbEpisodeProvider :
- IRemoteMetadataProvider<Episode, EpisodeInfo>,
- IHasOrder
+ public class OmdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
@@ -26,16 +23,27 @@ namespace MediaBrowser.Providers.Plugins.Omdb
private readonly IServerConfigurationManager _configurationManager;
private readonly IApplicationHost _appHost;
- public OmdbEpisodeProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
+ public OmdbEpisodeProvider(
+ IJsonSerializer jsonSerializer,
+ IApplicationHost appHost,
+ IHttpClient httpClient,
+ ILibraryManager libraryManager,
+ IFileSystem fileSystem,
+ IServerConfigurationManager configurationManager)
{
_jsonSerializer = jsonSerializer;
_httpClient = httpClient;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_appHost = appHost;
- _itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClient, logger, libraryManager, fileSystem, configurationManager);
+ _itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClient, libraryManager, fileSystem, configurationManager);
}
+ // After TheTvDb
+ public int Order => 1;
+
+ public string Name => "The Open Movie Database";
+
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
{
return _itemProvider.GetSearchResults(searchInfo, "episode", cancellationToken);
@@ -66,10 +74,6 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return result;
}
- // After TheTvDb
- public int Order => 1;
-
- public string Name => "The Open Movie Database";
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
{
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
index 3aadda5d0..64a75955a 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
@@ -17,7 +17,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Plugins.Omdb
{
@@ -26,22 +25,27 @@ namespace MediaBrowser.Providers.Plugins.Omdb
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
- private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly IApplicationHost _appHost;
- public OmdbItemProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
+ public OmdbItemProvider(
+ IJsonSerializer jsonSerializer,
+ IApplicationHost appHost,
+ IHttpClient httpClient,
+ ILibraryManager libraryManager,
+ IFileSystem fileSystem,
+ IServerConfigurationManager configurationManager)
{
_jsonSerializer = jsonSerializer;
_httpClient = httpClient;
- _logger = logger;
_libraryManager = libraryManager;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_appHost = appHost;
}
+
// After primary option
public int Order => 2;
@@ -80,7 +84,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var parsedName = _libraryManager.ParseName(name);
var yearInName = parsedName.Year;
name = parsedName.Name;
- year = year ?? yearInName;
+ year ??= yearInName;
}
if (string.IsNullOrWhiteSpace(imdbId))
@@ -312,6 +316,5 @@ namespace MediaBrowser.Providers.Plugins.Omdb
/// <value>The results.</value>
public List<SearchResult> Search { get; set; }
}
-
}
}
diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs
index 7195dc42a..6e3c26c26 100644
--- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs
@@ -27,9 +27,6 @@ namespace MediaBrowser.Providers.Tmdb.TV
public class TmdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
{
private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos,content_ratings";
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
- internal static TmdbSeriesProvider Current { get; private set; }
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
@@ -39,6 +36,10 @@ namespace MediaBrowser.Providers.Tmdb.TV
private readonly IHttpClient _httpClient;
private readonly ILibraryManager _libraryManager;
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+ internal static TmdbSeriesProvider Current { get; private set; }
+
public TmdbSeriesProvider(
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
@@ -217,10 +218,9 @@ namespace MediaBrowser.Providers.Tmdb.TV
var series = seriesResult.Item;
series.Name = seriesInfo.Name;
+ series.OriginalTitle = seriesInfo.Original_Name;
series.SetProviderId(MetadataProviders.Tmdb, seriesInfo.Id.ToString(_usCulture));
- //series.VoteCount = seriesInfo.vote_count;
-
string voteAvg = seriesInfo.Vote_Average.ToString(CultureInfo.InvariantCulture);
if (float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out float rating))
@@ -240,7 +240,7 @@ namespace MediaBrowser.Providers.Tmdb.TV
series.Genres = seriesInfo.Genres.Select(i => i.Name).ToArray();
}
- //series.HomePageUrl = seriesInfo.homepage;
+ series.HomePageUrl = seriesInfo.Homepage;
series.RunTimeTicks = seriesInfo.Episode_Run_Time.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault();
@@ -308,29 +308,61 @@ namespace MediaBrowser.Providers.Tmdb.TV
seriesResult.ResetPeople();
var tmdbImageUrl = settings.images.GetImageUrl("original");
- if (seriesInfo.Credits != null && seriesInfo.Credits.Cast != null)
+ if (seriesInfo.Credits != null)
{
- foreach (var actor in seriesInfo.Credits.Cast.OrderBy(a => a.Order))
+ if (seriesInfo.Credits.Cast != null)
{
- var personInfo = new PersonInfo
+ foreach (var actor in seriesInfo.Credits.Cast.OrderBy(a => a.Order))
{
- Name = actor.Name.Trim(),
- Role = actor.Character,
- Type = PersonType.Actor,
- SortOrder = actor.Order
- };
+ var personInfo = new PersonInfo
+ {
+ Name = actor.Name.Trim(),
+ Role = actor.Character,
+ Type = PersonType.Actor,
+ SortOrder = actor.Order
+ };
- if (!string.IsNullOrWhiteSpace(actor.Profile_Path))
- {
- personInfo.ImageUrl = tmdbImageUrl + actor.Profile_Path;
+ if (!string.IsNullOrWhiteSpace(actor.Profile_Path))
+ {
+ personInfo.ImageUrl = tmdbImageUrl + actor.Profile_Path;
+ }
+
+ if (actor.Id > 0)
+ {
+ personInfo.SetProviderId(MetadataProviders.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
+ }
+
+ seriesResult.AddPerson(personInfo);
}
+ }
- if (actor.Id > 0)
+ if (seriesInfo.Credits.Crew != null)
+ {
+ var keepTypes = new[]
{
- personInfo.SetProviderId(MetadataProviders.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture));
- }
+ PersonType.Director,
+ PersonType.Writer,
+ PersonType.Producer
+ };
- seriesResult.AddPerson(personInfo);
+ foreach (var person in seriesInfo.Credits.Crew)
+ {
+ // Normalize this
+ var type = TmdbUtils.MapCrewToPersonType(person);
+
+ if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase)
+ && !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ seriesResult.AddPerson(new PersonInfo
+ {
+ Name = person.Name.Trim(),
+ Role = person.Job,
+ Type = type
+ });
+ }
}
}
}
diff --git a/MediaBrowser.Providers/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Tmdb/TmdbUtils.cs
index 035b99c1a..7dacc7404 100644
--- a/MediaBrowser.Providers/Tmdb/TmdbUtils.cs
+++ b/MediaBrowser.Providers/Tmdb/TmdbUtils.cs
@@ -4,18 +4,51 @@ using MediaBrowser.Providers.Tmdb.Models.General;
namespace MediaBrowser.Providers.Tmdb
{
+ /// <summary>
+ /// Utilities for the TMDb provider
+ /// </summary>
public static class TmdbUtils
{
+ /// <summary>
+ /// URL of the TMDB instance to use.
+ /// </summary>
public const string BaseTmdbUrl = "https://www.themoviedb.org/";
+
+ /// <summary>
+ /// URL of the TMDB API instance to use.
+ /// </summary>
public const string BaseTmdbApiUrl = "https://api.themoviedb.org/";
+
+ /// <summary>
+ /// Name of the provider.
+ /// </summary>
public const string ProviderName = "TheMovieDb";
+
+ /// <summary>
+ /// API key to use when performing an API call.
+ /// </summary>
public const string ApiKey = "4219e299c89411838049ab0dab19ebd5";
+
+ /// <summary>
+ /// Value of the Accept header for requests to the provider.
+ /// </summary>
public const string AcceptHeader = "application/json,image/*";
+ /// <summary>
+ /// Maps the TMDB provided roles for crew members to Jellyfin roles.
+ /// </summary>
+ /// <param name="crew">Crew member to map against the Jellyfin person types.</param>
+ /// <returns>The Jellyfin person type.</returns>
public static string MapCrewToPersonType(Crew crew)
{
if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase)
- && crew.Job.IndexOf("producer", StringComparison.InvariantCultureIgnoreCase) != -1)
+ && crew.Job.Contains("director", StringComparison.InvariantCultureIgnoreCase))
+ {
+ return PersonType.Director;
+ }
+
+ if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase)
+ && crew.Job.Contains("producer", StringComparison.InvariantCultureIgnoreCase))
{
return PersonType.Producer;
}
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index 210e2b644..e100c0b1c 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30011.22
+# Visual Studio 15
+VisualStudioVersion = 15.0.26730.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}"
EndProject