aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations
diff options
context:
space:
mode:
authorDominik <git@secnd.me>2023-06-15 19:38:42 +0200
committerGitHub <noreply@github.com>2023-06-15 19:38:42 +0200
commit17f1e8d19b1fd693893d66d2275ed8ae2476344e (patch)
tree7f48be975faa92042769870957587b3c7864f631 /Jellyfin.Server.Implementations
parente8ae7e5c38e28f13fa8de295e26c930cb46d9b79 (diff)
parent6771b5cabe96b4b3cbd1cd0c998d564f3dd17ed4 (diff)
Merge branch 'master' into segment-deletion
Diffstat (limited to 'Jellyfin.Server.Implementations')
-rw-r--r--Jellyfin.Server.Implementations/Activity/ActivityManager.cs32
-rw-r--r--Jellyfin.Server.Implementations/Devices/DeviceManager.cs45
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs6
-rw-r--r--Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs8
-rw-r--r--Jellyfin.Server.Implementations/Events/EventManager.cs2
-rw-r--r--Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs9
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj26
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDb.cs162
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDbContext.cs188
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20201204223655_AddCustomDisplayPreferences.Designer.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20210814002109_AddDevices.Designer.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20221022080052_AddIndexActivityLogsDateCreated.Designer.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.Designer.cs650
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.cs164
-rw-r--r--Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs10
-rw-r--r--Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs36
-rw-r--r--Jellyfin.Server.Implementations/Security/AuthenticationManager.cs8
-rw-r--r--Jellyfin.Server.Implementations/Security/AuthorizationContext.cs13
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs4
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs8
-rw-r--r--Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs11
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs76
29 files changed, 1131 insertions, 347 deletions
diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs
index 9d6ca6aabe..ce1c54cbb2 100644
--- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs
+++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs
@@ -15,13 +15,13 @@ namespace Jellyfin.Server.Implementations.Activity
/// </summary>
public class ActivityManager : IActivityManager
{
- private readonly IDbContextFactory<JellyfinDb> _provider;
+ private readonly IDbContextFactory<JellyfinDbContext> _provider;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityManager"/> class.
/// </summary>
/// <param name="provider">The Jellyfin database provider.</param>
- public ActivityManager(IDbContextFactory<JellyfinDb> provider)
+ public ActivityManager(IDbContextFactory<JellyfinDbContext> provider)
{
_provider = provider;
}
@@ -48,18 +48,10 @@ namespace Jellyfin.Server.Implementations.Activity
var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
- IQueryable<ActivityLog> entries = dbContext.ActivityLogs
- .OrderByDescending(entry => entry.DateCreated);
-
- if (query.MinDate.HasValue)
- {
- entries = entries.Where(entry => entry.DateCreated >= query.MinDate);
- }
-
- if (query.HasUserId.HasValue)
- {
- entries = entries.Where(entry => (!entry.UserId.Equals(default)) == query.HasUserId.Value);
- }
+ var entries = dbContext.ActivityLogs
+ .OrderByDescending(entry => entry.DateCreated)
+ .Where(entry => query.MinDate == null || entry.DateCreated >= query.MinDate)
+ .Where(entry => !query.HasUserId.HasValue || entry.UserId.Equals(default) != query.HasUserId.Value);
return new QueryResult<ActivityLogEntry>(
query.Skip,
@@ -67,8 +59,16 @@ namespace Jellyfin.Server.Implementations.Activity
await entries
.Skip(query.Skip ?? 0)
.Take(query.Limit ?? 100)
- .AsAsyncEnumerable()
- .Select(ConvertToOldModel)
+ .Select(entity => new ActivityLogEntry(entity.Name, entity.Type, entity.UserId)
+ {
+ Id = entity.Id,
+ Overview = entity.Overview,
+ ShortOverview = entity.ShortOverview,
+ ItemId = entity.ItemId,
+ Date = entity.DateCreated,
+ Severity = entity.LogSeverity
+ })
+ .AsQueryable()
.ToListAsync()
.ConfigureAwait(false));
}
diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
index eeb958c620..a4b4c19599 100644
--- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
+++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
@@ -9,6 +9,7 @@ using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
using Jellyfin.Data.Queries;
using Jellyfin.Extensions;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Devices;
@@ -23,7 +24,7 @@ namespace Jellyfin.Server.Implementations.Devices
/// </summary>
public class DeviceManager : IDeviceManager
{
- private readonly IDbContextFactory<JellyfinDb> _dbProvider;
+ private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
private readonly IUserManager _userManager;
private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new();
@@ -32,7 +33,7 @@ namespace Jellyfin.Server.Implementations.Devices
/// </summary>
/// <param name="dbProvider">The database provider.</param>
/// <param name="userManager">The user manager.</param>
- public DeviceManager(IDbContextFactory<JellyfinDb> dbProvider, IUserManager userManager)
+ public DeviceManager(IDbContextFactory<JellyfinDbContext> dbProvider, IUserManager userManager)
{
_dbProvider = dbProvider;
_userManager = userManager;
@@ -54,8 +55,8 @@ namespace Jellyfin.Server.Implementations.Devices
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
- deviceOptions = await dbContext.DeviceOptions.AsQueryable().FirstOrDefaultAsync(dev => dev.DeviceId == deviceId).ConfigureAwait(false);
- if (deviceOptions == null)
+ deviceOptions = await dbContext.DeviceOptions.FirstOrDefaultAsync(dev => dev.DeviceId == deviceId).ConfigureAwait(false);
+ if (deviceOptions is null)
{
deviceOptions = new DeviceOptions(deviceId);
dbContext.DeviceOptions.Add(deviceOptions);
@@ -121,7 +122,7 @@ namespace Jellyfin.Server.Implementations.Devices
.ConfigureAwait(false);
}
- var deviceInfo = device == null ? null : ToDeviceInfo(device);
+ var deviceInfo = device is null ? null : ToDeviceInfo(device);
return deviceInfo;
}
@@ -132,22 +133,11 @@ namespace Jellyfin.Server.Implementations.Devices
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
- var devices = dbContext.Devices.AsQueryable();
-
- if (query.UserId.HasValue)
- {
- devices = devices.Where(device => device.UserId.Equals(query.UserId.Value));
- }
-
- if (query.DeviceId != null)
- {
- devices = devices.Where(device => device.DeviceId == query.DeviceId);
- }
-
- if (query.AccessToken != null)
- {
- devices = devices.Where(device => device.AccessToken == query.AccessToken);
- }
+ var devices = dbContext.Devices
+ .OrderBy(d => d.Id)
+ .Where(device => !query.UserId.HasValue || device.UserId.Equals(query.UserId.Value))
+ .Where(device => query.DeviceId == null || device.DeviceId == query.DeviceId)
+ .Where(device => query.AccessToken == null || device.AccessToken == query.AccessToken);
var count = await devices.CountAsync().ConfigureAwait(false);
@@ -179,11 +169,10 @@ namespace Jellyfin.Server.Implementations.Devices
/// <inheritdoc />
public async Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId, bool? supportsSync)
{
- IAsyncEnumerable<Device> sessions;
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
- sessions = dbContext.Devices
+ IAsyncEnumerable<Device> sessions = dbContext.Devices
.Include(d => d.User)
.OrderByDescending(d => d.DateLastActivity)
.ThenBy(d => d.DeviceId)
@@ -197,6 +186,10 @@ namespace Jellyfin.Server.Implementations.Devices
if (userId.HasValue)
{
var user = _userManager.GetUserById(userId.Value);
+ if (user is null)
+ {
+ throw new ResourceNotFoundException();
+ }
sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId));
}
@@ -222,11 +215,7 @@ namespace Jellyfin.Server.Implementations.Devices
public bool CanAccessDevice(User user, string deviceId)
{
ArgumentNullException.ThrowIfNull(user);
-
- if (string.IsNullOrEmpty(deviceId))
- {
- throw new ArgumentNullException(nameof(deviceId));
- }
+ ArgumentException.ThrowIfNullOrEmpty(deviceId);
if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator))
{
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
index aa6015caae..aeb62e814c 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStartLogger.cs
@@ -38,13 +38,13 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
/// <inheritdoc />
public async Task OnEvent(PlaybackStartEventArgs eventArgs)
{
- if (eventArgs.MediaInfo == null)
+ if (eventArgs.MediaInfo is null)
{
_logger.LogWarning("PlaybackStart reported with null media info.");
return;
}
- if (eventArgs.Item != null && eventArgs.Item.IsThemeMedia)
+ if (eventArgs.Item is not null && eventArgs.Item.IsThemeMedia)
{
// Don't report theme song or local trailer playback
return;
@@ -78,7 +78,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
name = item.SeriesName + " - " + name;
}
- if (item.Artists != null && item.Artists.Count > 0)
+ if (item.Artists is not null && item.Artists.Count > 0)
{
name = item.Artists[0] + " - " + name;
}
diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs
index 1648b1b475..dd7290fb84 100644
--- a/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs
+++ b/Jellyfin.Server.Implementations/Events/Consumers/Session/PlaybackStopLogger.cs
@@ -40,13 +40,13 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
{
var item = eventArgs.MediaInfo;
- if (item == null)
+ if (item is null)
{
_logger.LogWarning("PlaybackStopped reported with null media info.");
return;
}
- if (eventArgs.Item != null && eventArgs.Item.IsThemeMedia)
+ if (eventArgs.Item is not null && eventArgs.Item.IsThemeMedia)
{
// Don't report theme song or local trailer playback
return;
@@ -60,7 +60,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
var user = eventArgs.Users[0];
var notificationType = GetPlaybackStoppedNotificationType(item.MediaType);
- if (notificationType == null)
+ if (notificationType is null)
{
return;
}
@@ -86,7 +86,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
name = item.SeriesName + " - " + name;
}
- if (item.Artists != null && item.Artists.Count > 0)
+ if (item.Artists is not null && item.Artists.Count > 0)
{
name = item.Artists[0] + " - " + name;
}
diff --git a/Jellyfin.Server.Implementations/Events/EventManager.cs b/Jellyfin.Server.Implementations/Events/EventManager.cs
index 7f7c4750d8..f49ae8e27a 100644
--- a/Jellyfin.Server.Implementations/Events/EventManager.cs
+++ b/Jellyfin.Server.Implementations/Events/EventManager.cs
@@ -44,7 +44,7 @@ namespace Jellyfin.Server.Implementations.Events
where T : EventArgs
{
using var scope = _appHost.ServiceProvider?.CreateScope();
- if (scope == null)
+ if (scope is null)
{
return;
}
diff --git a/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs b/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs
index f98a0aede8..bb8d4dd14f 100644
--- a/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs
+++ b/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs
@@ -4,7 +4,6 @@ using EFCoreSecondLevelCacheInterceptor;
using MediaBrowser.Common.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Implementations.Extensions;
@@ -27,15 +26,13 @@ public static class ServiceCollectionExtensions
.UseCacheKeyPrefix("EF_")
// Don't cache null values. Remove this optional setting if it's not necessary.
.SkipCachingResults(result =>
- result.Value == null || (result.Value is EFTableRows rows && rows.RowsCount == 0)));
+ result.Value is null || (result.Value is EFTableRows rows && rows.RowsCount == 0)));
- serviceCollection.AddPooledDbContextFactory<JellyfinDb>((serviceProvider, opt) =>
+ serviceCollection.AddPooledDbContextFactory<JellyfinDbContext>((serviceProvider, opt) =>
{
var applicationPaths = serviceProvider.GetRequiredService<IApplicationPaths>();
- var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
opt.UseSqlite($"Filename={Path.Combine(applicationPaths.DataPath, "jellyfin.db")}")
- .AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>())
- .UseLoggerFactory(loggerFactory);
+ .AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>());
});
return serviceCollection;
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 5caac45233..390ed58b3b 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -1,24 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net6.0</TargetFramework>
+ <TargetFramework>net7.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
- </PropertyGroup>
-
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
+ <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
- <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
- <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
@@ -26,15 +22,15 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="EFCoreSecondLevelCacheInterceptor" Version="3.7.3" />
- <PackageReference Include="System.Linq.Async" Version="6.0.1" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.11" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.11" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.11">
+ <PackageReference Include="EFCoreSecondLevelCacheInterceptor" />
+ <PackageReference Include="System.Linq.Async" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.11">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs
deleted file mode 100644
index dc4f53913c..0000000000
--- a/Jellyfin.Server.Implementations/JellyfinDb.cs
+++ /dev/null
@@ -1,162 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-using System;
-using System.Linq;
-using Jellyfin.Data.Entities;
-using Jellyfin.Data.Entities.Security;
-using Jellyfin.Data.Interfaces;
-using Microsoft.EntityFrameworkCore;
-
-namespace Jellyfin.Server.Implementations
-{
- /// <inheritdoc/>
- public class JellyfinDb : DbContext
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="JellyfinDb"/> class.
- /// </summary>
- /// <param name="options">The database context options.</param>
- public JellyfinDb(DbContextOptions<JellyfinDb> options) : base(options)
- {
- }
-
- /// <summary>
- /// Gets or sets the default connection string.
- /// </summary>
- public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db";
-
- public virtual DbSet<AccessSchedule> AccessSchedules { get; set; }
-
- public virtual DbSet<ActivityLog> ActivityLogs { get; set; }
-
- public virtual DbSet<ApiKey> ApiKeys { get; set; }
-
- public virtual DbSet<Device> Devices { get; set; }
-
- public virtual DbSet<DeviceOptions> DeviceOptions { get; set; }
-
- public virtual DbSet<DisplayPreferences> DisplayPreferences { get; set; }
-
- public virtual DbSet<ImageInfo> ImageInfos { get; set; }
-
- public virtual DbSet<ItemDisplayPreferences> ItemDisplayPreferences { get; set; }
-
- public virtual DbSet<CustomItemDisplayPreferences> CustomItemDisplayPreferences { get; set; }
-
- public virtual DbSet<Permission> Permissions { get; set; }
-
- public virtual DbSet<Preference> Preferences { get; set; }
-
- public virtual DbSet<User> Users { get; set; }
-
- /*public virtual DbSet<Artwork> Artwork { get; set; }
-
- public virtual DbSet<Book> Books { get; set; }
-
- public virtual DbSet<BookMetadata> BookMetadata { get; set; }
-
- public virtual DbSet<Chapter> Chapters { get; set; }
-
- public virtual DbSet<Collection> Collections { get; set; }
-
- public virtual DbSet<CollectionItem> CollectionItems { get; set; }
-
- public virtual DbSet<Company> Companies { get; set; }
-
- public virtual DbSet<CompanyMetadata> CompanyMetadata { get; set; }
-
- public virtual DbSet<CustomItem> CustomItems { get; set; }
-
- public virtual DbSet<CustomItemMetadata> CustomItemMetadata { get; set; }
-
- public virtual DbSet<Episode> Episodes { get; set; }
-
- public virtual DbSet<EpisodeMetadata> EpisodeMetadata { get; set; }
-
- public virtual DbSet<Genre> Genres { get; set; }
-
- public virtual DbSet<Group> Groups { get; set; }
-
- public virtual DbSet<Library> Libraries { get; set; }
-
- public virtual DbSet<LibraryItem> LibraryItems { get; set; }
-
- public virtual DbSet<LibraryRoot> LibraryRoot { get; set; }
-
- public virtual DbSet<MediaFile> MediaFiles { get; set; }
-
- public virtual DbSet<MediaFileStream> MediaFileStream { get; set; }
-
- public virtual DbSet<Metadata> Metadata { get; set; }
-
- public virtual DbSet<MetadataProvider> MetadataProviders { get; set; }
-
- public virtual DbSet<MetadataProviderId> MetadataProviderIds { get; set; }
-
- public virtual DbSet<Movie> Movies { get; set; }
-
- public virtual DbSet<MovieMetadata> MovieMetadata { get; set; }
-
- public virtual DbSet<MusicAlbum> MusicAlbums { get; set; }
-
- public virtual DbSet<MusicAlbumMetadata> MusicAlbumMetadata { get; set; }
-
- public virtual DbSet<Person> People { get; set; }
-
- public virtual DbSet<PersonRole> PersonRoles { get; set; }
-
- public virtual DbSet<Photo> Photo { get; set; }
-
- public virtual DbSet<PhotoMetadata> PhotoMetadata { get; set; }
-
- public virtual DbSet<ProviderMapping> ProviderMappings { get; set; }
-
- public virtual DbSet<Rating> Ratings { get; set; }
-
- /// <summary>
- /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to
- /// store review ratings, not age ratings.
- /// </summary>
- public virtual DbSet<RatingSource> RatingSources { get; set; }
-
- public virtual DbSet<Release> Releases { get; set; }
-
- public virtual DbSet<Season> Seasons { get; set; }
-
- public virtual DbSet<SeasonMetadata> SeasonMetadata { get; set; }
-
- public virtual DbSet<Series> Series { get; set; }
-
- public virtual DbSet<SeriesMetadata> SeriesMetadata { get; set; }
-
- public virtual DbSet<Track> Tracks { get; set; }
-
- public virtual DbSet<TrackMetadata> TrackMetadata { get; set; }*/
-
- /// <inheritdoc/>
- public override int SaveChanges()
- {
- foreach (var saveEntity in ChangeTracker.Entries()
- .Where(e => e.State == EntityState.Modified)
- .Select(entry => entry.Entity)
- .OfType<IHasConcurrencyToken>())
- {
- saveEntity.OnSavingChanges();
- }
-
- return base.SaveChanges();
- }
-
- /// <inheritdoc />
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- modelBuilder.SetDefaultDateTimeKind(DateTimeKind.Utc);
- base.OnModelCreating(modelBuilder);
- modelBuilder.HasDefaultSchema("jellyfin");
-
- // Configuration for each entity is in it's own class inside 'ModelConfiguration'.
- modelBuilder.ApplyConfigurationsFromAssembly(typeof(JellyfinDb).Assembly);
- }
- }
-}
diff --git a/Jellyfin.Server.Implementations/JellyfinDbContext.cs b/Jellyfin.Server.Implementations/JellyfinDbContext.cs
new file mode 100644
index 0000000000..0d91707e3e
--- /dev/null
+++ b/Jellyfin.Server.Implementations/JellyfinDbContext.cs
@@ -0,0 +1,188 @@
+using System;
+using System.Linq;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Entities.Security;
+using Jellyfin.Data.Interfaces;
+using Microsoft.EntityFrameworkCore;
+
+namespace Jellyfin.Server.Implementations;
+
+/// <inheritdoc/>
+public class JellyfinDbContext : DbContext
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="JellyfinDbContext"/> class.
+ /// </summary>
+ /// <param name="options">The database context options.</param>
+ public JellyfinDbContext(DbContextOptions<JellyfinDbContext> options) : base(options)
+ {
+ }
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the access schedules.
+ /// </summary>
+ public DbSet<AccessSchedule> AccessSchedules => Set<AccessSchedule>();
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the activity logs.
+ /// </summary>
+ public DbSet<ActivityLog> ActivityLogs => Set<ActivityLog>();
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the API keys.
+ /// </summary>
+ public DbSet<ApiKey> ApiKeys => Set<ApiKey>();
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the devices.
+ /// </summary>
+ public DbSet<Device> Devices => Set<Device>();
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the device options.
+ /// </summary>
+ public DbSet<DeviceOptions> DeviceOptions => Set<DeviceOptions>();
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the display preferences.
+ /// </summary>
+ public DbSet<DisplayPreferences> DisplayPreferences => Set<DisplayPreferences>();
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the image infos.
+ /// </summary>
+ public DbSet<ImageInfo> ImageInfos => Set<ImageInfo>();
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the item display preferences.
+ /// </summary>
+ public DbSet<ItemDisplayPreferences> ItemDisplayPreferences => Set<ItemDisplayPreferences>();
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the custom item display preferences.
+ /// </summary>
+ public DbSet<CustomItemDisplayPreferences> CustomItemDisplayPreferences => Set<CustomItemDisplayPreferences>();
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the permissions.
+ /// </summary>
+ public DbSet<Permission> Permissions => Set<Permission>();
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the preferences.
+ /// </summary>
+ public DbSet<Preference> Preferences => Set<Preference>();
+
+ /// <summary>
+ /// Gets the <see cref="DbSet{TEntity}"/> containing the users.
+ /// </summary>
+ public DbSet<User> Users => Set<User>();
+
+ /*public DbSet<Artwork> Artwork => Set<Artwork>();
+
+ public DbSet<Book> Books => Set<Book>();
+
+ public DbSet<BookMetadata> BookMetadata => Set<BookMetadata>();
+
+ public DbSet<Chapter> Chapters => Set<Chapter>();
+
+ public DbSet<Collection> Collections => Set<Collection>();
+
+ public DbSet<CollectionItem> CollectionItems => Set<CollectionItem>();
+
+ public DbSet<Company> Companies => Set<Company>();
+
+ public DbSet<CompanyMetadata> CompanyMetadata => Set<CompanyMetadata>();
+
+ public DbSet<CustomItem> CustomItems => Set<CustomItem>();
+
+ public DbSet<CustomItemMetadata> CustomItemMetadata => Set<CustomItemMetadata>();
+
+ public DbSet<Episode> Episodes => Set<Episode>();
+
+ public DbSet<EpisodeMetadata> EpisodeMetadata => Set<EpisodeMetadata>();
+
+ public DbSet<Genre> Genres => Set<Genre>();
+
+ public DbSet<Group> Groups => Set<Groups>();
+
+ public DbSet<Library> Libraries => Set<Library>();
+
+ public DbSet<LibraryItem> LibraryItems => Set<LibraryItems>();
+
+ public DbSet<LibraryRoot> LibraryRoot => Set<LibraryRoot>();
+
+ public DbSet<MediaFile> MediaFiles => Set<MediaFiles>();
+
+ public DbSet<MediaFileStream> MediaFileStream => Set<MediaFileStream>();
+
+ public DbSet<Metadata> Metadata => Set<Metadata>();
+
+ public DbSet<MetadataProvider> MetadataProviders => Set<MetadataProvider>();
+
+ public DbSet<MetadataProviderId> MetadataProviderIds => Set<MetadataProviderId>();
+
+ public DbSet<Movie> Movies => Set<Movie>();
+
+ public DbSet<MovieMetadata> MovieMetadata => Set<MovieMetadata>();
+
+ public DbSet<MusicAlbum> MusicAlbums => Set<MusicAlbum>();
+
+ public DbSet<MusicAlbumMetadata> MusicAlbumMetadata => Set<MusicAlbumMetadata>();
+
+ public DbSet<Person> People => Set<Person>();
+
+ public DbSet<PersonRole> PersonRoles => Set<PersonRole>();
+
+ public DbSet<Photo> Photo => Set<Photo>();
+
+ public DbSet<PhotoMetadata> PhotoMetadata => Set<PhotoMetadata>();
+
+ public DbSet<ProviderMapping> ProviderMappings => Set<ProviderMapping>();
+
+ public DbSet<Rating> Ratings => Set<Rating>();
+
+ /// <summary>
+ /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to
+ /// store review ratings, not age ratings.
+ /// </summary>
+ public DbSet<RatingSource> RatingSources => Set<RatingSource>();
+
+ public DbSet<Release> Releases => Set<Release>();
+
+ public DbSet<Season> Seasons => Set<Season>();
+
+ public DbSet<SeasonMetadata> SeasonMetadata => Set<SeasonMetadata>();
+
+ public DbSet<Series> Series => Set<Series>();
+
+ public DbSet<SeriesMetadata> SeriesMetadata => Set<SeriesMetadata();
+
+ public DbSet<Track> Tracks => Set<Track>();
+
+ public DbSet<TrackMetadata> TrackMetadata => Set<TrackMetadata>();*/
+
+ /// <inheritdoc/>
+ public override int SaveChanges()
+ {
+ foreach (var saveEntity in ChangeTracker.Entries()
+ .Where(e => e.State == EntityState.Modified)
+ .Select(entry => entry.Entity)
+ .OfType<IHasConcurrencyToken>())
+ {
+ saveEntity.OnSavingChanges();
+ }
+
+ return base.SaveChanges();
+ }
+
+ /// <inheritdoc />
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.SetDefaultDateTimeKind(DateTimeKind.Utc);
+ base.OnModelCreating(modelBuilder);
+
+ // Configuration for each entity is in it's own class inside 'ModelConfiguration'.
+ modelBuilder.ApplyConfigurationsFromAssembly(typeof(JellyfinDbContext).Assembly);
+ }
+}
diff --git a/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs
index 98a83b7450..4be6c2faa3 100644
--- a/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs
@@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
- [DbContext(typeof(JellyfinDb))]
+ [DbContext(typeof(JellyfinDbContext))]
[Migration("20200514181226_AddActivityLog")]
partial class AddActivityLog
{
diff --git a/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs
index 6342ce9cf3..f3254734ad 100644
--- a/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs
@@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
- [DbContext(typeof(JellyfinDb))]
+ [DbContext(typeof(JellyfinDbContext))]
[Migration("20200613202153_AddUsers")]
partial class AddUsers
{
diff --git a/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs
index d44707d069..12d6faa8f5 100644
--- a/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20200728005145_AddDisplayPreferences.Designer.cs
@@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
- [DbContext(typeof(JellyfinDb))]
+ [DbContext(typeof(JellyfinDbContext))]
[Migration("20200728005145_AddDisplayPreferences")]
partial class AddDisplayPreferences
{
diff --git a/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs
index 2234f9d5fd..f1cc208058 100644
--- a/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20200905220533_FixDisplayPreferencesIndex.Designer.cs
@@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
- [DbContext(typeof(JellyfinDb))]
+ [DbContext(typeof(JellyfinDbContext))]
[Migration("20200905220533_FixDisplayPreferencesIndex")]
partial class FixDisplayPreferencesIndex
{
diff --git a/Jellyfin.Server.Implementations/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs
index e5c326a326..f134d363c8 100644
--- a/Jellyfin.Server.Implementations/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs
@@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
- [DbContext(typeof(JellyfinDb))]
+ [DbContext(typeof(JellyfinDbContext))]
[Migration("20201004171403_AddMaxActiveSessions")]
partial class AddMaxActiveSessions
{
diff --git a/Jellyfin.Server.Implementations/Migrations/20201204223655_AddCustomDisplayPreferences.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20201204223655_AddCustomDisplayPreferences.Designer.cs
index 10663d0655..ec65205d45 100644
--- a/Jellyfin.Server.Implementations/Migrations/20201204223655_AddCustomDisplayPreferences.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20201204223655_AddCustomDisplayPreferences.Designer.cs
@@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
- [DbContext(typeof(JellyfinDb))]
+ [DbContext(typeof(JellyfinDbContext))]
[Migration("20201204223655_AddCustomDisplayPreferences")]
partial class AddCustomDisplayPreferences
{
diff --git a/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs
index 8696768245..45dad6be68 100644
--- a/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20210320181425_AddIndexesAndCollations.Designer.cs
@@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
- [DbContext(typeof(JellyfinDb))]
+ [DbContext(typeof(JellyfinDbContext))]
[Migration("20210320181425_AddIndexesAndCollations")]
partial class AddIndexesAndCollations
{
diff --git a/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs
index d332d19f28..eff84b4574 100644
--- a/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20210407110544_NullableCustomPrefValue.Designer.cs
@@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
- [DbContext(typeof(JellyfinDb))]
+ [DbContext(typeof(JellyfinDbContext))]
[Migration("20210407110544_NullableCustomPrefValue")]
partial class NullableCustomPrefValue
{
diff --git a/Jellyfin.Server.Implementations/Migrations/20210814002109_AddDevices.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210814002109_AddDevices.Designer.cs
index 7e9566e2ea..ad7c2dd2c9 100644
--- a/Jellyfin.Server.Implementations/Migrations/20210814002109_AddDevices.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20210814002109_AddDevices.Designer.cs
@@ -10,7 +10,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
- [DbContext(typeof(JellyfinDb))]
+ [DbContext(typeof(JellyfinDbContext))]
[Migration("20210814002109_AddDevices")]
partial class AddDevices
{
diff --git a/Jellyfin.Server.Implementations/Migrations/20221022080052_AddIndexActivityLogsDateCreated.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20221022080052_AddIndexActivityLogsDateCreated.Designer.cs
index 03e3f3c921..f9497a3b69 100644
--- a/Jellyfin.Server.Implementations/Migrations/20221022080052_AddIndexActivityLogsDateCreated.Designer.cs
+++ b/Jellyfin.Server.Implementations/Migrations/20221022080052_AddIndexActivityLogsDateCreated.Designer.cs
@@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
- [DbContext(typeof(JellyfinDb))]
+ [DbContext(typeof(JellyfinDbContext))]
[Migration("20221022080052_AddIndexActivityLogsDateCreated")]
partial class AddIndexActivityLogsDateCreated
{
diff --git a/Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.Designer.cs
new file mode 100644
index 0000000000..00ccd9f0ff
--- /dev/null
+++ b/Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.Designer.cs
@@ -0,0 +1,650 @@
+// <auto-generated />
+using System;
+using Jellyfin.Server.Implementations;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Jellyfin.Server.Implementations.Migrations
+{
+ [DbContext(typeof(JellyfinDbContext))]
+ [Migration("20230526173516_RemoveEasyPassword")]
+ partial class RemoveEasyPassword
+ {
+ /// <inheritdoc />
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "7.0.5");
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("DayOfWeek")
+ .HasColumnType("INTEGER");
+
+ b.Property<double>("EndHour")
+ .HasColumnType("REAL");
+
+ b.Property<double>("StartHour")
+ .HasColumnType("REAL");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AccessSchedules");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime>("DateCreated")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("ItemId")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property<int>("LogSeverity")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Overview")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property<uint>("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("ShortOverview")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Type")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DateCreated");
+
+ b.ToTable("ActivityLogs");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Client")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property<Guid>("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Key")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "ItemId", "Client", "Key")
+ .IsUnique();
+
+ b.ToTable("CustomItemDisplayPreferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("ChromecastVersion")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Client")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property<string>("DashboardTheme")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property<bool>("EnableNextVideoInfoOverlay")
+ .HasColumnType("INTEGER");
+
+ b.Property<int?>("IndexBy")
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid>("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property<int>("ScrollDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("ShowBackdrop")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("ShowSidebar")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("SkipBackwardLength")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("SkipForwardLength")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("TvHome")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "ItemId", "Client")
+ .IsUnique();
+
+ b.ToTable("DisplayPreferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("DisplayPreferencesId")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DisplayPreferencesId");
+
+ b.ToTable("HomeSection");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property<Guid?>("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("ImageInfos");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Client")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property<int?>("IndexBy")
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid>("ItemId")
+ .HasColumnType("TEXT");
+
+ b.Property<bool>("RememberIndexing")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("RememberSorting")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("SortBy")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property<int>("SortOrder")
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property<int>("ViewType")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ItemDisplayPreferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid?>("Permission_Permissions_Guid")
+ .HasColumnType("TEXT");
+
+ b.Property<uint>("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid?>("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property<bool>("Value")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "Kind")
+ .IsUnique()
+ .HasFilter("[UserId] IS NOT NULL");
+
+ b.ToTable("Permissions");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid?>("Preference_Preferences_Guid")
+ .HasColumnType("TEXT");
+
+ b.Property<uint>("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid?>("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Value")
+ .IsRequired()
+ .HasMaxLength(65535)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "Kind")
+ .IsUnique()
+ .HasFilter("[UserId] IS NOT NULL");
+
+ b.ToTable("Preferences");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("AccessToken")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("DateCreated")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("DateLastActivity")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccessToken")
+ .IsUnique();
+
+ b.ToTable("ApiKeys");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("AccessToken")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property<string>("AppName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property<string>("AppVersion")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("DateCreated")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("DateLastActivity")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("DateModified")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("DeviceId")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property<string>("DeviceName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property<bool>("IsActive")
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeviceId");
+
+ b.HasIndex("AccessToken", "DateLastActivity");
+
+ b.HasIndex("DeviceId", "DateLastActivity");
+
+ b.HasIndex("UserId", "DeviceId");
+
+ b.ToTable("Devices");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("CustomName")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("DeviceId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeviceId")
+ .IsUnique();
+
+ b.ToTable("DeviceOptions");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
+ {
+ b.Property<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property<string>("AudioLanguagePreference")
+ .HasMaxLength(255)
+ .HasColumnType("TEXT");
+
+ b.Property<string>("AuthenticationProviderId")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("TEXT");
+
+ b.Property<bool>("DisplayCollectionsView")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("DisplayMissingEpisodes")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("EnableAutoLogin")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("EnableLocalPassword")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("EnableNextEpisodeAutoPlay")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("EnableUserPreferenceAccess")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("HidePlayedInLatest")
+ .HasColumnType("INTEGER");
+
+ b.Property<long>("InternalId")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("InvalidLoginAttemptCount")
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime?>("LastActivityDate")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime?>("LastLoginDate")
+ .HasColumnType("TEXT");
+
+ b.Property<int?>("LoginAttemptsBeforeLockout")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("MaxActiveSessions")
+ .HasColumnType("INTEGER");
+
+ b.Property<int?>("MaxParentalAgeRating")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("MustUpdatePassword")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Password")
+ .HasMaxLength(65535)
+ .HasColumnType("TEXT");
+
+ b.Property<string>("PasswordResetProviderId")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("TEXT");
+
+ b.Property<bool>("PlayDefaultAudioTrack")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("RememberAudioSelections")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("RememberSubtitleSelections")
+ .HasColumnType("INTEGER");
+
+ b.Property<int?>("RemoteClientBitrateLimit")
+ .HasColumnType("INTEGER");
+
+ b.Property<uint>("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("SubtitleLanguagePreference")
+ .HasMaxLength(255)
+ .HasColumnType("TEXT");
+
+ b.Property<int>("SubtitleMode")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("SyncPlayAccess")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("TEXT")
+ .UseCollation("NOCASE");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Username")
+ .IsUnique();
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.User", null)
+ .WithMany("AccessSchedules")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.User", null)
+ .WithMany("DisplayPreferences")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null)
+ .WithMany("HomeSections")
+ .HasForeignKey("DisplayPreferencesId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.User", null)
+ .WithOne("ProfileImage")
+ .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.User", null)
+ .WithMany("ItemDisplayPreferences")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.User", null)
+ .WithMany("Permissions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.User", null)
+ .WithMany("Preferences")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
+ {
+ b.HasOne("Jellyfin.Data.Entities.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
+ {
+ b.Navigation("HomeSections");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
+ {
+ b.Navigation("AccessSchedules");
+
+ b.Navigation("DisplayPreferences");
+
+ b.Navigation("ItemDisplayPreferences");
+
+ b.Navigation("Permissions");
+
+ b.Navigation("Preferences");
+
+ b.Navigation("ProfileImage");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.cs b/Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.cs
new file mode 100644
index 0000000000..9496ff3c0d
--- /dev/null
+++ b/Jellyfin.Server.Implementations/Migrations/20230526173516_RemoveEasyPassword.cs
@@ -0,0 +1,164 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Jellyfin.Server.Implementations.Migrations
+{
+ /// <inheritdoc />
+ public partial class RemoveEasyPassword : Migration
+ {
+ /// <inheritdoc />
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "EasyPassword",
+ schema: "jellyfin",
+ table: "Users");
+
+ migrationBuilder.RenameTable(
+ name: "Users",
+ schema: "jellyfin",
+ newName: "Users");
+
+ migrationBuilder.RenameTable(
+ name: "Preferences",
+ schema: "jellyfin",
+ newName: "Preferences");
+
+ migrationBuilder.RenameTable(
+ name: "Permissions",
+ schema: "jellyfin",
+ newName: "Permissions");
+
+ migrationBuilder.RenameTable(
+ name: "ItemDisplayPreferences",
+ schema: "jellyfin",
+ newName: "ItemDisplayPreferences");
+
+ migrationBuilder.RenameTable(
+ name: "ImageInfos",
+ schema: "jellyfin",
+ newName: "ImageInfos");
+
+ migrationBuilder.RenameTable(
+ name: "HomeSection",
+ schema: "jellyfin",
+ newName: "HomeSection");
+
+ migrationBuilder.RenameTable(
+ name: "DisplayPreferences",
+ schema: "jellyfin",
+ newName: "DisplayPreferences");
+
+ migrationBuilder.RenameTable(
+ name: "Devices",
+ schema: "jellyfin",
+ newName: "Devices");
+
+ migrationBuilder.RenameTable(
+ name: "DeviceOptions",
+ schema: "jellyfin",
+ newName: "DeviceOptions");
+
+ migrationBuilder.RenameTable(
+ name: "CustomItemDisplayPreferences",
+ schema: "jellyfin",
+ newName: "CustomItemDisplayPreferences");
+
+ migrationBuilder.RenameTable(
+ name: "ApiKeys",
+ schema: "jellyfin",
+ newName: "ApiKeys");
+
+ migrationBuilder.RenameTable(
+ name: "ActivityLogs",
+ schema: "jellyfin",
+ newName: "ActivityLogs");
+
+ migrationBuilder.RenameTable(
+ name: "AccessSchedules",
+ schema: "jellyfin",
+ newName: "AccessSchedules");
+ }
+
+ /// <inheritdoc />
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.EnsureSchema(
+ name: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "Users",
+ newName: "Users",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "Preferences",
+ newName: "Preferences",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "Permissions",
+ newName: "Permissions",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "ItemDisplayPreferences",
+ newName: "ItemDisplayPreferences",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "ImageInfos",
+ newName: "ImageInfos",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "HomeSection",
+ newName: "HomeSection",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "DisplayPreferences",
+ newName: "DisplayPreferences",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "Devices",
+ newName: "Devices",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "DeviceOptions",
+ newName: "DeviceOptions",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "CustomItemDisplayPreferences",
+ newName: "CustomItemDisplayPreferences",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "ApiKeys",
+ newName: "ApiKeys",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "ActivityLogs",
+ newName: "ActivityLogs",
+ newSchema: "jellyfin");
+
+ migrationBuilder.RenameTable(
+ name: "AccessSchedules",
+ newName: "AccessSchedules",
+ newSchema: "jellyfin");
+
+ migrationBuilder.AddColumn<string>(
+ name: "EasyPassword",
+ schema: "jellyfin",
+ table: "Users",
+ type: "TEXT",
+ maxLength: 65535,
+ nullable: true);
+ }
+ }
+}
diff --git a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs
index 72a4a8c3b6..940cf7c5d5 100644
--- a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs
+++ b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs
@@ -4,17 +4,17 @@ using Microsoft.EntityFrameworkCore.Design;
namespace Jellyfin.Server.Implementations.Migrations
{
/// <summary>
- /// The design time factory for <see cref="JellyfinDb"/>.
+ /// The design time factory for <see cref="JellyfinDbContext"/>.
/// This is only used for the creation of migrations and not during runtime.
/// </summary>
- internal class DesignTimeJellyfinDbFactory : IDesignTimeDbContextFactory<JellyfinDb>
+ internal class DesignTimeJellyfinDbFactory : IDesignTimeDbContextFactory<JellyfinDbContext>
{
- public JellyfinDb CreateDbContext(string[] args)
+ public JellyfinDbContext CreateDbContext(string[] args)
{
- var optionsBuilder = new DbContextOptionsBuilder<JellyfinDb>();
+ var optionsBuilder = new DbContextOptionsBuilder<JellyfinDbContext>();
optionsBuilder.UseSqlite("Data Source=jellyfin.db");
- return new JellyfinDb(optionsBuilder.Options);
+ return new JellyfinDbContext(optionsBuilder.Options);
}
}
}
diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs
index 2dd7b094aa..d23508096f 100644
--- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs
+++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs
@@ -9,15 +9,13 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
- [DbContext(typeof(JellyfinDb))]
+ [DbContext(typeof(JellyfinDbContext))]
partial class JellyfinDbModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
- modelBuilder
- .HasDefaultSchema("jellyfin")
- .HasAnnotation("ProductVersion", "6.0.9");
+ modelBuilder.HasAnnotation("ProductVersion", "7.0.5");
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
{
@@ -41,7 +39,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId");
- b.ToTable("AccessSchedules", "jellyfin");
+ b.ToTable("AccessSchedules");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
@@ -89,7 +87,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("DateCreated");
- b.ToTable("ActivityLogs", "jellyfin");
+ b.ToTable("ActivityLogs");
});
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
@@ -121,7 +119,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId", "ItemId", "Client", "Key")
.IsUnique();
- b.ToTable("CustomItemDisplayPreferences", "jellyfin");
+ b.ToTable("CustomItemDisplayPreferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
@@ -178,7 +176,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId", "ItemId", "Client")
.IsUnique();
- b.ToTable("DisplayPreferences", "jellyfin");
+ b.ToTable("DisplayPreferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
@@ -200,7 +198,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("DisplayPreferencesId");
- b.ToTable("HomeSection", "jellyfin");
+ b.ToTable("HomeSection");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
@@ -225,7 +223,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId")
.IsUnique();
- b.ToTable("ImageInfos", "jellyfin");
+ b.ToTable("ImageInfos");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
@@ -269,7 +267,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId");
- b.ToTable("ItemDisplayPreferences", "jellyfin");
+ b.ToTable("ItemDisplayPreferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
@@ -300,7 +298,7 @@ namespace Jellyfin.Server.Implementations.Migrations
.IsUnique()
.HasFilter("[UserId] IS NOT NULL");
- b.ToTable("Permissions", "jellyfin");
+ b.ToTable("Permissions");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
@@ -333,7 +331,7 @@ namespace Jellyfin.Server.Implementations.Migrations
.IsUnique()
.HasFilter("[UserId] IS NOT NULL");
- b.ToTable("Preferences", "jellyfin");
+ b.ToTable("Preferences");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
@@ -362,7 +360,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("AccessToken")
.IsUnique();
- b.ToTable("ApiKeys", "jellyfin");
+ b.ToTable("ApiKeys");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
@@ -420,7 +418,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId", "DeviceId");
- b.ToTable("Devices", "jellyfin");
+ b.ToTable("Devices");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
@@ -441,7 +439,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("DeviceId")
.IsUnique();
- b.ToTable("DeviceOptions", "jellyfin");
+ b.ToTable("DeviceOptions");
});
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
@@ -465,10 +463,6 @@ namespace Jellyfin.Server.Implementations.Migrations
b.Property<bool>("DisplayMissingEpisodes")
.HasColumnType("INTEGER");
- b.Property<string>("EasyPassword")
- .HasMaxLength(65535)
- .HasColumnType("TEXT");
-
b.Property<bool>("EnableAutoLogin")
.HasColumnType("INTEGER");
@@ -554,7 +548,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("Username")
.IsUnique();
- b.ToTable("Users", "jellyfin");
+ b.ToTable("Users");
});
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
diff --git a/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs b/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs
index 33c08c8c29..b2dfe60a14 100644
--- a/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs
+++ b/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs
@@ -10,13 +10,13 @@ namespace Jellyfin.Server.Implementations.Security
/// <inheritdoc />
public class AuthenticationManager : IAuthenticationManager
{
- private readonly IDbContextFactory<JellyfinDb> _dbProvider;
+ private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationManager"/> class.
/// </summary>
/// <param name="dbProvider">The database provider.</param>
- public AuthenticationManager(IDbContextFactory<JellyfinDb> dbProvider)
+ public AuthenticationManager(IDbContextFactory<JellyfinDbContext> dbProvider)
{
_dbProvider = dbProvider;
}
@@ -40,7 +40,6 @@ namespace Jellyfin.Server.Implementations.Security
await using (dbContext.ConfigureAwait(false))
{
return await dbContext.ApiKeys
- .AsAsyncEnumerable()
.Select(key => new AuthenticationInfo
{
AppName = key.Name,
@@ -60,12 +59,11 @@ namespace Jellyfin.Server.Implementations.Security
await using (dbContext.ConfigureAwait(false))
{
var key = await dbContext.ApiKeys
- .AsQueryable()
.Where(apiKey => apiKey.AccessToken == accessToken)
.FirstOrDefaultAsync()
.ConfigureAwait(false);
- if (key == null)
+ if (key is null)
{
return;
}
diff --git a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
index 4d1a1b3cf8..700e639700 100644
--- a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
+++ b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
-using EFCoreSecondLevelCacheInterceptor;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
@@ -16,12 +15,12 @@ namespace Jellyfin.Server.Implementations.Security
{
public class AuthorizationContext : IAuthorizationContext
{
- private readonly IDbContextFactory<JellyfinDb> _jellyfinDbProvider;
+ private readonly IDbContextFactory<JellyfinDbContext> _jellyfinDbProvider;
private readonly IUserManager _userManager;
private readonly IServerApplicationHost _serverApplicationHost;
public AuthorizationContext(
- IDbContextFactory<JellyfinDb> jellyfinDb,
+ IDbContextFactory<JellyfinDbContext> jellyfinDb,
IUserManager userManager,
IServerApplicationHost serverApplicationHost)
{
@@ -32,7 +31,7 @@ namespace Jellyfin.Server.Implementations.Security
public Task<AuthorizationInfo> GetAuthorizationInfo(HttpContext requestContext)
{
- if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached) && cached != null)
+ if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached) && cached is not null)
{
return Task.FromResult((AuthorizationInfo)cached); // Cache should never contain null
}
@@ -72,7 +71,7 @@ namespace Jellyfin.Server.Implementations.Security
string? version = null;
string? token = null;
- if (auth != null)
+ if (auth is not null)
{
auth.TryGetValue("DeviceId", out deviceId);
auth.TryGetValue("Device", out deviceName);
@@ -127,7 +126,7 @@ namespace Jellyfin.Server.Implementations.Security
{
var device = await dbContext.Devices.FirstOrDefaultAsync(d => d.AccessToken == token).ConfigureAwait(false);
- if (device != null)
+ if (device is not null)
{
authInfo.IsAuthenticated = true;
var updateToken = false;
@@ -189,7 +188,7 @@ namespace Jellyfin.Server.Implementations.Security
else
{
var key = await dbContext.ApiKeys.FirstOrDefaultAsync(apiKey => apiKey.AccessToken == token).ConfigureAwait(false);
- if (key != null)
+ if (key is not null)
{
authInfo.IsAuthenticated = true;
authInfo.Client = key.Name;
diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs
index 7480a05c25..72f3d6e8ec 100644
--- a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs
@@ -41,7 +41,7 @@ namespace Jellyfin.Server.Implementations.Users
// This is the version that we need to use for local users. Because reasons.
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
{
- if (resolvedUser == null)
+ if (resolvedUser is null)
{
throw new AuthenticationException("Specified user does not exist.");
}
@@ -58,7 +58,7 @@ namespace Jellyfin.Server.Implementations.Users
}
// Handle the case when the stored password is null, but the user tried to login with a password
- if (resolvedUser.Password == null)
+ if (resolvedUser.Password is null)
{
throw new AuthenticationException("Invalid username or password");
}
diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
index 4fda8f5a41..cefbd0624d 100644
--- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
@@ -54,7 +54,8 @@ namespace Jellyfin.Server.Implementations.Users
foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
{
SerializablePasswordReset spr;
- await using (var str = AsyncFile.OpenRead(resetFile))
+ var str = AsyncFile.OpenRead(resetFile);
+ await using (str.ConfigureAwait(false))
{
spr = await JsonSerializer.DeserializeAsync<SerializablePasswordReset>(str).ConfigureAwait(false)
?? throw new ResourceNotFoundException($"Provided path ({resetFile}) is not valid.");
@@ -107,13 +108,12 @@ namespace Jellyfin.Server.Implementations.Users
UserName = user.Username
};
- await using (FileStream fileStream = AsyncFile.OpenWrite(filePath))
+ FileStream fileStream = AsyncFile.OpenWrite(filePath);
+ await using (fileStream.ConfigureAwait(false))
{
await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false);
}
- user.EasyPassword = pin;
-
return new ForgotPasswordResult
{
Action = ForgotPasswordAction.PinCode,
diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
index 87babc05c8..bfae81e4ca 100644
--- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
+++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
@@ -15,13 +15,13 @@ namespace Jellyfin.Server.Implementations.Users
/// </summary>
public class DisplayPreferencesManager : IDisplayPreferencesManager
{
- private readonly JellyfinDb _dbContext;
+ private readonly JellyfinDbContext _dbContext;
/// <summary>
/// Initializes a new instance of the <see cref="DisplayPreferencesManager"/> class.
/// </summary>
/// <param name="dbContextFactory">The database context factory.</param>
- public DisplayPreferencesManager(IDbContextFactory<JellyfinDb> dbContextFactory)
+ public DisplayPreferencesManager(IDbContextFactory<JellyfinDbContext> dbContextFactory)
{
_dbContext = dbContextFactory.CreateDbContext();
}
@@ -34,7 +34,7 @@ namespace Jellyfin.Server.Implementations.Users
.FirstOrDefault(pref =>
pref.UserId.Equals(userId) && string.Equals(pref.Client, client) && pref.ItemId.Equals(itemId));
- if (prefs == null)
+ if (prefs is null)
{
prefs = new DisplayPreferences(userId, itemId, client);
_dbContext.DisplayPreferences.Add(prefs);
@@ -49,7 +49,7 @@ namespace Jellyfin.Server.Implementations.Users
var prefs = _dbContext.ItemDisplayPreferences
.FirstOrDefault(pref => pref.UserId.Equals(userId) && pref.ItemId.Equals(itemId) && string.Equals(pref.Client, client));
- if (prefs == null)
+ if (prefs is null)
{
prefs = new ItemDisplayPreferences(userId, Guid.Empty, client);
_dbContext.ItemDisplayPreferences.Add(prefs);
@@ -62,7 +62,6 @@ namespace Jellyfin.Server.Implementations.Users
public IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client)
{
return _dbContext.ItemDisplayPreferences
- .AsQueryable()
.Where(prefs => prefs.UserId.Equals(userId) && !prefs.ItemId.Equals(default) && string.Equals(prefs.Client, client))
.ToList();
}
@@ -71,7 +70,6 @@ namespace Jellyfin.Server.Implementations.Users
public Dictionary<string, string?> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client)
{
return _dbContext.CustomItemDisplayPreferences
- .AsQueryable()
.Where(prefs => prefs.UserId.Equals(userId)
&& prefs.ItemId.Equals(itemId)
&& string.Equals(prefs.Client, client))
@@ -82,7 +80,6 @@ namespace Jellyfin.Server.Implementations.Users
public void SetCustomItemDisplayPreferences(Guid userId, Guid itemId, string client, Dictionary<string, string?> customPreferences)
{
var existingPrefs = _dbContext.CustomItemDisplayPreferences
- .AsQueryable()
.Where(prefs => prefs.UserId.Equals(userId)
&& prefs.ItemId.Equals(itemId)
&& string.Equals(prefs.Client, client));
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 25560707ac..1d03baa4c6 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -33,7 +33,7 @@ namespace Jellyfin.Server.Implementations.Users
/// </summary>
public class UserManager : IUserManager
{
- private readonly IDbContextFactory<JellyfinDb> _dbProvider;
+ private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
private readonly IEventManager _eventManager;
private readonly ICryptoProvider _cryptoProvider;
private readonly INetworkManager _networkManager;
@@ -59,7 +59,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <param name="imageProcessor">The image processor.</param>
/// <param name="logger">The logger.</param>
public UserManager(
- IDbContextFactory<JellyfinDb> dbProvider,
+ IDbContextFactory<JellyfinDbContext> dbProvider,
IEventManager eventManager,
ICryptoProvider cryptoProvider,
INetworkManager networkManager,
@@ -85,6 +85,7 @@ namespace Jellyfin.Server.Implementations.Users
_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)
@@ -143,7 +144,6 @@ namespace Jellyfin.Server.Implementations.Users
await using (dbContext.ConfigureAwait(false))
{
if (await dbContext.Users
- .AsQueryable()
.AnyAsync(u => u.Username == newName && !u.Id.Equals(user.Id))
.ConfigureAwait(false))
{
@@ -157,7 +157,9 @@ namespace Jellyfin.Server.Implementations.Users
await UpdateUserInternalAsync(dbContext, user).ConfigureAwait(false);
}
- OnUserUpdated?.Invoke(this, new GenericEventArgs<User>(user));
+ var eventArgs = new UserUpdatedEventArgs(user);
+ await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
+ OnUserUpdated?.Invoke(this, eventArgs);
}
/// <inheritdoc/>
@@ -170,7 +172,7 @@ namespace Jellyfin.Server.Implementations.Users
}
}
- internal async Task<User> CreateUserInternalAsync(string name, JellyfinDb dbContext)
+ internal async Task<User> CreateUserInternalAsync(string name, JellyfinDbContext dbContext)
{
// TODO: Remove after user item data is migrated.
var max = await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false)
@@ -267,36 +269,15 @@ namespace Jellyfin.Server.Implementations.Users
}
/// <inheritdoc/>
- public Task ResetEasyPassword(User user)
- {
- return ChangeEasyPassword(user, string.Empty, null);
- }
-
- /// <inheritdoc/>
public async Task ChangePassword(User user, string newPassword)
{
ArgumentNullException.ThrowIfNull(user);
-
- await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
- await UpdateUserAsync(user).ConfigureAwait(false);
-
- await _eventManager.PublishAsync(new UserPasswordChangedEventArgs(user)).ConfigureAwait(false);
- }
-
- /// <inheritdoc/>
- public async Task ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1)
- {
- if (newPassword != null)
- {
- newPasswordSha1 = _cryptoProvider.CreatePasswordHash(newPassword).ToString();
- }
-
- if (string.IsNullOrWhiteSpace(newPasswordSha1))
+ if (user.HasPermission(PermissionKind.IsAdministrator) && string.IsNullOrWhiteSpace(newPassword))
{
- throw new ArgumentNullException(nameof(newPasswordSha1));
+ throw new ArgumentException("Admin user passwords must not be empty", nameof(newPassword));
}
- user.EasyPassword = newPasswordSha1;
+ await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
await UpdateUserAsync(user).ConfigureAwait(false);
await _eventManager.PublishAsync(new UserPasswordChangedEventArgs(user)).ConfigureAwait(false);
@@ -313,11 +294,10 @@ namespace Jellyfin.Server.Implementations.Users
ServerId = _appHost.SystemId,
HasPassword = hasPassword,
HasConfiguredPassword = hasPassword,
- HasConfiguredEasyPassword = !string.IsNullOrEmpty(user.EasyPassword),
EnableAutoLogin = user.EnableAutoLogin,
LastLoginDate = user.LastLoginDate,
LastActivityDate = user.LastActivityDate,
- PrimaryImageTag = user.ProfileImage != null ? _imageProcessor.GetImageCacheTag(user) : null,
+ PrimaryImageTag = user.ProfileImage is not null ? _imageProcessor.GetImageCacheTag(user) : null,
Configuration = new UserConfiguration
{
SubtitleMode = user.SubtitleMode,
@@ -367,8 +347,10 @@ namespace Jellyfin.Server.Implementations.Users
EnablePlaybackRemuxing = user.HasPermission(PermissionKind.EnablePlaybackRemuxing),
ForceRemoteSourceTranscoding = user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding),
EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing),
+ EnableCollectionManagement = user.HasPermission(PermissionKind.EnableCollectionManagement),
AccessSchedules = user.AccessSchedules.ToArray(),
BlockedTags = user.GetPreference(PreferenceKind.BlockedTags),
+ AllowedTags = user.GetPreference(PreferenceKind.AllowedTags),
EnabledChannels = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels),
EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices),
EnabledFolders = user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders),
@@ -401,12 +383,12 @@ namespace Jellyfin.Server.Implementations.Users
var authenticationProvider = authResult.AuthenticationProvider;
var success = authResult.Success;
- if (user == null)
+ if (user is null)
{
string updatedUsername = authResult.Username;
if (success
- && authenticationProvider != null
+ && authenticationProvider is not null
&& authenticationProvider is not DefaultAuthenticationProvider)
{
// Trust the username returned by the authentication provider
@@ -416,25 +398,25 @@ 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 && user != null)
+ if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy && user is not null)
{
await UpdatePolicyAsync(user.Id, hasNewUserPolicy.GetNewUserPolicy()).ConfigureAwait(false);
}
}
}
- if (success && user != null && authenticationProvider != null)
+ if (success && user is not null && authenticationProvider is not null)
{
var providerId = authenticationProvider.GetType().FullName;
- if (providerId != null && !string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase))
+ if (providerId is not null && !string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase))
{
user.AuthenticationProviderId = providerId;
await UpdateUserAsync(user).ConfigureAwait(false);
}
}
- if (user == null)
+ if (user is null)
{
_logger.LogInformation(
"Authentication request for {UserName} has been denied (IP: {IP}).",
@@ -501,7 +483,7 @@ namespace Jellyfin.Server.Implementations.Users
{
var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername);
- if (user != null && isInNetwork)
+ if (user is not null && isInNetwork)
{
var passwordResetProvider = GetPasswordResetProvider(user);
var result = await passwordResetProvider
@@ -682,6 +664,7 @@ namespace Jellyfin.Server.Implementations.Users
user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers);
user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
+ user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement);
user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
@@ -694,6 +677,7 @@ namespace Jellyfin.Server.Implementations.Users
// TODO: fix this at some point
user.SetPreference(PreferenceKind.BlockUnratedItems, policy.BlockUnratedItems ?? Array.Empty<UnratedItem>());
user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
+ user.SetPreference(PreferenceKind.AllowedTags, policy.AllowedTags);
user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
@@ -708,7 +692,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public async Task ClearProfileImageAsync(User user)
{
- if (user.ProfileImage == null)
+ if (user.ProfileImage is null)
{
return;
}
@@ -734,7 +718,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)", nameof(name));
}
- private static bool IsValidUsername(string name)
+ private static bool IsValidUsername(ReadOnlySpan<char> name)
{
// 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
@@ -826,16 +810,6 @@ namespace Jellyfin.Server.Implementations.Users
}
}
- if (!success
- && _networkManager.IsInLocalNetwork(remoteEndPoint)
- && user?.EnableLocalPassword == true
- && !string.IsNullOrEmpty(user.EasyPassword))
- {
- // Check easy password
- var passwordHash = PasswordHash.Parse(user.EasyPassword);
- success = _cryptoProvider.Verify(passwordHash, password);
- }
-
return (authenticationProvider, username, success);
}
@@ -884,7 +858,7 @@ namespace Jellyfin.Server.Implementations.Users
await UpdateUserAsync(user).ConfigureAwait(false);
}
- private async Task UpdateUserInternalAsync(JellyfinDb dbContext, User user)
+ private async Task UpdateUserInternalAsync(JellyfinDbContext dbContext, User user)
{
dbContext.Users.Update(user);
_users[user.Id] = user;