Jelajahi Sumber

Created ILibraryMonitor to replace IDirectoryWatchers

Luke Pulverenti 11 tahun lalu
induk
melakukan
7c5b222463

+ 13 - 24
MediaBrowser.Api/Library/LibraryStructureService.cs

@@ -187,7 +187,7 @@ namespace MediaBrowser.Api.Library
         /// </summary>
         private readonly ILibraryManager _libraryManager;
 
-        private readonly IDirectoryWatchers _directoryWatchers;
+        private readonly ILibraryMonitor _libraryMonitor;
 
         private readonly IFileSystem _fileSystem;
         private readonly ILogger _logger;
@@ -199,7 +199,7 @@ namespace MediaBrowser.Api.Library
         /// <param name="userManager">The user manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <exception cref="System.ArgumentNullException">appPaths</exception>
-        public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger)
+        public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger)
         {
             if (appPaths == null)
             {
@@ -209,7 +209,7 @@ namespace MediaBrowser.Api.Library
             _userManager = userManager;
             _appPaths = appPaths;
             _libraryManager = libraryManager;
-            _directoryWatchers = directoryWatchers;
+            _libraryMonitor = libraryMonitor;
             _fileSystem = fileSystem;
             _logger = logger;
         }
@@ -270,8 +270,7 @@ namespace MediaBrowser.Api.Library
                 throw new ArgumentException("There is already a media collection with the name " + name + ".");
             }
 
-            _directoryWatchers.Stop();
-            _directoryWatchers.TemporarilyIgnore(virtualFolderPath);
+            _libraryMonitor.Stop();
 
             try
             {
@@ -294,10 +293,8 @@ namespace MediaBrowser.Api.Library
                 // No need to start if scanning the library because it will handle it
                 if (!request.RefreshLibrary)
                 {
-                    _directoryWatchers.Start();
+                    _libraryMonitor.Start();
                 }
-
-                _directoryWatchers.RemoveTempIgnore(virtualFolderPath);
             }
 
             if (request.RefreshLibrary)
@@ -348,9 +345,7 @@ namespace MediaBrowser.Api.Library
                 throw new ArgumentException("There is already a media collection with the name " + newPath + ".");
             }
 
-            _directoryWatchers.Stop();
-            _directoryWatchers.TemporarilyIgnore(currentPath);
-            _directoryWatchers.TemporarilyIgnore(newPath);
+            _libraryMonitor.Stop();
 
             try
             {
@@ -376,11 +371,8 @@ namespace MediaBrowser.Api.Library
                 // No need to start if scanning the library because it will handle it
                 if (!request.RefreshLibrary)
                 {
-                    _directoryWatchers.Start();
+                    _libraryMonitor.Start();
                 }
-
-                _directoryWatchers.RemoveTempIgnore(currentPath);
-                _directoryWatchers.RemoveTempIgnore(newPath);
             }
 
             if (request.RefreshLibrary)
@@ -420,8 +412,7 @@ namespace MediaBrowser.Api.Library
                 throw new DirectoryNotFoundException("The media folder does not exist");
             }
 
-            _directoryWatchers.Stop();
-            _directoryWatchers.TemporarilyIgnore(path);
+            _libraryMonitor.Stop();
 
             try
             {
@@ -437,10 +428,8 @@ namespace MediaBrowser.Api.Library
                 // No need to start if scanning the library because it will handle it
                 if (!request.RefreshLibrary)
                 {
-                    _directoryWatchers.Start();
+                    _libraryMonitor.Start();
                 }
-
-                _directoryWatchers.RemoveTempIgnore(path);
             }
 
             if (request.RefreshLibrary)
@@ -460,7 +449,7 @@ namespace MediaBrowser.Api.Library
                 throw new ArgumentNullException("request");
             }
 
-            _directoryWatchers.Stop();
+            _libraryMonitor.Stop();
 
             try
             {
@@ -485,7 +474,7 @@ namespace MediaBrowser.Api.Library
                 // No need to start if scanning the library because it will handle it
                 if (!request.RefreshLibrary)
                 {
-                    _directoryWatchers.Start();
+                    _libraryMonitor.Start();
                 }
             }
 
