aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authororut34iop <orut34iop@users.noreply.github.com>2026-09-15 11:16:56 -0400
committerCody Robibero <cody@robibe.ro>2026-09-15 11:16:56 -0400
commit28be079779d4508b5ab2a2fd4b482f3fd929ced5 (patch)
tree11d98fbe9a6265883869342d92ec6e0d40dbb572
parent5a3a5a26884254bfc7d12beff038ef8f1a89a3cf (diff)
Backport pull request #18006 from jellyfin/release-12.z
Preserve active scans when requesting a background library refresh Original-merge: 949e0585a65a91f90644a2475c1e1009e5d51e90 Merged-by: crobibero <cody@robibe.ro> Backported-by: Cody Robibero <cody@robibe.ro>
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs19
-rw-r--r--Jellyfin.Api/Controllers/LibraryStructureController.cs6
-rw-r--r--src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManagerScanTests.cs55
4 files changed, 70 insertions, 12 deletions
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<RefreshMediaLibraryTask>();
+ 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<double>(), 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<double>(), 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<double>(), 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<double>(), 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<double>(), 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<Mock<IServerConfigurationManager>>();
+ configuration.Setup(c => c.Configuration).Returns(new ServerConfiguration());
+ configuration.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data");
+ var tasks = fixture.Freeze<Mock<ITaskManager>>();
+ var manager = fixture.Create<ServerLibraryManager>();
+ typeof(ServerLibraryManager).GetProperty(nameof(ServerLibraryManager.IsScanRunning))!.SetValue(manager, scanRunning);
+
+ await manager.StartScanInBackground().ConfigureAwait(true);
+
+ tasks.Verify(t => t.QueueScheduledTask<RefreshMediaLibraryTask>(), scanRunning ? Times.Never() : Times.Once());
+ tasks.Verify(t => t.CancelIfRunningAndQueue<RefreshMediaLibraryTask>(), Times.Never());
+ }
+
+ [Fact]
+ public async Task ValidateMediaLibrary_RestartsScheduledScan()
+ {
+ var fixture = new Fixture().Customize(new AutoMoqCustomization());
+ fixture.Register(() => new NamingOptions());
+ var configuration = fixture.Freeze<Mock<IServerConfigurationManager>>();
+ configuration.Setup(c => c.Configuration).Returns(new ServerConfiguration());
+ configuration.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data");
+ var tasks = fixture.Freeze<Mock<ITaskManager>>();
+ var manager = fixture.Create<ServerLibraryManager>();
+
+ await manager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None).ConfigureAwait(true);
+
+ tasks.Verify(t => t.CancelIfRunningAndQueue<RefreshMediaLibraryTask>(), Times.Once());
+ tasks.Verify(t => t.QueueScheduledTask<RefreshMediaLibraryTask>(), Times.Never());
+ }
+}