public void ProcessEvent(FileEvent fileEvent) { lock (LOCK) { var now = DateTime.Now.Ticks; // Check for spam if (events.Count == 0) { spamWarningLogged = false; spamCheckStartTime = now; } else if (!spamWarningLogged && spamCheckStartTime + EVENT_SPAM_WARNING_THRESHOLD < now) { spamWarningLogged = true; logger(string.Format("Warning: Watcher is busy catching up wit {0} file changes in 60 seconds. Latest path is '{1}'", events.Count, fileEvent.path)); } // Add into our queue events.Add(fileEvent); lastEventTime = now; // Process queue after delay if (delayTask == null) { // Create function to buffer events Action<Task> func = null; func = (Task value) => { lock (LOCK) { // Check if another event has been received in the meantime if (delayStarted == lastEventTime) { // Normalize and handle var normalized = NormalizeEvents(events.ToArray()); foreach (var e in normalized) { handleEvent(e); } // Reset events.Clear(); delayTask = null; } // Otherwise we have received a new event while this task was // delayed and we reschedule it. else { delayStarted = lastEventTime; delayTask = Task.Delay(EVENT_DELAY).ContinueWith(func); } } }; // Start function after delay delayStarted = lastEventTime; delayTask = Task.Delay(EVENT_DELAY).ContinueWith(func); } } }
private IEnumerable<FileEvent> NormalizeEvents(FileEvent[] events) { var mapPathToEvents = new Dictionary<string, FileEvent>(); var eventsWithoutDuplicates = new List<FileEvent>(); // Normalize Duplicates foreach (var e in events) { // Existing event if (mapPathToEvents.ContainsKey(e.path)) { var existingEvent = mapPathToEvents[e.path]; var currentChangeType = existingEvent.changeType; var newChangeType = e.changeType; // ignore CREATE followed by DELETE in one go if (currentChangeType == (int)ChangeType.CREATED && newChangeType == (int)ChangeType.DELETED) { mapPathToEvents.Remove(existingEvent.path); eventsWithoutDuplicates.Remove(existingEvent); } // flatten DELETE followed by CREATE into CHANGE else if (currentChangeType == (int)ChangeType.DELETED && newChangeType == (int)ChangeType.CREATED) { existingEvent.changeType = (int)ChangeType.CHANGED; } // Do nothing. Keep the created event else if (currentChangeType == (int)ChangeType.CREATED && newChangeType == (int)ChangeType.CHANGED) { } // Otherwise apply change type else { existingEvent.changeType = newChangeType; } } // New event else { mapPathToEvents.Add(e.path, e); eventsWithoutDuplicates.Add(e); } } // Handle deletes var addedChangeEvents = new List<FileEvent>(); var deletedPaths = new List<string>(); // This algorithm will remove all DELETE events up to the root folder // that got deleted if any. This ensures that we are not producing // DELETE events for each file inside a folder that gets deleted. // // 1.) split ADD/CHANGE and DELETED events // 2.) sort short deleted paths to the top // 3.) for each DELETE, check if there is a deleted parent and ignore the event in that case return eventsWithoutDuplicates .Where((e) => { if (e.changeType != (int)ChangeType.DELETED) { addedChangeEvents.Add(e); return false; // remove ADD / CHANGE } return true; }) .OrderBy((e) => e.path.Length) // shortest path first .Where((e) => { if (deletedPaths.Any(d => IsParent(e.path, d))) { return false; // DELETE is ignored if parent is deleted already } // otherwise mark as deleted deletedPaths.Add(e.path); return true; }) .Concat(addedChangeEvents); }