< Summary - Jellyfin

Information
Class: Jellyfin.Server.Implementations.Devices.DeviceManager
Assembly: Jellyfin.Server.Implementations
File(s): /srv/git/jellyfin/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
Line coverage
34%
Covered lines: 50
Uncovered lines: 94
Coverable lines: 144
Total lines: 313
Line coverage: 34.7%
Branch coverage
23%
Covered branches: 9
Total branches: 38
Branch coverage: 23.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/26/2026 - 12:14:14 AM Line coverage: 38.3% (43/112) Branch coverage: 29.4% (10/34) Total lines: 3114/19/2026 - 12:14:27 AM Line coverage: 35.2% (50/142) Branch coverage: 26.3% (10/38) Total lines: 3115/20/2026 - 12:15:44 AM Line coverage: 35.2% (50/142) Branch coverage: 23.6% (9/38) Total lines: 3117/6/2026 - 12:16:28 AM Line coverage: 34.7% (50/144) Branch coverage: 23.6% (9/38) Total lines: 313 3/26/2026 - 12:14:14 AM Line coverage: 38.3% (43/112) Branch coverage: 29.4% (10/34) Total lines: 3114/19/2026 - 12:14:27 AM Line coverage: 35.2% (50/142) Branch coverage: 26.3% (10/38) Total lines: 3115/20/2026 - 12:15:44 AM Line coverage: 35.2% (50/142) Branch coverage: 23.6% (9/38) Total lines: 3117/6/2026 - 12:16:28 AM Line coverage: 34.7% (50/144) Branch coverage: 23.6% (9/38) Total lines: 313

Coverage delta

Coverage delta 4 -4

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%4486.66%
SaveCapabilities(...)100%210%
UpdateDeviceOptions()0%2040%
CreateDevice()100%11100%
GetDeviceOptions(...)50%2266.66%
GetCapabilities(...)50%4480%
GetDevice(...)0%2040%
GetDevices(...)33.33%6683.33%
GetDeviceInfos(...)100%210%
GetDevicesForUser(...)0%2040%
DeleteDevice()100%210%
UpdateDevice()100%210%
CanAccessDevice(...)33.33%7666.66%
ToDeviceInfo(...)0%2040%
ToDeviceOptionsDto(...)100%210%
ToDeviceInfoDto(...)100%210%
ToClientCapabilitiesDto(...)100%11100%

File(s)

