Răsfoiți Sursa

Backport pull request #11901 from jellyfin/release-10.9.z

Implement Device Cache to replace EFCoreSecondLevelCacheInterceptor

Original-merge: b7bc0e1c96553675a490c0bd92a58ad9c5f0d0e1

Merged-by: joshuaboniface <joshua@boniface.me>

Backported-by: Bond_009 <bond.009@outlook.com>
gnattu 10 luni în urmă
părinte
comite
22d8528d90

+ 0 - 1
Directory.Packages.props

@@ -16,7 +16,6 @@
     <PackageVersion Include="Diacritics" Version="3.3.29" />
     <PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
     <PackageVersion Include="DotNet.Glob" Version="3.1.3" />
-    <PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.5.0" />
     <PackageVersion Include="FsCheck.Xunit" Version="2.16.6" />
     <PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.2" />
     <PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />

+ 1 - 2
Emby.Server.Implementations/ConfigurationOptions.cs

@@ -19,8 +19,7 @@ namespace Emby.Server.Implementations
             { FfmpegAnalyzeDurationKey, "200M" },
             { PlaylistsAllowDuplicatesKey, bool.FalseString },
             { BindToUnixSocketKey, bool.FalseString },
-            { SqliteCacheSizeKey, "20000" },
-            { SqliteDisableSecondLevelCacheKey, bool.FalseString }
+            { SqliteCacheSizeKey, "20000" }
         };
     }
 }

+ 15 - 15
Emby.Server.Implementations/Session/SessionManager.cs

@@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.Session
             ArgumentException.ThrowIfNullOrEmpty(deviceId);
 
             var activityDate = DateTime.UtcNow;
-            var session = await GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false);
+            var session = GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user);
             var lastActivityDate = session.LastActivityDate;
             session.LastActivityDate = activityDate;
 
@@ -435,7 +435,7 @@ namespace Emby.Server.Implementations.Session
         /// <param name="remoteEndPoint">The remote end point.</param>
         /// <param name="user">The user.</param>
         /// <returns>SessionInfo.</returns>