@@ -506,7 +495,7 @@ namespace MediaBrowser.Api.Library
                 throw new ArgumentNullException("request");
             }
 
-            _directoryWatchers.Stop();
+            _libraryMonitor.Stop();
 
             try
             {
@@ -531,7 +520,7 @@ namespace MediaBrowser.Api.Library
                 // No need to start if scanning the library because it will handle it
                 if (!request.RefreshLibrary)
                 {
-                    _directoryWatchers.Start();
+                    _libraryMonitor.Start();
                 }
             }
 

+ 0 - 29
MediaBrowser.Controller/IO/IDirectoryWatchers.cs

@@ -1,29 +0,0 @@
-using System;
-
-namespace MediaBrowser.Controller.IO
-{
-    public interface IDirectoryWatchers : IDisposable
-    {
-        /// <summary>
-        /// Add the path to our temporary ignore list.  Use when writing to a path within our listening scope.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        void TemporarilyIgnore(string path);
-
-        /// <summary>
-        /// Removes the temp ignore.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        void RemoveTempIgnore(string path);
-
-        /// <summary>
-        /// Starts this instance.
-        /// </summary>
-        void Start();
-
-        /// <summary>
-        /// Stops this instance.
-        /// </summary>
-        void Stop();
-    }
-}

+ 36 - 0
MediaBrowser.Controller/Library/ILibraryMonitor.cs

@@ -0,0 +1,36 @@
+using System;
+
+namespace MediaBrowser.Controller.Library
+{
+    public interface ILibraryMonitor : IDisposable
+    {
+        /// <summary>
+        /// Starts this instance.
+        /// </summary>
+        void Start();
+
+        /// <summary>
+        /// Stops this instance.
+        /// </summary>
+        void Stop();
+
+        /// <summary>
+        /// Reports the file system change beginning.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        void ReportFileSystemChangeBeginning(string path);
+
+        /// <summary>
+        /// Reports the file system change complete.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="refreshPath">if set to <c>true</c> [refresh path].</param>
+        void ReportFileSystemChangeComplete(string path, bool refreshPath);
+
+        /// <summary>
+        /// Reports the file system changed.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        void ReportFileSystemChanged(string path);
+    }
+}

+ 1 - 1
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -182,7 +182,7 @@
     <Compile Include="Entities\Video.cs" />
     <Compile Include="Entities\CollectionFolder.cs" />
     <Compile Include="Entities\Year.cs" />
-    <Compile Include="IO\IDirectoryWatchers.cs" />
+    <Compile Include="Library\ILibraryMonitor.cs" />
     <Compile Include="IServerApplicationHost.cs" />
     <Compile Include="IServerApplicationPaths.cs" />
     <Compile Include="Library\SearchHintInfo.cs" />

+ 2 - 2
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -165,7 +165,7 @@ namespace MediaBrowser.Model.Configuration
         /// different directories and files.
         /// </summary>
         /// <value>The file watcher delay.</value>
-        public int FileWatcherDelay { get; set; }
+        public int RealtimeWatcherDelay { get; set; }
 
         /// <summary>
         /// Gets or sets a value indicating whether [enable dashboard response caching].
@@ -250,7 +250,7 @@ namespace MediaBrowser.Model.Configuration
             MaxResumePct = 90;
             MinResumeDurationSeconds = Convert.ToInt32(TimeSpan.FromMinutes(5).TotalSeconds);
 
-            FileWatcherDelay = 8;
+            RealtimeWatcherDelay = 20;
 
             RecentItemDays = 10;
 

+ 11 - 11
MediaBrowser.Providers/Manager/ImageSaver.cs

@@ -3,7 +3,7 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
@@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager
         /// <summary>
         /// The _directory watchers
         /// </summary>
-        private readonly IDirectoryWatchers _directoryWatchers;
+        private readonly ILibraryMonitor _libraryMonitor;
         private readonly IFileSystem _fileSystem;
         private readonly ILogger _logger;
 
@@ -44,11 +44,11 @@ namespace MediaBrowser.Providers.Manager
         /// Initializes a new instance of the <see cref="ImageSaver"/> class.
         /// </summary>
         /// <param name="config">The config.</param>
