private static unsafe void DoQueuedCompletionWork( ISwatcherConfig config, IWindowsFacade facade, SafeLocalMemHandle completionPortHandle, ISubject <SwatcherCreatedEventArgs> createdSubject, ISubject <SwatcherEventArgs> deletedSubject, ISubject <SwatcherEventArgs> changedSubject, ISubject <RenamedInfo> renamedSubject) { //this is a blocking call... var completionStatus = WaitForCompletionStatus(facade, completionPortHandle); if (completionStatus == null) { return; } var overlapped = Overlapped.Unpack(completionStatus.Value.OverlappedPointer); var asyncResult = (SwatcherAsyncResult)overlapped.AsyncResult; var currentOffset = 0; // ReSharper disable once TooWideLocalVariableScope var nextOffset = 0; try { do { nextOffset = DeserializeMessage( config, asyncResult, ref currentOffset, createdSubject, deletedSubject, changedSubject, renamedSubject); } while (nextOffset != 0); } finally { //*always* free up the overlapped. otherwise, we will have a ~65kb memory leak after each callback. Overlapped.Free(completionStatus.Value.OverlappedPointer); } }
private IObservable <SwatcherRenamedEventArgs> CreatePublicRenamedStream(ISwatcherConfig config) { return(_renamedSubject.AsObservable() .CombineWithPrevious((previous, current) => { if ((previous?.Event == FileSystemItemEvent.RenamedOldName) && (current.Event == FileSystemItemEvent.RenamedNewName)) { return new SwatcherRenamedEventArgs(config, current.Name, previous.Name); } return null; }) .WhereNotNull() .Do(x => { if (!Config.LoggingEnabled) { return; } Logger.Debug( $"[Renamed] OldName: {x.OldName}, Name: {x.Name}, OccurredAt:{x.TimeOccurred.ToLocalTime()}"); }) .Publish() .RefCount()); }
private static unsafe int DeserializeMessage( ISwatcherConfig config, SwatcherAsyncResult asyncResult, ref int currentOffset, ISubject <SwatcherCreatedEventArgs> createdSubject, ISubject <SwatcherEventArgs> deletedSubject, ISubject <SwatcherEventArgs> changedSubject, ISubject <RenamedInfo> renamedSubject) { int nextOffset; string name; int @event; fixed(byte *bufferPointer = asyncResult.Buffer) { //buffer pointer was the address where we started. //add current offset to get the address of our the next offset. //4 bytes in an integer ;-). nextOffset = *(int *)(bufferPointer + currentOffset); // the next integer contains the action. @event = *(int *)(bufferPointer + currentOffset + 4); //next int pointer has the address that contains the length of the name //of the item that was created,changed,renamed or deleted. var nameLength = *(int *)(bufferPointer + currentOffset + 8); //finally, retrieve the string via char* using the name length from above. //we divide the length by 2 because a char is 2 bytes. name = new string((char *)(bufferPointer + currentOffset + 12), 0, nameLength / 2); } switch ((FileSystemItemEvent)@event) { case FileSystemItemEvent.Created: OnItemCreatedInternal(config, createdSubject, name); break; case FileSystemItemEvent.Deleted: OnItemDeletedInternal(config, deletedSubject, name); break; case FileSystemItemEvent.Changed: OnItemChangedInternal(config, changedSubject, name); break; case FileSystemItemEvent.RenamedOldName: OnItemRenamedInternal(config, renamedSubject, name, FileSystemItemEvent.RenamedOldName); break; case FileSystemItemEvent.RenamedNewName: OnItemRenamedInternal(config, renamedSubject, name, FileSystemItemEvent.RenamedNewName); break; default: if (config.LoggingEnabled) { Logger.Trace($"[Skipped] An event was skipped because it didn't map to a Swatcher FileSystemEvent. Value={@event}, Name={name ?? "null"}."); } break; } currentOffset += nextOffset; return(nextOffset); }
private static void OnItemDeletedInternal(ISwatcherConfig config, ISubject <SwatcherEventArgs> deletedSubject, string name) { if (config.ChangeTypes.HasFlag(WatcherChangeTypes.Deleted)) { deletedSubject.OnNext( new SwatcherEventArgs(config, WatcherChangeTypes.Deleted, name)); } }
/// <summary> /// Initializes a new instance of the <see cref="SwatcherRenamedEventArgs"/> class. /// </summary> /// <param name="config">Configuration for the <see cref="ISwatcher"/> that created this <see cref="SwatcherRenamedEventArgs"/> instance.</param> /// <param name="name">The <b>new</b> name of the item that changed.</param> /// <param name="oldName">The <b>old</b> name of the item changed.</param> public SwatcherRenamedEventArgs(ISwatcherConfig config, string name, string oldName) { SwatcherId = config.Id; OldName = oldName; FullPath = config.PathToWatch; Name = name; EventId = Guid.NewGuid(); TimeOccurred = DateTime.UtcNow; }
/// <summary> /// Initializes a new instance of the <see cref="SwatcherEventArgs" /> class. /// </summary> /// <param name="config">The configuration for the <see cref="Swatcher"/>that created this instance.</param> /// <param name="changeType">Type of the change.</param> /// <param name="name">The name.</param> public SwatcherEventArgs(ISwatcherConfig config, WatcherChangeTypes changeType, string name) { SwatcherId = config.Id; ChangeType = changeType; Name = name; TimeOccurred = DateTime.UtcNow; EventId = Guid.NewGuid(); FullPath = config.PathToWatch + name; }
private static void OnItemCreatedInternal(ISwatcherConfig config, ISubject <SwatcherCreatedEventArgs> createdSubject, string name) { if (config.ChangeTypes.HasFlag(WatcherChangeTypes.Created)) { createdSubject.OnNext( new SwatcherCreatedEventArgs(config, name)); } }
// ReSharper disable once SuggestVarOrType_Elsewhere private static unsafe void WatchFolderForChanges( object wrapper, ISwatcherConfig config, IWindowsFacade windowsFacade, SafeFileHandle directoryHandle) { var result = new SwatcherAsyncResult { Buffer = new byte[DefaultBufferSize] }; var overlapped = new Overlapped { AsyncResult = result }; //the first parameter is null because we're not using IO completion callbacks; they're too slow. //we're taking the byte array from our empty byte array and passing that as user data to the overlapped. var overlappedPointer = overlapped.UnsafePack(null, result.Buffer); var success = false; try { //now we wrap this section in a fixed block to pin it to the original address. //we cannot have it move because the OS will write information about the changes //into this byte array. fixed(byte *bufferPointer = result.Buffer) { var bytesReturned = 0; var bufferHandle = new HandleRef(result, (IntPtr)bufferPointer); var isRecursive = Convert.ToInt32(config.IsRecursive); //because we're using IO completion ports, we pass our overlapped pointer into this unmanaged //call. when a change has been received, the OS will callback via GetQueuedCompletionStatus //passing the overlapped pointer (which has our IAsyncResult/byte array) back to us. success = windowsFacade.ReadDirectoryChangesW( directoryHandle, bufferHandle, DefaultBufferSize, isRecursive, (int)config.NotificationTypes, bytesReturned, overlappedPointer, SafeLocalMemHandle.Empty); //in this usage of ReadDirectoryChangesW, we should *always* get 0 bytes returned. if (bytesReturned != 0) { Debugger.Break(); } } } finally { //if success is false, our directory handle has likely become invalid. attempt to re-establish it. if (!success) { Debugger.Break(); //before doing anything else, cleanup here to prevent memory leaks. Overlapped.Free(overlappedPointer); } } }
internal static IObservable <T> SelectConfiguredItemTypes <T>( this IObservable <T> @event, ISwatcherConfig config) where T : SwatcherEventArgs { return(@event .Select(x => new { ItemType = GetItemType(x.FullPath), Model = x }) .Where(x => config.ItemTypes.HasFlag(x.ItemType)) .Select(x => x.Model)); }
private static void OnItemRenamedInternal(ISwatcherConfig config, ISubject <RenamedInfo> renamedSubject, string name, FileSystemItemEvent @event) { if (!config.ChangeTypes.HasFlag(WatcherChangeTypes.Renamed)) { return; } renamedSubject.OnNext(new RenamedInfo { Name = name, Event = @event }); }
internal Swatcher(ISwatcherConfig config, IWindowsFacade windowsFacade) { Config = config; WindowsFacade = windowsFacade; var createdEventStream = CreateCreatedEventStream(); var finishedCreatedEventStream = CreateFinishedCreatedEventStream(createdEventStream); finishedCreatedEventStream .Delay(TimeSpan.FromSeconds(2.5)) .Subscribe(x => CreatedEventsInProgress.Remove(x.FullPath)); var createdEventWindows = CreateCreatedEventWindowStream(createdEventStream); var finishedCreatedEventWindows = CreateFinishedCreatedEventWindows(finishedCreatedEventStream); var changedEventStream = CreateChangedEventStream(); var changedEventWindows = CreatedChangedEventWindows(changedEventStream); Renamed = CreatePublicRenamedStream(Config); Deleted = CreatePublicDeletedStream(); Created = CreatePublicCreatedStream(finishedCreatedEventStream); Changed = CreatePublicChangedStream(changedEventStream, changedEventWindows, createdEventWindows, finishedCreatedEventWindows); ChangedEventPatternSource = Changed.Select(x => new EventPattern <SwatcherEventArgs>(this, x)).ToEventPattern(); ChangedEventPatternSource.OnNext += OnItemChanged; DeletedEventPatternSource = Deleted.Select(x => new EventPattern <SwatcherEventArgs>(this, x)).ToEventPattern(); DeletedEventPatternSource.OnNext += OnItemDeleted; CreatedEventPatternSource = Created.Select(x => new EventPattern <SwatcherCreatedEventArgs>(this, x)).ToEventPattern(); CreatedEventPatternSource.OnNext += OnItemCreated; RenamedEventPatternSource = Renamed.Select(x => new EventPattern <SwatcherRenamedEventArgs>(this, x)).ToEventPattern(); RenamedEventPatternSource.OnNext += OnItemRenamed; }
/// <summary> /// Initializes a new instance of the <see cref="SwatcherCreatedEventArgs"/> class. /// </summary> /// <param name="config">The configuration for the <see cref="Swatcher"/>that created this instance.</param> /// <param name="name">The name of the file system item that was created.</param> /// <param name="isCompleted">Indicated whether or not the <see cref="TimeCompleted"/> and <see cref="Duration"/> properties should be populated.</param> public SwatcherCreatedEventArgs(ISwatcherConfig config, string name) : base(config, WatcherChangeTypes.Created, name) { }
private bool IsWatchedItemType(SwatcherEventArgs e, ISwatcherConfig config) { var itemType = GetItemType(e.FullPath); return(config.ItemTypes.HasFlag(itemType)); }
public Swatcher(ISwatcherConfig config) : this(config, new WindowsFacade()) { }