From a2aa3d96d2a75b01f94354b541fe890b78e7c8bd Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 15 Sep 2026 11:15:49 -0400 Subject: Backport pull request #17884 from jellyfin/release-12.z Release a folder's children once its subtree has been scanned Original-merge: 2f2adae7d99537e53417f6d92aa3d40345a17b99 Merged-by: crobibero Backported-by: Cody Robibero --- .../Entities/Audio/MusicAlbum.cs | 12 ++++++++ MediaBrowser.Controller/Entities/Folder.cs | 33 +++++++++++++++++++++- MediaBrowser.Controller/Entities/TV/Series.cs | 13 +++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index d016d8f62b..281a98dad5 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -164,6 +164,18 @@ namespace MediaBrowser.Controller.Entities.Audio } public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress progress, CancellationToken cancellationToken) + { + try + { + await RefreshAllMetadataInternal(refreshOptions, progress, cancellationToken).ConfigureAwait(false); + } + finally + { + ReleaseCachedChildren(); + } + } + + private async Task RefreshAllMetadataInternal(MetadataRefreshOptions refreshOptions, IProgress progress, CancellationToken cancellationToken) { var items = GetRecursiveChildren(); diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index b3e7369205..25e19c8351 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -286,6 +286,27 @@ namespace MediaBrowser.Controller.Entities return GetCachedChildren(); } + /// + /// Drops the children this folder has materialised, and the ones held by every folder below + /// it, without loading anything that is not already in memory. + /// + public void ReleaseCachedChildren() + { + // Cleared before descending, so a folder already on the way down is not walked twice. + var children = _children; + _children = null; + + if (children is null) + { + return; + } + + foreach (var child in children) + { + (child as Folder)?.ReleaseCachedChildren(); + } + } + public override double? GetRefreshProgress() { return ProviderManager.GetRefreshProgress(Id); @@ -370,6 +391,9 @@ namespace MediaBrowser.Controller.Entities { ProviderManager.OnRefreshComplete(this); } + + // The subtree is done with, so stop holding it. + ReleaseCachedChildren(); } } @@ -835,7 +859,14 @@ namespace MediaBrowser.Controller.Entities if (recursive && child is Folder folder) { folder.Children = null; // invalidate cached children. - await folder.RefreshMetadataRecursive(folder.Children.Except([this, child]).ToList(), refreshOptions, true, progress, cancellationToken).ConfigureAwait(false); + try + { + await folder.RefreshMetadataRecursive(folder.Children.Except([this, child]).ToList(), refreshOptions, true, progress, cancellationToken).ConfigureAwait(false); + } + finally + { + folder.ReleaseCachedChildren(); + } } } } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 1a1da84b7a..126f4361ba 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -333,6 +333,19 @@ namespace MediaBrowser.Controller.Entities.TV public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress progress, CancellationToken cancellationToken) { Children = null; // invalidate cached children. + + try + { + await RefreshAllMetadataInternal(refreshOptions, progress, cancellationToken).ConfigureAwait(false); + } + finally + { + ReleaseCachedChildren(); + } + } + + private async Task RefreshAllMetadataInternal(MetadataRefreshOptions refreshOptions, IProgress progress, CancellationToken cancellationToken) + { // Refresh bottom up, seasons and episodes first, then the series var items = GetRecursiveChildren(); -- cgit v1.2.3