-        private async Task<SessionInfo> GetSessionInfo(
+        private SessionInfo GetSessionInfo(
             string appName,
             string appVersion,
             string deviceId,
@@ -453,7 +453,7 @@ namespace Emby.Server.Implementations.Session
 
             if (!_activeConnections.TryGetValue(key, out var sessionInfo))
             {
-                sessionInfo = await CreateSession(key, appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false);
+                sessionInfo = CreateSession(key, appName, appVersion, deviceId, deviceName, remoteEndPoint, user);
                 _activeConnections[key] = sessionInfo;
             }
 
@@ -478,7 +478,7 @@ namespace Emby.Server.Implementations.Session
             return sessionInfo;
         }
 
-        private async Task<SessionInfo> CreateSession(
+        private SessionInfo CreateSession(
             string key,
             string appName,
             string appVersion,
@@ -508,7 +508,7 @@ namespace Emby.Server.Implementations.Session
                 deviceName = "Network Device";
             }
 
-            var deviceOptions = await _deviceManager.GetDeviceOptions(deviceId).ConfigureAwait(false);
+            var deviceOptions = _deviceManager.GetDeviceOptions(deviceId);
             if (string.IsNullOrEmpty(deviceOptions.CustomName))
             {
                 sessionInfo.DeviceName = deviceName;
@@ -1297,7 +1297,7 @@ namespace Emby.Server.Implementations.Session
             return new[] { item };
         }
 
-        private IEnumerable<BaseItem> TranslateItemForInstantMix(Guid id, User user)
+        private List<BaseItem> TranslateItemForInstantMix(Guid id, User user)
         {
             var item = _libraryManager.GetItemById(id);
 
@@ -1307,7 +1307,7 @@ namespace Emby.Server.Implementations.Session
                 return new List<BaseItem>();
             }
 
-            return _musicManager.GetInstantMixFromItem(item, user, new DtoOptions(false) { EnableImages = false });
+            return _musicManager.GetInstantMixFromItem(item, user, new DtoOptions(false) { EnableImages = false }).ToList();
         }
 
         /// <inheritdoc />
@@ -1520,12 +1520,12 @@ namespace Emby.Server.Implementations.Session
             // This should be validated above, but if it isn't don't delete all tokens.
             ArgumentException.ThrowIfNullOrEmpty(deviceId);
 
-            var existing = (await _deviceManager.GetDevices(
+            var existing = _deviceManager.GetDevices(
                 new DeviceQuery
                 {
                     DeviceId = deviceId,
                     UserId = user.Id
-                }).ConfigureAwait(false)).Items;
+                }).Items;
 
             foreach (var auth in existing)
             {
@@ -1553,12 +1553,12 @@ namespace Emby.Server.Implementations.Session
 
             ArgumentException.ThrowIfNullOrEmpty(accessToken);
 
-            var existing = (await _deviceManager.GetDevices(
+            var existing = _deviceManager.GetDevices(
                 new DeviceQuery
                 {
                     Limit = 1,
                     AccessToken = accessToken
-                }).ConfigureAwait(false)).Items;
+                }).Items;
 
             if (existing.Count > 0)
             {
@@ -1597,10 +1597,10 @@ namespace Emby.Server.Implementations.Session
         {
             CheckDisposed();
 
-            var existing = await _deviceManager.GetDevices(new DeviceQuery
+            var existing = _deviceManager.GetDevices(new DeviceQuery
             {
                 UserId = userId
-            }).ConfigureAwait(false);
+            });
 
             foreach (var info in existing.Items)
             {
@@ -1787,11 +1787,11 @@ namespace Emby.Server.Implementations.Session
         /// <inheritdoc />
         public async Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint)
         {
-            var items = (await _deviceManager.GetDevices(new DeviceQuery
+            var items = _deviceManager.GetDevices(new DeviceQuery
             {
                 AccessToken = token,
                 Limit = 1
-            }).ConfigureAwait(false)).Items;
+            }).Items;
 
             if (items.Count == 0)
             {

+ 8 - 8
Jellyfin.Api/Controllers/DevicesController.cs

@@ -47,10 +47,10 @@ public class DevicesController : BaseJellyfinApiController
     /// <returns>An <see cref="OkResult"/> containing the list of devices.</returns>
     [HttpGet]
     [ProducesResponseType(StatusCodes.Status200OK)]
-    public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] Guid? userId)
+    public ActionResult<QueryResult<DeviceInfo>> GetDevices([FromQuery] Guid? userId)
     {
         userId = RequestHelpers.GetUserId(User, userId);
-        return await _deviceManager.GetDevicesForUser(userId).ConfigureAwait(false);
+        return _deviceManager.GetDevicesForUser(userId);
     }
 
     /// <summary>
@@ -63,9 +63,9 @@ public class DevicesController : BaseJellyfinApiController
     [HttpGet("Info")]
     [ProducesResponseType(StatusCodes.Status200OK)]
     [ProducesResponseType(StatusCodes.Status404NotFound)]
