Procházet zdrojové kódy

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>
gnattu před 1 rokem
rodič
revize
5fe7d7f0bf
1 změnil soubory, kde provedl 78 přidání a 81 odebrání
  1. 78 81
      Jellyfin.Server.Implementations/Users/UserManager.cs

+ 78 - 81
Jellyfin.Server.Implementations/Users/UserManager.cs

@@ -1,7 +1,7 @@
 #pragma warning disable CA1307
-#pragma warning disable CA1309 // Use ordinal string comparison - EF can't translate this
 
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
@@ -47,6 +47,8 @@ namespace Jellyfin.Server.Implementations.Users
         private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
         private readonly IServerConfigurationManager _serverConfigurationManager;
 
+        private readonly IDictionary<Guid, User> _users;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="UserManager"/> class.
         /// </summary>
@@ -84,30 +86,29 @@ namespace Jellyfin.Server.Implementations.Users
             _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
             _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().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/>
         public event EventHandler<GenericEventArgs<User>>? OnUserUpdated;
 
         /// <inheritdoc/>
-        public IEnumerable<User> Users
-        {
-            get
-            {
-                using var dbContext = _dbProvider.CreateDbContext();
-                return GetUsersInternal(dbContext).ToList();
-            }
-        }
+        public IEnumerable<User> Users => _users.Values;
 
         /// <inheritdoc/>
-        public IEnumerable<Guid> UsersIds
-        {
-            get
-            {
-                using var dbContext = _dbProvider.CreateDbContext();
-                return dbContext.Users.Select(u => u.Id).ToList();
-            }
-        }
+        public IEnumerable<Guid> UsersIds => _users.Keys;
 
         // 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
@@ -123,8 +124,8 @@ namespace Jellyfin.Server.Implementations.Users
                 throw new ArgumentException("Guid can't be empty", nameof(id));
             }
 
-            using var dbContext = _dbProvider.CreateDbContext();
-            return GetUsersInternal(dbContext).FirstOrDefault(u => u.Id.Equals(id));
+            _users.TryGetValue(id, out var user);
+            return user;
         }
 
         /// <inheritdoc/>
@@ -135,9 +136,7 @@ namespace Jellyfin.Server.Implementations.Users
                 throw new ArgumentException("Invalid username", nameof(name));
             }
 
-            using var dbContext = _dbProvider.CreateDbContext();
-            return GetUsersInternal(dbContext)
-                .FirstOrDefault(u => string.Equals(u.Username, name));
+            return _users.Values.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
         }
 
         /// <inheritdoc/>
@@ -202,6 +201,8 @@ namespace Jellyfin.Server.Implementations.Users
             user.AddDefaultPermissions();
             user.AddDefaultPreferences();
 
+            _users.Add(user.Id, user);
+
             return user;
         }
 
@@ -236,46 +237,40 @@ namespace Jellyfin.Server.Implementations.Users
         /// <inheritdoc/>
         public async Task DeleteUserAsync(Guid userId)
         {
-            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+            if (!_users.TryGetValue(userId, out var user))
+            {
+                throw new ResourceNotFoundException(nameof(userId));
+            }
 
-            await using (dbContext.ConfigureAwait(false))
+            if (_users.Count == 1)
             {
-                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 InvalidOperationException(string.Format(
+                    CultureInfo.InvariantCulture,
+                    "The user '{0}' cannot be deleted because there must be at least one user in the system.",
+                    user.Username));
+            }
 
-                if (await dbContext.Users.CountAsync().ConfigureAwait(false) == 1)
-                {
-                    throw new InvalidOperationException(string.Format(
+            if (user.HasPermission(PermissionKind.IsAdministrator)
+                && Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1)
+            {
+                throw new ArgumentException(
+                    string.Format(
                         CultureInfo.InvariantCulture,
-                        "The user '{0}' cannot be deleted because there must be at least one user in the system.",
-                        user.Username));
-                }
-
-                if (user.HasPermission(PermissionKind.IsAdministrator)
-                    && await dbContext.Users
-                        .CountAsync(u => u.Permissions.Any(p => p.Kind == PermissionKind.IsAdministrator && p.Value))
-                        .ConfigureAwait(false) == 1)
-                {
-                    throw new ArgumentException(
-                        string.Format(
-                            CultureInfo.InvariantCulture,
-                            "The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
-                            user.Username),
-                        nameof(userId));
-                }
+                        "The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
+                        user.Username),
+                    nameof(userId));
+            }
 
+            var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+            await using (dbContext.ConfigureAwait(false))
+            {
                 dbContext.Users.Remove(user);
                 await dbContext.SaveChangesAsync().ConfigureAwait(false);
-
-                await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false);
             }
+
+            _users.Remove(userId);
+
+            await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false);
         }
 
         /// <inheritdoc/>
@@ -542,23 +537,23 @@ namespace Jellyfin.Server.Implementations.Users
         /// <inheritdoc />
         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.
+            if (_users.Any())
             {
-                // TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
-                if (await dbContext.Users.AnyAsync().ConfigureAwait(false))
-                {
-                    return;
-                }
+                return;
+            }
 
-                var defaultName = Environment.UserName;
-                if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName))
-                {
-                    defaultName = "MyJellyfinUser";
-                }
+            var defaultName = Environment.UserName;
+            if (string.IsNullOrWhiteSpace(defaultName) || !ValidUsernameRegex().IsMatch(defaultName))
+            {
+                defaultName = "MyJellyfinUser";
+            }
 
-                _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);
                 newUser.SetPermission(PermissionKind.IsAdministrator, true);
                 newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
@@ -605,9 +600,12 @@ namespace Jellyfin.Server.Implementations.Users
             var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
             await using (dbContext.ConfigureAwait(false))
             {
-                var user = await GetUsersInternal(dbContext)
-                    .FirstOrDefaultAsync(u => u.Id.Equals(userId))
-                    .ConfigureAwait(false)
+                var user = dbContext.Users
+                               .Include(u => u.Permissions)
+                               .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!");
 
                 user.SubtitleMode = config.SubtitleMode;
@@ -635,6 +633,7 @@ namespace Jellyfin.Server.Implementations.Users
                 user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
 
                 dbContext.Update(user);
+                _users[user.Id] = user;
                 await dbContext.SaveChangesAsync().ConfigureAwait(false);
             }
         }
@@ -645,9 +644,12 @@ namespace Jellyfin.Server.Implementations.Users
             var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
             await using (dbContext.ConfigureAwait(false))
             {
-                var user = await GetUsersInternal(dbContext)
-                    .FirstOrDefaultAsync(u => u.Id.Equals(userId))
-                    .ConfigureAwait(false)
+                var user = dbContext.Users
+                               .Include(u => u.Permissions)
+                               .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!");
 
                 // 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);
 
                 dbContext.Update(user);
+                _users[user.Id] = user;
                 await dbContext.SaveChangesAsync().ConfigureAwait(false);
             }
         }
@@ -728,6 +731,7 @@ namespace Jellyfin.Server.Implementations.Users
             }
 
             user.ProfileImage = null;
+            _users[user.Id] = user;
         }
 
         internal static void ThrowIfInvalidUsername(string name)
@@ -874,15 +878,8 @@ namespace Jellyfin.Server.Implementations.Users
         private async Task UpdateUserInternalAsync(JellyfinDbContext dbContext, User user)
         {
             dbContext.Users.Update(user);
+            _users[user.Id] = user;
             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);
     }
 }