From 28be079779d4508b5ab2a2fd4b482f3fd929ced5 Mon Sep 17 00:00:00 2001 From: orut34iop Date: Tue, 15 Sep 2026 11:16:56 -0400 Subject: Backport pull request #18006 from jellyfin/release-12.z Preserve active scans when requesting a background library refresh Original-merge: 949e0585a65a91f90644a2475c1e1009e5d51e90 Merged-by: crobibero Backported-by: Cody Robibero --- .../Library/LibraryManager.cs | 19 ++++---- .../Controllers/LibraryStructureController.cs | 6 +-- .../Recordings/RecordingsManager.cs | 2 +- .../Library/LibraryManagerScanTests.cs | 55 ++++++++++++++++++++++ 4 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerScanTests.cs diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index f97cc7a0a0..2b3ccd5c99 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -318,7 +318,7 @@ namespace Emby.Server.Implementations.Library if (wizardChanged) { - _taskManager.CancelIfRunningAndQueue(); + QueueLibraryScan(); } } @@ -3799,7 +3799,7 @@ namespace Emby.Server.Implementations.Library if (refreshLibrary) { - StartScanInBackground(); + _ = StartScanInBackground(); } else { @@ -3867,13 +3867,16 @@ namespace Emby.Server.Implementations.Library } } - private void StartScanInBackground() + internal Task StartScanInBackground() { - Task.Run(() => + // An active scan already handles library structure changes, so this request can be dropped. + if (IsScanRunning) { - // No need to start if scanning the library because it will handle it - ValidateMediaLibrary(new Progress(), CancellationToken.None); - }); + return Task.CompletedTask; + } + + // Queue instead of restarting so a scan that starts after the check is allowed to finish. + return Task.Run(QueueLibraryScan); } public void AddMediaPath(string virtualFolderName, MediaPathInfo mediaPath) @@ -3984,7 +3987,7 @@ namespace Emby.Server.Implementations.Library { await ValidateTopLibraryFolders(CancellationToken.None, true).ConfigureAwait(false); - StartScanInBackground(); + _ = StartScanInBackground(); } else { diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs index 65bfe25d21..7f63e410a5 100644 --- a/Jellyfin.Api/Controllers/LibraryStructureController.cs +++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs @@ -213,7 +213,7 @@ public class LibraryStructureController : BaseJellyfinApiController { _libraryManager.ClearIgnoreRuleCache(); // We don't know if this one can be validated individually, trigger a new validation - await _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None).ConfigureAwait(false); + _libraryManager.QueueLibraryScan(); } _libraryManager.ClearIgnoreRuleCache(); @@ -260,7 +260,7 @@ public class LibraryStructureController : BaseJellyfinApiController // No need to start if scanning the library because it will handle it if (refreshLibrary) { - await _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None).ConfigureAwait(false); + _libraryManager.QueueLibraryScan(); } else { @@ -327,7 +327,7 @@ public class LibraryStructureController : BaseJellyfinApiController // No need to start if scanning the library because it will handle it if (refreshLibrary) { - await _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None).ConfigureAwait(false); + _libraryManager.QueueLibraryScan(); } else { diff --git a/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs b/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs index 62a06370da..8a5aeccba5 100644 --- a/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs +++ b/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs @@ -286,7 +286,7 @@ public sealed class RecordingsManager : IRecordingsManager, IDisposable if (requiresRefresh) { - await _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None).ConfigureAwait(false); + _libraryManager.QueueLibraryScan(); } } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerScanTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerScanTests.cs new file mode 100644 index 0000000000..6d0c491382 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerScanTests.cs @@ -0,0 +1,55 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AutoFixture; +using AutoFixture.AutoMoq; +using Emby.Naming.Common; +using Emby.Server.Implementations.ScheduledTasks.Tasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Tasks; +using Moq; +using Xunit; +using ServerLibraryManager = Emby.Server.Implementations.Library.LibraryManager; + +namespace Jellyfin.Server.Implementations.Tests.Library; + +public class LibraryManagerScanTests +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task StartScanInBackground_QueuesOnlyWhenIdle(bool scanRunning) + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + fixture.Register(() => new NamingOptions()); + var configuration = fixture.Freeze>(); + configuration.Setup(c => c.Configuration).Returns(new ServerConfiguration()); + configuration.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data"); + var tasks = fixture.Freeze>(); + var manager = fixture.Create(); + typeof(ServerLibraryManager).GetProperty(nameof(ServerLibraryManager.IsScanRunning))!.SetValue(manager, scanRunning); + + await manager.StartScanInBackground().ConfigureAwait(true); + + tasks.Verify(t => t.QueueScheduledTask(), scanRunning ? Times.Never() : Times.Once()); + tasks.Verify(t => t.CancelIfRunningAndQueue(), Times.Never()); + } + + [Fact] + public async Task ValidateMediaLibrary_RestartsScheduledScan() + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + fixture.Register(() => new NamingOptions()); + var configuration = fixture.Freeze>(); + configuration.Setup(c => c.Configuration).Returns(new ServerConfiguration()); + configuration.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data"); + var tasks = fixture.Freeze>(); + var manager = fixture.Create(); + + await manager.ValidateMediaLibrary(new Progress(), CancellationToken.None).ConfigureAwait(true); + + tasks.Verify(t => t.CancelIfRunningAndQueue(), Times.Once()); + tasks.Verify(t => t.QueueScheduledTask(), Times.Never()); + } +} -- cgit v1.2.3