From 7939f3b009e830e38a3f37455418011043429ee3 Mon Sep 17 00:00:00 2001
From: TheMelmacian <76712303+TheMelmacian@users.noreply.github.com>
Date: Mon, 25 May 2026 23:40:40 +0200
Subject: only fetch language codes for the requested library when generating
filter values
---
Emby.Server.Implementations/Library/LibraryManager.cs | 12 ++++++++++++
1 file changed, 12 insertions(+)
(limited to 'Emby.Server.Implementations/Library/LibraryManager.cs')
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index cc85f09d23..f075d2f649 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -3862,5 +3862,17 @@ namespace Emby.Server.Implementations.Library
{
return _mediaStreamRepository.GetMediaStreamLanguages(mediaStreamType);
}
+
+ ///
+ public IReadOnlyList GetMediaStreamLanguages(MediaStreamType mediaStreamType, InternalItemsQuery query)
+ {
+ if (query.User is not null)
+ {
+ AddUserToQuery(query, query.User);
+ }
+
+ SetTopParentOrAncestorIds(query);
+ return _itemRepository.GetMediaStreamLanguages(query, mediaStreamType);
+ }
}
}
--
cgit v1.2.3
From 5d580abb08d9d23f54e74050fdaa8fcdbc21571f Mon Sep 17 00:00:00 2001
From: Paolo Antinori
Date: Tue, 21 Jul 2026 08:20:40 +0200
Subject: fix: avoid NRE when sorting by user-dependent keys without a user
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A query sorted by a user-dependent key (PlayCount, IsFavoriteOrLiked,
DatePlayed, IsPlayed, IsUnplayed) but carrying no User caused a
NullReferenceException inside UserDataManager.GetUserData, surfacing as
"Failed to compare two elements in the array" (InvalidOperationException
wrapping the NRE from the LINQ sort) and 500-ing the /Items request.
Root cause: LibraryManager.GetComparer assigned comparer.User = user
without a null guard, so PlayCountComparer.GetValue called
UserDataManager.GetUserData(null, item), dereferencing user.Id.
Two-part fix:
- LibraryManager.GetComparer: when user is null and the sort key requires a
user (IUserBaseItemComparer), substitute the SortName comparer so the
result stays deterministic instead of 500-ing. SortName is the project's
canonical tiebreaker (ItemsController injects it for album-by-artist).
- UserDataManager.GetUserData: ArgumentNullException.ThrowIfNull(user) as
defense in depth (matches the existing guards on the SaveUserData
overloads in the same file). On master this overload was rewritten to use
ResolveUserDataRow, so the NRE dereferences user.Id rather than
user.InternalId as on the release branch — same bug, different line.
Also fixes DateLastMediaAddedComparer being statically mis-tagged as
IUserBaseItemComparer: its GetDate is static and never reads User, so it
does not need one. Without this, the SortName fallback above would wrongly
engage for DateLastContentAdded on anonymous queries (returning SortName
order instead of date order). Re-tagged to IBaseItemComparer and dropped the
unused User/UserManager/UserDataManager properties.
Tests:
- UserDataManagerTests.GetUserData_NullUser_ThrowsArgumentNullException:
reproduces the crash (NRE -> now ArgumentNullException). Added to master's
existing UserDataManagerTests.
- LibraryManagerSortTests.Sort_UserDependentKey_NullUser_FallsBackToSortNameWithoutThrowing:
Sort with a user-dependent key + null user no longer throws and returns
items ordered by the SortName fallback (direction preserved).
- LibraryManagerSortTests.Sort_DateLastContentAdded_NullUser_OrdersByDateNotSortName:
guards that DateLastContentAdded still sorts by date with no user (fixture
chosen so date-desc and SortName-desc disagree, so a revert is caught).
Full Jellyfin.Server.Implementations.Tests suite: 642 passed, 0 failed.
Fixes #17393
---
.../Library/LibraryManager.cs | 9 +-
.../Library/UserDataManager.cs | 1 +
.../Sorting/DateLastMediaAddedComparer.cs | 22 +----
.../Library/LibraryManagerSortTests.cs | 101 +++++++++++++++++++++
.../Library/UserDataManagerTests.cs | 7 ++
5 files changed, 118 insertions(+), 22 deletions(-)
create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerSortTests.cs
(limited to 'Emby.Server.Implementations/Library/LibraryManager.cs')
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 3691f4e19d..8d67f0c6c7 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -2315,9 +2315,16 @@ namespace Emby.Server.Implementations.Library
{
var comparer = Comparers.FirstOrDefault(c => name == c.Type);
- // If it requires a user, create a new one, and assign the user
+ // User-dependent comparers (IUserBaseItemComparer) need a User. With no user
+ // (anonymous/API-key /Items requests), a user-dependent sort key is a caller contract
+ // violation — throw rather than silently falling back to a different key.
if (comparer is IUserBaseItemComparer)
{
+ if (user is null)
+ {
+ throw new ArgumentException($"Sort key '{name}' requires a user, but none was provided.");
+ }
+
var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType())!; // only null for Nullable instances
userComparer.User = user;
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index f5c41e5670..0680046c11 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -352,6 +352,7 @@ namespace Emby.Server.Implementations.Library
///
public UserItemData? GetUserData(User user, BaseItem item)
{
+ ArgumentNullException.ThrowIfNull(user);
var row = ResolveUserDataRow(item, item.UserData?.Where(e => e.UserId.Equals(user.Id)));
return row is not null ? Map(row) : new UserItemData()
{
diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs
index f10e7fcbb7..4159f8cf7d 100644
--- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs
+++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs
@@ -3,34 +3,14 @@
using System;
using Jellyfin.Data.Enums;
-using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
- public class DateLastMediaAddedComparer : IUserBaseItemComparer
+ public class DateLastMediaAddedComparer : IBaseItemComparer
{
- ///
- /// Gets or sets the user.
- ///
- /// The user.
- public User User { get; set; }
-
- ///
- /// Gets or sets the user manager.
- ///
- /// The user manager.
- public IUserManager UserManager { get; set; }
-
- ///
- /// Gets or sets the user data manager.
- ///
- /// The user data manager.
- public IUserDataManager UserDataManager { get; set; }
-
///
/// Gets the name.
///
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerSortTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerSortTests.cs
new file mode 100644
index 0000000000..9cec9d6736
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerSortTests.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Emby.Naming.Common;
+using Emby.Server.Implementations.Library;
+using Emby.Server.Implementations.Sorting;
+using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Enums;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.IO;
+using Moq;
+using Xunit;
+using BaseItem = MediaBrowser.Controller.Entities.BaseItem;
+
+namespace Jellyfin.Server.Implementations.Tests.Library;
+
+public class LibraryManagerSortTests
+{
+ [Fact]
+ public void Sort_UserDependentKey_NullUser_ThrowsArgumentException()
+ {
+ var libraryManager = CreateLibraryManager(
+ new IBaseItemComparer[] { new PlayCountComparer(), new SortNameComparer() });
+
+ BaseItem[] items =
+ {
+ new Audio { Name = "Zulu", SortName = "Zulu", Id = Guid.NewGuid() },
+ new Audio { Name = "Alpha", SortName = "Alpha", Id = Guid.NewGuid() },
+ };
+
+ // A user-dependent sort key with no user is a caller contract violation — throw,
+ // don't silently fall back to a different key (review feedback on #17395).
+ Assert.Throws(() => libraryManager.Sort(
+ items,
+ user: null,
+ new[] { (ItemSortBy.PlayCount, SortOrder.Descending) }).ToArray());
+ }
+
+ [Fact]
+ public void Sort_DateLastContentAdded_NullUser_OrdersByDateNotSortName()
+ {
+ // DateLastMediaAddedComparer does not use User (its GetDate is static), so it must NOT be
+ // treated as a user-dependent comparer: with no user it should still sort by date, not fall
+ // back to SortName.
+ var libraryManager = CreateLibraryManager(
+ new IBaseItemComparer[] { new DateLastMediaAddedComparer(), new SortNameComparer() });
+
+ // Names are chosen so date-descending and SortName-descending DISAGREE: Alpha is newest
+ // (date-desc rank 1), but Zulu sorts last alphabetically (SortName-desc rank 1). If the
+ // comparer were still tagged IUserBaseItemComparer, the null-user SortName fallback would
+ // return [Zulu, Mike, Alpha] and this assertion would fail.
+ BaseItem[] items =
+ {
+ MakeFolder("Alpha", new DateTime(2026, 1, 1)),
+ MakeFolder("Mike", new DateTime(2025, 1, 1)),
+ MakeFolder("Zulu", new DateTime(2024, 1, 1))
+ };
+
+ var sorted = libraryManager.Sort(
+ items,
+ user: null,
+ new[] { (ItemSortBy.DateLastContentAdded, SortOrder.Descending) }).ToArray();
+
+ // Descending by date => newest first: Alpha, Mike, Zulu. (SortName-desc would be Zulu, Mike, Alpha.)
+ Assert.Equal(new[] { "Alpha", "Mike", "Zulu" }, sorted.Select(i => i.Name));
+ }
+
+ private static Folder MakeFolder(string name, DateTime dateLastMediaAdded)
+ => new() { Name = name, Id = Guid.NewGuid(), DateLastMediaAdded = dateLastMediaAdded };
+
+ private static Emby.Server.Implementations.Library.LibraryManager CreateLibraryManager(IReadOnlyCollection comparers)
+ {
+ var fixture = new Fixture().Customize(new AutoMoqCustomization());
+ fixture.Register(() => new NamingOptions());
+ var configMock = fixture.Freeze>();
+ configMock.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data");
+ // BaseItem.SortName/CreateSortName dereference this static; set it so SortName-fallback
+ // paths don't NRE in-process (mirrors AudioResolverTests in the sibling test project).
+ BaseItem.ConfigurationManager ??= configMock.Object;
+ var itemRepository = fixture.Freeze>();
+ itemRepository.Setup(i => i.RetrieveItem(It.IsAny())).Returns(null);
+ var fileSystemMock = fixture.Freeze>();
+ fileSystemMock.Setup(f => f.GetFileInfo(It.IsAny())).Returns(path => new FileSystemMetadata { FullName = path });
+
+ return fixture.Build().Do(s => s.AddParts(
+ fixture.Create>(),
+ fixture.Create>(),
+ fixture.Create>(),
+ comparers,
+ fixture.Create>()))
+ .Create();
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/UserDataManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/UserDataManagerTests.cs
index bd14ca008d..ba3127bc08 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/UserDataManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/UserDataManagerTests.cs
@@ -206,4 +206,11 @@ public sealed class UserDataManagerTests : IDisposable
Assert.Equal(222, result[fossilItem.Id].PlaybackPositionTicks);
Assert.Equal(333, result[retiredItem.Id].PlaybackPositionTicks);
}
+
+ [Fact]
+ public void GetUserData_NullUser_ThrowsArgumentNullException()
+ {
+ var item = CreateAudioBook();
+ Assert.Throws(() => _userDataManager.GetUserData(null!, item));
+ }
}
--
cgit v1.2.3
From 6382563440b69d816639abc2b4d03fca1d35eca2 Mon Sep 17 00:00:00 2001
From: Shadowghost
Date: Wed, 22 Jul 2026 08:09:33 +0200
Subject: Prefer null checks over HasValue everywhere
---
Emby.Server.Implementations/Library/LibraryManager.cs | 4 ++--
Emby.Server.Implementations/Sorting/PremiereDateComparer.cs | 2 +-
Emby.Server.Implementations/Sorting/ProductionYearComparer.cs | 2 +-
MediaBrowser.Controller/Entities/Movies/Movie.cs | 2 +-
MediaBrowser.Controller/Entities/MusicVideo.cs | 2 +-
MediaBrowser.Controller/Entities/TV/Series.cs | 2 +-
MediaBrowser.Controller/Entities/Trailer.cs | 2 +-
MediaBrowser.Controller/Entities/UserViewBuilder.cs | 2 +-
MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 2 +-
MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 2 +-
MediaBrowser.Providers/Manager/MetadataService.cs | 2 +-
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 6 +++---
MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs | 2 +-
MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 2 +-
src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs | 6 +++---
src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs | 2 +-
16 files changed, 21 insertions(+), 21 deletions(-)
(limited to 'Emby.Server.Implementations/Library/LibraryManager.cs')
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 3691f4e19d..35c47ecb5c 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -3195,11 +3195,11 @@ namespace Emby.Server.Implementations.Library
}
}
- if (!episode.ProductionYear.HasValue)
+ if (episode.ProductionYear is null)
{
episode.ProductionYear = episodeInfo.Year;
- if (episode.ProductionYear.HasValue)
+ if (episode.ProductionYear is not null)
{
changed = true;
}
diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs
index 8c8b8824f3..30b268bb60 100644
--- a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs
+++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs
@@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.Sorting
return x.PremiereDate.Value;
}
- if (x.ProductionYear.HasValue)
+ if (x.ProductionYear is not null)
{
try
{
diff --git a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs
index 9aec87f183..8774bd8d4f 100644
--- a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs
+++ b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs
@@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Sorting
return 0;
}
- if (x.ProductionYear.HasValue)
+ if (x.ProductionYear is not null)
{
return x.ProductionYear.Value;
}
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index e8817a29cf..8c3ce2ff58 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -90,7 +90,7 @@ namespace MediaBrowser.Controller.Entities.Movies
{
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
- if (!ProductionYear.HasValue)
+ if (ProductionYear is null)
{
var info = LibraryManager.ParseName(Name);
diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs
index 237ad5198c..effbf98820 100644
--- a/MediaBrowser.Controller/Entities/MusicVideo.cs
+++ b/MediaBrowser.Controller/Entities/MusicVideo.cs
@@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Entities
{
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
- if (!ProductionYear.HasValue)
+ if (ProductionYear is null)
{
var info = LibraryManager.ParseName(Name);
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 952187c6e1..d9f300ad20 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -507,7 +507,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
- if (!ProductionYear.HasValue)
+ if (ProductionYear is null)
{
var info = LibraryManager.ParseName(Name);
diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs
index 939709215c..a2465eedf0 100644
--- a/MediaBrowser.Controller/Entities/Trailer.cs
+++ b/MediaBrowser.Controller/Entities/Trailer.cs
@@ -49,7 +49,7 @@ namespace MediaBrowser.Controller.Entities
{
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
- if (!ProductionYear.HasValue)
+ if (ProductionYear is null)
{
var info = LibraryManager.ParseName(Name);
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index c57ed2faf8..9ba103cc8b 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -730,7 +730,7 @@ namespace MediaBrowser.Controller.Entities
// Apply year filter
if (query.Years.Length > 0)
{
- if (!(item.ProductionYear.HasValue && query.Years.Contains(item.ProductionYear.Value)))
+ if (item.ProductionYear is null || !query.Years.Contains(item.ProductionYear.Value))
{
return false;
}
diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
index b0f51aec71..bc184d82fb 100644
--- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
@@ -277,7 +277,7 @@ namespace MediaBrowser.LocalMetadata.Savers
await writer.WriteElementStringAsync(null, "Rating", null, item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
- if (item.ProductionYear.HasValue && item is not Person)
+ if (item.ProductionYear is not null && item is not Person)
{
await writer.WriteElementStringAsync(null, "ProductionYear", null, item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 989701350c..4d2f683a0e 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -189,7 +189,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
// Guess ProductionYear from PremiereDate if missing
- if (!info.ProductionYear.HasValue && info.PremiereDate.HasValue)
+ if (info.ProductionYear is null && info.PremiereDate is not null)
{
info.ProductionYear = info.PremiereDate.Value.Year;
}
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index a438a94c40..f09c4c876c 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -1114,7 +1114,7 @@ namespace MediaBrowser.Providers.Manager
target.PremiereDate = source.PremiereDate;
}
- if (replaceData || !target.ProductionYear.HasValue)
+ if (replaceData || target.ProductionYear is null)
{
target.ProductionYear = source.ProductionYear;
}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index f9d8883dff..ff14f87923 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -432,9 +432,9 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
- if (data.ProductionYear.HasValue)
+ if (data.ProductionYear is not null)
{
- if (!video.ProductionYear.HasValue || replaceData)
+ if (video.ProductionYear is null || replaceData)
{
video.ProductionYear = data.ProductionYear;
}
@@ -482,7 +482,7 @@ namespace MediaBrowser.Providers.MediaInfo
}
// If we don't have a ProductionYear try and get it from PremiereDate
- if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)
+ if (video.PremiereDate is not null && video.ProductionYear is null)
{
video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year;
}
diff --git a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
index b5ba2d24f2..af2557df78 100644
--- a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
@@ -82,7 +82,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString("title", album.Name);
}
- if (album.ProductionYear.HasValue)
+ if (album.ProductionYear is not null)
{
writer.WriteElementString("year", album.ProductionYear.Value.ToString(CultureInfo.InvariantCulture));
}
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
index 78907a5e68..5a9bbbef4f 100644
--- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
@@ -544,7 +544,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString("rating", item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture));
}
- if (item.ProductionYear.HasValue)
+ if (item.ProductionYear is not null)
{
writer.WriteElementString("year", item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture));
}
diff --git a/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs b/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs
index 846f9baf71..62a06370da 100644
--- a/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs
+++ b/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs
@@ -497,7 +497,7 @@ public sealed class RecordingsManager : IRecordingsManager, IDisposable
// trim trailing period from the folder name
var folderName = _fileSystem.GetValidFilename(timer.Name).Trim().TrimEnd('.').Trim();
- if (metadata is not null && metadata.ProductionYear.HasValue)
+ if (metadata is not null && metadata.ProductionYear is not null)
{
folderName += " (" + metadata.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
}
@@ -532,7 +532,7 @@ public sealed class RecordingsManager : IRecordingsManager, IDisposable
}
var folderName = _fileSystem.GetValidFilename(timer.Name).Trim();
- if (timer.ProductionYear.HasValue)
+ if (timer.ProductionYear is not null)
{
folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
}
@@ -550,7 +550,7 @@ public sealed class RecordingsManager : IRecordingsManager, IDisposable
}
var folderName = _fileSystem.GetValidFilename(timer.Name).Trim();
- if (timer.ProductionYear.HasValue)
+ if (timer.ProductionYear is not null)
{
folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
}
diff --git a/src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs b/src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs
index 7e68dbb547..e0e5a00fb9 100644
--- a/src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs
+++ b/src/Jellyfin.LiveTv/Recordings/RecordingsMetadataManager.cs
@@ -290,7 +290,7 @@ public class RecordingsMetadataManager
null,
DateTime.UtcNow.ToString(DateAddedFormat, CultureInfo.InvariantCulture)).ConfigureAwait(false);
- if (item.ProductionYear.HasValue)
+ if (item.ProductionYear is not null)
{
await writer.WriteElementStringAsync(null, "year", null, item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)).ConfigureAwait(false);
}
--
cgit v1.2.3
From 8b70582561754d22fababede84316beead46d3a9 Mon Sep 17 00:00:00 2001
From: Paolo Antinori
Date: Sat, 25 Jul 2026 12:50:32 +0200
Subject: Remove added comments (#17395 review)
---
Emby.Server.Implementations/Library/LibraryManager.cs | 3 ---
.../Library/LibraryManagerSortTests.cs | 12 ------------
2 files changed, 15 deletions(-)
(limited to 'Emby.Server.Implementations/Library/LibraryManager.cs')
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 8d67f0c6c7..1c2d314341 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -2315,9 +2315,6 @@ namespace Emby.Server.Implementations.Library
{
var comparer = Comparers.FirstOrDefault(c => name == c.Type);
- // User-dependent comparers (IUserBaseItemComparer) need a User. With no user
- // (anonymous/API-key /Items requests), a user-dependent sort key is a caller contract
- // violation — throw rather than silently falling back to a different key.
if (comparer is IUserBaseItemComparer)
{
if (user is null)
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerSortTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerSortTests.cs
index 9cec9d6736..65ec41291d 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerSortTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerSortTests.cs
@@ -36,8 +36,6 @@ public class LibraryManagerSortTests
new Audio { Name = "Alpha", SortName = "Alpha", Id = Guid.NewGuid() },
};
- // A user-dependent sort key with no user is a caller contract violation — throw,
- // don't silently fall back to a different key (review feedback on #17395).
Assert.Throws(() => libraryManager.Sort(
items,
user: null,
@@ -47,16 +45,9 @@ public class LibraryManagerSortTests
[Fact]
public void Sort_DateLastContentAdded_NullUser_OrdersByDateNotSortName()
{
- // DateLastMediaAddedComparer does not use User (its GetDate is static), so it must NOT be
- // treated as a user-dependent comparer: with no user it should still sort by date, not fall
- // back to SortName.
var libraryManager = CreateLibraryManager(
new IBaseItemComparer[] { new DateLastMediaAddedComparer(), new SortNameComparer() });
- // Names are chosen so date-descending and SortName-descending DISAGREE: Alpha is newest
- // (date-desc rank 1), but Zulu sorts last alphabetically (SortName-desc rank 1). If the
- // comparer were still tagged IUserBaseItemComparer, the null-user SortName fallback would
- // return [Zulu, Mike, Alpha] and this assertion would fail.
BaseItem[] items =
{
MakeFolder("Alpha", new DateTime(2026, 1, 1)),
@@ -69,7 +60,6 @@ public class LibraryManagerSortTests
user: null,
new[] { (ItemSortBy.DateLastContentAdded, SortOrder.Descending) }).ToArray();
- // Descending by date => newest first: Alpha, Mike, Zulu. (SortName-desc would be Zulu, Mike, Alpha.)
Assert.Equal(new[] { "Alpha", "Mike", "Zulu" }, sorted.Select(i => i.Name));
}
@@ -82,8 +72,6 @@ public class LibraryManagerSortTests
fixture.Register(() => new NamingOptions());
var configMock = fixture.Freeze>();
configMock.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data");
- // BaseItem.SortName/CreateSortName dereference this static; set it so SortName-fallback
- // paths don't NRE in-process (mirrors AudioResolverTests in the sibling test project).
BaseItem.ConfigurationManager ??= configMock.Object;
var itemRepository = fixture.Freeze>();
itemRepository.Setup(i => i.RetrieveItem(It.IsAny())).Returns(null);
--
cgit v1.2.3