-        /// <param name="directoryWatchers">The directory watchers.</param>
-        public ImageSaver(IServerConfigurationManager config, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem, ILogger logger)
+        /// <param name="libraryMonitor">The directory watchers.</param>
+        public ImageSaver(IServerConfigurationManager config, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, ILogger logger)
         {
             _config = config;
-            _directoryWatchers = directoryWatchers;
+            _libraryMonitor = libraryMonitor;
             _fileSystem = fileSystem;
             _logger = logger;
             _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath);
@@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.Manager
             // Delete the current path
             if (!string.IsNullOrEmpty(currentPath) && !paths.Contains(currentPath, StringComparer.OrdinalIgnoreCase))
             {
-                _directoryWatchers.TemporarilyIgnore(currentPath);
+                _libraryMonitor.ReportFileSystemChangeBeginning(currentPath);
 
                 try
                 {
@@ -179,7 +179,7 @@ namespace MediaBrowser.Providers.Manager
                 }
                 finally
                 {
-                    _directoryWatchers.RemoveTempIgnore(currentPath);
+                    _libraryMonitor.ReportFileSystemChangeComplete(currentPath, false);
                 }
             }
         }
@@ -197,8 +197,8 @@ namespace MediaBrowser.Providers.Manager
 
             var parentFolder = Path.GetDirectoryName(path);
 
-            _directoryWatchers.TemporarilyIgnore(path);
-            _directoryWatchers.TemporarilyIgnore(parentFolder);
+            _libraryMonitor.ReportFileSystemChangeBeginning(path);
+            _libraryMonitor.ReportFileSystemChangeBeginning(parentFolder);
 
             try
             {
@@ -223,8 +223,8 @@ namespace MediaBrowser.Providers.Manager
             }
             finally
             {
-                _directoryWatchers.RemoveTempIgnore(path);
-                _directoryWatchers.RemoveTempIgnore(parentFolder);
+                _libraryMonitor.ReportFileSystemChangeComplete(path, false);
+                _libraryMonitor.ReportFileSystemChangeComplete(parentFolder, false);
             }
         }
 

+ 1 - 17
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -38,7 +38,6 @@ namespace MediaBrowser.Providers.Manager
         public void AddParts(IEnumerable<IMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders)
         {
             _providers = providers.OfType<IMetadataProvider<TItemType>>()
-                .OrderBy(GetSortOrder)
                 .ToArray();
 
             _imageProviders = imageProviders.OrderBy(i => i.Order).ToArray();
@@ -179,21 +178,6 @@ namespace MediaBrowser.Providers.Manager
             return providers;
         }
 
-        /// <summary>
-        /// Gets the sort order.
-        /// </summary>
-        /// <param name="provider">The provider.</param>
-        /// <returns>System.Int32.</returns>
-        protected virtual int GetSortOrder(IMetadataProvider<TItemType> provider)
-        {
-            if (provider is IRemoteMetadataProvider)
-            {
-                return 1;
-            }
-
-            return 0;
-        }
-
         /// <summary>
         /// Determines whether this instance can refresh the specified provider.
         /// </summary>
@@ -217,7 +201,7 @@ namespace MediaBrowser.Providers.Manager
 
         protected abstract Task SaveItem(TItemType item, ItemUpdateType reason, CancellationToken cancellationToken);
 
-        protected virtual ItemId GetId(TItemType item)
+        protected virtual ItemId GetId(IHasMetadata item)
         {
             return new ItemId
             {

+ 8 - 8
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager
         /// <summary>
         /// The _directory watchers
         /// </summary>
-        private readonly IDirectoryWatchers _directoryWatchers;
+        private readonly ILibraryMonitor _libraryMonitor;
 
         /// <summary>
         /// Gets or sets the configuration manager.
@@ -57,23 +57,23 @@ namespace MediaBrowser.Providers.Manager
 
         private readonly IItemRepository _itemRepo;
 
-        private IMetadataService[] _metadataServices = {};
+        private IMetadataService[] _metadataServices = { };
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ProviderManager" /> class.
         /// </summary>
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="directoryWatchers">The directory watchers.</param>
+        /// <param name="libraryMonitor">The directory watchers.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="fileSystem">The file system.</param>
         /// <param name="itemRepo">The item repo.</param>
-        public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, IDirectoryWatchers directoryWatchers, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo)
+        public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IItemRepository itemRepo)
         {
             _logger = logManager.GetLogger("ProviderManager");
             _httpClient = httpClient;
             ConfigurationManager = configurationManager;
-            _directoryWatchers = directoryWatchers;
+            _libraryMonitor = libraryMonitor;
             _fileSystem = fileSystem;
             _itemRepo = itemRepo;
         }
