Backport pull request #11901 from jellyfin/release-10.9.z
Implement Device Cache to replace EFCoreSecondLevelCacheInterceptor
Original-merge: b7bc0e1c96
Merged-by: joshuaboniface <joshua@boniface.me>
Backported-by: Bond_009 <bond.009@outlook.com>
This commit is contained in:
parent
d5fdb9c3a7
commit
22d8528d90
|
@ -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" />
|
||||
|
|
|
@ -19,8 +19,7 @@ namespace Emby.Server.Implementations
|
|||
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||
{ PlaylistsAllowDuplicatesKey, bool.FalseString },
|
||||
{ BindToUnixSocketKey, bool.FalseString },
|
||||
{ SqliteCacheSizeKey, "20000" },
|
||||
{ SqliteDisableSecondLevelCacheKey, bool.FalseString }
|
||||
{ SqliteCacheSizeKey, "20000" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
return new QueryResult<Device>(query.Skip, count, await devices.ToListAsync().ConfigureAwait(false));
|
||||
devices = devices.Skip(query.Skip.Value);
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<DeviceInfo>.</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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user