repo creation with initial code after cloning public repo

This commit is contained in:
2026-02-19 07:36:25 -05:00
commit 460884fea3
2860 changed files with 650825 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,268 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
using MediaBrowser.Controller.Session;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Session
{
/// <summary>
/// Class SessionWebSocketListener.
/// </summary>
public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable
{
/// <summary>
/// The timeout in seconds after which a WebSocket is considered to be lost.
/// </summary>
private const int WebSocketLostTimeout = 60;
/// <summary>
/// The keep-alive interval factor; controls how often the watcher will check on the status of the WebSockets.
/// </summary>
private const float IntervalFactor = 0.2f;
/// <summary>
/// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent.
/// </summary>
private const float ForceKeepAliveFactor = 0.75f;
/// <summary>
/// The WebSocket watchlist.
/// </summary>
private readonly HashSet<IWebSocketConnection> _webSockets = new HashSet<IWebSocketConnection>();
/// <summary>
/// Lock used for accessing the WebSockets watchlist.
/// </summary>
private readonly Lock _webSocketsLock = new();
private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager;
private readonly ILogger<SessionWebSocketListener> _logger;
private readonly ILoggerFactory _loggerFactory;
/// <summary>
/// The KeepAlive cancellation token.
/// </summary>
private System.Timers.Timer _keepAlive;
/// <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="userManager">The user manager.</param>
/// <param name="loggerFactory">The logger factory.</param>
public SessionWebSocketListener(
ILogger<SessionWebSocketListener> logger,
ISessionManager sessionManager,
IUserManager userManager,
ILoggerFactory loggerFactory)
{
_logger = logger;
_sessionManager = sessionManager;
_userManager = userManager;
_loggerFactory = loggerFactory;
_keepAlive = new System.Timers.Timer(TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor))
{
AutoReset = true,
Enabled = false
};
_keepAlive.Elapsed += KeepAliveSockets;
}
/// <inheritdoc />
public void Dispose()
{
if (_keepAlive is not null)
{
_keepAlive.Stop();
_keepAlive.Elapsed -= KeepAliveSockets;
_keepAlive.Dispose();
_keepAlive = null!;
}
lock (_webSocketsLock)
{
foreach (var webSocket in _webSockets)
{
webSocket.Closed -= OnWebSocketClosed;
}
_webSockets.Clear();
}
}
/// <summary>
/// Processes the message.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>Task.</returns>
public Task ProcessMessageAsync(WebSocketMessageInfo message)
=> Task.CompletedTask;
/// <inheritdoc />
public async Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection, HttpContext httpContext)
{
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, httpContext).ConfigureAwait(false);
EnsureController(session, connection);
await KeepAliveWebSocket(connection).ConfigureAwait(false);
}
private void EnsureController(SessionInfo session, IWebSocketConnection connection)
{
var controllerInfo = session.EnsureController<WebSocketController>(
s => new WebSocketController(_loggerFactory.CreateLogger<WebSocketController>(), s, _sessionManager));
var controller = (WebSocketController)controllerInfo.Item1;
controller.AddWebSocket(connection);
_sessionManager.OnSessionControllerConnected(session);
}
/// <summary>
/// Called when a WebSocket is closed.
/// </summary>
/// <param name="sender">The WebSocket.</param>
/// <param name="e">The event arguments.</param>
private void OnWebSocketClosed(object? sender, EventArgs e)
{
if (sender is null)
{
return;
}
var webSocket = (IWebSocketConnection)sender;
_logger.LogDebug("WebSocket {0} is closed.", webSocket);
RemoveWebSocket(webSocket);
}
/// <summary>
/// Adds a WebSocket to the KeepAlive watchlist.
/// </summary>
/// <param name="webSocket">The WebSocket to monitor.</param>
private async Task KeepAliveWebSocket(IWebSocketConnection webSocket)
{
lock (_webSocketsLock)
{
if (!_webSockets.Add(webSocket))
{
_logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket);
return;
}
webSocket.Closed += OnWebSocketClosed;
webSocket.LastKeepAliveDate = DateTime.UtcNow;
_keepAlive.Start();
}
// Notify WebSocket about timeout
try
{
await SendForceKeepAlive(webSocket).ConfigureAwait(false);
}
catch (WebSocketException exception)
{
_logger.LogWarning(exception, "Cannot send ForceKeepAlive message to WebSocket {0}.", webSocket);
}
}
/// <summary>
/// Removes a WebSocket from the KeepAlive watchlist.
/// </summary>
/// <param name="webSocket">The WebSocket to remove.</param>
private void RemoveWebSocket(IWebSocketConnection webSocket)
{
lock (_webSocketsLock)
{
if (_webSockets.Remove(webSocket))
{
webSocket.Closed -= OnWebSocketClosed;
}
else
{
_logger.LogWarning("WebSocket {0} not on watchlist.", webSocket);
}
if (_webSockets.Count == 0)
{
_keepAlive.Stop();
}
}
}
/// <summary>
/// Checks status of KeepAlive of WebSockets.
/// </summary>
private async void KeepAliveSockets(object? o, EventArgs? e)
{
List<IWebSocketConnection> inactive;
List<IWebSocketConnection> lost;
lock (_webSocketsLock)
{
_logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count);
inactive = _webSockets.Where(i =>
{
var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds;
return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout);
}).ToList();
lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout).ToList();
}
if (inactive.Count > 0)
{
_logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count);
}
foreach (var webSocket in inactive)
{
try
{
await SendForceKeepAlive(webSocket).ConfigureAwait(false);
}
catch (WebSocketException exception)
{
_logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket.");
lost.Add(webSocket);
}
}
lock (_webSocketsLock)
{
if (lost.Count > 0)
{
_logger.LogInformation("Lost {0} WebSockets.", lost.Count);
foreach (var webSocket in lost)
{
// TODO: handle session relative to the lost webSocket
RemoveWebSocket(webSocket);
}
}
}
}
/// <summary>
/// Sends a ForceKeepAlive message to a WebSocket.
/// </summary>
/// <param name="webSocket">The WebSocket.</param>
/// <returns>Task.</returns>
private async Task SendForceKeepAlive(IWebSocketConnection webSocket)
{
await webSocket.SendAsync(
new ForceKeepAliveMessage(WebSocketLostTimeout),
CancellationToken.None).ConfigureAwait(false);
}
}
}
@@ -0,0 +1,186 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Net.WebSocketMessages;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Session
{
public sealed class WebSocketController : ISessionController, IAsyncDisposable, IDisposable
{
private readonly ILogger<WebSocketController> _logger;
private readonly ISessionManager _sessionManager;
private readonly SessionInfo _session;
private readonly List<IWebSocketConnection> _sockets;
private readonly ReaderWriterLockSlim _socketsLock;
private bool _disposed = false;
public WebSocketController(
ILogger<WebSocketController> logger,
SessionInfo session,
ISessionManager sessionManager)
{
_logger = logger;
_session = session;
_sessionManager = sessionManager;
_sockets = new();
_socketsLock = new();
}
private bool HasOpenSockets
{
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
try
{
_socketsLock.EnterReadLock();
return _sockets.Any(i => i.State == WebSocketState.Open);
}
finally
{
_socketsLock.ExitReadLock();
}
}
}
/// <inheritdoc />
public bool SupportsMediaControl => HasOpenSockets;
/// <inheritdoc />
public bool IsSessionActive => HasOpenSockets;
public void AddWebSocket(IWebSocketConnection connection)
{
_logger.LogDebug("Adding websocket to session {Session}", _session.Id);
ObjectDisposedException.ThrowIf(_disposed, this);
try
{
_socketsLock.EnterWriteLock();
_sockets.Add(connection);
connection.Closed += OnConnectionClosed;
}
finally
{
_socketsLock.ExitWriteLock();
}
}
private async void OnConnectionClosed(object? sender, EventArgs e)
{
var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender));
_logger.LogDebug("Removing websocket from session {Session}", _session.Id);
ObjectDisposedException.ThrowIf(_disposed, this);
try
{
_socketsLock.EnterWriteLock();
_sockets.Remove(connection);
connection.Closed -= OnConnectionClosed;
}
finally
{
_socketsLock.ExitWriteLock();
}
await _sessionManager.CloseIfNeededAsync(_session).ConfigureAwait(false);
}
/// <inheritdoc />
public Task SendMessage<T>(
SessionMessageType name,
Guid messageId,
T data,
CancellationToken cancellationToken)
{
ObjectDisposedException.ThrowIf(_disposed, this);
IWebSocketConnection? socket;
try
{
_socketsLock.EnterReadLock();
socket = _sockets.Where(i => i.State == WebSocketState.Open).MaxBy(i => i.LastActivityDate);
}
finally
{
_socketsLock.ExitReadLock();
}
if (socket is null)
{
return Task.CompletedTask;
}
return socket.SendAsync(
new OutboundWebSocketMessage<T>
{
Data = data,
MessageType = name,
MessageId = messageId
},
cancellationToken);
}
/// <inheritdoc />
public void Dispose()
{
if (_disposed)
{
return;
}
try
{
_socketsLock.EnterWriteLock();
foreach (var socket in _sockets)
{
socket.Closed -= OnConnectionClosed;
socket.Dispose();
}
_sockets.Clear();
}
finally
{
_socketsLock.ExitWriteLock();
}
_socketsLock.Dispose();
_disposed = true;
}
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}
try
{
_socketsLock.EnterWriteLock();
foreach (var socket in _sockets)
{
socket.Closed -= OnConnectionClosed;
await socket.DisposeAsync().ConfigureAwait(false);
}
_sockets.Clear();
}
finally
{
_socketsLock.ExitWriteLock();
}
_socketsLock.Dispose();
_disposed = true;
}
}
}