@@ -315,7 +315,7 @@ namespace MediaBrowser.Providers.Manager
             }
 
             //Tell the watchers to ignore
-            _directoryWatchers.TemporarilyIgnore(path);
+            _libraryMonitor.ReportFileSystemChangeBeginning(path);
 
             if (dataToSave.CanSeek)
             {
@@ -338,7 +338,7 @@ namespace MediaBrowser.Providers.Manager
             finally
             {
                 //Remove the ignore
-                _directoryWatchers.RemoveTempIgnore(path);
+                _libraryMonitor.ReportFileSystemChangeComplete(path, false);
             }
         }
 
@@ -380,7 +380,7 @@ namespace MediaBrowser.Providers.Manager
         /// <returns>Task.</returns>
         public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken)
         {
-            return new ImageSaver(ConfigurationManager, _directoryWatchers, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken);
+            return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken);
         }
 
         /// <summary>

+ 11 - 5
MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs

@@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 {
     public class EpisodeFileOrganizer
     {
-        private readonly IDirectoryWatchers _directoryWatchers;
+        private readonly ILibraryMonitor _libraryMonitor;
         private readonly ILibraryManager _libraryManager;
         private readonly ILogger _logger;
         private readonly IFileSystem _fileSystem;
@@ -31,14 +31,14 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
-        public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers)
+        public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor)
         {
             _organizationService = organizationService;
             _config = config;
             _fileSystem = fileSystem;
             _logger = logger;
             _libraryManager = libraryManager;
-            _directoryWatchers = directoryWatchers;
+            _libraryMonitor = libraryMonitor;
         }
 
         public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, TvFileOrganizationOptions options, bool overwriteExisting)
@@ -174,6 +174,8 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 {
                     _logger.Debug("Removing duplicate episode {0}", path);
 
+                    _libraryMonitor.ReportFileSystemChangeBeginning(path);
+
                     try
                     {
                         File.Delete(path);
@@ -182,6 +184,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                     {
                         _logger.ErrorException("Error removing duplicate episode", ex, path);
                     }
+                    finally
+                    {
+                        _libraryMonitor.ReportFileSystemChangeComplete(path, true);
+                    }
                 }
             }
         }
@@ -232,7 +238,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
         private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result)
         {
-            _directoryWatchers.TemporarilyIgnore(result.TargetPath);
+            _libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath);
 
             Directory.CreateDirectory(Path.GetDirectoryName(result.TargetPath));
 
@@ -264,7 +270,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             }
             finally
             {
-                _directoryWatchers.RemoveTempIgnore(result.TargetPath);
+                _libraryMonitor.ReportFileSystemChangeComplete(result.TargetPath, true);
             }
 
             if (copy)

+ 5 - 11
MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs

@@ -21,17 +21,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
         private readonly ITaskManager _taskManager;
         private readonly IFileOrganizationRepository _repo;
         private readonly ILogger _logger;
-        private readonly IDirectoryWatchers _directoryWatchers;
+        private readonly ILibraryMonitor _libraryMonitor;
         private readonly ILibraryManager _libraryManager;
         private readonly IServerConfigurationManager _config;
         private readonly IFileSystem _fileSystem;
 
-        public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem)
+        public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem)
         {
             _taskManager = taskManager;
             _repo = repo;
             _logger = logger;
-            _directoryWatchers = directoryWatchers;
+            _libraryMonitor = libraryMonitor;
             _libraryManager = libraryManager;
             _config = config;
             _fileSystem = fileSystem;
@@ -91,13 +91,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             }
 
             var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
-                _directoryWatchers);
+                _libraryMonitor);
 
             await organizer.OrganizeEpisodeFile(result.OriginalPath, _config.Configuration.TvFileOrganizationOptions, true)
                     .ConfigureAwait(false);
-
-            await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None)
-                    .ConfigureAwait(false);
         }
 
         public Task ClearLog()
