Skip to content

Commit

Permalink
Reworked library refresh implementation #2
Browse files Browse the repository at this point in the history
Library update requests are now grouped by parent folders.
These requests are now also timed individually. As soon as a child file
is changed, the group delay timer gets reset for the whole group.
But: Other folders are no longer affected (like before) but instead
handled independently.
  • Loading branch information
softworkz committed Oct 30, 2015
1 parent 607f418 commit cbe1016
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 99 deletions.
135 changes: 36 additions & 99 deletions MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,6 @@ public class LibraryMonitor : ILibraryMonitor
/// The file system watchers
/// </summary>
private readonly ConcurrentDictionary<string, FileSystemWatcher> _fileSystemWatchers = new ConcurrentDictionary<string, FileSystemWatcher>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// The update timer
/// </summary>
private Timer _updateTimer;
/// <summary>
/// The affected paths
/// </summary>
private readonly ConcurrentDictionary<string, string> _affectedPaths = new ConcurrentDictionary<string, string>();

/// <summary>
/// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications.
Expand All @@ -54,11 +46,6 @@ public class LibraryMonitor : ILibraryMonitor
"TempSBE"
};

/// <summary>
/// The timer lock
/// </summary>
private readonly object _timerLock = new object();

/// <summary>
/// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
/// </summary>
Expand Down Expand Up @@ -122,6 +109,7 @@ public async void ReportFileSystemChangeComplete(string path, bool refreshPath)

private readonly IFileSystem _fileSystem;
private readonly IServerApplicationHost _appHost;
private LibraryUpdateQueue _updateQueue;

/// <summary>
/// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
Expand All @@ -139,7 +127,8 @@ public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibrary
ConfigurationManager = configurationManager;
_fileSystem = fileSystem;
_appHost = appHost;

_updateQueue = new LibraryUpdateQueue(logManager, configurationManager, fileSystem);
_updateQueue.ItemReady += UpdateQueue_ItemReady;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
}

Expand Down Expand Up @@ -417,13 +406,37 @@ public void ReportFileSystemChanged(string path)
throw new ArgumentNullException("path");
}

var filename = Path.GetFileName(path);
if (_fileSystem.IsPathFile(path))
{
var filename = Path.GetFileName(path);
if (_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase))
{
return;
}
}

_updateQueue.AddPath(path);
}

void UpdateQueue_ItemReady(object sender, LibraryUpdateEventArgs e)
{
// Opposed to using foreach enumeration, .ToArray() returns a snapshot
// of the current dictionary contents
var affectedPathsSnapshot = e.Item.FilePaths.Keys.ToList();

if (affectedPathsSnapshot.Any(i => IsFileLocked(i)))
{
e.Cancel = true;
return;
}

var monitorPath = !(!string.IsNullOrEmpty(filename) && _alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase));
var path = e.Item.Folder;

// Ignore certain files
var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList();

// The tempIgnorePaths are now checked once library updates are due - not when queuing updates

// If the parent of an ignored path has a change event, ignore that too
if (tempIgnorePaths.Any(i =>
{
Expand Down Expand Up @@ -451,71 +464,15 @@ public void ReportFileSystemChanged(string path)
}

return false;

}))
{
monitorPath = false;
}

if (monitorPath)
{
// Avoid implicitly captured closure
var affectedPath = path;
_affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath);
EnsureTimer(false);
}

}

private void EnsureTimer(bool restart)
{
lock (_timerLock)
{
if (_updateTimer == null)
{
_updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeLibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
}
else if (restart)
{
_updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeLibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
}
}
}

/// <summary>
/// Timers the stopped.
/// </summary>
/// <param name="stateInfo">The state info.</param>
private async void TimerStopped(object stateInfo)
{
// Opposed to using foreach enumeration, .ToArray() returns a snapshot
// of the current dictionary contents
var affectedPathsSnapshot = _affectedPaths.ToArray();

// First, determine those paths which are not or no longer locked
// Use .ToList() to avoid re-evaluation of IsFileLocked on multiple enumerations
var nonLockedPaths = affectedPathsSnapshot.Where(p => !IsFileLocked(p.Key)).Select(q => q.Key).ToList();

foreach (var path in nonLockedPaths)
{
string removedValue;
_affectedPaths.TryRemove(path, out removedValue);
}

if (_affectedPaths.Count > 0)
{
Logger.Info("Timer restarted.");
EnsureTimer(true);
}
else
{
Logger.Debug("Timer stopped.");
DisposeTimer();
e.Cancel = true;
return;
}

try
{
await ProcessPathChanges(nonLockedPaths).ConfigureAwait(false);
ProcessPathChanges(affectedPathsSnapshot).ConfigureAwait(false);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -552,12 +509,8 @@ private bool IsFileLocked(string path)
{
using (_fileSystem.GetFileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
// In which way does the _updateTimer matter to determine the locking state??
if (_updateTimer != null)
{
//file is not locked
return false;
}
//file is not locked
return false;
}
}
catch (DirectoryNotFoundException)
Expand All @@ -582,24 +535,11 @@ private bool IsFileLocked(string path)
catch (Exception ex)
{
Logger.ErrorException("Error determining if file is locked: {0}", ex, path);
return false;
}

return false;
}

private void DisposeTimer()
{
lock (_timerLock)
{
if (_updateTimer != null)
{
_updateTimer.Dispose();
_updateTimer = null;
}
}
}

/// <summary>
/// Processes the path changes.
/// </summary>
Expand All @@ -621,8 +561,7 @@ private async Task ProcessPathChanges(List<string> paths)
// If the root folder changed, run the library task so the user can see it
if (itemsToRefresh.Any(i => i is AggregateFolder))
{
DisposeTimer();
_affectedPaths.Clear();
_updateQueue.Clear();
TaskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>();
return;
}
Expand Down Expand Up @@ -697,10 +636,8 @@ public void Stop()
watcher.Dispose();
}

DisposeTimer();

_fileSystemWatchers.Clear();
_affectedPaths.Clear();
_updateQueue.Clear();
}

/// <summary>
Expand Down
22 changes: 22 additions & 0 deletions MediaBrowser.Server.Implementations/IO/LibraryUpdateEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MediaBrowser.Server.Implementations.IO
{
class LibraryUpdateEventArgs : EventArgs
{
private LibraryUpdateItem _item;

public bool Cancel { get; set; }

public LibraryUpdateItem Item { get { return _item; }}

public LibraryUpdateEventArgs(LibraryUpdateItem item)
{
_item = item;
}
}
}
26 changes: 26 additions & 0 deletions MediaBrowser.Server.Implementations/IO/LibraryUpdateItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MediaBrowser.Server.Implementations.IO
{
class LibraryUpdateItem
{
private ConcurrentDictionary<string, string> _filePaths = new ConcurrentDictionary<string, string>();
private string _folder;

public LibraryUpdateItem(string folder)
{
_folder = folder;
}

public DateTime DueDate { get; set; }

public string Folder { get { return _folder; } }

public ConcurrentDictionary<string, string> FilePaths { get { return _filePaths; } }
}
}
Loading

0 comments on commit cbe1016

Please sign in to comment.