/// <summary> /// Determines whether the specified file should be processed. /// </summary> /// <param name="filePath">The candidate file for processing.</param> /// <returns>True if the file should be processed, false otherwise.</returns> public virtual bool ShouldProcessFile(string filePath) { if (IsStatusFile(filePath)) { return(false); } string statusFilePath = GetStatusFile(filePath); if (!File.Exists(statusFilePath)) { return(true); } StatusFileEntry statusEntry = null; try { GetLastStatus(statusFilePath, out statusEntry); } catch (IOException) { // if we get an exception reading the status file, it's // likely because someone started processing and has it locked return(false); } return(statusEntry == null || (statusEntry.State != ProcessingState.Processed && statusEntry.ProcessCount < MaxProcessCount)); }
internal bool GetLastStatus(string statusFilePath, out StatusFileEntry statusEntry) { statusEntry = null; if (!File.Exists(statusFilePath)) { return(false); } using (Stream stream = File.OpenRead(statusFilePath)) { statusEntry = GetLastStatus(stream); } return(statusEntry != null); }
internal StatusFileEntry GetLastStatus(Stream statusFileStream) { StatusFileEntry statusEntry = null; using (var reader = new StreamReader(statusFileStream, Encoding.UTF8, false, 1024, true)) { string text = reader.ReadToEnd(); string[] fileLines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); string lastLine = fileLines.LastOrDefault(); if (!string.IsNullOrEmpty(lastLine)) { using var stringReader = new StringReader(lastLine); statusEntry = (StatusFileEntry)_serializer.Deserialize(stringReader, typeof(StatusFileEntry)); } } statusFileStream.Seek(0, SeekOrigin.End); return(statusEntry); }
internal StreamWriter AcquireStatusFileLock(string filePath, WatcherChangeTypes changeType, out StatusFileEntry statusEntry) { Stream stream = null; statusEntry = null; try { // Attempt to create (or update) the companion status file and lock it. The status // file is the mechanism for handling multi-instance concurrency. string statusFilePath = GetStatusFile(filePath); stream = File.Open(statusFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); // Once we've established the lock, we need to check to ensure that another instance // hasn't already processed the file in the time between our getting the event and // acquiring the lock. statusEntry = GetLastStatus(stream); if (statusEntry != null && statusEntry.State == ProcessingState.Processed) { // For file Create, we have no additional checks to perform. However for // file Change, we need to also check the LastWrite value for the entry // since there can be multiple Processed entries in the file over time. if (changeType == WatcherChangeTypes.Created) { return(null); } else if (changeType == WatcherChangeTypes.Changed && File.GetLastWriteTimeUtc(filePath) == statusEntry.LastWrite) { return(null); } } stream.Seek(0, SeekOrigin.End); var streamReader = new StreamWriter(stream) { AutoFlush = true }; stream = null; return(streamReader); } catch { return(null); } finally { if (stream != null) { stream.Dispose(); } } }
/// <summary> /// Process the file indicated by the specified <see cref="FileSystemEventArgs"/>. /// </summary> /// <param name="eventArgs">The <see cref="FileSystemEventArgs"/> indicating the file to process.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param> /// <returns> /// A <see cref="Task"/> that returns true if the file was processed successfully, false otherwise. /// </returns> public virtual async Task <bool> ProcessFileAsync(FileSystemEventArgs eventArgs, CancellationToken cancellationToken) { try { StatusFileEntry status = null; string filePath = eventArgs.FullPath; using (var statusWriter = AcquireStatusFileLock(filePath, eventArgs.ChangeType, out status)) { if (statusWriter == null) { return(false); } // We've acquired the lock. The current status might be either Failed // or Processing (if processing failed before we were unable to update // the file status to Failed) int processCount = 0; if (status != null) { processCount = status.ProcessCount; } while (processCount++ < MaxProcessCount) { FunctionResult result = null; if (result != null) { var delay = GetRetryInterval(result, processCount); await Task.Delay(delay); } // write an entry indicating the file is being processed status = new StatusFileEntry { State = ProcessingState.Processing, Timestamp = DateTime.Now, LastWrite = File.GetLastWriteTimeUtc(filePath), ChangeType = eventArgs.ChangeType, InstanceId = InstanceId, ProcessCount = processCount }; _serializer.Serialize(statusWriter, status); statusWriter.WriteLine(); // invoke the job function var input = new TriggeredFunctionData { TriggerValue = eventArgs }; result = await _executor.TryExecuteAsync(input, cancellationToken); // write a status entry indicating the state of processing status.State = result.Succeeded ? ProcessingState.Processed : ProcessingState.Failed; status.Timestamp = DateTime.Now; _serializer.Serialize(statusWriter, status); statusWriter.WriteLine(); if (result.Succeeded) { return(true); } } return(false); } } catch { return(false); } }