@@ -108,12 +105,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
         public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request)
         {
             var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
-                _directoryWatchers);
+                _libraryMonitor);
 
             await organizer.OrganizeWithCorrection(request, _config.Configuration.TvFileOrganizationOptions).ConfigureAwait(false);
-
-            await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None)
-                    .ConfigureAwait(false);
         }
     }
 }

+ 4 - 4
MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs

@@ -14,16 +14,16 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 {
     public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask
     {
-        private readonly IDirectoryWatchers _directoryWatchers;
+        private readonly ILibraryMonitor _libraryMonitor;
         private readonly ILibraryManager _libraryManager;
         private readonly ILogger _logger;
         private readonly IFileSystem _fileSystem;
         private readonly IServerConfigurationManager _config;
         private readonly IFileOrganizationService _organizationService;
 
-        public OrganizerScheduledTask(IDirectoryWatchers directoryWatchers, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService)
+        public OrganizerScheduledTask(ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService)
         {
-            _directoryWatchers = directoryWatchers;
+            _libraryMonitor = libraryMonitor;
             _libraryManager = libraryManager;
             _logger = logger;
             _fileSystem = fileSystem;
@@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
         public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         {
-            return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _directoryWatchers, _organizationService, _config)
+            return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config)
                 .Organize(_config.Configuration.TvFileOrganizationOptions, cancellationToken, progress);
         }
 

+ 4 - 4
MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs

@@ -18,19 +18,19 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 {
     public class TvFolderOrganizer
     {
-        private readonly IDirectoryWatchers _directoryWatchers;
+        private readonly ILibraryMonitor _libraryMonitor;
         private readonly ILibraryManager _libraryManager;
         private readonly ILogger _logger;
         private readonly IFileSystem _fileSystem;
         private readonly IFileOrganizationService _organizationService;
         private readonly IServerConfigurationManager _config;
 
-        public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IDirectoryWatchers directoryWatchers, IFileOrganizationService organizationService, IServerConfigurationManager config)
+        public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IFileOrganizationService organizationService, IServerConfigurationManager config)
         {
             _libraryManager = libraryManager;
             _logger = logger;
             _fileSystem = fileSystem;
-            _directoryWatchers = directoryWatchers;
+            _libraryMonitor = libraryMonitor;
             _organizationService = organizationService;
             _config = config;
         }
@@ -57,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 foreach (var file in eligibleFiles)
                 {
                     var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager,
-                        _directoryWatchers);
+                        _libraryMonitor);
 
                     var result = await organizer.OrganizeEpisodeFile(file.FullName, options, false).ConfigureAwait(false);
 

