mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-03 19:17:24 -05:00 
			
		
		
		
	Backport pull request #11670 from jellyfin/release-10.9.z
Restore caching for UserManager Original-merge: f8b67ec44cc6432d89d918f54caffa45eef037d0 Merged-by: nielsvanvelzen <nielsvanvelzen@users.noreply.github.com> Backported-by: Joshua M. Boniface <joshua@boniface.me>
This commit is contained in:
		
							parent
							
								
									e109e54949
								
							
						
					
					
						commit
						5fe7d7f0bf
					
				@ -1,7 +1,7 @@
 | 
				
			|||||||
#pragma warning disable CA1307
 | 
					#pragma warning disable CA1307
 | 
				
			||||||
#pragma warning disable CA1309 // Use ordinal string comparison - EF can't translate this
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
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;
 | 
				
			||||||
@ -47,6 +47,8 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
        private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
 | 
					        private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
 | 
				
			||||||
        private readonly IServerConfigurationManager _serverConfigurationManager;
 | 
					        private readonly IServerConfigurationManager _serverConfigurationManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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>
 | 
				
			||||||
@ -84,30 +86,29 @@ 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.CreateDbContext();
 | 
				
			||||||
 | 
					            foreach (var user in dbContext.Users
 | 
				
			||||||
 | 
					                .AsSplitQuery()
 | 
				
			||||||
 | 
					                .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.CreateDbContext();
 | 
					 | 
				
			||||||
                return GetUsersInternal(dbContext).ToList();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <inheritdoc/>
 | 
					        /// <inheritdoc/>
 | 
				
			||||||
        public IEnumerable<Guid> UsersIds
 | 
					        public IEnumerable<Guid> UsersIds => _users.Keys;
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            get
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                using var dbContext = _dbProvider.CreateDbContext();
 | 
					 | 
				
			||||||
                return dbContext.Users.Select(u => u.Id).ToList();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // This is some regex that matches only on unicode "word" characters, as well as -, _ and @
 | 
					        // This is some regex that matches only on unicode "word" characters, as well as -, _ and @
 | 
				
			||||||
        // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
 | 
					        // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
 | 
				
			||||||
@ -123,8 +124,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.CreateDbContext();
 | 
					            _users.TryGetValue(id, out var user);
 | 
				
			||||||
            return GetUsersInternal(dbContext).FirstOrDefault(u => u.Id.Equals(id));
 | 
					            return user;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <inheritdoc/>
 | 
					        /// <inheritdoc/>
 | 
				
			||||||
@ -135,9 +136,7 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
                throw new ArgumentException("Invalid username", nameof(name));
 | 
					                throw new ArgumentException("Invalid username", nameof(name));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            using var dbContext = _dbProvider.CreateDbContext();
 | 
					            return _users.Values.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
 | 
				
			||||||
            return GetUsersInternal(dbContext)
 | 
					 | 
				
			||||||
                .FirstOrDefault(u => string.Equals(u.Username, name));
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <inheritdoc/>
 | 
					        /// <inheritdoc/>
 | 
				
			||||||
@ -202,6 +201,8 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
            user.AddDefaultPermissions();
 | 
					            user.AddDefaultPermissions();
 | 
				
			||||||
            user.AddDefaultPreferences();
 | 
					            user.AddDefaultPreferences();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _users.Add(user.Id, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return user;
 | 
					            return user;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -236,21 +237,12 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
        /// <inheritdoc/>
 | 
					        /// <inheritdoc/>
 | 
				
			||||||
        public async Task DeleteUserAsync(Guid userId)
 | 
					        public async Task DeleteUserAsync(Guid userId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 | 
					            if (!_users.TryGetValue(userId, out var user))
 | 
				
			||||||
 | 
					 | 
				
			||||||
            await using (dbContext.ConfigureAwait(false))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var user = await dbContext.Users
 | 
					 | 
				
			||||||
                    .AsSingleQuery()
 | 
					 | 
				
			||||||
                    .Include(u => u.Permissions)
 | 
					 | 
				
			||||||
                    .FirstOrDefaultAsync(u => u.Id.Equals(userId))
 | 
					 | 
				
			||||||
                    .ConfigureAwait(false);
 | 
					 | 
				
			||||||
                if (user is null)
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                throw new ResourceNotFoundException(nameof(userId));
 | 
					                throw new ResourceNotFoundException(nameof(userId));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (await dbContext.Users.CountAsync().ConfigureAwait(false) == 1)
 | 
					            if (_users.Count == 1)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                throw new InvalidOperationException(string.Format(
 | 
					                throw new InvalidOperationException(string.Format(
 | 
				
			||||||
                    CultureInfo.InvariantCulture,
 | 
					                    CultureInfo.InvariantCulture,
 | 
				
			||||||
@ -259,9 +251,7 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (user.HasPermission(PermissionKind.IsAdministrator)
 | 
					            if (user.HasPermission(PermissionKind.IsAdministrator)
 | 
				
			||||||
                    && await dbContext.Users
 | 
					                && Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1)
 | 
				
			||||||
                        .CountAsync(u => u.Permissions.Any(p => p.Kind == PermissionKind.IsAdministrator && p.Value))
 | 
					 | 
				
			||||||
                        .ConfigureAwait(false) == 1)
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                throw new ArgumentException(
 | 
					                throw new ArgumentException(
 | 
				
			||||||
                    string.Format(
 | 
					                    string.Format(
 | 
				
			||||||
@ -271,12 +261,17 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
                    nameof(userId));
 | 
					                    nameof(userId));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 | 
				
			||||||
 | 
					            await using (dbContext.ConfigureAwait(false))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
                dbContext.Users.Remove(user);
 | 
					                dbContext.Users.Remove(user);
 | 
				
			||||||
                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 | 
					                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _users.Remove(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false);
 | 
					            await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <inheritdoc/>
 | 
					        /// <inheritdoc/>
 | 
				
			||||||
        public Task ResetPassword(User user)
 | 
					        public Task ResetPassword(User user)
 | 
				
			||||||
@ -541,12 +536,9 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        /// <inheritdoc />
 | 
					        /// <inheritdoc />
 | 
				
			||||||
        public async Task InitializeAsync()
 | 
					        public async Task InitializeAsync()
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 | 
					 | 
				
			||||||
            await using (dbContext.ConfigureAwait(false))
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 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.
 | 
				
			||||||
                if (await dbContext.Users.AnyAsync().ConfigureAwait(false))
 | 
					            if (_users.Any())
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -559,6 +551,9 @@ 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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 | 
				
			||||||
 | 
					            await using (dbContext.ConfigureAwait(false))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
                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);
 | 
				
			||||||
@ -605,9 +600,12 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 | 
					            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 | 
				
			||||||
            await using (dbContext.ConfigureAwait(false))
 | 
					            await using (dbContext.ConfigureAwait(false))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var user = await GetUsersInternal(dbContext)
 | 
					                var user = dbContext.Users
 | 
				
			||||||
                    .FirstOrDefaultAsync(u => u.Id.Equals(userId))
 | 
					                               .Include(u => u.Permissions)
 | 
				
			||||||
                    .ConfigureAwait(false)
 | 
					                               .Include(u => u.Preferences)
 | 
				
			||||||
 | 
					                               .Include(u => u.AccessSchedules)
 | 
				
			||||||
 | 
					                               .Include(u => u.ProfileImage)
 | 
				
			||||||
 | 
					                               .FirstOrDefault(u => u.Id.Equals(userId))
 | 
				
			||||||
                           ?? throw new ArgumentException("No user exists with given Id!");
 | 
					                           ?? throw new ArgumentException("No user exists with given Id!");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                user.SubtitleMode = config.SubtitleMode;
 | 
					                user.SubtitleMode = config.SubtitleMode;
 | 
				
			||||||
@ -635,6 +633,7 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
                user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
 | 
					                user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                dbContext.Update(user);
 | 
					                dbContext.Update(user);
 | 
				
			||||||
 | 
					                _users[user.Id] = user;
 | 
				
			||||||
                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 | 
					                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -645,9 +644,12 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 | 
					            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
 | 
				
			||||||
            await using (dbContext.ConfigureAwait(false))
 | 
					            await using (dbContext.ConfigureAwait(false))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var user = await GetUsersInternal(dbContext)
 | 
					                var user = dbContext.Users
 | 
				
			||||||
                    .FirstOrDefaultAsync(u => u.Id.Equals(userId))
 | 
					                               .Include(u => u.Permissions)
 | 
				
			||||||
                    .ConfigureAwait(false)
 | 
					                               .Include(u => u.Preferences)
 | 
				
			||||||
 | 
					                               .Include(u => u.AccessSchedules)
 | 
				
			||||||
 | 
					                               .Include(u => u.ProfileImage)
 | 
				
			||||||
 | 
					                               .FirstOrDefault(u => u.Id.Equals(userId))
 | 
				
			||||||
                           ?? throw new ArgumentException("No user exists with given Id!");
 | 
					                           ?? throw new ArgumentException("No user exists with given Id!");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
 | 
					                // The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
 | 
				
			||||||
@ -708,6 +710,7 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
                user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
 | 
					                user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                dbContext.Update(user);
 | 
					                dbContext.Update(user);
 | 
				
			||||||
 | 
					                _users[user.Id] = user;
 | 
				
			||||||
                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 | 
					                await dbContext.SaveChangesAsync().ConfigureAwait(false);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -728,6 +731,7 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            user.ProfileImage = null;
 | 
					            user.ProfileImage = null;
 | 
				
			||||||
 | 
					            _users[user.Id] = user;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        internal static void ThrowIfInvalidUsername(string name)
 | 
					        internal static void ThrowIfInvalidUsername(string name)
 | 
				
			||||||
@ -874,15 +878,8 @@ namespace Jellyfin.Server.Implementations.Users
 | 
				
			|||||||
        private async Task UpdateUserInternalAsync(JellyfinDbContext dbContext, User user)
 | 
					        private async Task UpdateUserInternalAsync(JellyfinDbContext dbContext, User user)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            dbContext.Users.Update(user);
 | 
					            dbContext.Users.Update(user);
 | 
				
			||||||
 | 
					            _users[user.Id] = user;
 | 
				
			||||||
            await dbContext.SaveChangesAsync().ConfigureAwait(false);
 | 
					            await dbContext.SaveChangesAsync().ConfigureAwait(false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        private IQueryable<User> GetUsersInternal(JellyfinDbContext dbContext)
 | 
					 | 
				
			||||||
            => dbContext.Users
 | 
					 | 
				
			||||||
                .AsSplitQuery()
 | 
					 | 
				
			||||||
                .Include(user => user.Permissions)
 | 
					 | 
				
			||||||
                .Include(user => user.Preferences)
 | 
					 | 
				
			||||||
                .Include(user => user.AccessSchedules)
 | 
					 | 
				
			||||||
                .Include(user => user.ProfileImage);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user