aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShadowghost <Shadowghost@users.noreply.github.com>2026-09-15 11:16:09 -0400
committerCody Robibero <cody@robibe.ro>2026-09-15 11:16:09 -0400
commit6a317f45ce5def14b4feccc4ae84f2e27441b997 (patch)
treecf379969b969517367c181531fecb16bf639baeb
parent8c70920673ba82f2fec940574add6d65aa21be5e (diff)
Backport pull request #17950 from jellyfin/release-12.z
Fix collection creation when the collections library was just added Original-merge: 3c698bab7fb7c607e726a0bf79f0bb0f7616e962 Merged-by: crobibero <cody@robibe.ro> Backported-by: Cody Robibero <cody@robibe.ro>
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs4
-rw-r--r--MediaBrowser.Controller/Entities/AggregateFolder.cs17
-rw-r--r--tests/Jellyfin.Controller.Tests/Entities/AggregateFolderTests.cs59
3 files changed, 78 insertions, 2 deletions
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 84d50f5121..9567a29a98 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -114,7 +114,7 @@ namespace Emby.Server.Implementations.Collections
_libraryManager.RootFolder.Children = null;
- return FindFolders(path).First();
+ return FindFolders(path).FirstOrDefault();
}
internal string GetCollectionsFolderPath()
@@ -167,7 +167,7 @@ namespace Emby.Server.Implementations.Collections
if (parentFolder is null)
{
- throw new ArgumentException(nameof(parentFolder));
+ throw new InvalidOperationException("Unable to resolve the collections library folder, so the collection cannot be created.");
}
var path = Path.Combine(parentFolder.Path, folderName);
diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs
index a02802f41e..bb770fc6f3 100644
--- a/MediaBrowser.Controller/Entities/AggregateFolder.cs
+++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs
@@ -54,6 +54,23 @@ namespace MediaBrowser.Controller.Entities
public string[] PhysicalLocationsList { get; set; }
+ // Children caches the resolved items, _childrenIds the ids they were loaded from. Clearing
+ // only the former sends the next read back through LoadChildren, which replays the stale
+ // id list, so a caller invalidating this folder has to drop both.
+ public override IEnumerable<BaseItem> Children
+ {
+ get => base.Children;
+ set
+ {
+ if (value is null)
+ {
+ ClearCache();
+ }
+
+ base.Children = value;
+ }
+ }
+
public override bool CanDelete()
{
return false;
diff --git a/tests/Jellyfin.Controller.Tests/Entities/AggregateFolderTests.cs b/tests/Jellyfin.Controller.Tests/Entities/AggregateFolderTests.cs
new file mode 100644
index 0000000000..6c297673d4
--- /dev/null
+++ b/tests/Jellyfin.Controller.Tests/Entities/AggregateFolderTests.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Controller.Tests.Entities;
+
+public class AggregateFolderTests
+{
+ [Fact]
+ public void Children_ClearedAfterALibraryWasAdded_ListsTheNewLibrary()
+ {
+ var existing = new Folder { Id = Guid.NewGuid(), Path = "/libraries/movies" };
+ var added = new Folder { Id = Guid.NewGuid(), Path = "/libraries/collections" };
+
+ // What the repository holds grows once the new library has been resolved and stored.
+ var stored = new List<BaseItem> { existing };
+
+ var itemRepository = new Mock<IItemRepository>();
+ itemRepository.Setup(x => x.GetItemList(It.IsAny<InternalItemsQuery>()))
+ .Returns(() => stored.ToList());
+
+ var libraryManager = new Mock<ILibraryManager>();
+ libraryManager.Setup(x => x.GetItemById(It.IsAny<Guid>()))
+ .Returns((Guid id) => stored.Find(i => i.Id.Equals(id)));
+
+ BaseItem.ItemRepository = itemRepository.Object;
+ BaseItem.LibraryManager = libraryManager.Object;
+
+ var root = new AggregateFolder { Id = Guid.NewGuid(), Path = "/libraries" };
+
+ Assert.Equal([existing.Id], root.Children.Select(i => i.Id));
+
+ stored.Add(added);
+ root.Children = null;
+
+ // Null-forgiving: the setter takes null to mean "drop the cache", the getter reloads.
+ Assert.Equal([existing.Id, added.Id], root.Children!.Select(i => i.Id));
+ }
+
+ [Fact]
+ public void Children_AssignedASet_KeepsThatSet()
+ {
+ var itemRepository = new Mock<IItemRepository>(MockBehavior.Strict);
+ BaseItem.ItemRepository = itemRepository.Object;
+
+ var assigned = new Folder { Id = Guid.NewGuid(), Path = "/libraries/movies" };
+ var root = new AggregateFolder { Id = Guid.NewGuid(), Path = "/libraries" };
+
+ root.Children = [assigned];
+
+ // Never goes to the repository, so the strict mock stays unused.
+ Assert.Equal([assigned.Id], root.Children.Select(i => i.Id));
+ }
+}