//******************************************************************************************************// FileTracker.cs - Gbtc//// Copyright © 2014, Grid Protection Alliance. All Rights Reserved.//// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See// the NOTICE file distributed with this work for additional information regarding copyright ownership.// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may// not use this file except in compliance with the License. You may obtain a copy of the License at://// http://www.opensource.org/licenses/MIT//// Unless agreed to in writing, the subject software distributed under the License is distributed on an// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the// License for the specific language governing permissions and limitations.//// Code Modification History:// ----------------------------------------------------------------------------------------------------// 04/29/2014 - Stephen C. Wills// Generated original version of source code.////****************************************************************************************************** using System;using System.Collections.Generic;using System.Diagnostics.CodeAnalysis;using System.IO;using System.Linq;using System.Text;using System.Threading;using System.Timers;using GSF.Collections;using GSF.Threading;using CancellationToken = GSF.Threading.CancellationToken;using Timer = System.Timers.Timer; namespace GSF.IO{ /// <summary> /// Defines strategies for enumerating /// files in the watch directories. /// </summary> public enum FileEnumerationStrategy { /// <summary> /// Enumerate all files sequentially. /// </summary> Sequential, /// <summary> /// Enumerates the watch directories in parallel, /// but subdirectories are processed sequentially. /// </summary> ParallelWatchDirectories, /// <summary> /// Enumerates every directory, including subdirectories, in parallel. /// </summary> ParallelSubdirectories, /// <summary> /// Does not enumerate directories, relying only on /// the file watcher to handle file processing events. /// </summary> None } /// <summary> /// Arguments to events triggered by the <see cref="FileProcessor"/>. /// </summary> public class FileProcessorEventArgs : EventArgs { #region [ Members ] // Fields private readonly string m_fullPath; private readonly bool m_alreadyProcessed; private readonly bool m_raisedByFileWatcher; private readonly Func<int> m_retryCounter; private bool m_requeue; #endregion #region [ Constructors ] /// <summary> /// Creates a new instance of the <see cref="FileProcessorEventArgs"/> class. /// </summary> /// <param name="fullPath">The full path to the file to be processed.</param> /// <param name="alreadyProcessed">Flag indicating whether this file has been processed before.</param> /// <param name="raisedByFileWatcher">Flag indicating whether this event was raised by the file watcher.</param> /// <param name="retryCounter">The function that provides the value for <see cref="RetryCount"/>.</param> public FileProcessorEventArgs(string fullPath, bool alreadyProcessed, bool raisedByFileWatcher, Func<int> retryCounter) { m_fullPath = fullPath; m_alreadyProcessed = alreadyProcessed; m_raisedByFileWatcher = raisedByFileWatcher; m_retryCounter = retryCounter; } #endregion #region [ Properties ] /// <summary> /// Gets the full path to the file to be processed. /// </summary> public string FullPath { get { return m_fullPath; } } /// <summary> /// Gets the flag that indicates whether this file has been processed before. /// </summary> public bool AlreadyProcessed { get { return m_alreadyProcessed; } } /// <summary> /// Gets the flag that indicates whether this event was raised by the file watcher. /// </summary> public bool RaisedByFileWatcher { get { return m_raisedByFileWatcher; } } /// <summary> /// Gets the number of file processing attempts. /// </summary> public int RetryCount { get { return m_retryCounter(); } } /// <summary> /// Gets or sets a flag that indicates whether the /// file should be requeued by the file processor. /// </summary> public bool Requeue { get { return m_requeue; } set { m_requeue = value; } } #endregion } /// <summary> /// Tracks files processed in a list of directories, and /// notifies when new files become available to be processed. /// </summary> public sealed class FileProcessor : IDisposable { #region [ Members ] // Nested Types private sealed class TrackedDirectory : IDisposable { #region [ Members ] // Fields private FileProcessor m_fileProcessor; private SafeFileWatcher m_fileWatcher; private FileEnumerator m_fileEnumerator; private bool m_disposed; #endregion #region [ Constructors ] public TrackedDirectory(FileProcessor fileProcessor, string path) { m_fileProcessor = fileProcessor; Path = path; CreateFileWatcher(); } #endregion #region [ Properties ] public string Path { get; } public bool IsEnumerating { get { return m_fileEnumerator?.Active ?? false; } } public List<string> ActivelyEnumeratedPaths { get { return m_fileEnumerator.ActivelyVisitedPaths; } } #endregion #region [ Methods ] public void EnumerateFiles(FileEnumerationStrategy fileEnumerationStrategy) { if (m_disposed) return; if (IsEnumerating) throw new InvalidOperationException("Enumeration is already in progress."); CancellationToken cancellationToken = new CancellationToken(); m_fileEnumerator = new FileEnumerator(Path, m_fileProcessor, cancellationToken); m_fileEnumerator.Enumerate(fileEnumerationStrategy); } public void CancelEnumeration() { m_fileEnumerator?.Cancel(); } public void CheckFileWatcher() { if (m_disposed) return; if (!m_fileWatcher.EnableRaisingEvents) { DisposeFileWatcher(); CreateFileWatcher(); } } /// <summary> /// Releases all the resources used by the <see cref="TrackedDirectory"/> object. /// </summary> public void Dispose() { if (m_disposed) return; try { CancelEnumeration(); DisposeFileWatcher(); } finally { m_disposed = true; } } private void CreateFileWatcher() { m_fileWatcher = new SafeFileWatcher(Path); m_fileWatcher.InternalBufferSize = m_fileProcessor.InternalBufferSize; m_fileWatcher.IncludeSubdirectories = true; m_fileWatcher.Created += m_fileProcessor.Watcher_Created; m_fileWatcher.Changed += m_fileProcessor.Watcher_Changed; m_fileWatcher.Renamed += m_fileProcessor.Watcher_Renamed; m_fileWatcher.Deleted += m_fileProcessor.Watcher_Deleted; m_fileWatcher.Error += m_fileProcessor.Watcher_Error; m_fileWatcher.EnableRaisingEvents = true; } private void DisposeFileWatcher() { m_fileWatcher.Created -= m_fileProcessor.Watcher_Created; m_fileWatcher.Changed -= m_fileProcessor.Watcher_Changed; m_fileWatcher.Renamed -= m_fileProcessor.Watcher_Renamed; m_fileWatcher.Deleted -= m_fileProcessor.Watcher_Deleted; m_fileWatcher.Error -= m_fileProcessor.Watcher_Error; m_fileWatcher.Dispose(); } #endregion } private class FileEnumerator { #region [ Members ] // Fields private readonly FileProcessor m_fileProcessor; private readonly CancellationToken m_cancellationToken; private string m_lastVisitedPath; private bool m_active; #endregion #region [ Constructors ] public FileEnumerator(string path, FileProcessor fileProcessor, CancellationToken cancellationToken) { Path = path; m_fileProcessor = fileProcessor; m_cancellationToken = cancellationToken; } #endregion #region [ Properties ] public string Path { get; } public FileEnumerator[] SubdirectoryEnumerators { get; private set; } = new FileEnumerator[0]; public bool Active { get { if (m_active) return true; FileEnumerator[] subdirectoryEnumerators = SubdirectoryEnumerators; return m_active || subdirectoryEnumerators.Any(enumerator => enumerator.Active); } } public List<string> ActivelyVisitedPaths { get { List<string> activelyVisitedPaths = new List<string>(); BuildActivelyVisitedPaths(activelyVisitedPaths); return activelyVisitedPaths; } } #endregion #region [ Methods ] public void Enumerate(FileEnumerationStrategy enumerationStrategy) { if (Active) throw new InvalidOperationException("Enumeration is already in progress."); if ((object)LogicalThread.CurrentThread == null) throw new InvalidOperationException("Enumeration must be executed on a logical thread."); m_active = true; if (enumerationStrategy != FileEnumerationStrategy.ParallelSubdirectories) { SubdirectoryEnumerators = new FileEnumerator[0]; // If subdirectories do not need to be processed in parallel, // we can simply process a recursive call to enumerate all files IEnumerable<string> fileEnumerable = FilePath.EnumerateFiles(Path, exceptionHandler: m_fileProcessor.OnError); EnumerateNextFile(new EnumerableWrapper(fileEnumerable, m_cancellationToken)); } else { // To process subdirectories in parallel, we need to create a recursive structure // of enumerators to process each subdirectory on its own logical thread InitializeSubdirectoryEnumerators(); EnumerateNextDirectory(0); } } public void Cancel() { m_cancellationToken.Cancel(); } // Initializes the FileEnumerators for subdirectories. private void InitializeSubdirectoryEnumerators() { // Immediately enumerate all subdirectories so we can build the array of FileEnumerators. // Using a fixed-size collection like an array greatly simplifies multithreaded access to the collection. using (EnumerableWrapper wrapper = new EnumerableWrapper(EnumerateDirectories(), m_cancellationToken)) { List<string> subDirectories = new List<string>(); while (wrapper.MoveNext()) subDirectories.Add(wrapper.Current); SubdirectoryEnumerators = subDirectories .Select(path => new FileEnumerator(path, m_fileProcessor, m_cancellationToken)) .ToArray(); } } // Enumerates the next subdirectory in the list of subdirectory enumerators. private void EnumerateNextDirectory(int index) { try { m_active = !m_cancellationToken.IsCancelled; if (!m_active) return; FileEnumerator[] subdirectoryEnumerators = SubdirectoryEnumerators; if (index >= subdirectoryEnumerators.Length) { EnumerateNextFile(new EnumerableWrapper(EnumerateFiles(), m_cancellationToken)); return; } // Create a new thread for the subdirectory since this code is only invoked if the file // processor is using the enumeration strategy for processing subdirectories in parallel LogicalThread subdirectoryThread = m_fileProcessor.m_threadScheduler.CreateThread(); subdirectoryThread.Push(() => subdirectoryEnumerators[index].Enumerate(FileEnumerationStrategy.ParallelSubdirectories)); // Invoke the asynchronous call to enumerate the next directory LogicalThread.CurrentThread.Push(() => EnumerateNextDirectory(index + 1)); m_lastVisitedPath = subdirectoryEnumerators[index].Path; } catch { m_active = false; throw; } } // Enumerates the next file in the given enumerable. private void EnumerateNextFile(EnumerableWrapper wrapper) { try { m_active = AdvanceToNextMatch(wrapper); if (!m_active) return; ProcessCurrentFileAndEnumerateNextFile(wrapper); m_lastVisitedPath = wrapper.Current; } catch { m_active = false; wrapper.Dispose(); throw; } finally { if (!wrapper.LastMove) { m_active = false; wrapper.Dispose(); } } } // Advances enumeration until the next file that matches the filter. private bool AdvanceToNextMatch(EnumerableWrapper wrapper) { List<string> skippedFiles = new List<string>(); try { while (true) { if (!wrapper.MoveNext()) return false; if (m_fileProcessor.MatchesFilter(wrapper.Current)) return true; skippedFiles.Add(wrapper.Current); m_lastVisitedPath = wrapper.Current; } } finally { m_fileProcessor.m_processingThread.Push(1, () => { foreach (string filePath in skippedFiles) m_fileProcessor.TouchAndSkip(filePath); }); } } // Processes the current file in the given enumerable.Lines 1–500 of 1600. Load more
//****************************************************************************************************** // FileTracker.cs - Gbtc // // Copyright © 2014, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 04/29/2014 - Stephen C. Wills // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Timers; using GSF.Collections; using GSF.Threading; using CancellationToken = GSF.Threading.CancellationToken; using Timer = System.Timers.Timer; namespace GSF.IO { /// <summary> /// Defines strategies for enumerating /// files in the watch directories. /// </summary> public enum FileEnumerationStrategy { /// <summary> /// Enumerate all files sequentially. /// </summary> Sequential, /// <summary> /// Enumerates the watch directories in parallel, /// but subdirectories are processed sequentially. /// </summary> ParallelWatchDirectories, /// <summary> /// Enumerates every directory, including subdirectories, in parallel. /// </summary> ParallelSubdirectories, /// <summary> /// Does not enumerate directories, relying only on /// the file watcher to handle file processing events. /// </summary> None } /// <summary> /// Arguments to events triggered by the <see cref="FileProcessor"/>. /// </summary> public class FileProcessorEventArgs : EventArgs { #region [ Members ] // Fields private readonly string m_fullPath; private readonly bool m_alreadyProcessed; private readonly bool m_raisedByFileWatcher; private readonly Func<int> m_retryCounter; private bool m_requeue; #endregion #region [ Constructors ] /// <summary> /// Creates a new instance of the <see cref="FileProcessorEventArgs"/> class. /// </summary> /// <param name="fullPath">The full path to the file to be processed.</param> /// <param name="alreadyProcessed">Flag indicating whether this file has been processed before.</param> /// <param name="raisedByFileWatcher">Flag indicating whether this event was raised by the file watcher.</param> /// <param name="retryCounter">The function that provides the value for <see cref="RetryCount"/>.</param> public FileProcessorEventArgs(string fullPath, bool alreadyProcessed, bool raisedByFileWatcher, Func<int> retryCounter) { m_fullPath = fullPath; m_alreadyProcessed = alreadyProcessed; m_raisedByFileWatcher = raisedByFileWatcher; m_retryCounter = retryCounter; } #endregion #region [ Properties ] /// <summary> /// Gets the full path to the file to be processed. /// </summary> public string FullPath { get { return m_fullPath; } } /// <summary> /// Gets the flag that indicates whether this file has been processed before. /// </summary> public bool AlreadyProcessed { get { return m_alreadyProcessed; } } /// <summary> /// Gets the flag that indicates whether this event was raised by the file watcher. /// </summary> public bool RaisedByFileWatcher { get { return m_raisedByFileWatcher; } } /// <summary> /// Gets the number of file processing attempts. /// </summary> public int RetryCount { get { return m_retryCounter(); } } /// <summary> /// Gets or sets a flag that indicates whether the /// file should be requeued by the file processor. /// </summary> public bool Requeue { get { return m_requeue; } set { m_requeue = value; } } #endregion } /// <summary> /// Tracks files processed in a list of directories, and /// notifies when new files become available to be processed. /// </summary> public sealed class FileProcessor : IDisposable { #region [ Members ] // Nested Types private sealed class TrackedDirectory : IDisposable { #region [ Members ] // Fields private FileProcessor m_fileProcessor; private SafeFileWatcher m_fileWatcher; private FileEnumerator m_fileEnumerator; private bool m_disposed; #endregion #region [ Constructors ] public TrackedDirectory(FileProcessor fileProcessor, string path) { m_fileProcessor = fileProcessor; Path = path; CreateFileWatcher(); } #endregion #region [ Properties ] public string Path { get; } public bool IsEnumerating { get { return m_fileEnumerator?.Active ?? false; } } public List<string> ActivelyEnumeratedPaths { get { return m_fileEnumerator.ActivelyVisitedPaths; } } #endregion #region [ Methods ] public void EnumerateFiles(FileEnumerationStrategy fileEnumerationStrategy) { if (m_disposed) return; if (IsEnumerating) throw new InvalidOperationException("Enumeration is already in progress."); CancellationToken cancellationToken = new CancellationToken(); m_fileEnumerator = new FileEnumerator(Path, m_fileProcessor, cancellationToken); m_fileEnumerator.Enumerate(fileEnumerationStrategy); } public void CancelEnumeration() { m_fileEnumerator?.Cancel(); } public void CheckFileWatcher() { if (m_disposed) return; if (!m_fileWatcher.EnableRaisingEvents) { DisposeFileWatcher(); CreateFileWatcher(); } } /// <summary> /// Releases all the resources used by the <see cref="TrackedDirectory"/> object. /// </summary> public void Dispose() { if (m_disposed) return; try { CancelEnumeration(); DisposeFileWatcher(); } finally { m_disposed = true; } } private void CreateFileWatcher() { m_fileWatcher = new SafeFileWatcher(Path); m_fileWatcher.InternalBufferSize = m_fileProcessor.InternalBufferSize; m_fileWatcher.IncludeSubdirectories = true; m_fileWatcher.Created += m_fileProcessor.Watcher_Created; m_fileWatcher.Changed += m_fileProcessor.Watcher_Changed; m_fileWatcher.Renamed += m_fileProcessor.Watcher_Renamed; m_fileWatcher.Deleted += m_fileProcessor.Watcher_Deleted; m_fileWatcher.Error += m_fileProcessor.Watcher_Error; m_fileWatcher.EnableRaisingEvents = true; } private void DisposeFileWatcher() { m_fileWatcher.Created -= m_fileProcessor.Watcher_Created; m_fileWatcher.Changed -= m_fileProcessor.Watcher_Changed; m_fileWatcher.Renamed -= m_fileProcessor.Watcher_Renamed; m_fileWatcher.Deleted -= m_fileProcessor.Watcher_Deleted; m_fileWatcher.Error -= m_fileProcessor.Watcher_Error; m_fileWatcher.Dispose(); } #endregion } private class FileEnumerator { #region [ Members ] // Fields private readonly FileProcessor m_fileProcessor; private readonly CancellationToken m_cancellationToken; private string m_lastVisitedPath; private bool m_active; #endregion #region [ Constructors ] public FileEnumerator(string path, FileProcessor fileProcessor, CancellationToken cancellationToken) { Path = path; m_fileProcessor = fileProcessor; m_cancellationToken = cancellationToken; } #endregion #region [ Properties ] public string Path { get; } public FileEnumerator[] SubdirectoryEnumerators { get; private set; } = new FileEnumerator[0]; public bool Active { get { if (m_active) return true; FileEnumerator[] subdirectoryEnumerators = SubdirectoryEnumerators; return m_active || subdirectoryEnumerators.Any(enumerator => enumerator.Active); } } public List<string> ActivelyVisitedPaths { get { List<string> activelyVisitedPaths = new List<string>(); BuildActivelyVisitedPaths(activelyVisitedPaths); return activelyVisitedPaths; } } #endregion #region [ Methods ] public void Enumerate(FileEnumerationStrategy enumerationStrategy) { if (Active) throw new InvalidOperationException("Enumeration is already in progress."); if ((object)LogicalThread.CurrentThread == null) throw new InvalidOperationException("Enumeration must be executed on a logical thread."); m_active = true; if (enumerationStrategy != FileEnumerationStrategy.ParallelSubdirectories) { SubdirectoryEnumerators = new FileEnumerator[0]; // If subdirectories do not need to be processed in parallel, // we can simply process a recursive call to enumerate all files IEnumerable<string> fileEnumerable = FilePath.EnumerateFiles(Path, exceptionHandler: m_fileProcessor.OnError); EnumerateNextFile(new EnumerableWrapper(fileEnumerable, m_cancellationToken)); } else { // To process subdirectories in parallel, we need to create a recursive structure // of enumerators to process each subdirectory on its own logical thread InitializeSubdirectoryEnumerators(); EnumerateNextDirectory(0); } } public void Cancel() { m_cancellationToken.Cancel(); } // Initializes the FileEnumerators for subdirectories. private void InitializeSubdirectoryEnumerators() { // Immediately enumerate all subdirectories so we can build the array of FileEnumerators. // Using a fixed-size collection like an array greatly simplifies multithreaded access to the collection. using (EnumerableWrapper wrapper = new EnumerableWrapper(EnumerateDirectories(), m_cancellationToken)) { List<string> subDirectories = new List<string>(); while (wrapper.MoveNext()) subDirectories.Add(wrapper.Current); SubdirectoryEnumerators = subDirectories .Select(path => new FileEnumerator(path, m_fileProcessor, m_cancellationToken)) .ToArray(); } } // Enumerates the next subdirectory in the list of subdirectory enumerators. private void EnumerateNextDirectory(int index) { try { m_active = !m_cancellationToken.IsCancelled; if (!m_active) return; FileEnumerator[] subdirectoryEnumerators = SubdirectoryEnumerators; if (index >= subdirectoryEnumerators.Length) { EnumerateNextFile(new EnumerableWrapper(EnumerateFiles(), m_cancellationToken)); return; } // Create a new thread for the subdirectory since this code is only invoked if the file // processor is using the enumeration strategy for processing subdirectories in parallel LogicalThread subdirectoryThread = m_fileProcessor.m_threadScheduler.CreateThread(); subdirectoryThread.Push(() => subdirectoryEnumerators[index].Enumerate(FileEnumerationStrategy.ParallelSubdirectories)); // Invoke the asynchronous call to enumerate the next directory LogicalThread.CurrentThread.Push(() => EnumerateNextDirectory(index + 1)); m_lastVisitedPath = subdirectoryEnumerators[index].Path; } catch { m_active = false; throw; } } // Enumerates the next file in the given enumerable. private void EnumerateNextFile(EnumerableWrapper wrapper) { try { m_active = AdvanceToNextMatch(wrapper); if (!m_active) return; ProcessCurrentFileAndEnumerateNextFile(wrapper); m_lastVisitedPath = wrapper.Current; } catch { m_active = false; wrapper.Dispose(); throw; } finally { if (!wrapper.LastMove) { m_active = false; wrapper.Dispose(); } } } // Advances enumeration until the next file that matches the filter. private bool AdvanceToNextMatch(EnumerableWrapper wrapper) { List<string> skippedFiles = new List<string>(); try { while (true) { if (!wrapper.MoveNext()) return false; if (m_fileProcessor.MatchesFilter(wrapper.Current)) return true; skippedFiles.Add(wrapper.Current); m_lastVisitedPath = wrapper.Current; } } finally { m_fileProcessor.m_processingThread.Push(1, () => { foreach (string filePath in skippedFiles) m_fileProcessor.TouchAndSkip(filePath); }); } } // Processes the current file in the given enumerable.