diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2024-09-05 12:55:15 +0200 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2024-09-18 15:42:15 +0200 |
| commit | 7a2427bf07f9036d62c88a75855cd6dc7e8e3064 (patch) | |
| tree | 8a3211a265a36d35b2d707880d9ca504a5c5ccd4 | |
| parent | 569a41fc2a518672684b28a106241ecd8c9ceb67 (diff) | |
Add SessionInfoDto, DeviceInfoDto and implement JsonDelimitedArrayConverter.Write
17 files changed, 853 insertions, 308 deletions
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 72e164b52..6bcbe3ceb 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -68,13 +66,29 @@ namespace Emby.Server.Implementations.Session private Timer _inactiveTimer; private DtoOptions _itemInfoDtoOptions; - private bool _disposed = false; + private bool _disposed; + /// <summary> + /// Initializes a new instance of the <see cref="SessionManager"/> class. + /// </summary> + /// <param name="logger">Instance of <see cref="ILogger{SessionManager}"/> interface.</param> + /// <param name="eventManager">Instance of <see cref="IEventManager"/> interface.</param> + /// <param name="userDataManager">Instance of <see cref="IUserDataManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="libraryManager">Instance of <see cref="ILibraryManager"/> interface.</param> + /// <param name="userManager">Instance of <see cref="IUserManager"/> interface.</param> + /// <param name="musicManager">Instance of <see cref="IMusicManager"/> interface.</param> + /// <param name="dtoService">Instance of <see cref="IDtoService"/> interface.</param> + /// <param name="imageProcessor">Instance of <see cref="IImageProcessor"/> interface.</param> + /// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param> + /// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param> + /// <param name="mediaSourceManager">Instance of <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="hostApplicationLifetime">Instance of <see cref="IHostApplicationLifetime"/> interface.</param> public SessionManager( ILogger<SessionManager> logger, IEventManager eventManager, IUserDataManager userDataManager, - IServerConfigurationManager config, + IServerConfigurationManager serverConfigurationManager, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, @@ -88,7 +102,7 @@ namespace Emby.Server.Implementations.Session _logger = logger; _eventManager = eventManager; _userDataManager = userDataManager; - _config = config; + _config = serverConfigurationManager; _libraryManager = libraryManager; _userManager = userManager; _musicManager = musicManager; @@ -508,7 +522,10 @@ namespace Emby.Server.Implementations.Session deviceName = "Network Device"; } - var deviceOptions = _deviceManager.GetDeviceOptions(deviceId); + var deviceOptions = _deviceManager.GetDeviceOptions(deviceId) ?? new() + { + DeviceId = deviceId + }; if (string.IsNullOrEmpty(deviceOptions.CustomName)) { sessionInfo.DeviceName = deviceName; @@ -1076,6 +1093,42 @@ namespace Emby.Server.Implementations.Session return session; } + private SessionInfoDto ToSessionInfoDto(SessionInfo sessionInfo) + { + return new SessionInfoDto + { + PlayState = sessionInfo.PlayState, + AdditionalUsers = sessionInfo.AdditionalUsers, + Capabilities = _deviceManager.ToClientCapabilitiesDto(sessionInfo.Capabilities), + RemoteEndPoint = sessionInfo.RemoteEndPoint, + PlayableMediaTypes = sessionInfo.PlayableMediaTypes, + Id = sessionInfo.Id, + UserId = sessionInfo.UserId, + UserName = sessionInfo.UserName, + Client = sessionInfo.Client, + LastActivityDate = sessionInfo.LastActivityDate, + LastPlaybackCheckIn = sessionInfo.LastPlaybackCheckIn, + LastPausedDate = sessionInfo.LastPausedDate, + DeviceName = sessionInfo.DeviceName, + DeviceType = sessionInfo.DeviceType, + NowPlayingItem = sessionInfo.NowPlayingItem, + NowViewingItem = sessionInfo.NowViewingItem, + DeviceId = sessionInfo.DeviceId, + ApplicationVersion = sessionInfo.ApplicationVersion, + TranscodingInfo = sessionInfo.TranscodingInfo, + IsActive = sessionInfo.IsActive, + SupportsMediaControl = sessionInfo.SupportsMediaControl, + SupportsRemoteControl = sessionInfo.SupportsRemoteControl, + NowPlayingQueue = sessionInfo.NowPlayingQueue, + NowPlayingQueueFullItems = sessionInfo.NowPlayingQueueFullItems, + HasCustomDeviceName = sessionInfo.HasCustomDeviceName, + PlaylistItemId = sessionInfo.PlaylistItemId, + ServerId = sessionInfo.ServerId, + UserPrimaryImageTag = sessionInfo.UserPrimaryImageTag, + SupportedCommands = sessionInfo.SupportedCommands + }; + } + /// <inheritdoc /> public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken) { @@ -1393,7 +1446,7 @@ namespace Emby.Server.Implementations.Session UserName = user.Username }; - session.AdditionalUsers = [..session.AdditionalUsers, newUser]; + session.AdditionalUsers = [.. session.AdditionalUsers, newUser]; } } @@ -1505,7 +1558,7 @@ namespace Emby.Server.Implementations.Session var returnResult = new AuthenticationResult { User = _userManager.GetUserDto(user, request.RemoteEndPoint), - SessionInfo = session, + SessionInfo = ToSessionInfoDto(session), AccessToken = token, ServerId = _appHost.SystemId }; @@ -1800,6 +1853,74 @@ namespace Emby.Server.Implementations.Session return await GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null).ConfigureAwait(false); } + /// <inheritdoc/> + public IReadOnlyList<SessionInfoDto> GetSessions( + Guid userId, + string deviceId, + int? activeWithinSeconds, + Guid? controllableUserToCheck) + { + var result = Sessions; + var user = _userManager.GetUserById(userId); + if (!string.IsNullOrEmpty(deviceId)) + { + result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); + } + + if (!controllableUserToCheck.IsNullOrEmpty()) + { + result = result.Where(i => i.SupportsRemoteControl); + + var controlledUser = _userManager.GetUserById(controllableUserToCheck.Value); + if (controlledUser is null) + { + return []; + } + + if (!controlledUser.HasPermission(PermissionKind.EnableSharedDeviceControl)) + { + // Controlled user has device sharing disabled + result = result.Where(i => !i.UserId.IsEmpty()); + } + + if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) + { + // User cannot control other user's sessions, validate user id. + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(controllableUserToCheck.Value)); + } + + result = result.Where(i => + { + if (!string.IsNullOrWhiteSpace(i.DeviceId) && !_deviceManager.CanAccessDevice(user, i.DeviceId)) + { + return false; + } + + return true; + }); + } + else if (!user.HasPermission(PermissionKind.IsAdministrator)) + { + // Request isn't from administrator, limit to "own" sessions. + result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(userId)); + + // Don't report acceleration type for non-admin users. + result = result.Select(r => + { + r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none; + return r; + }); + } + + if (activeWithinSeconds.HasValue && activeWithinSeconds.Value > 0) + { + var minActiveDate = DateTime.UtcNow.AddSeconds(0 - activeWithinSeconds.Value); + result = result.Where(i => i.LastActivityDate >= minActiveDate); + } + + return result.Select(ToSessionInfoDto).ToList(); + } + /// <inheritdoc /> public Task SendMessageToAdminSessions<T>(SessionMessageType name, T data, CancellationToken cancellationToken) { diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 2a2ab4ad1..50050262f 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -1,15 +1,13 @@ using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; -using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Data.Dtos; -using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Queries; using MediaBrowser.Common.Api; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -47,7 +45,7 @@ public class DevicesController : BaseJellyfinApiController /// <returns>An <see cref="OkResult"/> containing the list of devices.</returns> [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<QueryResult<DeviceInfo>> GetDevices([FromQuery] Guid? userId) + public ActionResult<QueryResult<DeviceInfoDto>> GetDevices([FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); return _deviceManager.GetDevicesForUser(userId); @@ -63,7 +61,7 @@ public class DevicesController : BaseJellyfinApiController [HttpGet("Info")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, Required] string id) + public ActionResult<DeviceInfoDto> GetDeviceInfo([FromQuery, Required] string id) { var deviceInfo = _deviceManager.GetDevice(id); if (deviceInfo is null) @@ -84,7 +82,7 @@ public class DevicesController : BaseJellyfinApiController [HttpGet("Options")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, Required] string id) + public ActionResult<DeviceOptionsDto> GetDeviceOptions([FromQuery, Required] string id) { var deviceInfo = _deviceManager.GetDeviceOptions(id); if (deviceInfo is null) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 942bdeb9e..91a879b8e 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -1,18 +1,13 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; -using Jellyfin.Api.Models.SessionDtos; using Jellyfin.Data.Enums; -using Jellyfin.Extensions; using MediaBrowser.Common.Api; -using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; @@ -32,22 +27,18 @@ public class SessionController : BaseJellyfinApiController { private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; - private readonly IDeviceManager _deviceManager; /// <summary> /// Initializes a new instance of the <see cref="SessionController"/> class. /// </summary> /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param> /// <param name="userManager">Instance of <see cref="IUserManager"/> interface.</param> - /// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param> public SessionController( ISessionManager sessionManager, - IUserManager userManager, - IDeviceManager deviceManager) + IUserManager userManager) { _sessionManager = sessionManager; _userManager = userManager; - _deviceManager = deviceManager; } /// <summary> @@ -57,77 +48,25 @@ public class SessionController : BaseJellyfinApiController /// <param name="deviceId">Filter by device Id.</param> /// <param name="activeWithinSeconds">Optional. Filter by sessions that were active in the last n seconds.</param> /// <response code="200">List of sessions returned.</response> - /// <returns>An <see cref="IEnumerable{SessionInfo}"/> with the available sessions.</returns> + /// <returns>An <see cref="IReadOnlyList{SessionInfoDto}"/> with the available sessions.</returns> [HttpGet("Sessions")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<IEnumerable<SessionInfo>> GetSessions( + public ActionResult<IReadOnlyList<SessionInfoDto>> GetSessions( [FromQuery] Guid? controllableByUserId, [FromQuery] string? deviceId, [FromQuery] int? activeWithinSeconds) { - var result = _sessionManager.Sessions; - var isRequestingFromAdmin = User.IsInRole(UserRoles.Administrator); - - if (!string.IsNullOrEmpty(deviceId)) - { - result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); - } - - if (!controllableByUserId.IsNullOrEmpty()) + Guid? controllableUserToCheck = controllableByUserId is null ? null : RequestHelpers.GetUserId(User, controllableByUserId); + var result = _sessionManager.GetSessions( + User.GetUserId(), + deviceId, + activeWithinSeconds, + controllableUserToCheck); + + if (result.Count == 0) { - result = result.Where(i => i.SupportsRemoteControl); - - var user = _userManager.GetUserById(controllableByUserId.Value); - if (user is null) - { - return NotFound(); - } - - if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) - { - // User cannot control other user's sessions, validate user id. - result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(RequestHelpers.GetUserId(User, controllableByUserId))); - } - - if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl)) - { - result = result.Where(i => !i.UserId.IsEmpty()); - } - - result = result.Where(i => - { - if (!string.IsNullOrWhiteSpace(i.DeviceId)) - { - if (!_deviceManager.CanAccessDevice(user, i.DeviceId)) - { - return false; - } - } - - return true; - }); - } - else if (!isRequestingFromAdmin) - { - // Request isn't from administrator, limit to "own" sessions. - result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(User.GetUserId())); - } - - if (activeWithinSeconds.HasValue && activeWithinSeconds.Value > 0) - { - var minActiveDate = DateTime.UtcNow.AddSeconds(0 - activeWithinSeconds.Value); - result = result.Where(i => i.LastActivityDate >= minActiveDate); - } - - // Request isn't from administrator, don't report acceleration type. - if (!isRequestingFromAdmin) - { - result = result.Select(r => - { - r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none; - return r; - }); + return NotFound(); } return Ok(result); diff --git a/Jellyfin.Data/Dtos/DeviceOptionsDto.cs b/Jellyfin.Data/Dtos/DeviceOptionsDto.cs index 392ef5ff4..aad578709 100644 --- a/Jellyfin.Data/Dtos/DeviceOptionsDto.cs +++ b/Jellyfin.Data/Dtos/DeviceOptionsDto.cs @@ -1,23 +1,22 @@ -namespace Jellyfin.Data.Dtos +namespace Jellyfin.Data.Dtos; + +/// <summary> +/// A dto representing custom options for a device. +/// </summary> +public class DeviceOptionsDto { /// <summary> - /// A dto representing custom options for a device. + /// Gets or sets the id. /// </summary> - public class DeviceOptionsDto - { - /// <summary> - /// Gets or sets the id. - /// </summary> - public int Id { get; set; } + public int Id { get; set; } - /// <summary> - /// Gets or sets the device id. - /// </summary> - public string? DeviceId { get; set; } + /// <summary> + /// Gets or sets the device id. + /// </summary> + public string? DeviceId { get; set; } - /// <summary> - /// Gets or sets the custom name. - /// </summary> - public string? CustomName { get; set; } - } + /// <summary> + /// Gets or sets the custom name. + /// </summary> + public string? CustomName { get; set; } } diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index 415c04bbf..d3bff2936 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Dtos; using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Enums; @@ -13,6 +14,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using Microsoft.EntityFrameworkCore; @@ -68,7 +70,7 @@ namespace Jellyfin.Server.Implementations.Devices } /// <inheritdoc /> - public async Task UpdateDeviceOptions(string deviceId, string deviceName) + public async Task UpdateDeviceOptions(string deviceId, string? deviceName) { DeviceOptions? deviceOptions; var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); @@ -105,29 +107,37 @@ namespace Jellyfin.Server.Implementations.Devices } /// <inheritdoc /> - public DeviceOptions GetDeviceOptions(string deviceId) + public DeviceOptionsDto? GetDeviceOptions(string deviceId) { - _deviceOptions.TryGetValue(deviceId, out var deviceOptions); + if (_deviceOptions.TryGetValue(deviceId, out var deviceOptions)) + { + return ToDeviceOptionsDto(deviceOptions); + } - return deviceOptions ?? new DeviceOptions(deviceId); + return null; } /// <inheritdoc /> - public ClientCapabilities GetCapabilities(string deviceId) + public ClientCapabilities GetCapabilities(string? deviceId) { + if (deviceId is null) + { + return new(); + } + return _capabilitiesMap.TryGetValue(deviceId, out ClientCapabilities? result) ? result - : new ClientCapabilities(); + : new(); } /// <inheritdoc /> - public DeviceInfo? GetDevice(string id) + public DeviceInfoDto? GetDevice(string id) { var device = _devices.Values.Where(d => d.DeviceId == id).OrderByDescending(d => d.DateLastActivity).FirstOrDefault(); _deviceOptions.TryGetValue(id, out var deviceOption); var deviceInfo = device is null ? null : ToDeviceInfo(device, deviceOption); - return deviceInfo; + return deviceInfo is null ? null : ToDeviceInfoDto(deviceInfo); } /// <inheritdoc /> @@ -166,7 +176,7 @@ namespace Jellyfin.Server.Implementations.Devices } /// <inheritdoc /> - public QueryResult<DeviceInfo> GetDevicesForUser(Guid? userId) + public QueryResult<DeviceInfoDto> GetDevicesForUser(Guid? userId) { IEnumerable<Device> devices = _devices.Values .OrderByDescending(d => d.DateLastActivity) @@ -187,9 +197,11 @@ namespace Jellyfin.Server.Implementations.Devices { _deviceOptions.TryGetValue(device.DeviceId, out var option); return ToDeviceInfo(device, option); - }).ToArray(); + }) + .Select(ToDeviceInfoDto) + .ToArray(); - return new QueryResult<DeviceInfo>(array); + return new QueryResult<DeviceInfoDto>(array); } /// <inheritdoc /> @@ -235,13 +247,9 @@ namespace Jellyfin.Server.Implementations.Devices private DeviceInfo ToDeviceInfo(Device authInfo, DeviceOptions? options = null) { var caps = GetCapabilities(authInfo.DeviceId); - var user = _userManager.GetUserById(authInfo.UserId); - if (user is null) - { - throw new ResourceNotFoundException("User with UserId " + authInfo.UserId + " not found"); - } + var user = _userManager.GetUserById(authInfo.UserId) ?? throw new ResourceNotFoundException("User with UserId " + authInfo.UserId + " not found"); - return new DeviceInfo + return new() { AppName = authInfo.AppName, AppVersion = authInfo.AppVersion, @@ -254,5 +262,48 @@ namespace Jellyfin.Server.Implementations.Devices CustomName = options?.CustomName, }; } + + private DeviceOptionsDto ToDeviceOptionsDto(DeviceOptions options) + { + return new() + { + Id = options.Id, + DeviceId = options.DeviceId, + CustomName = options.CustomName, + }; + } + + private DeviceInfoDto ToDeviceInfoDto(DeviceInfo info) + { + return new() + { + Name = info.Name, + CustomName = info.CustomName, + AccessToken = info.AccessToken, + Id = info.Id, + LastUserName = info.LastUserName, + AppName = info.AppName, + AppVersion = info.AppVersion, + LastUserId = info.LastUserId, + DateLastActivity = info.DateLastActivity, + Capabilities = ToClientCapabilitiesDto(info.Capabilities), + IconUrl = info.IconUrl + }; + } + + /// <inheritdoc /> + public ClientCapabilitiesDto ToClientCapabilitiesDto(ClientCapabilities capabilities) + { + return new() + { + PlayableMediaTypes = capabilities.PlayableMediaTypes, + SupportedCommands = capabilities.SupportedCommands, + SupportsMediaControl = capabilities.SupportsMediaControl, + SupportsPersistentIdentifier = capabilities.SupportsPersistentIdentifier, + DeviceProfile = capabilities.DeviceProfile, + AppStoreUrl = capabilities.AppStoreUrl, + IconUrl = capabilities.IconUrl + }; + } } } diff --git a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs index 635e4eb3d..daf4d9631 100644 --- a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs +++ b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs @@ -1,20 +1,31 @@ #nullable disable -#pragma warning disable CS1591 - -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; -namespace MediaBrowser.Controller.Authentication +namespace MediaBrowser.Controller.Authentication; + +/// <summary> +/// A class representing an authentication result. +/// </summary> +public class AuthenticationResult { - public class AuthenticationResult - { - public UserDto User { get; set; } + /// <summary> + /// Gets or sets the user. + /// </summary> + public UserDto User { get; set; } - public SessionInfo SessionInfo { get; set; } + /// <summary> + /// Gets or sets the session info. + /// </summary> + public SessionInfoDto SessionInfo { get; set; } - public string AccessToken { get; set; } + /// <summary> + /// Gets or sets the access token. + /// </summary> + public string AccessToken { get; set; } - public string ServerId { get; set; } - } + /// <summary> + /// Gets or sets the server id. + /// </summary> + public string ServerId { get; set; } } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 5566421cb..cade53d99 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,81 +1,117 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Threading.Tasks; +using Jellyfin.Data.Dtos; using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Events; using Jellyfin.Data.Queries; using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; -namespace MediaBrowser.Controller.Devices +namespace MediaBrowser.Controller.Devices; + +/// <summary> +/// Device manager interface. +/// </summary> +public interface IDeviceManager { - public interface IDeviceManager - { - event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated; + /// <summary> + /// Event handler for updated device options. + /// </summary> + event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated; + + /// <summary> + /// Creates a new device. + /// </summary> + /// <param name="device">The device to create.</param> + /// <returns>A <see cref="Task{Device}"/> representing the creation of the device.</returns> + Task<Device> CreateDevice(Device device); - /// <summary> - /// Creates a new device. - /// </summary> - /// <param name="device">The device to create.</param> - /// <returns>A <see cref="Task{Device}"/> representing the creation of the device.</returns> - Task<Device> CreateDevice(Device device); + /// <summary> + /// Saves the capabilities. + /// </summary> + /// <param name="deviceId">The device id.</param> + /// <param name="capabilities">The capabilities.</param> + void SaveCapabilities(string deviceId, ClientCapabilities capabilities); - /// <summary> - /// Saves the capabilities. - /// </summary> - /// <param name="deviceId">The device id.</param> - /// <param name="capabilities">The capabilities.</param> - void SaveCapabilities(string deviceId, ClientCapabilities capabilities); + /// <summary> + /// Gets the capabilities. + /// </summary> + /// <param name="deviceId">The device id.</param> + /// <returns>ClientCapabilities.</returns> + ClientCapabilities GetCapabilities(string? deviceId); - /// <summary> - /// Gets the capabilities. - /// </summary> - /// <param name="deviceId">The device id.</param> - /// <returns>ClientCapabilities.</returns> - ClientCapabilities GetCapabilities(string deviceId); + /// <summary> + /// Gets the device information. + /// </summary> + /// <param name="id">The identifier.</param> + /// <returns>DeviceInfoDto.</returns> + DeviceInfoDto? GetDevice(string id); - /// <summary> - /// Gets the device information. - /// </summary> - /// <param name="id">The identifier.</param> - /// <returns>DeviceInfo.</returns> - DeviceInfo GetDevice(string id); + /// <summary> + /// Gets devices based on the provided query. + /// </summary> + /// <param name="query">The device query.</param> + /// <returns>A <see cref="Task{QueryResult}"/> representing the retrieval of the devices.</returns> + QueryResult<Device> GetDevices(DeviceQuery query); - /// <summary> - /// Gets devices based on the provided query. - /// </summary> - /// <param name="query">The device query.</param> - /// <returns>A <see cref="Task{QueryResult}"/> representing the retrieval of the devices.</returns> - QueryResult<Device> GetDevices(DeviceQuery query); + /// <summary> + /// Gets device infromation based on the provided query. + /// </summary> + /// <param name="query">The device query.</param> + /// <returns>A <see cref="Task{QueryResult}"/> representing the retrieval of the device information.</returns> + QueryResult<DeviceInfo> GetDeviceInfos(DeviceQuery query); - QueryResult<DeviceInfo> GetDeviceInfos(DeviceQuery query); + /// <summary> + /// Gets the device information. + /// </summary> + /// <param name="userId">The user's id, or <c>null</c>.</param> + /// <returns>IEnumerable<DeviceInfoDto>.</returns> + QueryResult<DeviceInfoDto> GetDevicesForUser(Guid? userId); - /// <summary> - /// Gets the devices. - /// </summary> - /// <param name="userId">The user's id, or <c>null</c>.</param> - /// <returns>IEnumerable<DeviceInfo>.</returns> - QueryResult<DeviceInfo> GetDevicesForUser(Guid? userId); + /// <summary> + /// Deletes a device. + /// </summary> + /// <param name="device">The device.</param> + /// <returns>A <see cref="Task"/> representing the deletion of the device.</returns> + Task DeleteDevice(Device device); - Task DeleteDevice(Device device); + /// <summary> + /// Updates a device. + /// </summary> + /// <param name="device">The device.</param> + /// <returns>A <see cref="Task"/> representing the update of the device.</returns> + Task UpdateDevice(Device device); - Task UpdateDevice(Device device); + /// <summary> + /// Determines whether this instance [can access device] the specified user identifier. + /// </summary> + /// <param name="user">The user to test.</param> + /// <param name="deviceId">The device id to test.</param> + /// <returns>Whether the user can access the device.</returns> + bool CanAccessDevice(User user, string deviceId); - /// <summary> - /// Determines whether this instance [can access device] the specified user identifier. - /// </summary> - /// <param name="user">The user to test.</param> - /// <param name="deviceId">The device id to test.</param> - /// <returns>Whether the user can access the device.</returns> - bool CanAccessDevice(User user, string deviceId); + /// <summary> + /// Updates the options of a device. + /// </summary> + /// <param name="deviceId">The device id.</param> + /// <param name="deviceName">The device name.</param> + /// <returns>A <see cref="Task"/> representing the update of the device options.</returns> + Task UpdateDeviceOptions(string deviceId, string? deviceName); - Task UpdateDeviceOptions(string deviceId, string deviceName); + /// <summary> + /// Gets the options of a device. + /// </summary> + /// <param name="deviceId">The device id.</param> + /// <returns><see cref="DeviceOptions"/> of the device.</returns> + DeviceOptionsDto? GetDeviceOptions(string deviceId); - DeviceOptions GetDeviceOptions(string deviceId); - } + /// <summary> + /// Gets the dto for client capabilites. + /// </summary> + /// <param name="capabilities">The client capabilities.</param> + /// <returns><see cref="ClientCapabilitiesDto"/> of the device.</returns> + ClientCapabilitiesDto ToClientCapabilitiesDto(ClientCapabilities capabilities); } diff --git a/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs index 357ef9406..1542c58b3 100644 --- a/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs +++ b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs @@ -1,6 +1,5 @@ using System; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; namespace MediaBrowser.Controller.Events.Authentication; @@ -29,7 +28,7 @@ public class AuthenticationResultEventArgs : EventArgs /// <summary> /// Gets or sets the session information. /// </summary> - public SessionInfo? SessionInfo { get; set; } + public SessionInfoDto? SessionInfo { get; set; } /// <summary> /// Gets or sets the server id. diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs index 3504831b8..833074541 100644 --- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs +++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.ComponentModel; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound; @@ -8,13 +9,13 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound; /// <summary> /// Sessions message. /// </summary> -public class SessionsMessage : OutboundWebSocketMessage<IReadOnlyList<SessionInfo>> +public class SessionsMessage : OutboundWebSocketMessage<IReadOnlyList<SessionInfoDto>> { /// <summary> /// Initializes a new instance of the <see cref="SessionsMessage"/> class. /// </summary> /// <param name="data">Session info.</param> - public SessionsMessage(IReadOnlyList<SessionInfo> data) + public SessionsMessage(IReadOnlyList<SessionInfoDto> data) : base(data) { } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 5a47236f9..f2e98dd78 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities.Security; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; @@ -293,6 +294,16 @@ namespace MediaBrowser.Controller.Session SessionInfo GetSession(string deviceId, string client, string version); /// <summary> + /// Gets all sessions available to a user. + /// </summary> + /// <param name="userId">The session identifier.</param> + /// <param name="deviceId">The device id.</param> + /// <param name="activeWithinSeconds">Active within session limit.</param> + /// <param name="controllableUserToCheck">Filter for sessions remote controllable for this user.</param> + /// <returns>IReadOnlyList{SessionInfoDto}.</returns> + IReadOnlyList<SessionInfoDto> GetSessions(Guid userId, string deviceId, int? activeWithinSeconds, Guid? controllableUserToCheck); + + /// <summary> /// Gets the session by authentication token. /// </summary> /// <param name="token">The token.</param> diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 9e3358818..3ba1bfce4 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Linq; @@ -27,28 +25,45 @@ namespace MediaBrowser.Controller.Session private readonly ISessionManager _sessionManager; private readonly ILogger _logger; - private readonly object _progressLock = new object(); + private readonly object _progressLock = new(); private Timer _progressTimer; private PlaybackProgressInfo _lastProgressInfo; - private bool _disposed = false; + private bool _disposed; + /// <summary> + /// Initializes a new instance of the <see cref="SessionInfo"/> class. + /// </summary> + /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param> + /// <param name="logger">Instance of <see cref="ILogger"/> interface.</param> public SessionInfo(ISessionManager sessionManager, ILogger logger) { _sessionManager = sessionManager; _logger = logger; - AdditionalUsers = Array.Empty<SessionUserInfo>(); + AdditionalUsers = []; PlayState = new PlayerStateInfo(); - SessionControllers = Array.Empty<ISessionController>(); - NowPlayingQueue = Array.Empty<QueueItem>(); - NowPlayingQueueFullItems = Array.Empty<BaseItemDto>(); + SessionControllers = []; + NowPlayingQueue = []; + NowPlayingQueueFullItems = []; } + /// <summary> + /// Gets or sets the play state. + /// </summary> + /// <value>The play state.</value> public PlayerStateInfo PlayState { get; set; } - public SessionUserInfo[] AdditionalUsers { get; set; } + /// <summary> + /// Gets or sets the additional users. + /// </summary> + /// <value>The additional users.</value> + public IReadOnlyList<SessionUserInfo> AdditionalUsers { get; set; } + /// <summary> + /// Gets or sets the client capabilities. + /// </summary> + /// <value>The client capabilities.</value> public ClientCapabilities Capabilities { get; set; } /// <summary> @@ -67,7 +82,7 @@ namespace MediaBrowser.Controller.Session { if (Capabilities is null) { - return Array.Empty<MediaType>(); + return []; } return Capabilities.PlayableMediaTypes; @@ -134,9 +149,17 @@ namespace MediaBrowser.Controller.Session /// <value>The now playing item.</value> public BaseItemDto NowPlayingItem { get; set; } + /// <summary> + /// Gets or sets the now playing queue full items. + /// </summary> + /// <value>The now playing queue full items.</value> [JsonIgnore] public BaseItem FullNowPlayingItem { get; set; } + /// <summary> + /// Gets or sets the now viewing item. + /// </summary> + /// <value>The now viewing item.</value> public BaseItemDto NowViewingItem { get; set; } /// <summary> @@ -156,8 +179,12 @@ namespace MediaBrowser.Controller.Session /// </summary> /// <value>The session controller.</value> [JsonIgnore] - public ISessionController[] SessionControllers { get; set; } + public IReadOnlyList<ISessionController> SessionControllers { get; set; } + /// <summary> + /// Gets or sets the transcoding info. + /// </summary> + /// <value>The transcoding info.</value> public TranscodingInfo TranscodingInfo { get; set; } /// <summary> @@ -177,7 +204,7 @@ namespace MediaBrowser.Controller.Session } } - if (controllers.Length > 0) + if (controllers.Count > 0) { return false; } @@ -186,6 +213,10 @@ namespace MediaBrowser.Controller.Session } } + /// <summary> + /// Gets a value indicating whether the session supports media control. + /// </summary> + /// <value><c>true</c> if this session supports media control; otherwise, <c>false</c>.</value> public bool SupportsMediaControl { get @@ -208,6 +239,10 @@ namespace MediaBrowser.Controller.Session } } + /// <summary> + /// Gets a value indicating whether the session supports remote control. + /// </summary> + /// <value><c>true</c> if this session supports remote control; otherwise, <c>false</c>.</value> public bool SupportsRemoteControl { get @@ -230,16 +265,40 @@ namespace MediaBrowser.Controller.Session } } + /// <summary> + /// Gets or sets the now playing queue. + /// </summary> + /// <value>The now playing queue.</value> public IReadOnlyList<QueueItem> NowPlayingQueue { get; set; } + /// <summary> + /// Gets or sets the now playing queue full items. + /// </summary> + /// <value>The now playing queue full items.</value> public IReadOnlyList<BaseItemDto> NowPlayingQueueFullItems { get; set; } + /// <summary> + /// Gets or sets a value indicating whether the session has a custom device name. + /// </summary> + /// <value><c>true</c> if this session has a custom device name; otherwise, <c>false</c>.</value> public bool HasCustomDeviceName { get; set; } + /// <summary> + /// Gets or sets the playlist item id. + /// </summary> + /// <value>The splaylist item id.</value> public string PlaylistItemId { get; set; } + /// <summary> + /// Gets or sets the server id. + /// </summary> + /// <value>The server id.</value> public string ServerId { get; set; } + /// <summary> + /// Gets or sets the user primary image tag. + /// </summary> + /// <value>The user primary image tag.</value> public string UserPrimaryImageTag { get; set; } /// <summary> @@ -247,8 +306,14 @@ namespace MediaBrowser.Controller.Session /// </summary> /// <value>The supported commands.</value> public IReadOnlyList<GeneralCommandType> SupportedCommands - => Capabilities is null ? Array.Empty<GeneralCommandType>() : Capabilities.SupportedCommands; + => Capabilities is null ? [] : Capabilities.SupportedCommands; + /// <summary> + /// Ensures a controller of type exists. + /// </summary> + /// <typeparam name="T">Class to register.</typeparam> + /// <param name="factory">The factory.</param> + /// <returns>Tuple{ISessionController, bool}.</returns> public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory) { var controllers = SessionControllers.ToList(); @@ -261,18 +326,27 @@ namespace MediaBrowser.Controller.Session } var newController = factory(this); - _logger.LogDebug("Creating new {0}", newController.GetType().Name); + _logger.LogDebug("Creating new {Factory}", newController.GetType().Name); controllers.Add(newController); - SessionControllers = controllers.ToArray(); + SessionControllers = [.. controllers]; return new Tuple<ISessionController, bool>(newController, true); } + /// <summary> + /// Adds a controller to the session. + /// </summary> + /// <param name="controller">The controller.</param> public void AddController(ISessionController controller) { - SessionControllers = [..SessionControllers, controller]; + SessionControllers = [.. SessionControllers, controller]; } + /// <summary> + /// Gets a value indicating whether the session contains a user. + /// </summary> + /// <param name="userId">The user id to check.</param> + /// <returns><c>true</c> if this session contains the user; otherwise, <c>false</c>.</returns> public bool ContainsUser(Guid userId) { if (UserId.Equals(userId)) @@ -291,6 +365,11 @@ namespace MediaBrowser.Controller.Session return false; } + /// <summary> + /// Starts automatic progressing. + /// </summary> + /// <param name="progressInfo">The playback progress info.</param> + /// <value>The supported commands.</value> public void StartAutomaticProgress(PlaybackProgressInfo progressInfo) { if (_disposed) @@ -359,6 +438,9 @@ namespace MediaBrowser.Controller.Session } } + /// <summary> + /// Stops automatic progressing. + /// </summary> public void StopAutomaticProgress() { lock (_progressLock) @@ -373,6 +455,10 @@ namespace MediaBrowser.Controller.Session } } + /// <summary> + /// Disposes the instance async. + /// </summary> + /// <returns>ValueTask.</returns> public async ValueTask DisposeAsync() { _disposed = true; @@ -380,7 +466,7 @@ namespace MediaBrowser.Controller.Session StopAutomaticProgress(); var controllers = SessionControllers.ToList(); - SessionControllers = Array.Empty<ISessionController>(); + SessionControllers = []; foreach (var controller in controllers) { diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs index 4962992a0..115598613 100644 --- a/MediaBrowser.Model/Devices/DeviceInfo.cs +++ b/MediaBrowser.Model/Devices/DeviceInfo.cs @@ -1,69 +1,84 @@ -#nullable disable -#pragma warning disable CS1591 - using System; using MediaBrowser.Model.Session; -namespace MediaBrowser.Model.Devices +namespace MediaBrowser.Model.Devices; + +/// <summary> +/// A class for device Information. +/// </summary> +public class DeviceInfo { - public class DeviceInfo + /// <summary> + /// Initializes a new instance of the <see cref="DeviceInfo"/> class. + /// </summary> + public DeviceInfo() { - public DeviceInfo() - { - Capabilities = new ClientCapabilities(); - } + Capabilities = new ClientCapabilities(); + } - public string Name { get; set; } + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + public string? Name { get; set; } - public string CustomName { get; set; } + /// <summary> + /// Gets or sets the custom name. + /// </summary> + /// <value>The custom name.</value> + public string? CustomName { get; set; } - /// <summary> - /// Gets or sets the access token. - /// </summary> - public string AccessToken { get; set; } + /// <summary> + /// Gets or sets the access token. + /// </summary> + /// <value>The access token.</value> + public string? AccessToken { get; set; } - /// <summary> - /// Gets or sets the identifier. - /// </summary> - /// <value>The identifier.</value> - public string Id { get; set; } + /// <summary> + /// Gets or sets the identifier. + /// </summary> + /// <value>The identifier.</value> + public string? Id { get; set; } - /// <summary> - /// Gets or sets the last name of the user. - /// </summary> - /// <value>The last name of the user.</value> - public string LastUserName { get; set; } + /// <summary> + /// Gets or sets the last name of the user. + /// </summary> + /// <value>The last name of the user.</value> + public string? LastUserName { get; set; } - /// <summary> - /// Gets or sets the name of the application. - /// </summary> - /// <value>The name of the application.</value> - public string AppName { get; set; } + /// <summary> + /// Gets or sets the name of the application. + /// </summary> + /// <value>The name of the application.</value> + public string? AppName { get; set; } - /// <summary> - /// Gets or sets the application version. - /// </summary> - /// <value>The application version.</value> - public string AppVersion { get; set; } + /// <summary> + /// Gets or sets the application version. + /// </summary> + /// <value>The application version.</value> + public string? AppVersion { get; set; } - /// <summary> - /// Gets or sets the last user identifier. - /// </summary> - /// <value>The last user identifier.</value> - public Guid LastUserId { get; set; } + /// <summary> + /// Gets or sets the last user identifier. + /// </summary> + /// <value>The last user identifier.</value> + public Guid? LastUserId { get; set; } - /// <summary> - /// Gets or sets the date last modified. - /// </summary> - /// <value>The date last modified.</value> - public DateTime DateLastActivity { get; set; } + /// <summary> + /// Gets or sets the date last modified. + /// </summary> + /// <value>The date last modified.</value> + public DateTime? DateLastActivity { get; set; } - /// <summary> - /// Gets or sets the capabilities. - /// </summary> - /// <value>The capabilities.</value> - public ClientCapabilities Capabilities { get; set; } + /// <summary> + /// Gets or sets the capabilities. + /// </summary> + /// <value>The capabilities.</value> + public ClientCapabilities Capabilities { get; set; } - public string IconUrl { get; set; } - } + /// <summary> + /// Gets or sets the icon URL. + /// </summary> + /// <value>The icon URL.</value> + public string? IconUrl { get; set; } } diff --git a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs index c699c469d..5963ed270 100644 --- a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs +++ b/MediaBrowser.Model/Dto/ClientCapabilitiesDto.cs @@ -1,13 +1,11 @@ -using System; using System.Collections.Generic; -using System.ComponentModel; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Session; -namespace Jellyfin.Api.Models.SessionDtos; +namespace MediaBrowser.Model.Dto; /// <summary> /// Client capabilities dto. @@ -18,13 +16,13 @@ public class ClientCapabilitiesDto /// Gets or sets the list of playable media types. /// </summary> [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] - public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } = Array.Empty<MediaType>(); + public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } = []; /// <summary> /// Gets or sets the list of supported commands. /// </summary> [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] - public IReadOnlyList<GeneralCommandType> SupportedCommands { get; set; } = Array.Empty<GeneralCommandType>(); + public IReadOnlyList<GeneralCommandType> SupportedCommands { get; set; } = []; /// <summary> /// Gets or sets a value indicating whether session supports media control. @@ -51,18 +49,6 @@ public class ClientCapabilitiesDto /// </summary> public string? IconUrl { get; set; } -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - // TODO: Remove after 10.9 - [Obsolete("Unused")] - [DefaultValue(false)] - public bool? SupportsContentUploading { get; set; } = false; - - // TODO: Remove after 10.9 - [Obsolete("Unused")] - [DefaultValue(false)] - public bool? SupportsSync { get; set; } = false; -#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member - /// <summary> /// Convert the dto to the full <see cref="ClientCapabilities"/> model. /// </summary> diff --git a/MediaBrowser.Model/Dto/DeviceInfoDto.cs b/MediaBrowser.Model/Dto/DeviceInfoDto.cs new file mode 100644 index 000000000..ac7a731a9 --- /dev/null +++ b/MediaBrowser.Model/Dto/DeviceInfoDto.cs @@ -0,0 +1,83 @@ +using System; + +namespace MediaBrowser.Model.Dto; + +/// <summary> +/// A DTO representing device information. +/// </summary> +public class DeviceInfoDto +{ + /// <summary> + /// Initializes a new instance of the <see cref="DeviceInfoDto"/> class. + /// </summary> + public DeviceInfoDto() + { + Capabilities = new ClientCapabilitiesDto(); + } + + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + public string? Name { get; set; } + + /// <summary> + /// Gets or sets the custom name. + /// </summary> + /// <value>The custom name.</value> + public string? CustomName { get; set; } + + /// <summary> + /// Gets or sets the access token. + /// </summary> + /// <value>The access token.</value> + public string? AccessToken { get; set; } + + /// <summary> + /// Gets or sets the identifier. + /// </summary> + /// <value>The identifier.</value> + public string? Id { get; set; } + + /// <summary> + /// Gets or sets the last name of the user. + /// </summary> + /// <value>The last name of the user.</value> + public string? LastUserName { get; set; } + + /// <summary> + /// Gets or sets the name of the application. + /// </summary> + /// <value>The name of the application.</value> + public string? AppName { get; set; } + + /// <summary> + /// Gets or sets the application version. + /// </summary> + /// <value>The application version.</value> + public string? AppVersion { get; set; } + + /// <summary> + /// Gets or sets the last user identifier. + /// </summary> + /// <value>The last user identifier.</value> + public Guid? LastUserId { get; set; } + + /// <summary> + /// Gets or sets the date last modified. + /// </summary> + /// <value>The date last modified.</value> + public DateTime? DateLastActivity { get; set; } + + /// <summary> + /// Gets or sets the capabilities. + /// </summary> + /// <value>The capabilities.</value> + public ClientCapabilitiesDto Capabilities { get; set; } + + /// <summary> + /// Gets or sets the icon URL. + /// </summary> + /// <value>The icon URL.</value> + public string? IconUrl { get; set; } +} diff --git a/MediaBrowser.Model/Dto/SessionInfoDto.cs b/MediaBrowser.Model/Dto/SessionInfoDto.cs new file mode 100644 index 000000000..2496c933a --- /dev/null +++ b/MediaBrowser.Model/Dto/SessionInfoDto.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using Jellyfin.Data.Enums; +using MediaBrowser.Model.Session; + +namespace MediaBrowser.Model.Dto; + +/// <summary> +/// Session info DTO. +/// </summary> +public class SessionInfoDto +{ + /// <summary> + /// Gets or sets the play state. + /// </summary> + /// <value>The play state.</value> + public PlayerStateInfo? PlayState { get; set; } + + /// <summary> + /// Gets or sets the additional users. + /// </summary> + /// <value>The additional users.</value> + public IReadOnlyList<SessionUserInfo>? AdditionalUsers { get; set; } + + /// <summary> + /// Gets or sets the client capabilities. + /// </summary> + /// <value>The client capabilities.</value> + public ClientCapabilitiesDto? Capabilities { get; set; } + + /// <summary> + /// Gets or sets the remote end point. + /// </summary> + /// <value>The remote end point.</value> + public string? RemoteEndPoint { get; set; } + + /// <summary> + /// Gets or sets the playable media types. + /// </summary> + /// <value>The playable media types.</value> + public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } = []; + + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + public string? Id { get; set; } + + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + public Guid UserId { get; set; } + + /// <summary> + /// Gets or sets the username. + /// </summary> + /// <value>The username.</value> + public string? UserName { get; set; } + + /// <summary> + /// Gets or sets the type of the client. + /// </summary> + /// <value>The type of the client.</value> + public string? Client { get; set; } + + /// <summary> + /// Gets or sets the last activity date. + /// </summary> + /// <value>The last activity date.</value> + public DateTime LastActivityDate { get; set; } + + /// <summary> + /// Gets or sets the last playback check in. + /// </summary> + /// <value>The last playback check in.</value> + public DateTime LastPlaybackCheckIn { get; set; } + + /// <summary> + /// Gets or sets the last paused date. + /// </summary> + /// <value>The last paused date.</value> + public DateTime? LastPausedDate { get; set; } + + /// <summary> + /// Gets or sets the name of the device. + /// </summary> + /// <value>The name of the device.</value> + public string? DeviceName { get; set; } + + /// <summary> + /// Gets or sets the type of the device. + /// </summary> + /// <value>The type of the device.</value> + public string? DeviceType { get; set; } + + /// <summary> + /// Gets or sets the now playing item. + /// </summary> + /// <value>The now playing item.</value> + public BaseItemDto? NowPlayingItem { get; set; } + + /// <summary> + /// Gets or sets the now viewing item. + /// </summary> + /// <value>The now viewing item.</value> + public BaseItemDto? NowViewingItem { get; set; } + + /// <summary> + /// Gets or sets the device id. + /// </summary> + /// <value>The device id.</value> + public string? DeviceId { get; set; } + + /// <summary> + /// Gets or sets the application version. + /// </summary> + /// <value>The application version.</value> + public string? ApplicationVersion { get; set; } + + /// <summary> + /// Gets or sets the transcoding info. + /// </summary> + /// <value>The transcoding info.</value> + public TranscodingInfo? TranscodingInfo { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this session is active. + /// </summary> + /// <value><c>true</c> if this session is active; otherwise, <c>false</c>.</value> + public bool IsActive { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the session supports media control. + /// </summary> + /// <value><c>true</c> if this session supports media control; otherwise, <c>false</c>.</value> + public bool SupportsMediaControl { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the session supports remote control. + /// </summary> + /// <value><c>true</c> if this session supports remote control; otherwise, <c>false</c>.</value> + public bool SupportsRemoteControl { get; set; } + + /// <summary> + /// Gets or sets the now playing queue. + /// </summary> + /// <value>The now playing queue.</value> + public IReadOnlyList<QueueItem>? NowPlayingQueue { get; set; } + + /// <summary> + /// Gets or sets the now playing queue full items. + /// </summary> + /// <value>The now playing queue full items.</value> + public IReadOnlyList<BaseItemDto>? NowPlayingQueueFullItems { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the session has a custom device name. + /// </summary> + /// <value><c>true</c> if this session has a custom device name; otherwise, <c>false</c>.</value> + public bool HasCustomDeviceName { get; set; } + + /// <summary> + /// Gets or sets the playlist item id. + /// </summary> + /// <value>The splaylist item id.</value> + public string? PlaylistItemId { get; set; } + + /// <summary> + /// Gets or sets the server id. + /// </summary> + /// <value>The server id.</value> + public string? ServerId { get; set; } + + /// <summary> + /// Gets or sets the user primary image tag. + /// </summary> + /// <value>The user primary image tag.</value> + public string? UserPrimaryImageTag { get; set; } + + /// <summary> + /// Gets or sets the supported commands. + /// </summary> + /// <value>The supported commands.</value> + public IReadOnlyList<GeneralCommandType> SupportedCommands { get; set; } = []; +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs index 1466d3a71..b9477ce6b 100644 --- a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs +++ b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; @@ -35,38 +37,27 @@ namespace Jellyfin.Extensions.Json.Converters var stringEntries = reader.GetString()!.Split(Delimiter, StringSplitOptions.RemoveEmptyEntries); if (stringEntries.Length == 0) { - return Array.Empty<T>(); + return []; } - var parsedValues = new object[stringEntries.Length]; - var convertedCount = 0; + var typedValues = new List<T>(); for (var i = 0; i < stringEntries.Length; i++) { try { - parsedValues[i] = _typeConverter.ConvertFromInvariantString(stringEntries[i].Trim()) ?? throw new FormatException(); - convertedCount++; + var parsedValue = _typeConverter.ConvertFromInvariantString(stringEntries[i].Trim()); + if (parsedValue is not null) + { + typedValues.Add((T)parsedValue); + } } catch (FormatException) { - // TODO log when upgraded to .Net6 - // https://github.com/dotnet/runtime/issues/42975 - // _logger.LogDebug(e, "Error converting value."); + // Ignore unconvertable inputs } } - var typedValues = new T[convertedCount]; - var typedValueIndex = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - if (parsedValues[i] is not null) - { - typedValues.SetValue(parsedValues[i], typedValueIndex); - typedValueIndex++; - } - } - - return typedValues; + return [.. typedValues]; } return JsonSerializer.Deserialize<T[]>(ref reader, options); @@ -75,7 +66,39 @@ namespace Jellyfin.Extensions.Json.Converters /// <inheritdoc /> public override void Write(Utf8JsonWriter writer, T[]? value, JsonSerializerOptions options) { - throw new NotImplementedException(); + if (value is not null) + { + writer.WriteStartArray(); + if (value.Length > 0) + { + var toWrite = value.Length - 1; + foreach (var it in value) + { + var wrote = false; + if (it is not null) + { + writer.WriteStringValue(it.ToString()); + wrote = true; + } + + if (toWrite > 0) + { + if (wrote) + { + writer.WriteStringValue(Delimiter.ToString()); + } + + toWrite--; + } + } + } + + writer.WriteEndArray(); + } + else + { + writer.WriteNullValue(); + } } } } diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs index 61105b42b..9fc015823 100644 --- a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs @@ -41,7 +41,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel<string> { - Value = new[] { "a", "b", "c" } + Value = ["a", "b", "c"] }; var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a,b,c"" }", _jsonOptions); @@ -53,7 +53,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel<string> { - Value = new[] { "a", "b", "c" } + Value = ["a", "b", "c"] }; var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a, b, c"" }", _jsonOptions); @@ -65,7 +65,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel<GeneralCommandType> { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", _jsonOptions); @@ -77,7 +77,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel<GeneralCommandType> { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", _jsonOptions); @@ -89,7 +89,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel<GeneralCommandType> { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,TotallyNotAVallidCommand,MoveDown"" }", _jsonOptions); @@ -101,7 +101,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel<GeneralCommandType> { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", _jsonOptions); @@ -113,7 +113,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel<string> { - Value = new[] { "a", "b", "c" } + Value = ["a", "b", "c"] }; var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", _jsonOptions); @@ -125,7 +125,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters { var desiredValue = new GenericBodyArrayModel<GeneralCommandType> { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + Value = [GeneralCommandType.MoveUp, GeneralCommandType.MoveDown] }; var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", _jsonOptions); |