-    public async Task<ActionResult<DeviceInfo>> GetDeviceInfo([FromQuery, Required] string id)
+    public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, Required] string id)
     {
-        var deviceInfo = await _deviceManager.GetDevice(id).ConfigureAwait(false);
+        var deviceInfo = _deviceManager.GetDevice(id);
         if (deviceInfo is null)
         {
             return NotFound();
@@ -84,9 +84,9 @@ public class DevicesController : BaseJellyfinApiController
     [HttpGet("Options")]
     [ProducesResponseType(StatusCodes.Status200OK)]
     [ProducesResponseType(StatusCodes.Status404NotFound)]
-    public async Task<ActionResult<DeviceOptions>> GetDeviceOptions([FromQuery, Required] string id)
+    public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, Required] string id)
     {
-        var deviceInfo = await _deviceManager.GetDeviceOptions(id).ConfigureAwait(false);
+        var deviceInfo = _deviceManager.GetDeviceOptions(id);
         if (deviceInfo is null)
         {
             return NotFound();
@@ -124,13 +124,13 @@ public class DevicesController : BaseJellyfinApiController
     [ProducesResponseType(StatusCodes.Status404NotFound)]
     public async Task<ActionResult> DeleteDevice([FromQuery, Required] string id)
     {
-        var existingDevice = await _deviceManager.GetDevice(id).ConfigureAwait(false);
+        var existingDevice = _deviceManager.GetDevice(id);
         if (existingDevice is null)
         {
             return NotFound();
         }
 
-        var sessions = await _deviceManager.GetDevices(new DeviceQuery { DeviceId = id }).ConfigureAwait(false);
+        var sessions = _deviceManager.GetDevices(new DeviceQuery { DeviceId = id });
 
         foreach (var session in sessions.Items)
         {

+ 86 - 71
Jellyfin.Server.Implementations/Devices/DeviceManager.cs

@@ -27,6 +27,8 @@ namespace Jellyfin.Server.Implementations.Devices
         private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
         private readonly IUserManager _userManager;
         private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new();
+        private readonly ConcurrentDictionary<int, Device> _devices;
+        private readonly ConcurrentDictionary<string, DeviceOptions> _deviceOptions;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="DeviceManager"/> class.
@@ -37,6 +39,23 @@ namespace Jellyfin.Server.Implementations.Devices
         {
             _dbProvider = dbProvider;
             _userManager = userManager;
+            _devices = new ConcurrentDictionary<int, Device>();
+            _deviceOptions = new ConcurrentDictionary<string, DeviceOptions>();
+
+            using var dbContext = _dbProvider.CreateDbContext();
+            foreach (var device in dbContext.Devices
+                         .OrderBy(d => d.Id)
+                         .AsEnumerable())
+            {
+                _devices.TryAdd(device.Id, device);
+            }
+
+            foreach (var deviceOption in dbContext.DeviceOptions
+                         .OrderBy(d => d.Id)
+                         .AsEnumerable())
+            {
+                _deviceOptions.TryAdd(deviceOption.DeviceId, deviceOption);
+            }
         }
 
         /// <inheritdoc />
@@ -66,6 +85,8 @@ namespace Jellyfin.Server.Implementations.Devices
                 await dbContext.SaveChangesAsync().ConfigureAwait(false);
             }
 
+            _deviceOptions[deviceId] = deviceOptions;
+
             DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, deviceOptions)));
         }
 
@@ -76,25 +97,17 @@ namespace Jellyfin.Server.Implementations.Devices
             await using (dbContext.ConfigureAwait(false))
             {
                 dbContext.Devices.Add(device);
-
                 await dbContext.SaveChangesAsync().ConfigureAwait(false);
+                _devices.TryAdd(device.Id, device);
             }
 
             return device;
         }
 
         /// <inheritdoc />
-        public async Task<DeviceOptions> GetDeviceOptions(string deviceId)
+        public DeviceOptions GetDeviceOptions(string deviceId)
         {
-            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
-            DeviceOptions? deviceOptions;
-            await using (dbContext.ConfigureAwait(false))
-            {
-                deviceOptions = await dbContext.DeviceOptions
-                    .AsNoTracking()
-                    .FirstOrDefaultAsync(d => d.DeviceId == deviceId)
-                    .ConfigureAwait(false);
-            }
+            _deviceOptions.TryGetValue(deviceId, out var deviceOptions);
 
             return deviceOptions ?? new DeviceOptions(deviceId);
         }
@@ -108,57 +121,43 @@ namespace Jellyfin.Server.Implementations.Devices
         }
 
         /// <inheritdoc />
