aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/IO
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations/IO')
-rw-r--r--Emby.Server.Implementations/IO/FileRefresher.cs27
-rw-r--r--Emby.Server.Implementations/IO/LibraryMonitor.cs39
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs13
3 files changed, 69 insertions, 10 deletions
diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs
index f634084034..b31cf0f1f5 100644
--- a/Emby.Server.Implementations/IO/FileRefresher.cs
+++ b/Emby.Server.Implementations/IO/FileRefresher.cs
@@ -109,6 +109,11 @@ namespace Emby.Server.Implementations.IO
lock (_timerLock)
{
+ if (_disposed)
+ {
+ return;
+ }
+
paths = _affectedPaths.ToList();
}
@@ -129,11 +134,12 @@ namespace Emby.Server.Implementations.IO
private void ProcessPathChanges(List<string> paths)
{
- IEnumerable<BaseItem> itemsToRefresh = paths
+ var itemsToRefresh = paths
.Distinct()
- .Select(GetAffectedBaseItem)
- .Where(item => item is not null)
- .DistinctBy(x => x!.Id)!; // Removed null values in the previous .Where()
+ .Select(TryGetAffectedBaseItem)
+ .OfType<BaseItem>()
+ .DistinctBy(x => x.Id)
+ .ToList();
foreach (var item in itemsToRefresh)
{
@@ -155,6 +161,19 @@ namespace Emby.Server.Implementations.IO
}
}
+ private BaseItem? TryGetAffectedBaseItem(string path)
+ {
+ try
+ {
+ return GetAffectedBaseItem(path);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error finding the item affected by changes to {Path}", path);
+ return null;
+ }
+ }
+
/// <summary>
/// Gets the affected base item.
/// </summary>
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index 1bf0f8c76c..e51c863f86 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -3,11 +3,13 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Library;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -21,6 +23,7 @@ namespace Emby.Server.Implementations.IO
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
+ private readonly IDirectoryService _directoryService;
private readonly DotIgnoreIgnoreRule _dotIgnoreIgnoreRule;
/// <summary>
@@ -38,6 +41,12 @@ namespace Emby.Server.Implementations.IO
/// </summary>
private readonly ConcurrentDictionary<string, string> _tempIgnoredPaths = new(StringComparer.OrdinalIgnoreCase);
+ /// <summary>
+ /// Incremented by every <see cref="Stop"/> so watchers still being created on a background
+ /// task can tell that the sweep they should have been caught by has already run.
+ /// </summary>
+ private int _watcherGeneration;
+
private bool _disposed;
/// <summary>
@@ -47,6 +56,7 @@ namespace Emby.Server.Implementations.IO
/// <param name="libraryManager">The library manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="fileSystem">The filesystem.</param>
+ /// <param name="directoryService">The directory service.</param>
/// <param name="appLifetime">The <see cref="IHostApplicationLifetime"/>.</param>
/// <param name="dotIgnoreIgnoreRule">The .ignore rule handler.</param>
public LibraryMonitor(
@@ -54,6 +64,7 @@ namespace Emby.Server.Implementations.IO
ILibraryManager libraryManager,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
+ IDirectoryService directoryService,
IHostApplicationLifetime appLifetime,
DotIgnoreIgnoreRule dotIgnoreIgnoreRule)
{
@@ -61,10 +72,11 @@ namespace Emby.Server.Implementations.IO
_logger = logger;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
+ _directoryService = directoryService;
_dotIgnoreIgnoreRule = dotIgnoreIgnoreRule;
appLifetime.ApplicationStarted.Register(Start);
- appLifetime.ApplicationStopping.Register(Stop);
+ appLifetime.ApplicationStopping.Register(Dispose);
}
/// <inheritdoc />
@@ -115,6 +127,11 @@ namespace Emby.Server.Implementations.IO
/// <inheritdoc />
public void Start()
{
+ if (_disposed)
+ {
+ return;
+ }
+
_libraryManager.ItemAdded += OnLibraryManagerItemAdded;
_libraryManager.ItemRemoved += OnLibraryManagerItemRemoved;
@@ -228,6 +245,8 @@ namespace Emby.Server.Implementations.IO
return;
}
+ var generation = Volatile.Read(ref _watcherGeneration);
+
// Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel
Task.Run(() =>
{
@@ -251,7 +270,11 @@ namespace Emby.Server.Implementations.IO
newWatcher.Changed += OnWatcherChanged;
newWatcher.Error += OnWatcherError;
- if (_fileSystemWatchers.TryAdd(path, newWatcher))
+ if (_disposed || Volatile.Read(ref _watcherGeneration) != generation)
+ {
+ DisposeWatcher(newWatcher, false);
+ }
+ else if (_fileSystemWatchers.TryAdd(path, newWatcher))
{
newWatcher.EnableRaisingEvents = true;
_logger.LogInformation("Watching directory {Path}", path);
@@ -352,6 +375,11 @@ namespace Emby.Server.Implementations.IO
{
ArgumentException.ThrowIfNullOrEmpty(path);
+ if (_disposed)
+ {
+ return;
+ }
+
if (IgnorePatterns.ShouldIgnore(path))
{
return;
@@ -363,6 +391,8 @@ namespace Emby.Server.Implementations.IO
return;
}
+ _directoryService.Invalidate(path);
+
// Ignore certain files, If the parent of an ignored path has a change event, ignore that too
foreach (var i in _tempIgnoredPaths.Keys)
{
@@ -445,6 +475,8 @@ namespace Emby.Server.Implementations.IO
/// </summary>
public void Stop()
{
+ Interlocked.Increment(ref _watcherGeneration);
+
_libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
_libraryManager.ItemRemoved -= OnLibraryManagerItemRemoved;
@@ -489,8 +521,9 @@ namespace Emby.Server.Implementations.IO
return;
}
- Stop();
+ // Set before stopping so anything racing us stops handing out new work.
_disposed = true;
+ Stop();
}
}
}
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index ede9b27592..db743c8d31 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -489,11 +489,18 @@ namespace Emby.Server.Implementations.IO
ArgumentException.ThrowIfNullOrEmpty(parentPath);
ArgumentException.ThrowIfNullOrEmpty(path);
- return path.Contains(
- Path.TrimEndingDirectorySeparator(parentPath) + Path.DirectorySeparatorChar,
- _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+ var parent = Path.TrimEndingDirectorySeparator(parentPath);
+
+ // The parent has to be an anchored prefix of the path, otherwise unrelated paths that merely
+ // contain the parent as a segment (e.g. /media and /data/media/tv) would be treated as related.
+ return path.Length > parent.Length
+ && path.StartsWith(parent, _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)
+ && (Path.EndsInDirectorySeparator(parent) || IsDirectorySeparator(path[parent.Length]));
}
+ private static bool IsDirectorySeparator(char c)
+ => c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
+
/// <inheritdoc />
public virtual bool AreEqual(string path1, string path2)
{