+ 54 - 121
MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs → MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -1,8 +1,6 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
@@ -18,10 +16,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Server.Implementations.IO
 {
-    /// <summary>
-    /// Class DirectoryWatchers
-    /// </summary>
-    public class DirectoryWatchers : IDirectoryWatchers
+    public class LibraryMonitor : ILibraryMonitor
     {
         /// <summary>
         /// The file system watchers
@@ -55,7 +50,7 @@ namespace MediaBrowser.Server.Implementations.IO
         /// Add the path to our temporary ignore list.  Use when writing to a path within our listening scope.
         /// </summary>
         /// <param name="path">The path.</param>
-        public void TemporarilyIgnore(string path)
+        private void TemporarilyIgnore(string path)
         {
             _tempIgnoredPaths[path] = path;
         }
@@ -64,7 +59,7 @@ namespace MediaBrowser.Server.Implementations.IO
         /// Removes the temp ignore.
         /// </summary>
         /// <param name="path">The path.</param>
-        public async void RemoveTempIgnore(string path)
+        private async void RemoveTempIgnore(string path)
         {
             // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. 
             // Seeing long delays in some situations, especially over the network.
@@ -75,6 +70,21 @@ namespace MediaBrowser.Server.Implementations.IO
             _tempIgnoredPaths.TryRemove(path, out val);
         }
 
+        public void ReportFileSystemChangeBeginning(string path)
+        {
+            TemporarilyIgnore(path);
+        }
+
+        public void ReportFileSystemChangeComplete(string path, bool refreshPath)
+        {
+            RemoveTempIgnore(path);
+
+            if (refreshPath)
+            {
+                ReportFileSystemChanged(path);
+            }
+        }
+
         /// <summary>
         /// Gets or sets the logger.
         /// </summary>
@@ -90,12 +100,10 @@ namespace MediaBrowser.Server.Implementations.IO
         private ILibraryManager LibraryManager { get; set; }
         private IServerConfigurationManager ConfigurationManager { get; set; }
 
-        private readonly IFileSystem _fileSystem;
-        
         /// <summary>
-        /// Initializes a new instance of the <see cref="DirectoryWatchers" /> class.
+        /// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
         /// </summary>
-        public DirectoryWatchers(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
+        public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager)
         {
             if (taskManager == null)
             {
@@ -104,9 +112,8 @@ namespace MediaBrowser.Server.Implementations.IO
 
             LibraryManager = libraryManager;
             TaskManager = taskManager;
-            Logger = logManager.GetLogger("DirectoryWatchers");
+            Logger = logManager.GetLogger(GetType().Name);
             ConfigurationManager = configurationManager;
-            _fileSystem = fileSystem;
 
             SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
         }
@@ -328,31 +335,25 @@ namespace MediaBrowser.Server.Implementations.IO
             {
                 OnWatcherChanged(e);
             }
-            catch (IOException ex)
+            catch (Exception ex)
             {
-                Logger.ErrorException("IOException in watcher changed. Path: {0}", ex, e.FullPath);
+                Logger.ErrorException("Exception in watcher changed. Path: {0}", ex, e.FullPath);
             }
         }
 
         private void OnWatcherChanged(FileSystemEventArgs e)
         {
-            var name = e.Name;
+            Logger.Debug("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath);
 
-            // Ignore certain files
-            if (_alwaysIgnoreFiles.Contains(name, StringComparer.OrdinalIgnoreCase))
-            {
-                return;
-            }
+            ReportFileSystemChanged(e.FullPath);
+        }
 
-            var nameFromFullPath = Path.GetFileName(e.FullPath);
-            // Ignore certain files
-            if (!string.IsNullOrEmpty(nameFromFullPath) && _alwaysIgnoreFiles.Contains(nameFromFullPath, StringComparer.OrdinalIgnoreCase))
-            {
-                return;
-            }
+        public void ReportFileSystemChanged(string path)
+        {
+            var filename = Path.GetFileName(path);
 
-            // Ignore when someone manually creates a new folder
-            if (e.ChangeType == WatcherChangeTypes.Created && name == "New folder")
+            // Ignore certain files
+            if (!string.IsNullOrEmpty(filename) && _alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase))
             {
                 return;
             }
@@ -362,17 +363,17 @@ namespace MediaBrowser.Server.Implementations.IO
             // If the parent of an ignored path has a change event, ignore that too
             if (tempIgnorePaths.Any(i =>
             {
-                if (string.Equals(i, e.FullPath, StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(i, path, StringComparison.OrdinalIgnoreCase))
                 {
-                    Logger.Debug("Watcher ignoring change to {0}", e.FullPath);
+                    Logger.Debug("Ignoring change to {0}", path);
                     return true;
                 }
 
                 // Go up a level
                 var parent = Path.GetDirectoryName(i);
-                if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase))
                 {
-                    Logger.Debug("Watcher ignoring change to {0}", e.FullPath);
+                    Logger.Debug("Ignoring change to {0}", path);
                     return true;
                 }
 
@@ -380,17 +381,17 @@ namespace MediaBrowser.Server.Implementations.IO
                 if (!string.IsNullOrEmpty(parent))
                 {
                     parent = Path.GetDirectoryName(i);
-                    if (string.Equals(parent, e.FullPath, StringComparison.OrdinalIgnoreCase))
+                    if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase))
                     {
-                        Logger.Debug("Watcher ignoring change to {0}", e.FullPath);
+                        Logger.Debug("Ignoring change to {0}", path);
                         return true;
                     }
                 }
 
-                if (i.StartsWith(e.FullPath, StringComparison.OrdinalIgnoreCase) || 
-                    e.FullPath.StartsWith(i, StringComparison.OrdinalIgnoreCase))
+                if (i.StartsWith(path, StringComparison.OrdinalIgnoreCase) ||
+                    path.StartsWith(i, StringComparison.OrdinalIgnoreCase))
                 {
-                    Logger.Debug("Watcher ignoring change to {0}", e.FullPath);
+                    Logger.Debug("Ignoring change to {0}", path);
                     return true;
                 }
 
@@ -401,22 +402,19 @@ namespace MediaBrowser.Server.Implementations.IO
                 return;
             }
 
-            Logger.Info("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath);
-
-            //Since we're watching created, deleted and renamed we always want the parent of the item to be the affected path
-            var affectedPath = e.FullPath;
-
-            _affectedPaths.AddOrUpdate(affectedPath, affectedPath, (key, oldValue) => affectedPath);
+            // Avoid implicitly captured closure
+            var affectedPath = path;
+            _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath);
 
             lock (_timerLock)
             {
                 if (_updateTimer == null)
                 {
-                    _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1));
+                    _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1));
                 }
                 else
                 {
-                    _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1));
+                    _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeWatcherDelay), TimeSpan.FromMilliseconds(-1));
                 }
             }
         }
