From 8bb44b85d7008ee167f2260934a1f325abb06e2d Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Mon, 1 May 2023 16:24:15 +0200 Subject: close inactive sessions after 10 minutes --- .../Session/SessionManager.cs | 74 ++++++++++++++++++++-- 1 file changed, 70 insertions(+), 4 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 5f6dc93fb..32eff350b 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -63,6 +63,9 @@ namespace Emby.Server.Implementations.Session private readonly ConcurrentDictionary _activeConnections = new(StringComparer.OrdinalIgnoreCase); private Timer _idleTimer; + private Timer _inactiveTimer; + + private int inactiveMinutesThreshold = 10; private DtoOptions _itemInfoDtoOptions; private bool _disposed = false; @@ -171,9 +174,11 @@ namespace Emby.Server.Implementations.Session if (disposing) { _idleTimer?.Dispose(); + _inactiveTimer?.Dispose(); } _idleTimer = null; + _inactiveTimer = null; _deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated; @@ -397,6 +402,15 @@ namespace Emby.Server.Implementations.Session session.LastPlaybackCheckIn = DateTime.UtcNow; } + if (info.IsPaused && session.LastPausedDate.HasValue == false) + { + session.LastPausedDate = DateTime.UtcNow; + } + else if (!info.IsPaused) + { + session.LastPausedDate = null; + } + session.PlayState.IsPaused = info.IsPaused; session.PlayState.PositionTicks = info.PositionTicks; session.PlayState.MediaSourceId = info.MediaSourceId; @@ -564,9 +578,10 @@ namespace Emby.Server.Implementations.Session return users; } - private void StartIdleCheckTimer() + private void StartCheckTimers() { _idleTimer ??= new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); + _inactiveTimer ??= new Timer(CheckForInactiveSteams, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); } private void StopIdleCheckTimer() @@ -578,6 +593,15 @@ namespace Emby.Server.Implementations.Session } } + private void StopInactiveCheckTimer() + { + if (_inactiveTimer is not null) + { + _inactiveTimer.Dispose(); + _inactiveTimer = null; + } + } + private async void CheckForIdlePlayback(object state) { var playingSessions = Sessions.Where(i => i.NowPlayingItem is not null) @@ -613,10 +637,52 @@ namespace Emby.Server.Implementations.Session playingSessions = Sessions.Where(i => i.NowPlayingItem is not null) .ToList(); } + else + { + StopIdleCheckTimer(); + } + } + + private async void CheckForInactiveSteams(object state) + { + var pausedSessions = Sessions.Where(i => + (i.NowPlayingItem is not null) && + (i.PlayState.IsPaused == true) && + (i.LastPausedDate is not null)).ToList(); + if (pausedSessions.Count > 0) + { + var inactiveSessions = Sessions.Where(i => (DateTime.UtcNow - i.LastPausedDate).Value.TotalMinutes > inactiveMinutesThreshold).ToList(); + + foreach (var session in inactiveSessions) + { + _logger.LogDebug("Session {0} has been inactive for {1} minutes. Stopping it.", session.Id, inactiveMinutesThreshold); + + try + { + await SendPlaystateCommand( + session.Id, + session.Id, + new PlaystateRequest() + { + Command = PlaystateCommand.Stop, + ControllingUserId = session.UserId.ToString(), + SeekPositionTicks = session.PlayState?.PositionTicks + }, + CancellationToken.None).ConfigureAwait(true); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Error calling SendPlaystateCommand for stopping inactive session {0}.", session.Id); + } + } + } + + var playingSessions = Sessions.Where(i => i.NowPlayingItem is not null) + .ToList(); if (playingSessions.Count == 0) { - StopIdleCheckTimer(); + StopInactiveCheckTimer(); } } @@ -696,7 +762,7 @@ namespace Emby.Server.Implementations.Session eventArgs, _logger); - StartIdleCheckTimer(); + StartCheckTimers(); } /// @@ -790,7 +856,7 @@ namespace Emby.Server.Implementations.Session session.StartAutomaticProgress(info); } - StartIdleCheckTimer(); + StartCheckTimers(); } private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info) -- cgit v1.2.3 From e1190d15d6ca0b7cad2b4991e524b35ecca35949 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Mon, 1 May 2023 20:11:22 +0200 Subject: option to disable and configure inactive session threshold --- .../Session/SessionManager.cs | 20 +++++++++++++++----- .../Configuration/ServerConfiguration.cs | 6 ++++++ 2 files changed, 21 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 32eff350b..056c3e4b6 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -19,6 +19,7 @@ using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -46,6 +47,7 @@ namespace Emby.Server.Implementations.Session public class SessionManager : ISessionManager, IDisposable { private readonly IUserDataManager _userDataManager; + private readonly IServerConfigurationManager _config; private readonly ILogger _logger; private readonly IEventManager _eventManager; private readonly ILibraryManager _libraryManager; @@ -65,8 +67,6 @@ namespace Emby.Server.Implementations.Session private Timer _idleTimer; private Timer _inactiveTimer; - private int inactiveMinutesThreshold = 10; - private DtoOptions _itemInfoDtoOptions; private bool _disposed = false; @@ -74,6 +74,7 @@ namespace Emby.Server.Implementations.Session ILogger logger, IEventManager eventManager, IUserDataManager userDataManager, + IServerConfigurationManager config, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, @@ -86,6 +87,7 @@ namespace Emby.Server.Implementations.Session _logger = logger; _eventManager = eventManager; _userDataManager = userDataManager; + _config = config; _libraryManager = libraryManager; _userManager = userManager; _musicManager = musicManager; @@ -581,7 +583,15 @@ namespace Emby.Server.Implementations.Session private void StartCheckTimers() { _idleTimer ??= new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); - _inactiveTimer ??= new Timer(CheckForInactiveSteams, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); + + if (_config.Configuration.InactiveSessionThreshold > 0) + { + _inactiveTimer ??= new Timer(CheckForInactiveSteams, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); + } + else + { + StopInactiveCheckTimer(); + } } private void StopIdleCheckTimer() @@ -652,11 +662,11 @@ namespace Emby.Server.Implementations.Session if (pausedSessions.Count > 0) { - var inactiveSessions = Sessions.Where(i => (DateTime.UtcNow - i.LastPausedDate).Value.TotalMinutes > inactiveMinutesThreshold).ToList(); + var inactiveSessions = pausedSessions.Where(i => (DateTime.UtcNow - i.LastPausedDate).Value.TotalMinutes > _config.Configuration.InactiveSessionThreshold).ToList(); foreach (var session in inactiveSessions) { - _logger.LogDebug("Session {0} has been inactive for {1} minutes. Stopping it.", session.Id, inactiveMinutesThreshold); + _logger.LogDebug("Session {0} has been inactive for {1} minutes. Stopping it.", session.Id, _config.Configuration.InactiveSessionThreshold); try { diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 07f02d187..f619f384c 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -157,6 +157,12 @@ namespace MediaBrowser.Model.Configuration /// The remaining time in minutes. public int MaxAudiobookResume { get; set; } = 5; + /// + /// Gets or sets the threshold in minutes after a inactive session gets closed automatically. + /// + /// The close inactive session threshold in minutes. + public int InactiveSessionThreshold { get; set; } = 10; + /// /// Gets or sets the delay in seconds that we will wait after a file system change to try and discover what has been added/removed /// Some delay is necessary with some items because their creation is not atomic. It involves the creation of several -- cgit v1.2.3 From ace89e45976a65a365a3d9d7a2ed737a61d584d4 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 14 May 2023 15:05:03 +0200 Subject: fix formatting and update summary --- Emby.Server.Implementations/Session/SessionManager.cs | 9 +++++---- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 056c3e4b6..14a62626c 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -404,7 +404,7 @@ namespace Emby.Server.Implementations.Session session.LastPlaybackCheckIn = DateTime.UtcNow; } - if (info.IsPaused && session.LastPausedDate.HasValue == false) + if (info.IsPaused && session.LastPausedDate is null) { session.LastPausedDate = DateTime.UtcNow; } @@ -656,9 +656,10 @@ namespace Emby.Server.Implementations.Session private async void CheckForInactiveSteams(object state) { var pausedSessions = Sessions.Where(i => - (i.NowPlayingItem is not null) && - (i.PlayState.IsPaused == true) && - (i.LastPausedDate is not null)).ToList(); + i.NowPlayingItem is not null + && i.PlayState.IsPaused + && i.LastPausedDate is not null) + .ToList(); if (pausedSessions.Count > 0) { diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index f619f384c..40dcf50b7 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -159,8 +159,9 @@ namespace MediaBrowser.Model.Configuration /// /// Gets or sets the threshold in minutes after a inactive session gets closed automatically. + /// If set to 0 the check for inactive sessions gets disabled. /// - /// The close inactive session threshold in minutes. + /// The close inactive session threshold in minutes. 0 to disable. public int InactiveSessionThreshold { get; set; } = 10; /// -- cgit v1.2.3 From 56aa37a314df17ba7608d7d9d00925f9e4816a89 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sat, 7 Oct 2023 20:52:16 +0200 Subject: Switch to named placeholders --- Emby.Server.Implementations/Session/SessionManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 14a62626c..a6155aa62 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -667,7 +667,7 @@ namespace Emby.Server.Implementations.Session foreach (var session in inactiveSessions) { - _logger.LogDebug("Session {0} has been inactive for {1} minutes. Stopping it.", session.Id, _config.Configuration.InactiveSessionThreshold); + _logger.LogDebug("Session {Session} has been inactive for {InactiveTime} minutes. Stopping it.", session.Id, _config.Configuration.InactiveSessionThreshold); try { @@ -684,7 +684,7 @@ namespace Emby.Server.Implementations.Session } catch (Exception ex) { - _logger.LogDebug(ex, "Error calling SendPlaystateCommand for stopping inactive session {0}.", session.Id); + _logger.LogDebug(ex, "Error calling SendPlaystateCommand for stopping inactive session {Session}.", session.Id); } } } -- cgit v1.2.3 From 994619afb2287b0ee80eab2511393a7803a3ddb6 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Sun, 8 Oct 2023 13:49:35 +0200 Subject: fix formatting for build process --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b5d8f6c3b..329138feb 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1873,7 +1873,7 @@ namespace Emby.Server.Implementations.Session _idleTimer = null; } - if(_inactiveTimer is not null) + if (_inactiveTimer is not null) { await _inactiveTimer.DisposeAsync().ConfigureAwait(false); _inactiveTimer = null; -- cgit v1.2.3 From e8a05ad996ab35909cd5928d49c4e8c46e9985d5 Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:15:25 +0200 Subject: optimize checkForInactiveStreams logic --- .../Session/SessionManager.cs | 50 ++++++++++------------ 1 file changed, 22 insertions(+), 28 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 329138feb..dc59a4523 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -625,43 +625,37 @@ namespace Emby.Server.Implementations.Session private async void CheckForInactiveSteams(object state) { - var pausedSessions = Sessions.Where(i => + var inactiveSessions = Sessions.Where(i => i.NowPlayingItem is not null && i.PlayState.IsPaused - && i.LastPausedDate is not null) - .ToList(); + && (DateTime.UtcNow - i.LastPausedDate).Value.TotalMinutes > _config.Configuration.InactiveSessionThreshold); - if (pausedSessions.Count > 0) + foreach (var session in inactiveSessions) { - var inactiveSessions = pausedSessions.Where(i => (DateTime.UtcNow - i.LastPausedDate).Value.TotalMinutes > _config.Configuration.InactiveSessionThreshold).ToList(); + _logger.LogDebug("Session {Session} has been inactive for {InactiveTime} minutes. Stopping it.", session.Id, _config.Configuration.InactiveSessionThreshold); - foreach (var session in inactiveSessions) + try { - _logger.LogDebug("Session {Session} has been inactive for {InactiveTime} minutes. Stopping it.", session.Id, _config.Configuration.InactiveSessionThreshold); - - try - { - await SendPlaystateCommand( - session.Id, - session.Id, - new PlaystateRequest() - { - Command = PlaystateCommand.Stop, - ControllingUserId = session.UserId.ToString(), - SeekPositionTicks = session.PlayState?.PositionTicks - }, - CancellationToken.None).ConfigureAwait(true); - } - catch (Exception ex) - { - _logger.LogDebug(ex, "Error calling SendPlaystateCommand for stopping inactive session {Session}.", session.Id); - } + await SendPlaystateCommand( + session.Id, + session.Id, + new PlaystateRequest() + { + Command = PlaystateCommand.Stop, + ControllingUserId = session.UserId.ToString(), + SeekPositionTicks = session.PlayState?.PositionTicks + }, + CancellationToken.None).ConfigureAwait(true); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Error calling SendPlaystateCommand for stopping inactive session {Session}.", session.Id); } } - var playingSessions = Sessions.Where(i => i.NowPlayingItem is not null) - .ToList(); - if (playingSessions.Count == 0) + bool playingSessions = Sessions.Any(i => i.NowPlayingItem is not null); + + if (!playingSessions) { StopInactiveCheckTimer(); } -- cgit v1.2.3 From 8c5fc8028240adec57a4b39147dbeac81a1835a0 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Mon, 30 Oct 2023 15:31:13 -0600 Subject: Don't remove all tokens if invalid header (#10490) --- .../Session/SessionManager.cs | 12 ++- .../SessionManager/SessionManagerTests.cs | 111 +++++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionManagerTests.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index dc59a4523..e8e63d286 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1453,10 +1453,15 @@ namespace Emby.Server.Implementations.Session return AuthenticateNewSessionInternal(request, false); } - private async Task AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword) + internal async Task AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword) { CheckDisposed(); + ArgumentException.ThrowIfNullOrEmpty(request.App); + ArgumentException.ThrowIfNullOrEmpty(request.DeviceId); + ArgumentException.ThrowIfNullOrEmpty(request.DeviceName); + ArgumentException.ThrowIfNullOrEmpty(request.AppVersion); + User user = null; if (!request.UserId.Equals(default)) { @@ -1517,8 +1522,11 @@ namespace Emby.Server.Implementations.Session return returnResult; } - private async Task GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) + internal async Task GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) { + // This should be validated above, but if it isn't don't delete all tokens. + ArgumentException.ThrowIfNullOrEmpty(deviceId); + var existing = (await _deviceManager.GetDevices( new DeviceQuery { diff --git a/tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionManagerTests.cs new file mode 100644 index 000000000..ebd3a3891 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionManagerTests.cs @@ -0,0 +1,111 @@ +using System; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.SessionManager; + +public class SessionManagerTests +{ + [Theory] + [InlineData("", typeof(ArgumentException))] + [InlineData(null, typeof(ArgumentNullException))] + public async Task GetAuthorizationToken_Should_ThrowException(string deviceId, Type exceptionType) + { + await using var sessionManager = new Emby.Server.Implementations.Session.SessionManager( + NullLogger.Instance, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of()); + + await Assert.ThrowsAsync(exceptionType, () => sessionManager.GetAuthorizationToken( + new User("test", "default", "default"), + deviceId, + "app_name", + "0.0.0", + "device_name")); + } + + [Theory] + [MemberData(nameof(AuthenticateNewSessionInternal_Exception_TestData))] + public async Task AuthenticateNewSessionInternal_Should_ThrowException(AuthenticationRequest authenticationRequest, Type exceptionType) + { + await using var sessionManager = new Emby.Server.Implementations.Session.SessionManager( + NullLogger.Instance, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of()); + + await Assert.ThrowsAsync(exceptionType, () => sessionManager.AuthenticateNewSessionInternal(authenticationRequest, false)); + } + + public static TheoryData AuthenticateNewSessionInternal_Exception_TestData() + { + var data = new TheoryData + { + { + new AuthenticationRequest { App = string.Empty, DeviceId = "device_id", DeviceName = "device_name", AppVersion = "app_version" }, + typeof(ArgumentException) + }, + { + new AuthenticationRequest { App = null, DeviceId = "device_id", DeviceName = "device_name", AppVersion = "app_version" }, + typeof(ArgumentNullException) + }, + { + new AuthenticationRequest { App = "app_name", DeviceId = string.Empty, DeviceName = "device_name", AppVersion = "app_version" }, + typeof(ArgumentException) + }, + { + new AuthenticationRequest { App = "app_name", DeviceId = null, DeviceName = "device_name", AppVersion = "app_version" }, + typeof(ArgumentNullException) + }, + { + new AuthenticationRequest { App = "app_name", DeviceId = "device_id", DeviceName = string.Empty, AppVersion = "app_version" }, + typeof(ArgumentException) + }, + { + new AuthenticationRequest { App = "app_name", DeviceId = "device_id", DeviceName = null, AppVersion = "app_version" }, + typeof(ArgumentNullException) + }, + { + new AuthenticationRequest { App = "app_name", DeviceId = "device_id", DeviceName = "device_name", AppVersion = string.Empty }, + typeof(ArgumentException) + }, + { + new AuthenticationRequest { App = "app_name", DeviceId = "device_id", DeviceName = "device_name", AppVersion = null }, + typeof(ArgumentNullException) + } + }; + + return data; + } +} -- cgit v1.2.3