/IO/FileSystemWatcher.cs |
@@ -0,0 +1,73 @@ |
using System; |
using System.IO; |
using System.Runtime.Caching; |
|
namespace wasSharpNET.IO |
{ |
/// <summary> |
/// Filesystem watcher that ensures events are not fired twice |
/// </summary> |
/// <remarks>derived from Ben Hall @ benhall.io</remarks> |
public class FileSystemWatcher : IDisposable |
{ |
public event FileSystemEventHandler FilesytemEvent; |
private readonly CacheItemPolicy _cacheItemPolicy; |
private readonly int _cacheTimeMilliseconds = 1000; |
private readonly MemoryCache _memCache; |
|
public FileSystemWatcher(string path, NotifyFilters notifyFilters, string filter) |
{ |
_memCache = MemoryCache.Default; |
|
var watcher = new System.IO.FileSystemWatcher |
{ |
Path = path, |
NotifyFilter = notifyFilters, |
Filter = filter |
}; |
|
_cacheItemPolicy = new CacheItemPolicy |
{ |
RemovedCallback = OnRemovedFromCache |
}; |
|
watcher.Changed += OnChanged; |
watcher.EnableRaisingEvents = true; |
} |
|
public FileSystemWatcher(string path, NotifyFilters notifyFilters, string filter, int timeout) : this(path, |
notifyFilters, filter) |
{ |
_cacheTimeMilliseconds = timeout; |
} |
|
// Add file event to cache for CacheTimeMilliseconds |
private void OnChanged(object source, FileSystemEventArgs e) |
{ |
_cacheItemPolicy.AbsoluteExpiration = |
DateTimeOffset.Now.AddMilliseconds(_cacheTimeMilliseconds); |
|
// Only add if it is not there already (swallow others) |
_memCache.AddOrGetExisting(e.Name, e, _cacheItemPolicy); |
} |
|
// Handle cache item expiring |
private void OnRemovedFromCache(CacheEntryRemovedArguments args) |
{ |
if (args.RemovedReason != CacheEntryRemovedReason.Expired) return; |
|
// Now actually handle file event. |
OnFileSystemEvent((FileSystemEventArgs)args.CacheItem.Value); |
} |
|
protected virtual void OnFileSystemEvent(FileSystemEventArgs e) |
{ |
FilesytemEvent?.Invoke(this, e); |
} |
|
public void Dispose() |
{ |
_memCache.Dispose(); |
} |
} |
} |
/IO/SafeFileStream.cs |
@@ -6,8 +6,6 @@ |
|
using System; |
using System.IO; |
using System.Runtime.InteropServices; |
using System.Runtime.InteropServices.ComTypes; |
using System.Threading; |
|
namespace wasSharpNET.IO |
@@ -22,7 +20,7 @@ |
|
public SafeFileStream(string path, FileMode mode, FileAccess access, FileShare share, uint milliscondsTimeout) |
{ |
m_mutex = new Mutex(false, string.Format("Global\\{0}", path.Replace('\\', '/'))); |
m_mutex = new Mutex(false, $"Global\\{path.Replace('\\', '/')}"); |
m_path = path; |
m_fileMode = mode; |
m_fileAccess = access; |
/IO/Utilities/IOExtensions.cs |
@@ -0,0 +1,81 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2017 - License: GNU GPLv3 // |
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // |
// rights of fair usage, the disclaimer and warranty conditions. // |
/////////////////////////////////////////////////////////////////////////// |
|
using System; |
using System.Diagnostics; |
using System.IO; |
|
namespace wasSharpNET.IO.Utilities |
{ |
public static class IOExtensions |
{ |
/// <summary> |
/// Attempt to obtain a filestream by polling a file till the handle becomes available. |
/// </summary> |
/// <param name="path">the path to the file</param> |
/// <param name="mode">the file mode to use</param> |
/// <param name="access">the level of access to the file</param> |
/// <param name="share">the type of file share locking</param> |
/// <param name="timeout">the timeout in milliseconds to fail opening the file</param> |
/// <returns>a filestream if the file was opened successfully</returns> |
public static FileStream GetWriteStream(string path, FileMode mode, FileAccess access, FileShare share, |
int timeout) |
{ |
var time = Stopwatch.StartNew(); |
while (time.ElapsedMilliseconds < timeout) |
try |
{ |
return new FileStream(path, mode, access, share); |
} |
catch (IOException e) |
{ |
// access error |
if (e.HResult != -2147024864) |
throw; |
} |
|
throw new TimeoutException($"Failed to get a write handle to {path} within {timeout}ms."); |
} |
|
public static bool isRootedIn(this string path, string root) |
{ |
// Path is empty and root is empty. |
if (string.IsNullOrEmpty(path) && string.IsNullOrEmpty(root)) |
return true; |
|
// Path is empty but the root path is not empty. |
if (string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(root)) |
return true; |
|
// The supplied root path is empty. |
if (string.IsNullOrEmpty(root)) |
return false; |
|
// The path is empty and the root is not. |
if (string.IsNullOrEmpty(path)) |
return true; |
|
var p = new DirectoryInfo(path); |
var r = new DirectoryInfo(root); |
|
return string.Equals(p.Parent?.FullName, r.Parent?.FullName) || isRootedIn(p.Parent?.FullName, root); |
} |
|
public static void Empty(this string directory) |
{ |
var dir = new DirectoryInfo(directory); |
|
foreach (var fi in dir.GetFiles()) |
fi.Delete(); |
|
foreach (var di in dir.GetDirectories()) |
{ |
Empty(di.FullName); |
di.Delete(); |
} |
} |
} |
} |