@@ -427,24 +425,9 @@ namespace MediaBrowser.Server.Implementations.IO
         /// <param name="stateInfo">The state info.</param>
         private async void TimerStopped(object stateInfo)
         {
-            lock (_timerLock)
-            {
-                // Extend the timer as long as any of the paths are still being written to.
-                if (_affectedPaths.Any(p => IsFileLocked(p.Key)))
-                {
-                    Logger.Info("Timer extended.");
-                    _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.FileWatcherDelay), TimeSpan.FromMilliseconds(-1));
-                    return;
-                }
+            Logger.Debug("Timer stopped.");
 
-                Logger.Info("Timer stopped.");
-
-                if (_updateTimer != null)
-                {
-                    _updateTimer.Dispose();
-                    _updateTimer = null;
-                }
-            }
+            DisposeTimer();
 
             var paths = _affectedPaths.Keys.ToList();
             _affectedPaths.Clear();
@@ -452,59 +435,16 @@ namespace MediaBrowser.Server.Implementations.IO
             await ProcessPathChanges(paths).ConfigureAwait(false);
         }
 
-        /// <summary>
-        /// Try and determine if a file is locked
-        /// This is not perfect, and is subject to race conditions, so I'd rather not make this a re-usable library method.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns><c>true</c> if [is file locked] [the specified path]; otherwise, <c>false</c>.</returns>
-        private bool IsFileLocked(string path)
+        private void DisposeTimer()
         {
-            try
-            {
-                var data = _fileSystem.GetFileSystemInfo(path);
-
-                if (!data.Exists
-                    || data.Attributes.HasFlag(FileAttributes.Directory)
-                    || data.Attributes.HasFlag(FileAttributes.ReadOnly))
-                {
-                    return false;
-                }
-            }
-            catch (IOException)
-            {
-                return false;
-            }
-
-            try
+            lock (_timerLock)
             {
-                using (_fileSystem.GetFileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
+                if (_updateTimer != null)
                 {
-                    //file is not locked
-                    return false;
+                    _updateTimer.Dispose();
+                    _updateTimer = null;
                 }
             }
-            catch (DirectoryNotFoundException)
-            {
-                return false;
-            }
-            catch (FileNotFoundException)
-            {
-                return false;
-            }
-            catch (IOException)
-            {
-                //the file is unavailable because it is:
-                //still being written to
-                //or being processed by another thread
-                //or does not exist (has already been processed)
-                Logger.Debug("{0} is locked.", path);
-                return true;
-            }
-            catch
-            {
-                return false;
-            }
         }
 
         /// <summary>
@@ -599,14 +539,7 @@ namespace MediaBrowser.Server.Implementations.IO
                 watcher.Dispose();
             }
 
-            lock (_timerLock)
-            {
-                if (_updateTimer != null)
-                {
-                    _updateTimer.Dispose();
-                    _updateTimer = null;
-                }
-            }
+            DisposeTimer();
 
             _fileSystemWatchers.Clear();
             _affectedPaths.Clear();

+ 8 - 8
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
         private IEnumerable<IMetadataSaver> _savers;
 