/srv/git/jellyfin/Jellyfin.Server.Implementations/Devices/DeviceManager.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.Linq;
 5using System.Threading.Tasks;
 6using Jellyfin.Data;
 7using Jellyfin.Data.Dtos;
 8using Jellyfin.Data.Events;
 9using Jellyfin.Data.Queries;
 10using Jellyfin.Database.Implementations;
 11using Jellyfin.Database.Implementations.Entities;
 12using Jellyfin.Database.Implementations.Entities.Security;
 13using Jellyfin.Database.Implementations.Enums;
 14using Jellyfin.Extensions;
 15using MediaBrowser.Common.Extensions;
 16using MediaBrowser.Controller.Devices;
 17using MediaBrowser.Controller.Library;
 18using MediaBrowser.Model.Devices;
 19using MediaBrowser.Model.Dto;
 20using MediaBrowser.Model.Querying;
 21using MediaBrowser.Model.Session;
 22using Microsoft.EntityFrameworkCore;
 23
 24namespace Jellyfin.Server.Implementations.Devices
 25{
 26    /// <summary>
 27    /// Manages the creation, updating, and retrieval of devices.
 28    /// </summary>
 29    public class DeviceManager : IDeviceManager
 30    {
 31        private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 32        private readonly IUserManager _userManager;
 2233        private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new();
 34        private readonly ConcurrentDictionary<int, Device> _devices;
 35        private readonly ConcurrentDictionary<string, DeviceOptions> _deviceOptions;
 36
 37        /// <summary>
 38        /// Initializes a new instance of the <see cref="DeviceManager"/> class.
 39        /// </summary>
 40        /// <param name="dbProvider">The database provider.</param>
 41        /// <param name="userManager">The user manager.</param>
 42        public DeviceManager(IDbContextFactory<JellyfinDbContext> dbProvider, IUserManager userManager)
 43        {
 2244            _dbProvider = dbProvider;
 2245            _userManager = userManager;
 2246            _devices = new ConcurrentDictionary<int, Device>();
 2247            _deviceOptions = new ConcurrentDictionary<string, DeviceOptions>();
 48
 2249            using var dbContext = _dbProvider.CreateDbContext();
 4450            foreach (var device in dbContext.Devices
 2251                         .OrderBy(d => d.Id)
 2252                         .AsEnumerable())
 53            {
 054                _devices.TryAdd(device.Id, device);
 55            }
 56
 4457            foreach (var deviceOption in dbContext.DeviceOptions
 2258                         .OrderBy(d => d.Id)
 2259                         .AsEnumerable())
 60            {
 061                _deviceOptions.TryAdd(deviceOption.DeviceId, deviceOption);
 62            }
 2263        }
 64
 65        /// <inheritdoc />
 66        public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>>? DeviceOptionsUpdated;
 67
 68        /// <inheritdoc />
 69        public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
 70        {
 071            _capabilitiesMap[deviceId] = capabilities;
 072        }
 73
 74        /// <inheritdoc />
 75        public async Task UpdateDeviceOptions(string deviceId, string? deviceName)
 76        {
 77            DeviceOptions? deviceOptions;
 078            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 079            await using (dbContext.ConfigureAwait(false))
 80            {
 081                deviceOptions = await dbContext.DeviceOptions.FirstOrDefaultAsync(dev => dev.DeviceId == deviceId).Confi
 082                if (deviceOptions is null)
 83                {
 084                    deviceOptions = new DeviceOptions(deviceId);
 085                    dbContext.DeviceOptions.Add(deviceOptions);
 86                }
 87
 088                deviceOptions.CustomName = deviceName;
 089                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 90            }
 91
 092            _deviceOptions[deviceId] = deviceOptions;
 93
 094            DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, Devi
 095        }
 96
 97        /// <inheritdoc />
 98        public async Task<Device> CreateDevice(Device device)
 99        {
 16100            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 16101            await using (dbContext.ConfigureAwait(false))
 102            {
 16103                dbContext.Devices.Add(device);
 16104                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 16105                _devices.TryAdd(device.Id, device);
 106            }
 107
 16108            return device;
 16109        }
 110
 111        /// <inheritdoc />
 112        public DeviceOptionsDto? GetDeviceOptions(string deviceId)
 113        {
 40114            if (_deviceOptions.TryGetValue(deviceId, out var deviceOptions))
 115            {
 0116                return ToDeviceOptionsDto(deviceOptions);
 117            }
 118
 40119            return null;
 120        }
 121
 122        /// <inheritdoc />
 123        public ClientCapabilities GetCapabilities(string? deviceId)
 124        {
 17125            if (deviceId is null)
 126            {
 0127                return new();
 128            }
 129
 17130            return _capabilitiesMap.TryGetValue(deviceId, out ClientCapabilities? result)
 17131                ? result
 17132                : new();
 133        }
 134
 135        /// <inheritdoc />
 136        public DeviceInfoDto? GetDevice(string id)
 137        {
 0138            var device = _devices.Values.Where(d => d.DeviceId == id).OrderByDescending(d => d.DateLastActivity).FirstOr
 0139            _deviceOptions.TryGetValue(id, out var deviceOption);
 140
 0141            var deviceInfo = device is null ? null : ToDeviceInfo(device, deviceOption);
 0142            return deviceInfo is null ? null : ToDeviceInfoDto(deviceInfo);
 143        }
 144
 145        /// <inheritdoc />
 146        public QueryResult<Device> GetDevices(DeviceQuery query)
 147        {
 143148            IEnumerable<Device> devices = _devices.Values
 143149                .Where(device => !query.UserId.HasValue || device.UserId.Equals(query.UserId.Value))
 143150                .Where(device => query.DeviceId is null || device.DeviceId == query.DeviceId)
 143151                .Where(device => query.AccessToken is null || device.AccessToken == query.AccessToken)
 143152                .OrderBy(d => d.Id)
 143153                .ToList();
 143154            var count = devices.Count();
 155
 143156            if (query.Skip.HasValue)
 157            {
 0158                devices = devices.Skip(query.Skip.Value);
 159            }
 160
 143161            if (query.Limit.HasValue && query.Limit.Value > 0)
 162            {
 0163                devices = devices.Take(query.Limit.Value);
 164            }
 165
 143166            return new QueryResult<Device>(query.Skip, count, devices.ToList());
 167        }
 168
 169        /// <inheritdoc />
 170        public QueryResult<DeviceInfo> GetDeviceInfos(DeviceQuery query)
 171        {
 0172            var devices = GetDevices(query);
 173
 0174            return new QueryResult<DeviceInfo>(
 0175                devices.StartIndex,
 0176                devices.TotalRecordCount,
 0177                devices.Items.Select(device => ToDeviceInfo(device)).ToList());
 178        }
 179
 180        /// <inheritdoc />
 181        public QueryResult<DeviceInfoDto> GetDevicesForUser(Guid? userId)
 182        {
 0183            IEnumerable<Device> devices = _devices.Values
 0184                .OrderByDescending(d => d.DateLastActivity)
 0185                .ThenBy(d => d.DeviceId);
 186
 0187            if (!userId.IsNullOrEmpty())
 188            {
 0189                var user = _userManager.GetUserById(userId.Value);
 0190                if (user is null)
 191                {
 0192                    throw new ResourceNotFoundException();
 193                }
 194
 0195                devices = devices.Where(i => CanAccessDevice(user, i.DeviceId));
 196            }
 197
 0198            var array = devices.Select(device =>
 0199                {
 0200                    _deviceOptions.TryGetValue(device.DeviceId, out var option);
 0201                    return ToDeviceInfo(device, option);
 0202                })
 0203                .Select(ToDeviceInfoDto)
 0204                .ToArray();
 205
 0206            return new QueryResult<DeviceInfoDto>(array);
 207        }
 208
 209        /// <inheritdoc />
 210        public async Task DeleteDevice(Device device)
 211        {
 0212            _devices.TryRemove(device.Id, out _);
 0213            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0214            await using (dbContext.ConfigureAwait(false))
 215            {
 0216                await dbContext.Devices
 0217                    .Where(d => d.Id == device.Id)
 0218                    .ExecuteDeleteAsync()
 0219                    .ConfigureAwait(false);
 220            }
 0221        }
 222
 223        /// <inheritdoc />
 224        public async Task UpdateDevice(Device device)
 225        {
 0226            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 0227            await using (dbContext.ConfigureAwait(false))
 228            {
 0229                dbContext.Devices.Update(device);
 0230                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 231            }
 232
 0233            _devices[device.Id] = device;
 0234        }
 235
 236        /// <inheritdoc />
 237        public bool CanAccessDevice(User user, string deviceId)
 238        {
 16239            ArgumentNullException.ThrowIfNull(user);
 16240            ArgumentException.ThrowIfNullOrEmpty(deviceId);
 241
 16242            if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator
 243            {
 16244                return true;
 245            }
 246
 0247            return user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparison.OrdinalIgnoreCa
 0248                   || !GetCapabilities(deviceId).SupportsPersistentIdentifier;
 249        }
 250
 251        private DeviceInfo ToDeviceInfo(Device authInfo, DeviceOptions? options = null)
 252        {
 0253            var caps = GetCapabilities(authInfo.DeviceId);
 0254            var user = _userManager.GetUserById(authInfo.UserId) ?? throw new ResourceNotFoundException("User with UserI
 255
 0256            return new()
 0257            {
 0258                AppName = authInfo.AppName,
 0259                AppVersion = authInfo.AppVersion,
 0260                Id = authInfo.DeviceId,
 0261                LastUserId = authInfo.UserId,
 0262                LastUserName = user.Username,
 0263                Name = authInfo.DeviceName,
 0264                DateLastActivity = authInfo.DateLastActivity,
 0265                IconUrl = caps.IconUrl,
 0266                CustomName = options?.CustomName,
 0267            };
 268        }
 269
 270        private DeviceOptionsDto ToDeviceOptionsDto(DeviceOptions options)
 271        {
 0272            return new()
 0273            {
 0274                Id = options.Id,
 0275                DeviceId = options.DeviceId,
 0276                CustomName = options.CustomName,
 0277            };
 278        }
 279
 280        private DeviceInfoDto ToDeviceInfoDto(DeviceInfo info)
 281        {
 0282            return new()
 0283            {
 0284                Name = info.Name,
 0285                CustomName = info.CustomName,
 0286                AccessToken = info.AccessToken,
 0287                Id = info.Id,
 0288                LastUserName = info.LastUserName,
 0289                AppName = info.AppName,
 0290                AppVersion = info.AppVersion,
 0291                LastUserId = info.LastUserId,
 0292                DateLastActivity = info.DateLastActivity,
 0293                Capabilities = ToClientCapabilitiesDto(info.Capabilities),
 0294                IconUrl = info.IconUrl
 0295            };
 296        }
 297
 298        /// <inheritdoc />
 299        public ClientCapabilitiesDto ToClientCapabilitiesDto(ClientCapabilities capabilities)
 300        {
 16301            return new()
 16302            {
 16303                PlayableMediaTypes = capabilities.PlayableMediaTypes,
 16304                SupportedCommands = capabilities.SupportedCommands,
 16305                SupportsMediaControl = capabilities.SupportsMediaControl,
 16306                SupportsPersistentIdentifier = capabilities.SupportsPersistentIdentifier,
 16307                DeviceProfile = capabilities.DeviceProfile,
 16308                AppStoreUrl = capabilities.AppStoreUrl,
 16309                IconUrl = capabilities.IconUrl
 16310            };
 311        }
 312    }
 313}