-        public async Task<DeviceInfo?> GetDevice(string id)
+        public DeviceInfo? GetDevice(string id)
         {
-            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
-            await using (dbContext.ConfigureAwait(false))
-            {
-                var device = await dbContext.Devices
-                    .Where(d => d.DeviceId == id)
-                    .OrderByDescending(d => d.DateLastActivity)
-                    .Include(d => d.User)
-                    .SelectMany(d => dbContext.DeviceOptions.Where(o => o.DeviceId == d.DeviceId).DefaultIfEmpty(), (d, o) => new { Device = d, Options = o })
-                    .FirstOrDefaultAsync()
-                    .ConfigureAwait(false);
+            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.Device, device.Options);
-
-                return deviceInfo;
-            }
+            var deviceInfo = device is null ? null : ToDeviceInfo(device, deviceOption);
+            return deviceInfo;
         }
 
         /// <inheritdoc />
-        public async Task<QueryResult<Device>> GetDevices(DeviceQuery query)
+        public QueryResult<Device> GetDevices(DeviceQuery query)
         {
-            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
-            await using (dbContext.ConfigureAwait(false))
+            IEnumerable<Device> devices = _devices.Values
+                .Where(device => !query.UserId.HasValue || device.UserId.Equals(query.UserId.Value))
+                .Where(device => query.DeviceId == null || device.DeviceId == query.DeviceId)
+                .Where(device => query.AccessToken == null || device.AccessToken == query.AccessToken)
+                .OrderBy(d => d.Id)
+                .ToList();
+            var count = devices.Count();
+
+            if (query.Skip.HasValue)
             {
-                var devices = dbContext.Devices
-                    .OrderBy(d => d.Id)
-                    .Where(device => !query.UserId.HasValue || device.UserId.Equals(query.UserId.Value))
-                    .Where(device => query.DeviceId == null || device.DeviceId == query.DeviceId)
-                    .Where(device => query.AccessToken == null || device.AccessToken == query.AccessToken);
-
-                var count = await devices.CountAsync().ConfigureAwait(false);
-
-                if (query.Skip.HasValue)
-                {
-                    devices = devices.Skip(query.Skip.Value);
-                }
-
-                if (query.Limit.HasValue)
-                {
-                    devices = devices.Take(query.Limit.Value);
-                }
+                devices = devices.Skip(query.Skip.Value);
+            }
 
-                return new QueryResult<Device>(query.Skip, count, await devices.ToListAsync().ConfigureAwait(false));
+            if (query.Limit.HasValue)
+            {
+                devices = devices.Take(query.Limit.Value);
             }
+
+            return new QueryResult<Device>(query.Skip, count, devices.ToList());
         }
 
         /// <inheritdoc />
-        public async Task<QueryResult<DeviceInfo>> GetDeviceInfos(DeviceQuery query)
+        public QueryResult<DeviceInfo> GetDeviceInfos(DeviceQuery query)
         {
-            var devices = await GetDevices(query).ConfigureAwait(false);
+            var devices = GetDevices(query);
 
             return new QueryResult<DeviceInfo>(
                 devices.StartIndex,
@@ -167,38 +166,36 @@ namespace Jellyfin.Server.Implementations.Devices
         }
 
         /// <inheritdoc />
-        public async Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId)
+        public QueryResult<DeviceInfo> GetDevicesForUser(Guid? userId)
         {
-            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
-            await using (dbContext.ConfigureAwait(false))
+            IEnumerable<Device> devices = _devices.Values
+                .OrderByDescending(d => d.DateLastActivity)
+                .ThenBy(d => d.DeviceId);
+
+            if (!userId.IsNullOrEmpty())
             {
-                var sessions = dbContext.Devices
-                    .Include(d => d.User)
-                    .OrderByDescending(d => d.DateLastActivity)
-                    .ThenBy(d => d.DeviceId)
-                    .SelectMany(d => dbContext.DeviceOptions.Where(o => o.DeviceId == d.DeviceId).DefaultIfEmpty(), (d, o) => new { Device = d, Options = o })
-                    .AsAsyncEnumerable();
-
-                if (!userId.IsNullOrEmpty())
+                var user = _userManager.GetUserById(userId.Value);
+                if (user is null)
                 {
-                    var user = _userManager.GetUserById(userId.Value);
-                    if (user is null)
-                    {
-                        throw new ResourceNotFoundException();
-                    }
-
-                    sessions = sessions.Where(i => CanAccessDevice(user, i.Device.DeviceId));
+                    throw new ResourceNotFoundException();
                 }
 
-                var array = await sessions.Select(device => ToDeviceInfo(device.Device, device.Options)).ToArrayAsync().ConfigureAwait(false);
-
-                return new QueryResult<DeviceInfo>(array);
+                devices = devices.Where(i => CanAccessDevice(user, i.DeviceId));
             }
+
+            var array = devices.Select(device =>
+                {
+                    _deviceOptions.TryGetValue(device.DeviceId, out var option);
+                    return ToDeviceInfo(device, option);
+                }).ToArray();
+
+            return new QueryResult<DeviceInfo>(array);
         }
 
         /// <inheritdoc />
         public async Task DeleteDevice(Device device)
         {
+            _devices.TryRemove(device.Id, out _);
             var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
             await using (dbContext.ConfigureAwait(false))
             {
@@ -207,6 +204,19 @@ namespace Jellyfin.Server.Implementations.Devices
             }
         }
 
