瀏覽代碼

stub out file refresher

Luke Pulverenti 9 年之前
父節點
當前提交
e0a213601b

+ 241 - 0
MediaBrowser.Server.Implementations/IO/FileRefresher.cs

@@ -0,0 +1,241 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Server.Implementations.ScheduledTasks;
+
+namespace MediaBrowser.Server.Implementations.IO
+{
+    public class FileRefresher : IDisposable
+    {
+        private ILogger Logger { get; set; }
+        private ITaskManager TaskManager { get; set; }
+        private ILibraryManager LibraryManager { get; set; }
+        private IServerConfigurationManager ConfigurationManager { get; set; }
+        private readonly IFileSystem _fileSystem;
+        private readonly List<string> _affectedPaths = new List<string>();
+        private Timer _timer;
+        private readonly object _timerLock = new object();
+
+        public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger)
+        {
+            _affectedPaths.Add(path);
+
+            _fileSystem = fileSystem;
+            ConfigurationManager = configurationManager;
+            LibraryManager = libraryManager;
+            TaskManager = taskManager;
+            Logger = logger;
+        }
+
+        private void RestartTimer()
+        {
+            lock (_timerLock)
+            {
+                if (_timer == null)
+                {
+                    _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
+                }
+                else
+                {
+                    _timer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
+                }
+            }
+        }
+
+        private async void OnTimerCallback(object state)
+        {
+            // Extend the timer as long as any of the paths are still being written to.
+            if (_affectedPaths.Any(IsFileLocked))
+            {
+                Logger.Info("Timer extended.");
+                RestartTimer();
+                return;
+            }
+
+            Logger.Debug("Timer stopped.");
+
+            DisposeTimer();
+
+            try
+            {
+                await ProcessPathChanges(_affectedPaths).ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                Logger.ErrorException("Error processing directory changes", ex);
+            }
+        }
+
+        private async Task ProcessPathChanges(List<string> paths)
+        {
+            var itemsToRefresh = paths
+                .Select(GetAffectedBaseItem)
+                .Where(item => item != null)
+                .Distinct()
+                .ToList();
+
+            foreach (var p in paths)
+            {
+                Logger.Info(p + " reports change.");
+            }
+
+            // If the root folder changed, run the library task so the user can see it
+            if (itemsToRefresh.Any(i => i is AggregateFolder))
+            {
+                TaskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>();
+                return;
+            }
+
+            foreach (var item in itemsToRefresh)
+            {
+                Logger.Info(item.Name + " (" + item.Path + ") will be refreshed.");
+
+                try
+                {
+                    await item.ChangedExternally().ConfigureAwait(false);
+                }
+                catch (IOException ex)
+                {
+                    // For now swallow and log. 
+                    // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
+                    // Should we remove it from it's parent?
+                    Logger.ErrorException("Error refreshing {0}", ex, item.Name);
+                }
+                catch (Exception ex)
+                {
+                    Logger.ErrorException("Error refreshing {0}", ex, item.Name);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets the affected base item.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <returns>BaseItem.</returns>
+        private BaseItem GetAffectedBaseItem(string path)
+        {
+            BaseItem item = null;
+
+            while (item == null && !string.IsNullOrEmpty(path))
+            {
+                item = LibraryManager.FindByPath(path, null);
+
+                path = Path.GetDirectoryName(path);
+            }
+
+            if (item != null)
+            {
+                // If the item has been deleted find the first valid parent that still exists
+                while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
+                {
+                    item = item.GetParent();
+
+                    if (item == null)
+                    {
+                        break;
+                    }
+                }
+            }
+
+            return item;
+        }
+
+        private bool IsFileLocked(string path)
+        {
+            if (Environment.OSVersion.Platform != PlatformID.Win32NT)
+            {
+                // Causing lockups on linux
+                return false;
+            }
+
+            try
+            {
+                var data = _fileSystem.GetFileSystemInfo(path);
+
+                if (!data.Exists
+                    || data.IsDirectory
+
+                    // Opening a writable stream will fail with readonly files
+                    || data.Attributes.HasFlag(FileAttributes.ReadOnly))
+                {
+                    return false;
+                }
+            }
+            catch (IOException)
+            {
+                return false;
+            }
+            catch (Exception ex)
+            {
+                Logger.ErrorException("Error getting file system info for: {0}", ex, path);
+                return false;
+            }
+
+            // In order to determine if the file is being written to, we have to request write access
+            // But if the server only has readonly access, this is going to cause this entire algorithm to fail
+            // So we'll take a best guess about our access level
+            var requestedFileAccess = ConfigurationManager.Configuration.SaveLocalMeta
+                ? FileAccess.ReadWrite
+                : FileAccess.Read;
+
+            try
+            {
+                using (_fileSystem.GetFileStream(path, FileMode.Open, requestedFileAccess, FileShare.ReadWrite))
+                {
+                    //file is not locked
+                    return false;
+                }
+            }
+            catch (DirectoryNotFoundException)
+            {
+                // File may have been deleted
+                return false;
+            }
+            catch (FileNotFoundException)
+            {
+                // File may have been deleted
+                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 (Exception ex)
+            {
+                Logger.ErrorException("Error determining if file is locked: {0}", ex, path);
+                return false;
+            }
+        }
+
+        public void DisposeTimer()
+        {
+            lock (_timerLock)
+            {
+                if (_timer != null)
+                {
+                    _timer.Dispose();
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            DisposeTimer();
+        }
+    }
+}

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

@@ -180,6 +180,7 @@
     <Compile Include="HttpServer\SocketSharp\WebSocketSharpRequest.cs" />
     <Compile Include="HttpServer\SocketSharp\WebSocketSharpResponse.cs" />
     <Compile Include="Intros\DefaultIntroProvider.cs" />
+    <Compile Include="IO\FileRefresher.cs" />
     <Compile Include="IO\LibraryMonitor.cs" />
     <Compile Include="Library\CoreResolutionIgnoreRule.cs" />
     <Compile Include="Library\LibraryManager.cs" />