Merge pull request #4377 from barronpm/user-caching

Add caching to users
This commit is contained in:
Bond-009 2020-10-29 17:34:15 +01:00 committed by GitHub
commit 9bf2f4d8e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -2,6 +2,7 @@
#pragma warning disable CA1307 #pragma warning disable CA1307
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@ -48,6 +49,8 @@ namespace Jellyfin.Server.Implementations.Users
private readonly DefaultAuthenticationProvider _defaultAuthenticationProvider; private readonly DefaultAuthenticationProvider _defaultAuthenticationProvider;
private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider; private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
private readonly IDictionary<Guid, User> _users;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UserManager"/> class. /// Initializes a new instance of the <see cref="UserManager"/> class.
/// </summary> /// </summary>
@ -81,38 +84,28 @@ namespace Jellyfin.Server.Implementations.Users
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First(); _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First(); _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First(); _defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
_users = new ConcurrentDictionary<Guid, User>();
using var dbContext = _dbProvider.CreateContext();
foreach (var user in dbContext.Users
.Include(user => user.Permissions)
.Include(user => user.Preferences)
.Include(user => user.AccessSchedules)
.Include(user => user.ProfileImage)
.AsEnumerable())
{
_users.Add(user.Id, user);
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<GenericEventArgs<User>>? OnUserUpdated; public event EventHandler<GenericEventArgs<User>>? OnUserUpdated;
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<User> Users public IEnumerable<User> Users => _users.Values;
{
get
{
using var dbContext = _dbProvider.CreateContext();
return dbContext.Users
.Include(user => user.Permissions)
.Include(user => user.Preferences)
.Include(user => user.AccessSchedules)
.Include(user => user.ProfileImage)
.ToList();
}
}
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<Guid> UsersIds public IEnumerable<Guid> UsersIds => _users.Keys;
{
get
{
using var dbContext = _dbProvider.CreateContext();
return dbContext.Users
.AsQueryable()
.Select(user => user.Id)
.ToList();
}
}
/// <inheritdoc/> /// <inheritdoc/>
public User? GetUserById(Guid id) public User? GetUserById(Guid id)
@ -122,13 +115,8 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Guid can't be empty", nameof(id)); throw new ArgumentException("Guid can't be empty", nameof(id));
} }
using var dbContext = _dbProvider.CreateContext(); _users.TryGetValue(id, out var user);
return dbContext.Users return user;
.Include(user => user.Permissions)
.Include(user => user.Preferences)
.Include(user => user.AccessSchedules)
.Include(user => user.ProfileImage)
.FirstOrDefault(user => user.Id == id);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -139,14 +127,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Invalid username", nameof(name)); throw new ArgumentException("Invalid username", nameof(name));
} }
using var dbContext = _dbProvider.CreateContext(); return _users.Values.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
return dbContext.Users
.Include(user => user.Permissions)
.Include(user => user.Preferences)
.Include(user => user.AccessSchedules)
.Include(user => user.ProfileImage)
.AsEnumerable()
.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -205,13 +186,17 @@ namespace Jellyfin.Server.Implementations.Users
? await dbContext.Users.AsQueryable().Select(u => u.InternalId).MaxAsync().ConfigureAwait(false) ? await dbContext.Users.AsQueryable().Select(u => u.InternalId).MaxAsync().ConfigureAwait(false)
: 0; : 0;
return new User( var user = new User(
name, name,
_defaultAuthenticationProvider.GetType().FullName, _defaultAuthenticationProvider.GetType().FullName,
_defaultPasswordResetProvider.GetType().FullName) _defaultPasswordResetProvider.GetType().FullName)
{ {
InternalId = max + 1 InternalId = max + 1
}; };
_users.Add(user.Id, user);
return user;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -237,28 +222,12 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/> /// <inheritdoc/>
public void DeleteUser(Guid userId) public void DeleteUser(Guid userId)
{ {
using var dbContext = _dbProvider.CreateContext(); if (!_users.TryGetValue(userId, out var user))
var user = dbContext.Users
.Include(u => u.Permissions)
.Include(u => u.Preferences)
.Include(u => u.AccessSchedules)
.Include(u => u.ProfileImage)
.FirstOrDefault(u => u.Id == userId);
if (user == null)
{ {
throw new ResourceNotFoundException(nameof(userId)); throw new ResourceNotFoundException(nameof(userId));
} }
if (dbContext.Users.Find(user.Id) == null) if (_users.Count == 1)
{
throw new ArgumentException(string.Format(
CultureInfo.InvariantCulture,
"The user cannot be deleted because there is no user with the Name {0} and Id {1}.",
user.Username,
user.Id));
}
if (dbContext.Users.Count() == 1)
{ {
throw new InvalidOperationException(string.Format( throw new InvalidOperationException(string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
@ -277,6 +246,8 @@ namespace Jellyfin.Server.Implementations.Users
nameof(userId)); nameof(userId));
} }
using var dbContext = _dbProvider.CreateContext();
// Clear all entities related to the user from the database. // Clear all entities related to the user from the database.
if (user.ProfileImage != null) if (user.ProfileImage != null)
{ {
@ -288,6 +259,7 @@ namespace Jellyfin.Server.Implementations.Users
dbContext.RemoveRange(user.AccessSchedules); dbContext.RemoveRange(user.AccessSchedules);
dbContext.Users.Remove(user); dbContext.Users.Remove(user);
dbContext.SaveChanges(); dbContext.SaveChanges();
_users.Remove(userId);
_eventManager.Publish(new UserDeletedEventArgs(user)); _eventManager.Publish(new UserDeletedEventArgs(user));
} }
@ -589,9 +561,7 @@ namespace Jellyfin.Server.Implementations.Users
public async Task InitializeAsync() public async Task InitializeAsync()
{ {
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist. // TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
await using var dbContext = _dbProvider.CreateContext(); if (_users.Any())
if (await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false))
{ {
return; return;
} }
@ -604,6 +574,7 @@ namespace Jellyfin.Server.Implementations.Users
_logger.LogWarning("No users, creating one with username {UserName}", defaultName); _logger.LogWarning("No users, creating one with username {UserName}", defaultName);
await using var dbContext = _dbProvider.CreateContext();
var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false); var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
newUser.SetPermission(PermissionKind.IsAdministrator, true); newUser.SetPermission(PermissionKind.IsAdministrator, true);
newUser.SetPermission(PermissionKind.EnableContentDeletion, true); newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
@ -753,6 +724,7 @@ namespace Jellyfin.Server.Implementations.Users
using var dbContext = _dbProvider.CreateContext(); using var dbContext = _dbProvider.CreateContext();
dbContext.Remove(user.ProfileImage); dbContext.Remove(user.ProfileImage);
dbContext.SaveChanges(); dbContext.SaveChanges();
user.ProfileImage = null;
} }
private static bool IsValidUsername(string name) private static bool IsValidUsername(string name)