+        /// <inheritdoc />
+        public async Task UpdateDevice(Device device)
+        {
+            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+            await using (dbContext.ConfigureAwait(false))
+            {
+                dbContext.Devices.Update(device);
+                await dbContext.SaveChangesAsync().ConfigureAwait(false);
+            }
+
+            _devices[device.Id] = device;
+        }
+
         /// <inheritdoc />
         public bool CanAccessDevice(User user, string deviceId)
         {
@@ -225,6 +235,11 @@ 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");
+            }
 
             return new DeviceInfo
             {
@@ -232,7 +247,7 @@ namespace Jellyfin.Server.Implementations.Devices
                 AppVersion = authInfo.AppVersion,
                 Id = authInfo.DeviceId,
                 LastUserId = authInfo.UserId,
-                LastUserName = authInfo.User.Username,
+                LastUserName = user.Username,
                 Name = authInfo.DeviceName,
                 DateLastActivity = authInfo.DateLastActivity,
                 IconUrl = caps.IconUrl,

+ 2 - 18
Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs

@@ -1,6 +1,5 @@
 using System;
 using System.IO;
-using EFCoreSecondLevelCacheInterceptor;
 using MediaBrowser.Common.Configuration;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.DependencyInjection;
@@ -16,28 +15,13 @@ public static class ServiceCollectionExtensions
     /// Adds the <see cref="IDbContextFactory{TContext}"/> interface to the service collection with second level caching enabled.
     /// </summary>
     /// <param name="serviceCollection">An instance of the <see cref="IServiceCollection"/> interface.</param>
-    /// <param name="disableSecondLevelCache">Whether second level cache disabled..</param>
     /// <returns>The updated service collection.</returns>
-    public static IServiceCollection AddJellyfinDbContext(this IServiceCollection serviceCollection, bool disableSecondLevelCache)
+    public static IServiceCollection AddJellyfinDbContext(this IServiceCollection serviceCollection)
     {
-        if (!disableSecondLevelCache)
-        {
-            serviceCollection.AddEFSecondLevelCache(options =>
-                options.UseMemoryCacheProvider()
-                    .CacheAllQueries(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(10))
-                    .UseCacheKeyPrefix("EF_")
-                    // Don't cache null values. Remove this optional setting if it's not necessary.
-                    .SkipCachingResults(result => result.Value is null or EFTableRows { RowsCount: 0 }));
-        }
-
         serviceCollection.AddPooledDbContextFactory<JellyfinDbContext>((serviceProvider, opt) =>
         {
             var applicationPaths = serviceProvider.GetRequiredService<IApplicationPaths>();
-            var dbOpt = opt.UseSqlite($"Filename={Path.Combine(applicationPaths.DataPath, "jellyfin.db")}");
-            if (!disableSecondLevelCache)
-            {
-                dbOpt.AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>());
-            }
+            opt.UseSqlite($"Filename={Path.Combine(applicationPaths.DataPath, "jellyfin.db")}");
         });
 
         return serviceCollection;

+ 0 - 1
Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj

@@ -27,7 +27,6 @@
 
   <ItemGroup>
     <PackageReference Include="AsyncKeyedLock" />
-    <PackageReference Include="EFCoreSecondLevelCacheInterceptor" />
     <PackageReference Include="System.Linq.Async" />
     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
     <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />

+ 12 - 3
Jellyfin.Server.Implementations/Security/AuthorizationContext.cs

@@ -4,7 +4,10 @@ using System;
 using System.Collections.Generic;
 using System.Net;
 using System.Threading.Tasks;
+using Jellyfin.Data.Queries;
+using Jellyfin.Extensions;
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using Microsoft.AspNetCore.Http;
@@ -17,15 +20,18 @@ namespace Jellyfin.Server.Implementations.Security
     {
         private readonly IDbContextFactory<JellyfinDbContext> _jellyfinDbProvider;
         private readonly IUserManager _userManager;
+        private readonly IDeviceManager _deviceManager;
         private readonly IServerApplicationHost _serverApplicationHost;
 
         public AuthorizationContext(
             IDbContextFactory<JellyfinDbContext> jellyfinDb,
             IUserManager userManager,
+            IDeviceManager deviceManager,
             IServerApplicationHost serverApplicationHost)
         {
             _jellyfinDbProvider = jellyfinDb;
             _userManager = userManager;
+            _deviceManager = deviceManager;
             _serverApplicationHost = serverApplicationHost;
         }
 
@@ -121,7 +127,11 @@ namespace Jellyfin.Server.Implementations.Security
             var dbContext = await _jellyfinDbProvider.CreateDbContextAsync().ConfigureAwait(false);
             await using (dbContext.ConfigureAwait(false))
             {
-                var device = await dbContext.Devices.FirstOrDefaultAsync(d => d.AccessToken == token).ConfigureAwait(false);
+                var device = _deviceManager.GetDevices(
+                    new DeviceQuery
+                    {
+                        AccessToken = token
+                    }).Items.FirstOrDefault();
 
                 if (device is not null)
                 {
@@ -178,8 +188,7 @@ namespace Jellyfin.Server.Implementations.Security
 
                     if (updateToken)
                     {
-                        dbContext.Devices.Update(device);
-                        await dbContext.SaveChangesAsync().ConfigureAwait(false);
+                        await _deviceManager.UpdateDevice(device).ConfigureAwait(false);
                     }
                 }
                 else

+ 2 - 2
Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs

@@ -60,10 +60,10 @@ public sealed class DeviceAccessHost : IHostedService
 
     private async Task UpdateDeviceAccess(User user)
     {
-        var existing = (await _deviceManager.GetDevices(new DeviceQuery
+        var existing = _deviceManager.GetDevices(new DeviceQuery
         {
             UserId = user.Id
-        }).ConfigureAwait(false)).Items;
+        }).Items;
 
         foreach (var device in existing)
         {

+ 1 - 1
Jellyfin.Server/Extensions/WebHostBuilderExtensions.cs

@@ -85,6 +85,6 @@ public static class WebHostBuilderExtensions
                     logger.LogInformation("Kestrel listening to unix socket {SocketPath}", socketPath);
                 }
             })
-            .UseStartup(_ => new Startup(appHost, startupConfig));
+            .UseStartup(_ => new Startup(appHost));
     }
 }

