Backport pull request #17950 from jellyfin/release-12.z

Fix collection creation when the collections library was just added

Original-merge: 3c698bab7f

Merged-by: crobibero <cody@robibe.ro>

Backported-by: Cody Robibero <cody@robibe.ro>
This commit is contained in:
Shadowghost authored and Cody Robibero committed 2026-09-15 11:16:09 -04:00
1 parent 8c70920673
commit 6a317f45ce
3 files changed
+78 -2

No files matched your search

@@ -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);
@@ -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;
@@ -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));
}
}