aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Naming/Common/NamingOptions.cs1
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs13
-rw-r--r--Emby.Server.Implementations/Localization/Core/et.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/lt-LT.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/nl.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/ro.json47
-rw-r--r--Emby.Server.Implementations/Localization/Core/sk.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/vi.json3
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs29
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs36
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs2
-rw-r--r--Jellyfin.Server.Implementations/Activity/ActivityManager.cs73
-rw-r--r--Jellyfin.Server.Implementations/Devices/DeviceManager.cs168
-rw-r--r--Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs43
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj1
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDbProvider.cs51
-rw-r--r--Jellyfin.Server.Implementations/Security/AuthenticationManager.cs70
-rw-r--r--Jellyfin.Server.Implementations/Security/AuthorizationContext.cs142
-rw-r--r--Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs6
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs293
-rw-r--r--Jellyfin.Server/CoreAppHost.cs9
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs6
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs7
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs7
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs7
-rw-r--r--Jellyfin.Server/Program.cs10
-rw-r--r--Jellyfin.Server/Startup.cs3
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs9
-rw-r--r--MediaBrowser.Model/Dto/MediaSourceInfo.cs10
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs26
30 files changed, 568 insertions, 519 deletions
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 25131e9c5..0119fa38c 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -175,6 +175,7 @@ namespace Emby.Naming.Common
AlbumStackingPrefixes = new[]
{
"cd",
+ "digital media",
"disc",
"disk",
"vol",
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 8db55a6ae..8e4c13def 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -48,6 +48,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.MediaEncoding.Hls.Playlist;
using Jellyfin.Networking.Configuration;
using Jellyfin.Networking.Manager;
+using Jellyfin.Server.Implementations;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
@@ -101,6 +102,7 @@ using MediaBrowser.Providers.Subtitles;
using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -652,6 +654,17 @@ namespace Emby.Server.Implementations
/// <returns>A task representing the service initialization operation.</returns>
public async Task InitializeServices()
{
+ var jellyfinDb = await Resolve<IDbContextFactory<JellyfinDb>>().CreateDbContextAsync().ConfigureAwait(false);
+ await using (jellyfinDb.ConfigureAwait(false))
+ {
+ if ((await jellyfinDb.Database.GetPendingMigrationsAsync().ConfigureAwait(false)).Any())
+ {
+ Logger.LogInformation("There are pending EFCore migrations in the database. Applying... (This may take a while, do not stop Jellyfin)");
+ await jellyfinDb.Database.MigrateAsync().ConfigureAwait(false);
+ Logger.LogInformation("EFCore migrations applied successfully");
+ }
+ }
+
var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
await localizationManager.LoadAll().ConfigureAwait(false);
diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json
index da44e53d0..081462407 100644
--- a/Emby.Server.Implementations/Localization/Core/et.json
+++ b/Emby.Server.Implementations/Localization/Core/et.json
@@ -120,5 +120,8 @@
"UserPolicyUpdatedWithName": "Kasutaja {0} õigusi värskendati",
"UserStoppedPlayingItemWithValues": "{0} lõpetas {1} taasesituse seadmes {2}",
"UserOnlineFromDevice": "{0} on ühendatud seadmest {1}",
- "External": "Väline"
+ "External": "Väline",
+ "HearingImpaired": "Kuulmispuudega",
+ "TaskKeyframeExtractorDescription": "Eraldab videofailidest võtmekaadreid, et luua täpsemaid HLS-i esitusloendeid. See ülesanne võib kesta pikka aega.",
+ "TaskKeyframeExtractor": "Võtmekaadri ekstraktor"
}
diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json
index 232b3ec93..e1c937b6c 100644
--- a/Emby.Server.Implementations/Localization/Core/lt-LT.json
+++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json
@@ -123,5 +123,6 @@
"TaskKeyframeExtractorDescription": "Iš vaizdo įrašo paruošia reikšminius kadrus, kad būtų sukuriamas tikslenis HLS grojaraštis. Šios užduoties vykdymas gali ilgai užtrukti.",
"TaskKeyframeExtractor": "Pagrindinių kadrų ištraukėjas",
"TaskOptimizeDatabaseDescription": "Suspaudžia duomenų bazę ir atlaisvina vietą. Paleidžiant šią užduotį, po bibliotekos skenavimo arba kitų veiksmų kurie galimai modifikuoja duomenų bazė, gali pagerinti greitaveiką.",
- "External": "Išorinis"
+ "External": "Išorinis",
+ "HearingImpaired": "Su klausos sutrikimais"
}
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index d7b2bc00c..c05114f01 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -5,7 +5,7 @@
"Artists": "Artiesten",
"AuthenticationSucceededWithUserName": "{0} is succesvol geauthenticeerd",
"Books": "Boeken",
- "CameraImageUploadedFrom": "Nieuwe camera afbeelding toegevoegd vanaf {0}",
+ "CameraImageUploadedFrom": "Nieuwe camera-afbeelding toegevoegd vanaf {0}",
"Channels": "Kanalen",
"ChapterNameValue": "Hoofdstuk {0}",
"Collections": "Verzamelingen",
@@ -15,7 +15,7 @@
"Favorites": "Favorieten",
"Folders": "Mappen",
"Genres": "Genres",
- "HeaderAlbumArtists": "Album Artiesten",
+ "HeaderAlbumArtists": "Albumartiesten",
"HeaderContinueWatching": "Kijken hervatten",
"HeaderFavoriteAlbums": "Favoriete albums",
"HeaderFavoriteArtists": "Favoriete artiesten",
diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json
index 53456269a..2c10bb477 100644
--- a/Emby.Server.Implementations/Localization/Core/ro.json
+++ b/Emby.Server.Implementations/Localization/Core/ro.json
@@ -11,7 +11,7 @@
"UserOfflineFromDevice": "{0} s-a deconectat de la {1}",
"UserLockedOutWithName": "Utilizatorul {0} a fost blocat",
"UserDownloadingItemWithValues": "{0} descarcă {1}",
- "UserDeletedWithName": "Utilizatorul {0} a fost eliminat",
+ "UserDeletedWithName": "Utilizatorul {0} a fost șters",
"UserCreatedWithName": "Utilizatorul {0} a fost creat",
"User": "Utilizator",
"TvShows": "Seriale TV",
@@ -20,33 +20,33 @@
"SubtitleDownloadFailureFromForItem": "Subtitrările nu au putut fi descărcate de la {0} pentru {1}",
"StartupEmbyServerIsLoading": "Se încarcă serverul Jellyfin. Încercați din nou în scurt timp.",
"Songs": "Melodii",
- "Shows": "Spectacole",
- "ServerNameNeedsToBeRestarted": "{0} trebuie repornit",
+ "Shows": "Seriale",
+ "ServerNameNeedsToBeRestarted": "{0} trebuie să fie repornit",
"ScheduledTaskStartedWithName": "{0} pornit/ă",
"ScheduledTaskFailedWithName": "{0} eșuat/ă",
"ProviderValue": "Furnizor: {0}",
"PluginUpdatedWithName": "{0} a fost actualizat/ă",
"PluginUninstalledWithName": "{0} a fost dezinstalat",
"PluginInstalledWithName": "{0} a fost instalat",
- "Plugin": "Plugin",
- "Playlists": "Liste redare",
+ "Plugin": "Extensie",
+ "Playlists": "Liste de redare",
"Photos": "Fotografii",
"NotificationOptionVideoPlaybackStopped": "Redarea video oprită",
"NotificationOptionVideoPlayback": "Redare video începută",
"NotificationOptionUserLockedOut": "Utilizatorul a fost blocat",
- "NotificationOptionTaskFailed": "Activitate programata eșuată",
+ "NotificationOptionTaskFailed": "Activitate programată eșuată",
"NotificationOptionServerRestartRequired": "Este necesară repornirea serverului",
- "NotificationOptionPluginUpdateInstalled": "Actualizare plugin instalată",
- "NotificationOptionPluginUninstalled": "Plugin dezinstalat",
- "NotificationOptionPluginInstalled": "Plugin instalat",
- "NotificationOptionPluginError": "Plugin-ul a eșuat",
- "NotificationOptionNewLibraryContent": "Adăugat conținut nou",
- "NotificationOptionInstallationFailed": "Eșec la instalare",
- "NotificationOptionCameraImageUploaded": "Încarcată imagine cameră",
+ "NotificationOptionPluginUpdateInstalled": "Actualizarea extensiei este instalată",
+ "NotificationOptionPluginUninstalled": "Extensie dezinstalată",
+ "NotificationOptionPluginInstalled": "Extensie instalată",
+ "NotificationOptionPluginError": "Eroare de extensie",
+ "NotificationOptionNewLibraryContent": "A fost adăugat conținut nou",
+ "NotificationOptionInstallationFailed": "Instalare eșuată",
+ "NotificationOptionCameraImageUploaded": "Imagine încarcată",
"NotificationOptionAudioPlaybackStopped": "Redare audio oprită",
"NotificationOptionAudioPlayback": "A început redarea audio",
"NotificationOptionApplicationUpdateInstalled": "Actualizarea aplicației a fost instalată",
- "NotificationOptionApplicationUpdateAvailable": "Disponibilă o actualizare a aplicației",
+ "NotificationOptionApplicationUpdateAvailable": "Este disponibilă o actualizare a aplicației",
"NewVersionIsAvailable": "O nouă versiune a Jellyfin Server este disponibilă pentru descărcare.",
"NameSeasonUnknown": "Sezon Necunoscut",
"NameSeasonNumber": "Sezonul {0}",
@@ -54,8 +54,8 @@
"MusicVideos": "Videoclipuri muzicale",
"Music": "Muzică",
"Movies": "Filme",
- "MixedContent": "Conținut mixt",
- "MessageServerConfigurationUpdated": "Configurația serverului a fost actualizată",
+ "MixedContent": "Conținut amestecat",
+ "MessageServerConfigurationUpdated": "Configurarea serverului a fost actualizată",
"MessageNamedServerConfigurationUpdatedWithValue": "Secțiunea de configurare a serverului {0} a fost acualizata",
"MessageApplicationUpdatedTo": "Jellyfin Server a fost actualizat la {0}",
"MessageApplicationUpdated": "Jellyfin Server a fost actualizat",
@@ -69,7 +69,7 @@
"HeaderRecordingGroups": "Grupuri de înregistrare",
"HeaderLiveTV": "TV în Direct",
"HeaderFavoriteSongs": "Melodii Favorite",
- "HeaderFavoriteShows": "Spectacole Favorite",
+ "HeaderFavoriteShows": "Seriale TV Favorite",
"HeaderFavoriteEpisodes": "Episoade Favorite",
"HeaderFavoriteArtists": "Artiști Favoriți",
"HeaderFavoriteAlbums": "Albume Favorite",
@@ -97,10 +97,10 @@
"TaskRefreshChannels": "Actualizează canale",
"TaskCleanTranscodeDescription": "Șterge fișierele de transcodare mai vechi de o zi.",
"TaskCleanTranscode": "Curățați directorul de transcodare",
- "TaskUpdatePluginsDescription": "Descarcă și instalează actualizări pentru pluginuri care sunt configurate să se actualizeze automat.",
- "TaskUpdatePlugins": "Actualizați plugin-uri",
+ "TaskUpdatePluginsDescription": "Descarcă și instalează actualizări pentru extensiile care sunt configurate să se actualizeze automat.",
+ "TaskUpdatePlugins": "Actualizați Extensile",
"TaskRefreshPeopleDescription": "Actualizează metadatele pentru actori și regizori din biblioteca media.",
- "TaskRefreshPeople": "Actualizează oamenii",
+ "TaskRefreshPeople": "Actualizează Persoanele",
"TaskCleanLogsDescription": "Șterge fișierele jurnal care au mai mult de {0} zile.",
"TaskCleanLogs": "Curățare director jurnal",
"TaskRefreshLibraryDescription": "Scanează biblioteca media pentru fișiere noi și reîmprospătează metadatele.",
@@ -114,13 +114,14 @@
"TasksLibraryCategory": "Librărie",
"TasksMaintenanceCategory": "Mentenanță",
"TaskCleanActivityLogDescription": "Șterge intrările din jurnalul de activitate mai vechi de data configurată.",
- "TaskCleanActivityLog": "Curăță Jurnalul de Activitate",
+ "TaskCleanActivityLog": "Curăță Jurnalul de Activități",
"Undefined": "Nedefinit",
"Forced": "Forțat",
"Default": "Implicit",
- "TaskOptimizeDatabaseDescription": "Compactează baza de date și trunchiază spațiul liber. Rularea acestei sarcini după scanarea bibliotecii sau după efectuarea altor modificări care implică modificări ale bazei de date poate îmbunătăți performanța.",
+ "TaskOptimizeDatabaseDescription": "Comprimă baza de date și trunchiază spațiul liber. Rularea acestei sarcini după scanarea bibliotecii sau după efectuarea altor modificări care implică modificări ale bazei de date poate îmbunătăți performanța.",
"TaskOptimizeDatabase": "Optimizează baza de date",
"TaskKeyframeExtractorDescription": "Extrage cadrele cheie din fișierele video pentru a crea liste de redare HLS mai precise. Această sarcină poate rula o perioadă lungă de timp.",
"External": "Extern",
- "TaskKeyframeExtractor": "Extractor de cadre cheie"
+ "TaskKeyframeExtractor": "Extractor de cadre cheie",
+ "HearingImpaired": "Ascultare Impară"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json
index 7502969a6..858cc40dd 100644
--- a/Emby.Server.Implementations/Localization/Core/sk.json
+++ b/Emby.Server.Implementations/Localization/Core/sk.json
@@ -123,5 +123,6 @@
"TaskOptimizeDatabase": "Optimalizovať databázu",
"TaskKeyframeExtractorDescription": "Extrahuje kľúčové snímky z video súborov na vytvorenie presnejších HLS playlistov. Táto úloha môže trvať dlhšiu dobu.",
"TaskKeyframeExtractor": "Extraktor kľúčových snímkov",
- "External": "Externé"
+ "External": "Externé",
+ "HearingImpaired": "Sluchovo Postihnutý"
}
diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json
index b9e2f1e6c..44ce4ac5b 100644
--- a/Emby.Server.Implementations/Localization/Core/vi.json
+++ b/Emby.Server.Implementations/Localization/Core/vi.json
@@ -122,5 +122,6 @@
"TaskOptimizeDatabase": "Tối ưu hóa cơ sở dữ liệu",
"TaskKeyframeExtractor": "Trích Xuất Khung Hình",
"TaskKeyframeExtractorDescription": "Trích xuất khung hình chính từ các tệp video để tạo danh sách phát HLS chính xác hơn. Tác vụ này có thể chạy trong một thời gian dài.",
- "External": "Bên ngoài"
+ "External": "Bên ngoài",
+ "HearingImpaired": "Khiếm Thính"
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs
index 98e45fa46..1efacd856 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs
@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
private readonly ILogger<OptimizeDatabaseTask> _logger;
private readonly ILocalizationManager _localization;
- private readonly JellyfinDbProvider _provider;
+ private readonly IDbContextFactory<JellyfinDb> _provider;
/// <summary>
/// Initializes a new instance of the <see cref="OptimizeDatabaseTask" /> class.
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
public OptimizeDatabaseTask(
ILogger<OptimizeDatabaseTask> logger,
ILocalizationManager localization,
- JellyfinDbProvider provider)
+ IDbContextFactory<JellyfinDb> provider)
{
_logger = logger;
_localization = localization;
@@ -70,30 +70,31 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
}
/// <inheritdoc />
- public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+ public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
_logger.LogInformation("Optimizing and vacuuming jellyfin.db...");
try
{
- using var context = _provider.CreateContext();
- if (context.Database.IsSqlite())
+ var context = await _provider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
+ await using (context.ConfigureAwait(false))
{
- context.Database.ExecuteSqlRaw("PRAGMA optimize");
- context.Database.ExecuteSqlRaw("VACUUM");
- _logger.LogInformation("jellyfin.db optimized successfully!");
- }
- else
- {
- _logger.LogInformation("This database doesn't support optimization");
+ if (context.Database.IsSqlite())
+ {
+ await context.Database.ExecuteSqlRawAsync("PRAGMA optimize", cancellationToken).ConfigureAwait(false);
+ await context.Database.ExecuteSqlRawAsync("VACUUM", cancellationToken).ConfigureAwait(false);
+ _logger.LogInformation("jellyfin.db optimized successfully!");
+ }
+ else
+ {
+ _logger.LogInformation("This database doesn't support optimization");
+ }
}
}
catch (Exception e)
{
_logger.LogError(e, "Error while optimizing jellyfin.db");
}
-
- return Task.CompletedTask;
}
}
}
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 80ae5abcb..33b67b389 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -282,39 +282,13 @@ namespace Jellyfin.Api.Controllers
includeItemTypes = new[] { BaseItemKind.Playlist };
}
- var enabledChannels = isApiKey
- ? Array.Empty<Guid>()
- : user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
-
- // api keys are always enabled for all folders
- bool isInEnabledFolder = isApiKey
- || Array.IndexOf(user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1
- // Assume all folders inside an EnabledChannel are enabled
- || Array.IndexOf(enabledChannels, item.Id) != -1
- // Assume all items inside an EnabledChannel are enabled
- || Array.IndexOf(enabledChannels, item.ChannelId) != -1;
-
- if (!isInEnabledFolder)
- {
- var collectionFolders = _libraryManager.GetCollectionFolders(item);
- foreach (var collectionFolder in collectionFolders)
- {
- // api keys never enter this block, so user is never null
- if (user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id))
- {
- isInEnabledFolder = true;
- }
- }
- }
-
- // api keys are always enabled for all folders, so user is never null
if (item is not UserRootFolder
- && !isInEnabledFolder
- && !user!.HasPermission(PermissionKind.EnableAllFolders)
- && !user.HasPermission(PermissionKind.EnableAllChannels)
- && !string.Equals(collectionType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase))
+ // api keys can always access all folders
+ && !isApiKey
+ // check the item is visible for the user
+ && !item.IsVisible(user))
{
- _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}", user.Username, item.Name);
+ _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}", user!.Username, item.Name);
return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
}
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index e9492a6a4..7a57bf1a2 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -485,7 +485,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Media folders returned.</response>
/// <returns>List of user media folders.</returns>
[HttpGet("Library/MediaFolders")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetMediaFolders([FromQuery] bool? isHidden)
{
diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs
index 592c53fe5..9d6ca6aab 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 JellyfinDbProvider _provider;
+ private readonly IDbContextFactory<JellyfinDb> _provider;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityManager"/> class.
/// </summary>
/// <param name="provider">The Jellyfin database provider.</param>
- public ActivityManager(JellyfinDbProvider provider)
+ public ActivityManager(IDbContextFactory<JellyfinDb> provider)
{
_provider = provider;
}
@@ -32,10 +32,12 @@ namespace Jellyfin.Server.Implementations.Activity
/// <inheritdoc/>
public async Task CreateAsync(ActivityLog entry)
{
- await using var dbContext = _provider.CreateContext();
-
- dbContext.ActivityLogs.Add(entry);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ dbContext.ActivityLogs.Add(entry);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(ConvertToOldModel(entry)));
}
@@ -43,44 +45,47 @@ namespace Jellyfin.Server.Implementations.Activity
/// <inheritdoc/>
public async Task<QueryResult<ActivityLogEntry>> GetPagedResultAsync(ActivityLogQuery query)
{
- await using var dbContext = _provider.CreateContext();
+ var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ IQueryable<ActivityLog> entries = dbContext.ActivityLogs
+ .OrderByDescending(entry => entry.DateCreated);
- IQueryable<ActivityLog> entries = dbContext.ActivityLogs
- .AsQueryable()
- .OrderByDescending(entry => entry.DateCreated);
+ if (query.MinDate.HasValue)
+ {
+ entries = entries.Where(entry => entry.DateCreated >= query.MinDate);
+ }
- 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);
+ }
- if (query.HasUserId.HasValue)
- {
- entries = entries.Where(entry => (!entry.UserId.Equals(default)) == query.HasUserId.Value);
+ return new QueryResult<ActivityLogEntry>(
+ query.Skip,
+ await entries.CountAsync().ConfigureAwait(false),
+ await entries
+ .Skip(query.Skip ?? 0)
+ .Take(query.Limit ?? 100)
+ .AsAsyncEnumerable()
+ .Select(ConvertToOldModel)
+ .ToListAsync()
+ .ConfigureAwait(false));
}
-
- return new QueryResult<ActivityLogEntry>(
- query.Skip,
- await entries.CountAsync().ConfigureAwait(false),
- await entries
- .Skip(query.Skip ?? 0)
- .Take(query.Limit ?? 100)
- .AsAsyncEnumerable()
- .Select(ConvertToOldModel)
- .ToListAsync()
- .ConfigureAwait(false));
}
/// <inheritdoc />
public async Task CleanAsync(DateTime startDate)
{
- await using var dbContext = _provider.CreateContext();
- var entries = dbContext.ActivityLogs
- .AsQueryable()
- .Where(entry => entry.DateCreated <= startDate);
+ var dbContext = await _provider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ var entries = dbContext.ActivityLogs
+ .Where(entry => entry.DateCreated <= startDate);
- dbContext.RemoveRange(entries);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ dbContext.RemoveRange(entries);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
private static ActivityLogEntry ConvertToOldModel(ActivityLog entry)
diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
index 0728f1179..eeb958c62 100644
--- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
+++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
@@ -22,7 +23,7 @@ namespace Jellyfin.Server.Implementations.Devices
/// </summary>
public class DeviceManager : IDeviceManager
{
- private readonly JellyfinDbProvider _dbProvider;
+ private readonly IDbContextFactory<JellyfinDb> _dbProvider;
private readonly IUserManager _userManager;
private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new();
@@ -31,7 +32,7 @@ namespace Jellyfin.Server.Implementations.Devices
/// </summary>
/// <param name="dbProvider">The database provider.</param>
/// <param name="userManager">The user manager.</param>
- public DeviceManager(JellyfinDbProvider dbProvider, IUserManager userManager)
+ public DeviceManager(IDbContextFactory<JellyfinDb> dbProvider, IUserManager userManager)
{
_dbProvider = dbProvider;
_userManager = userManager;
@@ -49,39 +50,50 @@ namespace Jellyfin.Server.Implementations.Devices
/// <inheritdoc />
public async Task UpdateDeviceOptions(string deviceId, string deviceName)
{
- await using var dbContext = _dbProvider.CreateContext();
- var deviceOptions = await dbContext.DeviceOptions.AsQueryable().FirstOrDefaultAsync(dev => dev.DeviceId == deviceId).ConfigureAwait(false);
- if (deviceOptions == null)
+ DeviceOptions? deviceOptions;
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
{
- deviceOptions = new DeviceOptions(deviceId);
- dbContext.DeviceOptions.Add(deviceOptions);
+ deviceOptions = await dbContext.DeviceOptions.AsQueryable().FirstOrDefaultAsync(dev => dev.DeviceId == deviceId).ConfigureAwait(false);
+ if (deviceOptions == null)
+ {
+ deviceOptions = new DeviceOptions(deviceId);
+ dbContext.DeviceOptions.Add(deviceOptions);
+ }
+
+ deviceOptions.CustomName = deviceName;
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
}
- deviceOptions.CustomName = deviceName;
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
-
DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, deviceOptions)));
}
/// <inheritdoc />
public async Task<Device> CreateDevice(Device device)
{
- await using var dbContext = _dbProvider.CreateContext();
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ dbContext.Devices.Add(device);
- dbContext.Devices.Add(device);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
return device;
}
/// <inheritdoc />
public async Task<DeviceOptions> GetDeviceOptions(string deviceId)
{
- await using var dbContext = _dbProvider.CreateContext();
- var deviceOptions = await dbContext.DeviceOptions
- .AsQueryable()
- .FirstOrDefaultAsync(d => d.DeviceId == deviceId)
- .ConfigureAwait(false);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ DeviceOptions? deviceOptions;
+ await using (dbContext.ConfigureAwait(false))
+ {
+ deviceOptions = await dbContext.DeviceOptions
+ .AsNoTracking()
+ .FirstOrDefaultAsync(d => d.DeviceId == deviceId)
+ .ConfigureAwait(false);
+ }
return deviceOptions ?? new DeviceOptions(deviceId);
}
@@ -97,14 +109,17 @@ namespace Jellyfin.Server.Implementations.Devices
/// <inheritdoc />
public async Task<DeviceInfo?> GetDevice(string id)
{
- await using var dbContext = _dbProvider.CreateContext();
- var device = await dbContext.Devices
- .AsQueryable()
- .Where(d => d.DeviceId == id)
- .OrderByDescending(d => d.DateLastActivity)
- .Include(d => d.User)
- .FirstOrDefaultAsync()
- .ConfigureAwait(false);
+ Device? device;
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ device = await dbContext.Devices
+ .Where(d => d.DeviceId == id)
+ .OrderByDescending(d => d.DateLastActivity)
+ .Include(d => d.User)
+ .FirstOrDefaultAsync()
+ .ConfigureAwait(false);
+ }
var deviceInfo = device == null ? null : ToDeviceInfo(device);
@@ -114,41 +129,40 @@ namespace Jellyfin.Server.Implementations.Devices
/// <inheritdoc />
public async Task<QueryResult<Device>> GetDevices(DeviceQuery query)
{
- await using var dbContext = _dbProvider.CreateContext();
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ var devices = dbContext.Devices.AsQueryable();
- var devices = dbContext.Devices.AsQueryable();
+ if (query.UserId.HasValue)
+ {
+ devices = devices.Where(device => device.UserId.Equals(query.UserId.Value));
+ }
- 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.DeviceId != null)
- {
- devices = devices.Where(device => device.DeviceId == query.DeviceId);
- }
+ if (query.AccessToken != null)
+ {
+ devices = devices.Where(device => device.AccessToken == query.AccessToken);
+ }
- if (query.AccessToken != null)
- {
- devices = devices.Where(device => device.AccessToken == query.AccessToken);
- }
+ var count = await devices.CountAsync().ConfigureAwait(false);
- var count = await devices.CountAsync().ConfigureAwait(false);
+ if (query.Skip.HasValue)
+ {
+ devices = devices.Skip(query.Skip.Value);
+ }
- if (query.Skip.HasValue)
- {
- devices = devices.Skip(query.Skip.Value);
- }
+ if (query.Limit.HasValue)
+ {
+ devices = devices.Take(query.Limit.Value);
+ }
- if (query.Limit.HasValue)
- {
- devices = devices.Take(query.Limit.Value);
+ return new QueryResult<Device>(query.Skip, count, await devices.ToListAsync().ConfigureAwait(false));
}
-
- return new QueryResult<Device>(
- query.Skip,
- count,
- await devices.ToListAsync().ConfigureAwait(false));
}
/// <inheritdoc />
@@ -165,37 +179,43 @@ namespace Jellyfin.Server.Implementations.Devices
/// <inheritdoc />
public async Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId, bool? supportsSync)
{
- await using var dbContext = _dbProvider.CreateContext();
- var sessions = dbContext.Devices
- .Include(d => d.User)
- .AsQueryable()
- .OrderByDescending(d => d.DateLastActivity)
- .ThenBy(d => d.DeviceId)
- .AsAsyncEnumerable();
-
- if (supportsSync.HasValue)
+ IAsyncEnumerable<Device> sessions;
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
{
- sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == supportsSync.Value);
- }
+ sessions = dbContext.Devices
+ .Include(d => d.User)
+ .OrderByDescending(d => d.DateLastActivity)
+ .ThenBy(d => d.DeviceId)
+ .AsAsyncEnumerable();
- if (userId.HasValue)
- {
- var user = _userManager.GetUserById(userId.Value);
+ if (supportsSync.HasValue)
+ {
+ sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == supportsSync.Value);
+ }
- sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId));
- }
+ if (userId.HasValue)
+ {
+ var user = _userManager.GetUserById(userId.Value);
+
+ sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId));
+ }
- var array = await sessions.Select(device => ToDeviceInfo(device)).ToArrayAsync().ConfigureAwait(false);
+ var array = await sessions.Select(device => ToDeviceInfo(device)).ToArrayAsync().ConfigureAwait(false);
- return new QueryResult<DeviceInfo>(array);
+ return new QueryResult<DeviceInfo>(array);
+ }
}
/// <inheritdoc />
public async Task DeleteDevice(Device device)
{
- await using var dbContext = _dbProvider.CreateContext();
- dbContext.Devices.Remove(device);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ dbContext.Devices.Remove(device);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
/// <inheritdoc />
diff --git a/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs b/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 000000000..f98a0aede
--- /dev/null
+++ b/Jellyfin.Server.Implementations/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,43 @@
+using System;
+using System.IO;
+using EFCoreSecondLevelCacheInterceptor;
+using MediaBrowser.Common.Configuration;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Server.Implementations.Extensions;
+
+/// <summary>
+/// Extensions for the <see cref="IServiceCollection"/> interface.
+/// </summary>
+public static class ServiceCollectionExtensions
+{
+ /// <summary>
+ /// Adds the <see cref="IDbContextFactory{TContext}"/> interface to the service collection with second level caching enabled.
+ /// </summary>
+ /// <param name="serviceCollection">An instance of the <see cref="IServiceCollection"/> interface.</param>
+ /// <returns>The updated service collection.</returns>
+ public static IServiceCollection AddJellyfinDbContext(this IServiceCollection serviceCollection)
+ {
+ serviceCollection.AddEFSecondLevelCache(options =>
+ options.UseMemoryCacheProvider()
+ .CacheAllQueries(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(10))
+ .DisableLogging(true)
+ .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)));
+
+ serviceCollection.AddPooledDbContextFactory<JellyfinDb>((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);
+ });
+
+ return serviceCollection;
+ }
+}
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 2640f529e..5caac4523 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -26,6 +26,7 @@
</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" />
diff --git a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs
deleted file mode 100644
index c2c5198d1..000000000
--- a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-using System.IO;
-using System.Linq;
-using MediaBrowser.Common.Configuration;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-
-namespace Jellyfin.Server.Implementations
-{
- /// <summary>
- /// Factory class for generating new <see cref="JellyfinDb"/> instances.
- /// </summary>
- public class JellyfinDbProvider
- {
- private readonly IServiceProvider _serviceProvider;
- private readonly IApplicationPaths _appPaths;
- private readonly ILogger<JellyfinDbProvider> _logger;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="JellyfinDbProvider"/> class.
- /// </summary>
- /// <param name="serviceProvider">The application's service provider.</param>
- /// <param name="appPaths">The application paths.</param>
- /// <param name="logger">The logger.</param>
- public JellyfinDbProvider(IServiceProvider serviceProvider, IApplicationPaths appPaths, ILogger<JellyfinDbProvider> logger)
- {
- _serviceProvider = serviceProvider;
- _appPaths = appPaths;
- _logger = logger;
-
- using var jellyfinDb = CreateContext();
- if (jellyfinDb.Database.GetPendingMigrations().Any())
- {
- _logger.LogInformation("There are pending EFCore migrations in the database. Applying... (This may take a while, do not stop Jellyfin)");
- jellyfinDb.Database.Migrate();
- _logger.LogInformation("EFCore migrations applied successfully");
- }
- }
-
- /// <summary>
- /// Creates a new <see cref="JellyfinDb"/> context.
- /// </summary>
- /// <returns>The newly created context.</returns>
- public JellyfinDb CreateContext()
- {
- var contextOptions = new DbContextOptionsBuilder<JellyfinDb>().UseSqlite($"Filename={Path.Combine(_appPaths.DataPath, "jellyfin.db")}");
- return ActivatorUtilities.CreateInstance<JellyfinDb>(_serviceProvider, contextOptions.Options);
- }
- }
-}
diff --git a/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs b/Jellyfin.Server.Implementations/Security/AuthenticationManager.cs
index b79e46469..33c08c8c2 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 JellyfinDbProvider _dbProvider;
+ private readonly IDbContextFactory<JellyfinDb> _dbProvider;
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationManager"/> class.
/// </summary>
/// <param name="dbProvider">The database provider.</param>
- public AuthenticationManager(JellyfinDbProvider dbProvider)
+ public AuthenticationManager(IDbContextFactory<JellyfinDb> dbProvider)
{
_dbProvider = dbProvider;
}
@@ -24,50 +24,56 @@ namespace Jellyfin.Server.Implementations.Security
/// <inheritdoc />
public async Task CreateApiKey(string name)
{
- await using var dbContext = _dbProvider.CreateContext();
-
- dbContext.ApiKeys.Add(new ApiKey(name));
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ dbContext.ApiKeys.Add(new ApiKey(name));
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
/// <inheritdoc />
public async Task<IReadOnlyList<AuthenticationInfo>> GetApiKeys()
{
- await using var dbContext = _dbProvider.CreateContext();
-
- return await dbContext.ApiKeys
- .AsAsyncEnumerable()
- .Select(key => new AuthenticationInfo
- {
- AppName = key.Name,
- AccessToken = key.AccessToken,
- DateCreated = key.DateCreated,
- DeviceId = string.Empty,
- DeviceName = string.Empty,
- AppVersion = string.Empty
- }).ToListAsync().ConfigureAwait(false);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ return await dbContext.ApiKeys
+ .AsAsyncEnumerable()
+ .Select(key => new AuthenticationInfo
+ {
+ AppName = key.Name,
+ AccessToken = key.AccessToken,
+ DateCreated = key.DateCreated,
+ DeviceId = string.Empty,
+ DeviceName = string.Empty,
+ AppVersion = string.Empty
+ }).ToListAsync().ConfigureAwait(false);
+ }
}
/// <inheritdoc />
public async Task DeleteApiKey(string accessToken)
{
- await using var dbContext = _dbProvider.CreateContext();
-
- var key = await dbContext.ApiKeys
- .AsQueryable()
- .Where(apiKey => apiKey.AccessToken == accessToken)
- .FirstOrDefaultAsync()
- .ConfigureAwait(false);
-
- if (key == null)
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
{
- return;
- }
+ var key = await dbContext.ApiKeys
+ .AsQueryable()
+ .Where(apiKey => apiKey.AccessToken == accessToken)
+ .FirstOrDefaultAsync()
+ .ConfigureAwait(false);
- dbContext.Remove(key);
+ if (key == null)
+ {
+ return;
+ }
+
+ dbContext.Remove(key);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
}
}
diff --git a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
index 9f813f532..4d1a1b3cf 100644
--- a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
+++ b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
@@ -4,6 +4,7 @@ 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;
@@ -15,12 +16,12 @@ namespace Jellyfin.Server.Implementations.Security
{
public class AuthorizationContext : IAuthorizationContext
{
- private readonly JellyfinDbProvider _jellyfinDbProvider;
+ private readonly IDbContextFactory<JellyfinDb> _jellyfinDbProvider;
private readonly IUserManager _userManager;
private readonly IServerApplicationHost _serverApplicationHost;
public AuthorizationContext(
- JellyfinDbProvider jellyfinDb,
+ IDbContextFactory<JellyfinDb> jellyfinDb,
IUserManager userManager,
IServerApplicationHost serverApplicationHost)
{
@@ -121,96 +122,99 @@ namespace Jellyfin.Server.Implementations.Security
#pragma warning restore CA1508
authInfo.HasToken = true;
- await using var dbContext = _jellyfinDbProvider.CreateContext();
- var device = await dbContext.Devices.FirstOrDefaultAsync(d => d.AccessToken == token).ConfigureAwait(false);
-
- if (device != null)
+ var dbContext = await _jellyfinDbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
{
- authInfo.IsAuthenticated = true;
- var updateToken = false;
-
- // TODO: Remove these checks for IsNullOrWhiteSpace
- if (string.IsNullOrWhiteSpace(authInfo.Client))
- {
- authInfo.Client = device.AppName;
- }
+ var device = await dbContext.Devices.FirstOrDefaultAsync(d => d.AccessToken == token).ConfigureAwait(false);
- if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
+ if (device != null)
{
- authInfo.DeviceId = device.DeviceId;
- }
-
- // Temporary. TODO - allow clients to specify that the token has been shared with a casting device
- var allowTokenInfoUpdate = !authInfo.Client.Contains("chromecast", StringComparison.OrdinalIgnoreCase);
+ authInfo.IsAuthenticated = true;
+ var updateToken = false;
- if (string.IsNullOrWhiteSpace(authInfo.Device))
- {
- authInfo.Device = device.DeviceName;
- }
- else if (!string.Equals(authInfo.Device, device.DeviceName, StringComparison.OrdinalIgnoreCase))
- {
- if (allowTokenInfoUpdate)
+ // TODO: Remove these checks for IsNullOrWhiteSpace
+ if (string.IsNullOrWhiteSpace(authInfo.Client))
{
- updateToken = true;
- device.DeviceName = authInfo.Device;
+ authInfo.Client = device.AppName;
}
- }
- if (string.IsNullOrWhiteSpace(authInfo.Version))
- {
- authInfo.Version = device.AppVersion;
- }
- else if (!string.Equals(authInfo.Version, device.AppVersion, StringComparison.OrdinalIgnoreCase))
- {
- if (allowTokenInfoUpdate)
+ if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
{
- updateToken = true;
- device.AppVersion = authInfo.Version;
+ authInfo.DeviceId = device.DeviceId;
}
- }
- if ((DateTime.UtcNow - device.DateLastActivity).TotalMinutes > 3)
- {
- device.DateLastActivity = DateTime.UtcNow;
- updateToken = true;
- }
+ // Temporary. TODO - allow clients to specify that the token has been shared with a casting device
+ var allowTokenInfoUpdate = !authInfo.Client.Contains("chromecast", StringComparison.OrdinalIgnoreCase);
- authInfo.User = _userManager.GetUserById(device.UserId);
+ if (string.IsNullOrWhiteSpace(authInfo.Device))
+ {
+ authInfo.Device = device.DeviceName;
+ }
+ else if (!string.Equals(authInfo.Device, device.DeviceName, StringComparison.OrdinalIgnoreCase))
+ {
+ if (allowTokenInfoUpdate)
+ {
+ updateToken = true;
+ device.DeviceName = authInfo.Device;
+ }
+ }
- if (updateToken)
- {
- dbContext.Devices.Update(device);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
- }
- }
- else
- {
- var key = await dbContext.ApiKeys.FirstOrDefaultAsync(apiKey => apiKey.AccessToken == token).ConfigureAwait(false);
- if (key != null)
- {
- authInfo.IsAuthenticated = true;
- authInfo.Client = key.Name;
- authInfo.Token = key.AccessToken;
- if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
+ if (string.IsNullOrWhiteSpace(authInfo.Version))
{
- authInfo.DeviceId = _serverApplicationHost.SystemId;
+ authInfo.Version = device.AppVersion;
+ }
+ else if (!string.Equals(authInfo.Version, device.AppVersion, StringComparison.OrdinalIgnoreCase))
+ {
+ if (allowTokenInfoUpdate)
+ {
+ updateToken = true;
+ device.AppVersion = authInfo.Version;
+ }
}
- if (string.IsNullOrWhiteSpace(authInfo.Device))
+ if ((DateTime.UtcNow - device.DateLastActivity).TotalMinutes > 3)
{
- authInfo.Device = _serverApplicationHost.Name;
+ device.DateLastActivity = DateTime.UtcNow;
+ updateToken = true;
}
- if (string.IsNullOrWhiteSpace(authInfo.Version))
+ authInfo.User = _userManager.GetUserById(device.UserId);
+
+ if (updateToken)
{
- authInfo.Version = _serverApplicationHost.ApplicationVersionString;
+ dbContext.Devices.Update(device);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
}
+ }
+ else
+ {
+ var key = await dbContext.ApiKeys.FirstOrDefaultAsync(apiKey => apiKey.AccessToken == token).ConfigureAwait(false);
+ if (key != null)
+ {
+ authInfo.IsAuthenticated = true;
+ authInfo.Client = key.Name;
+ authInfo.Token = key.AccessToken;
+ if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
+ {
+ authInfo.DeviceId = _serverApplicationHost.SystemId;
+ }
+
+ if (string.IsNullOrWhiteSpace(authInfo.Device))
+ {
+ authInfo.Device = _serverApplicationHost.Name;
+ }
+
+ if (string.IsNullOrWhiteSpace(authInfo.Version))
+ {
+ authInfo.Version = _serverApplicationHost.ApplicationVersionString;
+ }
- authInfo.IsApiKey = true;
+ authInfo.IsApiKey = true;
+ }
}
- }
- return authInfo;
+ return authInfo;
+ }
}
/// <summary>
diff --git a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
index 65edb30ad..87babc05c 100644
--- a/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
+++ b/Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
@@ -20,10 +20,10 @@ namespace Jellyfin.Server.Implementations.Users
/// <summary>
/// Initializes a new instance of the <see cref="DisplayPreferencesManager"/> class.
/// </summary>
- /// <param name="dbContext">The database context.</param>
- public DisplayPreferencesManager(JellyfinDb dbContext)
+ /// <param name="dbContextFactory">The database context factory.</param>
+ public DisplayPreferencesManager(IDbContextFactory<JellyfinDb> dbContextFactory)
{
- _dbContext = dbContext;
+ _dbContext = dbContextFactory.CreateDbContext();
}
/// <inheritdoc />
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index ed90e81c6..25560707a 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 JellyfinDbProvider _dbProvider;
+ private readonly IDbContextFactory<JellyfinDb> _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(
- JellyfinDbProvider dbProvider,
+ IDbContextFactory<JellyfinDb> dbProvider,
IEventManager eventManager,
ICryptoProvider cryptoProvider,
INetworkManager networkManager,
@@ -83,7 +83,7 @@ namespace Jellyfin.Server.Implementations.Users
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
_users = new ConcurrentDictionary<Guid, User>();
- using var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateDbContext();
foreach (var user in dbContext.Users
.Include(user => user.Permissions)
.Include(user => user.Preferences)
@@ -139,31 +139,35 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("The new and old names must be different.");
}
- await using var dbContext = _dbProvider.CreateContext();
-
- if (await dbContext.Users
- .AsQueryable()
- .AnyAsync(u => u.Username == newName && !u.Id.Equals(user.Id))
- .ConfigureAwait(false))
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
{
- throw new ArgumentException(string.Format(
- CultureInfo.InvariantCulture,
- "A user with the name '{0}' already exists.",
- newName));
+ if (await dbContext.Users
+ .AsQueryable()
+ .AnyAsync(u => u.Username == newName && !u.Id.Equals(user.Id))
+ .ConfigureAwait(false))
+ {
+ throw new ArgumentException(string.Format(
+ CultureInfo.InvariantCulture,
+ "A user with the name '{0}' already exists.",
+ newName));
+ }
+
+ user.Username = newName;
+ await UpdateUserInternalAsync(dbContext, user).ConfigureAwait(false);
}
- user.Username = newName;
- await UpdateUserAsync(user).ConfigureAwait(false);
OnUserUpdated?.Invoke(this, new GenericEventArgs<User>(user));
}
/// <inheritdoc/>
public async Task UpdateUserAsync(User user)
{
- await using var dbContext = _dbProvider.CreateContext();
- dbContext.Users.Update(user);
- _users[user.Id] = user;
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ await UpdateUserInternalAsync(dbContext, user).ConfigureAwait(false);
+ }
}
internal async Task<User> CreateUserInternalAsync(string name, JellyfinDb dbContext)
@@ -202,12 +206,15 @@ namespace Jellyfin.Server.Implementations.Users
name));
}
- await using var dbContext = _dbProvider.CreateContext();
-
- var newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false);
+ User newUser;
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false);
- dbContext.Users.Add(newUser);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ dbContext.Users.Add(newUser);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
await _eventManager.PublishAsync(new UserCreatedEventArgs(newUser)).ConfigureAwait(false);
@@ -241,9 +248,13 @@ namespace Jellyfin.Server.Implementations.Users
nameof(userId));
}
- await using var dbContext = _dbProvider.CreateContext();
- dbContext.Users.Remove(user);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ dbContext.Users.Remove(user);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
+
_users.Remove(userId);
await _eventManager.PublishAsync(new UserDeletedEventArgs(user)).ConfigureAwait(false);
@@ -288,7 +299,7 @@ namespace Jellyfin.Server.Implementations.Users
user.EasyPassword = newPasswordSha1;
await UpdateUserAsync(user).ConfigureAwait(false);
- _eventManager.Publish(new UserPasswordChangedEventArgs(user));
+ await _eventManager.PublishAsync(new UserPasswordChangedEventArgs(user)).ConfigureAwait(false);
}
/// <inheritdoc/>
@@ -541,14 +552,17 @@ 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);
- newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
+ newUser.SetPermission(PermissionKind.IsAdministrator, true);
+ newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
+ newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true);
- dbContext.Users.Add(newUser);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ dbContext.Users.Add(newUser);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
/// <inheritdoc/>
@@ -584,105 +598,111 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public async Task UpdateConfigurationAsync(Guid userId, UserConfiguration config)
{
- await 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.Equals(userId))
- ?? throw new ArgumentException("No user exists with given Id!");
-
- user.SubtitleMode = config.SubtitleMode;
- user.HidePlayedInLatest = config.HidePlayedInLatest;
- user.EnableLocalPassword = config.EnableLocalPassword;
- user.PlayDefaultAudioTrack = config.PlayDefaultAudioTrack;
- user.DisplayCollectionsView = config.DisplayCollectionsView;
- user.DisplayMissingEpisodes = config.DisplayMissingEpisodes;
- user.AudioLanguagePreference = config.AudioLanguagePreference;
- user.RememberAudioSelections = config.RememberAudioSelections;
- user.EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay;
- user.RememberSubtitleSelections = config.RememberSubtitleSelections;
- user.SubtitleLanguagePreference = config.SubtitleLanguagePreference;
-
- user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
- user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
- user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
- user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
-
- dbContext.Update(user);
- _users[user.Id] = user;
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id.Equals(userId))
+ ?? throw new ArgumentException("No user exists with given Id!");
+
+ user.SubtitleMode = config.SubtitleMode;
+ user.HidePlayedInLatest = config.HidePlayedInLatest;
+ user.EnableLocalPassword = config.EnableLocalPassword;
+ user.PlayDefaultAudioTrack = config.PlayDefaultAudioTrack;
+ user.DisplayCollectionsView = config.DisplayCollectionsView;
+ user.DisplayMissingEpisodes = config.DisplayMissingEpisodes;
+ user.AudioLanguagePreference = config.AudioLanguagePreference;
+ user.RememberAudioSelections = config.RememberAudioSelections;
+ user.EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay;
+ user.RememberSubtitleSelections = config.RememberSubtitleSelections;
+ user.SubtitleLanguagePreference = config.SubtitleLanguagePreference;
+
+ user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
+ user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
+ user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
+ user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
+
+ dbContext.Update(user);
+ _users[user.Id] = user;
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
/// <inheritdoc/>
public async Task UpdatePolicyAsync(Guid userId, UserPolicy policy)
{
- await 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.Equals(userId))
- ?? throw new ArgumentException("No user exists with given Id!");
-
- // The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
- int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
- {
- -1 => null,
- 0 => 3,
- _ => policy.LoginAttemptsBeforeLockout
- };
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id.Equals(userId))
+ ?? throw new ArgumentException("No user exists with given Id!");
+
+ // The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
+ int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
+ {
+ -1 => null,
+ 0 => 3,
+ _ => policy.LoginAttemptsBeforeLockout
+ };
- user.MaxParentalAgeRating = policy.MaxParentalRating;
- user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess;
- user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit;
- user.AuthenticationProviderId = policy.AuthenticationProviderId;
- user.PasswordResetProviderId = policy.PasswordResetProviderId;
- user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount;
- user.LoginAttemptsBeforeLockout = maxLoginAttempts;
- user.MaxActiveSessions = policy.MaxActiveSessions;
- user.SyncPlayAccess = policy.SyncPlayAccess;
- user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
- user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
- user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
- user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
- user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
- user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
- user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
- user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
- user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding);
- user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding);
- user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
- user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
- user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
- user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
- user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
- user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
- user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
- user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers);
- user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
- user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
- user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
-
- user.AccessSchedules.Clear();
- foreach (var policyAccessSchedule in policy.AccessSchedules)
- {
- user.AccessSchedules.Add(policyAccessSchedule);
- }
-
- // TODO: fix this at some point
- user.SetPreference(PreferenceKind.BlockUnratedItems, policy.BlockUnratedItems ?? Array.Empty<UnratedItem>());
- user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
- user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
- user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
- user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
- user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
-
- dbContext.Update(user);
- _users[user.Id] = user;
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ user.MaxParentalAgeRating = policy.MaxParentalRating;
+ user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess;
+ user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit;
+ user.AuthenticationProviderId = policy.AuthenticationProviderId;
+ user.PasswordResetProviderId = policy.PasswordResetProviderId;
+ user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount;
+ user.LoginAttemptsBeforeLockout = maxLoginAttempts;
+ user.MaxActiveSessions = policy.MaxActiveSessions;
+ user.SyncPlayAccess = policy.SyncPlayAccess;
+ user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
+ user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
+ user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
+ user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
+ user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
+ user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
+ user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
+ user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
+ user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding);
+ user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding);
+ user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
+ user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
+ user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
+ user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
+ user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
+ user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
+ user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
+ user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers);
+ user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
+ user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
+ user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
+
+ user.AccessSchedules.Clear();
+ foreach (var policyAccessSchedule in policy.AccessSchedules)
+ {
+ user.AccessSchedules.Add(policyAccessSchedule);
+ }
+
+ // TODO: fix this at some point
+ user.SetPreference(PreferenceKind.BlockUnratedItems, policy.BlockUnratedItems ?? Array.Empty<UnratedItem>());
+ user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
+ user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
+ user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
+ user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
+ user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
+
+ dbContext.Update(user);
+ _users[user.Id] = user;
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
/// <inheritdoc/>
@@ -693,9 +713,13 @@ namespace Jellyfin.Server.Implementations.Users
return;
}
- await using var dbContext = _dbProvider.CreateContext();
- dbContext.Remove(user.ProfileImage);
- await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ dbContext.Remove(user.ProfileImage);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
+
user.ProfileImage = null;
_users[user.Id] = user;
}
@@ -859,5 +883,12 @@ namespace Jellyfin.Server.Implementations.Users
await UpdateUserAsync(user).ConfigureAwait(false);
}
+
+ private async Task UpdateUserInternalAsync(JellyfinDb dbContext, User user)
+ {
+ dbContext.Users.Update(user);
+ _users[user.Id] = user;
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
}
}
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index 984711dc2..002193baf 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Reflection;
using Emby.Drawing;
using Emby.Server.Implementations;
@@ -71,19 +70,13 @@ namespace Jellyfin.Server
Logger.LogWarning("Skia not available. Will fallback to {ImageEncoder}.", nameof(NullImageEncoder));
}
- serviceCollection.AddDbContextPool<JellyfinDb>(
- options => options
- .UseLoggerFactory(LoggerFactory)
- .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"));
-
serviceCollection.AddEventServices();
serviceCollection.AddSingleton<IBaseItemManager, BaseItemManager>();
serviceCollection.AddSingleton<IEventManager, EventManager>();
- serviceCollection.AddSingleton<JellyfinDbProvider>();
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
serviceCollection.AddSingleton<IUserManager, UserManager>();
- serviceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>();
+ serviceCollection.AddScoped<IDisplayPreferencesManager, DisplayPreferencesManager>();
serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
// TODO search the assemblies instead of adding them manually?
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
index 9e22978ae..bf66f75ff 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
@@ -19,7 +19,7 @@ namespace Jellyfin.Server.Migrations.Routines
private const string DbFilename = "activitylog.db";
private readonly ILogger<MigrateActivityLogDb> _logger;
- private readonly JellyfinDbProvider _provider;
+ private readonly IDbContextFactory<JellyfinDb> _provider;
private readonly IServerApplicationPaths _paths;
/// <summary>
@@ -28,7 +28,7 @@ namespace Jellyfin.Server.Migrations.Routines
/// <param name="logger">The logger.</param>
/// <param name="paths">The server application paths.</param>
/// <param name="provider">The database provider.</param>
- public MigrateActivityLogDb(ILogger<MigrateActivityLogDb> logger, IServerApplicationPaths paths, JellyfinDbProvider provider)
+ public MigrateActivityLogDb(ILogger<MigrateActivityLogDb> logger, IServerApplicationPaths paths, IDbContextFactory<JellyfinDb> provider)
{
_logger = logger;
_provider = provider;
@@ -68,7 +68,7 @@ namespace Jellyfin.Server.Migrations.Routines
{
using var userDbConnection = SQLite3.Open(Path.Combine(dataPath, "users.db"), ConnectionFlags.ReadOnly, null);
_logger.LogWarning("Migrating the activity database may take a while, do not stop Jellyfin.");
- using var dbContext = _provider.CreateContext();
+ using var dbContext = _provider.CreateDbContext();
var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id");
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
index ba0e33585..bf1ea8233 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
@@ -6,6 +6,7 @@ using Jellyfin.Data.Entities.Security;
using Jellyfin.Server.Implementations;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
@@ -19,7 +20,7 @@ namespace Jellyfin.Server.Migrations.Routines
private const string DbFilename = "authentication.db";
private readonly ILogger<MigrateAuthenticationDb> _logger;
- private readonly JellyfinDbProvider _dbProvider;
+ private readonly IDbContextFactory<JellyfinDb> _dbProvider;
private readonly IServerApplicationPaths _appPaths;
private readonly IUserManager _userManager;
@@ -32,7 +33,7 @@ namespace Jellyfin.Server.Migrations.Routines
/// <param name="userManager">The user manager.</param>
public MigrateAuthenticationDb(
ILogger<MigrateAuthenticationDb> logger,
- JellyfinDbProvider dbProvider,
+ IDbContextFactory<JellyfinDb> dbProvider,
IServerApplicationPaths appPaths,
IUserManager userManager)
{
@@ -60,7 +61,7 @@ namespace Jellyfin.Server.Migrations.Routines
ConnectionFlags.ReadOnly,
null))
{
- using var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateDbContext();
var authenticatedDevices = connection.Query("SELECT * FROM Tokens");
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
index 74f2349f5..37716482c 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
@@ -10,6 +10,7 @@ using Jellyfin.Server.Implementations;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
@@ -24,7 +25,7 @@ namespace Jellyfin.Server.Migrations.Routines
private readonly ILogger<MigrateDisplayPreferencesDb> _logger;
private readonly IServerApplicationPaths _paths;
- private readonly JellyfinDbProvider _provider;
+ private readonly IDbContextFactory<JellyfinDb> _provider;
private readonly JsonSerializerOptions _jsonOptions;
private readonly IUserManager _userManager;
@@ -38,7 +39,7 @@ namespace Jellyfin.Server.Migrations.Routines
public MigrateDisplayPreferencesDb(
ILogger<MigrateDisplayPreferencesDb> logger,
IServerApplicationPaths paths,
- JellyfinDbProvider provider,
+ IDbContextFactory<JellyfinDb> provider,
IUserManager userManager)
{
_logger = logger;
@@ -84,7 +85,7 @@ namespace Jellyfin.Server.Migrations.Routines
var dbFilePath = Path.Combine(_paths.DataPath, DbFilename);
using (var connection = SQLite3.Open(dbFilePath, ConnectionFlags.ReadOnly, null))
{
- using var dbContext = _provider.CreateContext();
+ using var dbContext = _provider.CreateDbContext();
var results = connection.Query("SELECT * FROM userdisplaypreferences");
foreach (var result in results)
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
index 9b2d603c7..0c2cc69a7 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
@@ -11,6 +11,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Users;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
using JsonSerializer = System.Text.Json.JsonSerializer;
@@ -26,7 +27,7 @@ namespace Jellyfin.Server.Migrations.Routines
private readonly ILogger<MigrateUserDb> _logger;
private readonly IServerApplicationPaths _paths;
- private readonly JellyfinDbProvider _provider;
+ private readonly IDbContextFactory<JellyfinDb> _provider;
private readonly IXmlSerializer _xmlSerializer;
/// <summary>
@@ -39,7 +40,7 @@ namespace Jellyfin.Server.Migrations.Routines
public MigrateUserDb(
ILogger<MigrateUserDb> logger,
IServerApplicationPaths paths,
- JellyfinDbProvider provider,
+ IDbContextFactory<JellyfinDb> provider,
IXmlSerializer xmlSerializer)
{
_logger = logger;
@@ -65,7 +66,7 @@ namespace Jellyfin.Server.Migrations.Routines
using (var connection = SQLite3.Open(Path.Combine(dataPath, DbFilename), ConnectionFlags.ReadOnly, null))
{
- var dbContext = _provider.CreateContext();
+ var dbContext = _provider.CreateDbContext();
var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index a6f0b705d..cb763dfa3 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -192,6 +192,7 @@ namespace Jellyfin.Server
// Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection.
appHost.ServiceProvider = webHost.Services;
+
await appHost.InitializeServices().ConfigureAwait(false);
Migrations.MigrationRunner.Run(appHost, _loggerFactory);
@@ -236,10 +237,13 @@ namespace Jellyfin.Server
{
_logger.LogInformation("Running query planner optimizations in the database... This might take a while");
// Run before disposing the application
- using var context = appHost.Resolve<JellyfinDbProvider>().CreateContext();
- if (context.Database.IsSqlite())
+ var context = await appHost.ServiceProvider.GetRequiredService<IDbContextFactory<JellyfinDb>>().CreateDbContextAsync().ConfigureAwait(false);
+ await using (context.ConfigureAwait(false))
{
- context.Database.ExecuteSqlRaw("PRAGMA optimize");
+ if (context.Database.IsSqlite())
+ {
+ await context.Database.ExecuteSqlRawAsync("PRAGMA optimize").ConfigureAwait(false);
+ }
}
}
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 1954a5c55..49a57aa68 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -9,6 +9,7 @@ using Jellyfin.MediaEncoding.Hls.Extensions;
using Jellyfin.Networking.Configuration;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.Implementations;
+using Jellyfin.Server.Implementations.Extensions;
using Jellyfin.Server.Infrastructure;
using Jellyfin.Server.Middleware;
using MediaBrowser.Common.Net;
@@ -65,7 +66,7 @@ namespace Jellyfin.Server
// TODO remove once this is fixed upstream https://github.com/dotnet/aspnetcore/issues/34371
services.AddSingleton<IActionResultExecutor<PhysicalFileResult>, SymlinkFollowingPhysicalFileResultExecutor>();
services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies(), _serverConfigurationManager.GetNetworkConfiguration());
-
+ services.AddJellyfinDbContext();
services.AddJellyfinApiSwagger();
// configure custom legacy authentication
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index da1e6c3d3..6e9b943f7 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -1085,9 +1085,6 @@ namespace MediaBrowser.Model.Dlna
bool? isInterlaced = videoStream?.IsInterlaced;
string videoCodecTag = videoStream?.CodecTag;
bool? isAvc = videoStream?.IsAVC;
- // Audio
- var defaultLanguage = audioStream?.Language ?? string.Empty;
- var defaultMarked = audioStream?.IsDefault ?? false;
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp;
int? packetLength = videoStream?.PacketLength;
@@ -1119,7 +1116,7 @@ namespace MediaBrowser.Model.Dlna
.SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
// Check audiocandidates profile conditions
- var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked));
+ var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream));
TranscodeReason subtitleProfileReasons = 0;
if (subtitleStream != null)
@@ -1240,10 +1237,10 @@ namespace MediaBrowser.Model.Dlna
return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons);
}
- private TranscodeReason CheckVideoAudioStreamDirectPlay(VideoOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream, string language, bool isDefault)
+ private TranscodeReason CheckVideoAudioStreamDirectPlay(VideoOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream)
{
var profile = options.Profile;
- var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, !audioStream.IsDefault);
+ var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, mediaSource.IsSecondaryAudio(audioStream));
var audioStreamFailureReasons = AggregateFailureConditions(mediaSource, profile, "VideoAudioCodecProfile", audioFailureConditions);
if (audioStream?.IsExternal == true)
diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
index bb9848848..c348e83ae 100644
--- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
@@ -230,19 +230,15 @@ namespace MediaBrowser.Model.Dto
public bool? IsSecondaryAudio(MediaStream stream)
{
- // Look for the first audio track marked as default
- foreach (var currentStream in MediaStreams)
+ if (stream.IsExternal)
{
- if (currentStream.Type == MediaStreamType.Audio && currentStream.IsDefault)
- {
- return currentStream.Index != stream.Index;
- }
+ return false;
}
// Look for the first audio track
foreach (var currentStream in MediaStreams)
{
- if (currentStream.Type == MediaStreamType.Audio)
+ if (currentStream.Type == MediaStreamType.Audio && !currentStream.IsExternal)
{
return currentStream.Index != stream.Index;
}
diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
index 9baf6877d..c279b6b4b 100644
--- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
+++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
@@ -21,8 +21,8 @@ namespace Jellyfin.Model.Tests
[Theory]
// Chrome
[InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
- [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
- [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+ [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
+ [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
@@ -32,8 +32,8 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Firefox
[InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
- [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
- [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+ [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
+ [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Firefox", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
@@ -59,11 +59,11 @@ namespace Jellyfin.Model.Tests
[InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
// Yatse
[InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
- [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
- [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+ [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
+ [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
[InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
- [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+ [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
// RokuSSPlus
[InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
[InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 should be DirectPlay
@@ -83,8 +83,8 @@ namespace Jellyfin.Model.Tests
[InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450
// Chrome-NoHLS
[InlineData("Chrome-NoHLS", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
- [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
- [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450
+ [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
+ [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
[InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
@@ -273,15 +273,15 @@ namespace Jellyfin.Model.Tests
[Theory]
// Chrome
- [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+ [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
// Firefox
- [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
+ [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
// Yatse
- [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
- [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
+ [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
+ [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
// RokuSSPlus
[InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
[InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450