+ 2 - 5
Jellyfin.Server/Startup.cs

@@ -40,18 +40,15 @@ namespace Jellyfin.Server
     {
         private readonly CoreAppHost _serverApplicationHost;
         private readonly IServerConfigurationManager _serverConfigurationManager;
-        private readonly IConfiguration _startupConfig;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="Startup" /> class.
         /// </summary>
         /// <param name="appHost">The server application host.</param>
-        /// <param name="startupConfig">The server startupConfig.</param>
-        public Startup(CoreAppHost appHost, IConfiguration startupConfig)
+        public Startup(CoreAppHost appHost)
         {
             _serverApplicationHost = appHost;
             _serverConfigurationManager = appHost.ConfigurationManager;
-            _startupConfig = startupConfig;
         }
 
         /// <summary>
@@ -70,7 +67,7 @@ namespace Jellyfin.Server
             // TODO remove once this is fixed upstream https://github.com/dotnet/aspnetcore/issues/34371
             services.AddSingleton<IActionResultExecutor<PhysicalFileResult>, SymlinkFollowingPhysicalFileResultExecutor>();
             services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies(), _serverConfigurationManager.GetNetworkConfiguration());
-            services.AddJellyfinDbContext(_startupConfig.GetSqliteSecondLevelCacheDisabled());
+            services.AddJellyfinDbContext();
             services.AddJellyfinApiSwagger();
 
             // configure custom legacy authentication