-        private readonly Func<IDirectoryWatchers> _directoryWatchersFactory;
+        private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
 
         /// <summary>
         /// The _library items cache
@@ -180,14 +180,14 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <param name="userManager">The user manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="userDataRepository">The user data repository.</param>
-        public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<IDirectoryWatchers> directoryWatchersFactory, IFileSystem fileSystem)
+        public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem)
         {
             _logger = logger;
             _taskManager = taskManager;
             _userManager = userManager;
             ConfigurationManager = configurationManager;
             _userDataRepository = userDataRepository;
-            _directoryWatchersFactory = directoryWatchersFactory;
+            _libraryMonitorFactory = libraryMonitorFactory;
             _fileSystem = fileSystem;
             ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>();
 
@@ -934,7 +934,7 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <returns>Task.</returns>
         public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            _directoryWatchersFactory().Stop();
+            _libraryMonitorFactory().Stop();
 
             try
             {
@@ -942,7 +942,7 @@ namespace MediaBrowser.Server.Implementations.Library
             }
             finally
             {
-                _directoryWatchersFactory().Start();
+                _libraryMonitorFactory().Start();
             }
         }
 
@@ -1462,13 +1462,13 @@ namespace MediaBrowser.Server.Implementations.Library
 
                 var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1));
 
-                var directoryWatchers = _directoryWatchersFactory();
+                var directoryWatchers = _libraryMonitorFactory();
 
                 await semaphore.WaitAsync().ConfigureAwait(false);
 
                 try
                 {
-                    directoryWatchers.TemporarilyIgnore(path);
+                    directoryWatchers.ReportFileSystemChangeBeginning(path);
                     saver.Save(item, CancellationToken.None);
                 }
                 catch (Exception ex)
@@ -1477,7 +1477,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 }
                 finally
                 {
-                    directoryWatchers.RemoveTempIgnore(path);
+                    directoryWatchers.ReportFileSystemChangeComplete(path, false);
                     semaphore.Release();
                 }
             }

+ 1 - 1
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -137,7 +137,7 @@
     <Compile Include="HttpServer\StreamWriter.cs" />
     <Compile Include="HttpServer\SwaggerService.cs" />
     <Compile Include="Drawing\ImageProcessor.cs" />
-    <Compile Include="IO\DirectoryWatchers.cs" />
+    <Compile Include="IO\LibraryMonitor.cs" />
     <Compile Include="Library\CoreResolutionIgnoreRule.cs" />
     <Compile Include="Library\LibraryManager.cs" />
     <Compile Include="Library\SearchEngine.cs" />

+ 6 - 6
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -137,7 +137,7 @@ namespace MediaBrowser.ServerApplication
         /// Gets or sets the directory watchers.
         /// </summary>
         /// <value>The directory watchers.</value>
-        private IDirectoryWatchers DirectoryWatchers { get; set; }
+        private ILibraryMonitor LibraryMonitor { get; set; }
         /// <summary>
         /// Gets or sets the provider manager.
         /// </summary>
@@ -273,13 +273,13 @@ namespace MediaBrowser.ServerApplication
             UserManager = new UserManager(Logger, ServerConfigurationManager, UserRepository);
             RegisterSingleInstance(UserManager);
 
-            LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => DirectoryWatchers, FileSystemManager);
+            LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager);
             RegisterSingleInstance(LibraryManager);
 
-            DirectoryWatchers = new DirectoryWatchers(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager);
-            RegisterSingleInstance(DirectoryWatchers);
+            LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager);
+            RegisterSingleInstance(LibraryMonitor);
 
-            ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, DirectoryWatchers, LogManager, FileSystemManager, ItemRepository);
+            ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ItemRepository);
             RegisterSingleInstance(ProviderManager);
 
             RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
@@ -306,7 +306,7 @@ namespace MediaBrowser.ServerApplication
             var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer);
             RegisterSingleInstance<INewsService>(newsService);
 
-            var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, Logger, DirectoryWatchers, LibraryManager, ServerConfigurationManager, FileSystemManager);
+            var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, Logger, LibraryMonitor, LibraryManager, ServerConfigurationManager, FileSystemManager);
             RegisterSingleInstance<IFileOrganizationService>(fileOrganizationService);
 
             progress.Report(15);