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>
This commit is contained in:
gnattu 2024-08-05 10:58:22 -04:00 committed by Bond_009
parent d5fdb9c3a7
commit 22d8528d90
13 changed files with 137 additions and 148 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using EFCoreSecondLevelCacheInterceptor;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; 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. /// Adds the <see cref="IDbContextFactory{TContext}"/> interface to the service collection with second level caching enabled.
/// </summary> /// </summary>
/// <param name="serviceCollection">An instance of the <see cref="IServiceCollection"/> interface.</param> /// <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> /// <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) => serviceCollection.AddPooledDbContextFactory<JellyfinDbContext>((serviceProvider, opt) =>
{ {
var applicationPaths = serviceProvider.GetRequiredService<IApplicationPaths>(); var applicationPaths = serviceProvider.GetRequiredService<IApplicationPaths>();
var dbOpt = opt.UseSqlite($"Filename={Path.Combine(applicationPaths.DataPath, "jellyfin.db")}"); opt.UseSqlite($"Filename={Path.Combine(applicationPaths.DataPath, "jellyfin.db")}");
if (!disableSecondLevelCache)
{
dbOpt.AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>());
}
}); });
return serviceCollection; return serviceCollection;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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