aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Users
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server.Implementations/Users')
-rw-r--r--Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs24
-rw-r--r--Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs75
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs10
3 files changed, 85 insertions, 24 deletions
diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs
index 92e2bb4fa7..7c46ef7721 100644
--- a/Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs
+++ b/Jellyfin.Server.Implementations/Users/DeviceAccessHost.cs
@@ -1,3 +1,4 @@
+using System;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data;
@@ -9,6 +10,7 @@ using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Session;
using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Implementations.Users;
@@ -20,6 +22,7 @@ public sealed class DeviceAccessHost : IHostedService
private readonly IUserManager _userManager;
private readonly IDeviceManager _deviceManager;
private readonly ISessionManager _sessionManager;
+ private readonly ILogger<DeviceAccessHost> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="DeviceAccessHost"/> class.
@@ -27,11 +30,17 @@ public sealed class DeviceAccessHost : IHostedService
/// <param name="userManager">The <see cref="IUserManager"/>.</param>
/// <param name="deviceManager">The <see cref="IDeviceManager"/>.</param>
/// <param name="sessionManager">The <see cref="ISessionManager"/>.</param>
- public DeviceAccessHost(IUserManager userManager, IDeviceManager deviceManager, ISessionManager sessionManager)
+ /// <param name="logger">The <see cref="ILogger{TCategoryName}"/>.</param>
+ public DeviceAccessHost(
+ IUserManager userManager,
+ IDeviceManager deviceManager,
+ ISessionManager sessionManager,
+ ILogger<DeviceAccessHost> logger)
{
_userManager = userManager;
_deviceManager = deviceManager;
_sessionManager = sessionManager;
+ _logger = logger;
}
/// <inheritdoc />
@@ -53,9 +62,18 @@ public sealed class DeviceAccessHost : IHostedService
private async void OnUserUpdated(object? sender, GenericEventArgs<User> e)
{
var user = e.Argument;
- if (!user.HasPermission(PermissionKind.EnableAllDevices))
+
+ // This handler is async void, so an escaping exception would terminate the process.
+ try
+ {
+ if (!user.HasPermission(PermissionKind.EnableAllDevices))
+ {
+ await UpdateDeviceAccess(user).ConfigureAwait(false);
+ }
+ }
+ catch (Exception ex)
{
- await UpdateDeviceAccess(user).ConfigureAwait(false);
+ _logger.LogError(ex, "Error updating device access for user {UserId}", user.Id);
}
}
diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
index 0e126fe9a0..dd3c63ae32 100644
--- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
+++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
@@ -13,6 +13,8 @@ namespace Jellyfin.Server.Implementations.Users;
/// </summary>
public sealed class DisplayPreferencesManager : IDisplayPreferencesManager
{
+ private const int MaxSaveAttempts = 3;
+
private readonly IDbContextFactory<JellyfinDbContext> _dbContextFactory;
/// <summary>
@@ -28,17 +30,31 @@ public sealed class DisplayPreferencesManager : IDisplayPreferencesManager
public DisplayPreferences GetDisplayPreferences(Guid userId, Guid itemId, string client)
{
using var dbContext = _dbContextFactory.CreateDbContext();
- var prefs = dbContext.DisplayPreferences
- .Include(pref => pref.HomeSections)
- .FirstOrDefault(pref =>
- pref.UserId.Equals(userId) && pref.Client == client && pref.ItemId.Equals(itemId));
+ var prefs = FindDisplayPreferences(dbContext, userId, itemId, client);
+ if (prefs is not null)
+ {
+ return prefs;
+ }
- if (prefs is null)
+ prefs = new DisplayPreferences(userId, itemId, client);
+ dbContext.DisplayPreferences.Add(prefs);
+ try
{
- prefs = new DisplayPreferences(userId, itemId, client);
- dbContext.DisplayPreferences.Add(prefs);
dbContext.SaveChanges();
}
+ catch (DbUpdateException)
+ {
+ // Another request may have stored the preferences between the lookup and the insert, and the unique index
+ // rejected this one. Return the stored preferences; if there are none, the insert failed for another reason.
+ using var retryContext = _dbContextFactory.CreateDbContext();
+ var stored = FindDisplayPreferences(retryContext, userId, itemId, client);
+ if (stored is null)
+ {
+ throw;
+ }
+
+ return stored;
+ }
return prefs;
}
@@ -52,7 +68,7 @@ public sealed class DisplayPreferencesManager : IDisplayPreferencesManager
if (prefs is null)
{
- prefs = new ItemDisplayPreferences(userId, Guid.Empty, client);
+ prefs = new ItemDisplayPreferences(userId, itemId, client);
dbContext.ItemDisplayPreferences.Add(prefs);
dbContext.SaveChanges();
}
@@ -83,19 +99,35 @@ public sealed class DisplayPreferencesManager : IDisplayPreferencesManager
/// <inheritdoc />
public void SetCustomItemDisplayPreferences(Guid userId, Guid itemId, string client, Dictionary<string, string?> customPreferences)
{
- using var dbContext = _dbContextFactory.CreateDbContext();
- dbContext.CustomItemDisplayPreferences.Where(prefs => prefs.UserId.Equals(userId)
- && prefs.ItemId.Equals(itemId)
- && prefs.Client == client)
- .ExecuteDelete();
-
- foreach (var (key, value) in customPreferences)
+ // Another request can store one of these keys after this one's delete, and the unique index then rejects the
+ // insert. Replacing the set again gives the same result, so the replace is repeated.
+ for (var attempt = 1; ; attempt++)
{
- dbContext.CustomItemDisplayPreferences
- .Add(new CustomItemDisplayPreferences(userId, itemId, client, key, value));
+ using var dbContext = _dbContextFactory.CreateDbContext();
+ using var transaction = dbContext.Database.BeginTransaction();
+ dbContext.CustomItemDisplayPreferences.Where(prefs => prefs.UserId.Equals(userId)
+ && prefs.ItemId.Equals(itemId)
+ && prefs.Client == client)
+ .ExecuteDelete();
+
+ foreach (var (key, value) in customPreferences)
+ {
+ dbContext.CustomItemDisplayPreferences
+ .Add(new CustomItemDisplayPreferences(userId, itemId, client, key, value));
+ }
+
+ try
+ {
+ dbContext.SaveChanges();
+ }
+ catch (DbUpdateException) when (attempt < MaxSaveAttempts)
+ {
+ continue;
+ }
+
+ transaction.Commit();
+ return;
}
-
- dbContext.SaveChanges();
}
/// <inheritdoc/>
@@ -113,4 +145,9 @@ public sealed class DisplayPreferencesManager : IDisplayPreferencesManager
dbContext.ItemDisplayPreferences.Attach(itemDisplayPreferences).State = EntityState.Modified;
dbContext.SaveChanges();
}
+
+ private static DisplayPreferences? FindDisplayPreferences(JellyfinDbContext dbContext, Guid userId, Guid itemId, string client)
+ => dbContext.DisplayPreferences
+ .Include(pref => pref.HomeSections)
+ .FirstOrDefault(pref => pref.UserId.Equals(userId) && pref.Client == client && pref.ItemId.Equals(itemId));
}
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index fea6084267..b15f6b98b2 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -847,14 +847,16 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public async Task UpdatePolicyAsync(Guid userId, UserPolicy policy)
{
+ User user;
using (await _userLock.LockAsync(userId).ConfigureAwait(false))
{
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
- var user = UserQuery(dbContext)
+ user = await UserQuery(dbContext)
.AsTracking()
- .FirstOrDefault(u => u.Id.Equals(userId))
+ .FirstOrDefaultAsync(u => u.Id.Equals(userId))
+ .ConfigureAwait(false)
?? 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"
@@ -919,6 +921,10 @@ namespace Jellyfin.Server.Implementations.Users
await dbContext.SaveChangesAsync().ConfigureAwait(false);
}
}
+
+ var eventArgs = new UserUpdatedEventArgs(user);
+ await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
+ OnUserUpdated?.Invoke(this, eventArgs);
}
/// <inheritdoc/>