diff options
Diffstat (limited to 'Jellyfin.Server.Implementations')
4 files changed, 79 insertions, 96 deletions
diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index abdd290d4..7bde4f35b 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -3,8 +3,10 @@ using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Querying; +using Microsoft.EntityFrameworkCore; namespace Jellyfin.Server.Implementations.Activity { @@ -39,39 +41,47 @@ namespace Jellyfin.Server.Implementations.Activity } /// <inheritdoc/> - public QueryResult<ActivityLogEntry> GetPagedResult( - Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>> func, - int? startIndex, - int? limit) + public async Task<QueryResult<ActivityLogEntry>> GetPagedResultAsync(ActivityLogQuery query) { - using var dbContext = _provider.CreateContext(); + await using var dbContext = _provider.CreateContext(); - var query = func(dbContext.ActivityLogs.OrderByDescending(entry => entry.DateCreated)); + IQueryable<ActivityLog> entries = dbContext.ActivityLogs + .AsQueryable() + .OrderByDescending(entry => entry.DateCreated); - if (startIndex.HasValue) + if (query.MinDate.HasValue) { - query = query.Skip(startIndex.Value); + entries = entries.Where(entry => entry.DateCreated >= query.MinDate); } - if (limit.HasValue) + if (query.HasUserId.HasValue) { - query = query.Take(limit.Value); + entries = entries.Where(entry => entry.UserId != Guid.Empty == query.HasUserId.Value ); } - // This converts the objects from the new database model to the old for compatibility with the existing API. - var list = query.Select(ConvertToOldModel).ToList(); - return new QueryResult<ActivityLogEntry> { - Items = list, - TotalRecordCount = func(dbContext.ActivityLogs).Count() + Items = await entries + .Skip(query.StartIndex ?? 0) + .Take(query.Limit ?? 100) + .AsAsyncEnumerable() + .Select(ConvertToOldModel) + .ToListAsync() + .ConfigureAwait(false), + TotalRecordCount = await entries.CountAsync().ConfigureAwait(false) }; } - /// <inheritdoc/> - public QueryResult<ActivityLogEntry> GetPagedResult(int? startIndex, int? limit) + /// <inheritdoc /> + public async Task CleanAsync(DateTime startDate) { - return GetPagedResult(logs => logs, startIndex, limit); + await using var dbContext = _provider.CreateContext(); + var entries = dbContext.ActivityLogs + .AsQueryable() + .Where(entry => entry.DateCreated <= startDate); + + dbContext.RemoveRange(entries); + await dbContext.SaveChangesAsync().ConfigureAwait(false); } private static ActivityLogEntry ConvertToOldModel(ActivityLog entry) diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 4e79dd8d6..c52be3b8a 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -24,11 +24,12 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.8"> + <PackageReference Include="System.Linq.Async" Version="4.1.1" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.9"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.8"> + <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.9"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs index 46f1c618f..76f943385 100644 --- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs +++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs @@ -61,6 +61,7 @@ namespace Jellyfin.Server.Implementations.Users public IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client) { return _dbContext.ItemDisplayPreferences + .AsQueryable() .Where(prefs => prefs.UserId == userId && prefs.ItemId != Guid.Empty && string.Equals(prefs.Client, client)) .ToList(); } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index c6cc639fa..40b89ed28 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -2,6 +2,7 @@ #pragma warning disable CA1307 using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -48,6 +49,8 @@ namespace Jellyfin.Server.Implementations.Users private readonly DefaultAuthenticationProvider _defaultAuthenticationProvider; private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider; + private readonly IDictionary<Guid, User> _users; + /// <summary> /// Initializes a new instance of the <see cref="UserManager"/> class. /// </summary> @@ -81,37 +84,28 @@ 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.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/> public event EventHandler<GenericEventArgs<User>>? OnUserUpdated; /// <inheritdoc/> - public IEnumerable<User> Users - { - 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(); - } - } + public IEnumerable<User> Users => _users.Values; /// <inheritdoc/> - public IEnumerable<Guid> UsersIds - { - get - { - using var dbContext = _dbProvider.CreateContext(); - return dbContext.Users - .Select(user => user.Id) - .ToList(); - } - } + public IEnumerable<Guid> UsersIds => _users.Keys; /// <inheritdoc/> public User? GetUserById(Guid id) @@ -121,13 +115,8 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("Guid can't be empty", nameof(id)); } - using var dbContext = _dbProvider.CreateContext(); - return dbContext.Users - .Include(user => user.Permissions) - .Include(user => user.Preferences) - .Include(user => user.AccessSchedules) - .Include(user => user.ProfileImage) - .FirstOrDefault(user => user.Id == id); + _users.TryGetValue(id, out var user); + return user; } /// <inheritdoc/> @@ -138,14 +127,7 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("Invalid username", nameof(name)); } - using var dbContext = _dbProvider.CreateContext(); - 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)); + return _users.Values.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase)); } /// <inheritdoc/> @@ -200,17 +182,21 @@ namespace Jellyfin.Server.Implementations.Users internal async Task<User> CreateUserInternalAsync(string name, JellyfinDb dbContext) { // TODO: Remove after user item data is migrated. - var max = await dbContext.Users.AnyAsync().ConfigureAwait(false) - ? await dbContext.Users.Select(u => u.InternalId).MaxAsync().ConfigureAwait(false) + var max = await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false) + ? await dbContext.Users.AsQueryable().Select(u => u.InternalId).MaxAsync().ConfigureAwait(false) : 0; - return new User( + var user = new User( name, _defaultAuthenticationProvider.GetType().FullName, _defaultPasswordResetProvider.GetType().FullName) { InternalId = max + 1 }; + + _users.Add(user.Id, user); + + return user; } /// <inheritdoc/> @@ -221,7 +207,7 @@ namespace Jellyfin.Server.Implementations.Users throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); } - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); var newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false); @@ -236,28 +222,12 @@ namespace Jellyfin.Server.Implementations.Users /// <inheritdoc/> public void DeleteUser(Guid userId) { - using var dbContext = _dbProvider.CreateContext(); - 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) + if (!_users.TryGetValue(userId, out var user)) { throw new ResourceNotFoundException(nameof(userId)); } - if (dbContext.Users.Find(user.Id) == null) - { - 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) + if (_users.Count == 1) { throw new InvalidOperationException(string.Format( CultureInfo.InvariantCulture, @@ -276,6 +246,8 @@ namespace Jellyfin.Server.Implementations.Users nameof(userId)); } + using var dbContext = _dbProvider.CreateContext(); + // Clear all entities related to the user from the database. if (user.ProfileImage != null) { @@ -287,6 +259,7 @@ namespace Jellyfin.Server.Implementations.Users dbContext.RemoveRange(user.AccessSchedules); dbContext.Users.Remove(user); dbContext.SaveChanges(); + _users.Remove(userId); _eventManager.Publish(new UserDeletedEventArgs(user)); } @@ -459,11 +432,9 @@ namespace Jellyfin.Server.Implementations.Users // the authentication provider might have created it user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); - if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) + if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy && user != null) { - UpdatePolicy(user.Id, hasNewUserPolicy.GetNewUserPolicy()); - - await UpdateUserAsync(user).ConfigureAwait(false); + await UpdatePolicyAsync(user.Id, hasNewUserPolicy.GetNewUserPolicy()).ConfigureAwait(false); } } } @@ -588,9 +559,7 @@ namespace Jellyfin.Server.Implementations.Users public async Task InitializeAsync() { // TODO: Refactor the startup wizard so that it doesn't require a user to already exist. - using var dbContext = _dbProvider.CreateContext(); - - if (await dbContext.Users.AnyAsync().ConfigureAwait(false)) + if (_users.Any()) { return; } @@ -603,6 +572,7 @@ namespace Jellyfin.Server.Implementations.Users _logger.LogWarning("No users, creating one with username {UserName}", defaultName); + await using var dbContext = _dbProvider.CreateContext(); var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false); newUser.SetPermission(PermissionKind.IsAdministrator, true); newUser.SetPermission(PermissionKind.EnableContentDeletion, true); @@ -643,9 +613,9 @@ namespace Jellyfin.Server.Implementations.Users } /// <inheritdoc/> - public void UpdateConfiguration(Guid userId, UserConfiguration config) + public async Task UpdateConfigurationAsync(Guid userId, UserConfiguration config) { - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); var user = dbContext.Users .Include(u => u.Permissions) .Include(u => u.Preferences) @@ -672,13 +642,13 @@ namespace Jellyfin.Server.Implementations.Users user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); dbContext.Update(user); - dbContext.SaveChanges(); + await dbContext.SaveChangesAsync().ConfigureAwait(false); } /// <inheritdoc/> - public void UpdatePolicy(Guid userId, UserPolicy policy) + public async Task UpdatePolicyAsync(Guid userId, UserPolicy policy) { - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); var user = dbContext.Users .Include(u => u.Permissions) .Include(u => u.Preferences) @@ -743,15 +713,16 @@ namespace Jellyfin.Server.Implementations.Users user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); dbContext.Update(user); - dbContext.SaveChanges(); + await dbContext.SaveChangesAsync().ConfigureAwait(false); } /// <inheritdoc/> - public void ClearProfileImage(User user) + public async Task ClearProfileImageAsync(User user) { - using var dbContext = _dbProvider.CreateContext(); + await using var dbContext = _dbProvider.CreateContext(); dbContext.Remove(user.ProfileImage); - dbContext.SaveChanges(); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + user.ProfileImage = null; } private static bool IsValidUsername(string name) |