+ 7 - 5
MediaBrowser.Controller/Devices/IDeviceManager.cs

@@ -44,26 +44,28 @@ namespace MediaBrowser.Controller.Devices
         /// </summary>
         /// <param name="id">The identifier.</param>
         /// <returns>DeviceInfo.</returns>
-        Task<DeviceInfo> GetDevice(string id);
+        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>
-        Task<QueryResult<Device>> GetDevices(DeviceQuery query);
+        QueryResult<Device> GetDevices(DeviceQuery query);
 
-        Task<QueryResult<DeviceInfo>> GetDeviceInfos(DeviceQuery query);
+        QueryResult<DeviceInfo> GetDeviceInfos(DeviceQuery query);
 
         /// <summary>
         /// Gets the devices.
         /// </summary>
         /// <param name="userId">The user's id, or <c>null</c>.</param>
         /// <returns>IEnumerable&lt;DeviceInfo&gt;.</returns>
-        Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId);
+        QueryResult<DeviceInfo> GetDevicesForUser(Guid? userId);
 
         Task DeleteDevice(Device device);
 
+        Task UpdateDevice(Device device);
+
         /// <summary>
         /// Determines whether this instance [can access device] the specified user identifier.
         /// </summary>
@@ -74,6 +76,6 @@ namespace MediaBrowser.Controller.Devices
 
         Task UpdateDeviceOptions(string deviceId, string deviceName);
 
-        Task<DeviceOptions> GetDeviceOptions(string deviceId);
+        DeviceOptions GetDeviceOptions(string deviceId);
     }
 }

+ 0 - 15
MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs

@@ -64,11 +64,6 @@ namespace MediaBrowser.Controller.Extensions
         /// </summary>
         public const string SqliteCacheSizeKey = "sqlite:cacheSize";
 
-        /// <summary>
-        /// Disable second level cache of sqlite.
-        /// </summary>
-        public const string SqliteDisableSecondLevelCacheKey = "sqlite:disableSecondLevelCache";
-
         /// <summary>
         /// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
         /// </summary>
@@ -133,15 +128,5 @@ namespace MediaBrowser.Controller.Extensions
         /// <returns>The sqlite cache size.</returns>
         public static int? GetSqliteCacheSize(this IConfiguration configuration)
             => configuration.GetValue<int?>(SqliteCacheSizeKey);
-
-        /// <summary>
-        /// Gets whether second level cache disabled from the <see cref="IConfiguration" />.
-        /// </summary>
-        /// <param name="configuration">The configuration to read the setting from.</param>
-        /// <returns>Whether second level cache disabled.</returns>
-        public static bool GetSqliteSecondLevelCacheDisabled(this IConfiguration configuration)
-        {
-            return configuration.GetValue<bool>(SqliteDisableSecondLevelCacheKey);
-        }
     }
 }