/* * Copyright (c) 2008-2015 Peter Palotas, Jeffrey Jangli, Alexandr Normuradov * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software (Alphaleonis) and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * */ #region using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using NLog; #endregion namespace WbLib.FileSystem { //////////////////////////////////////////////////////////////////////////////////////////////////// /// /// This class implements a high-performance, recursive file scanner, which uses Win32 methods to /// achieve maximum performance and to work with paths that are longer than the .NET limit of 260ish /// characters. /// /// /// Mgardner, 10/27/2016. /// /// Generic type parameter. //////////////////////////////////////////////////////////////////////////////////////////////////// public sealed class FastFileScanner : IDisposable { #region ENUMERATIONS #endregion #region FIELDS /// NLog logging. private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); /// Indicates whether the resources have already been disposed. [DebuggerDisplay("_alreadyDisposed = {_alreadyDisposed}")] private bool _alreadyDisposed = false; #endregion #region PROPERTIES #endregion #region DELEGATES //////////////////////////////////////////////////////////////////////////////////////////////////// /// Executes the caller file action operation. /// /// Mgardner, 10/27/2016. /// /// Information describing the file. /// The metadata. /// /// A Task<bool> //////////////////////////////////////////////////////////////////////////////////////////////////// public delegate Task DoCallerFileAction(Alphaleonis.Win32.Filesystem.FileInfo fileInfo, T metadata); //////////////////////////////////////////////////////////////////////////////////////////////////// /// Executes the caller folder action operation. /// /// Mgardner, 10/27/2016. /// /// Information describing the dir. /// The metadata. /// /// A bool. //////////////////////////////////////////////////////////////////////////////////////////////////// public delegate bool DoCallerFolderAction(Alphaleonis.Win32.Filesystem.DirectoryInfo dirInfo, T metadata); #endregion #region CONSTRUCTORS/DESTRUCTORS //////////////////////////////////////////////////////////////////////////////////////////////////// /// Constructor. /// /// Mgardner, 10/27/2016. //////////////////////////////////////////////////////////////////////////////////////////////////// public FastFileScanner() { } #endregion #region METHODS //////////////////////////////////////////////////////////////////////////////////////////////////// /// /// This is the public method used to recursively scan for files starting from an initial /// directory. For maximum performance, we are using .NET's ability to execute parallel tasks. /// /// /// Mgardner, 10/27/2016. /// /// Thrown when an Aggregate error condition occurs. /// /// The directory to scan. /// The filter for files. /// Metadata about the folder holding the files. /// The user supplied action to perform for each folder. /// The user supplied action to perform for each file. /// Recurse subfolders. /// (Optional) True to use parallel processing. /// /// True if it succeeds, false if it fails. //////////////////////////////////////////////////////////////////////////////////////////////////// public static bool ScanDirectories( Alphaleonis.Win32.Filesystem.DirectoryInfo dirInfo, string filter, T metadata, DoCallerFolderAction folderAction, DoCallerFileAction fileAction, bool recursive, bool useParallelProcessing = false) { // Use ConcurrentQueue to enable safe enqueueing from multiple threads. var exceptions = new ConcurrentQueue(); try { // Get the files in our current directory. var files = dirInfo.EnumerateFiles(filter); // For each file in the current directory, do our fileAction method. DoFileAction(files, metadata, fileAction, exceptions, useParallelProcessing); if (recursive) { // Get the directories in the current directory. // For each directory, scan it's files and folders... var folders = dirInfo.EnumerateDirectories(filter); if (folders.Count() > 0) { DoFolderAction(folders, filter, metadata, folderAction, fileAction, recursive, exceptions, useParallelProcessing); } } } catch (Exception ex) { // Catch file access errors from enumeration. // Log the error and continue with loop processing. Logger.Error(ex); } // Throw the exceptions here after the loops completes. if (exceptions.Count > 0) { throw new AggregateException(exceptions); } // FIXME: Make this method return a real status. return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// /// /// This is the priavet method used to recursively scan for files starting from an initial /// directory. For maximum performance, we are using .NET's ability to execute parallel tasks. /// /// /// Mgardner, 10/27/2016. /// /// The directory to scan. /// The filter for files. /// T about the folder holding the files. /// The user supplied action to perform for each folder. /// The user supplied action to perform for each file. /// Recurse subfolders. /// True to use parallel processing. //////////////////////////////////////////////////////////////////////////////////////////////////// private static void ScanDirectory( Alphaleonis.Win32.Filesystem.DirectoryInfo dirInfo, string filter, T metadata, DoCallerFolderAction folderAction, DoCallerFileAction fileAction, bool recursive, bool useParallelProcessing) { ScanDirectories(dirInfo, filter, metadata, folderAction, fileAction, recursive, useParallelProcessing); } //////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Performs an action on a file either with parallel processing, or sequentially. /// /// /// Mgardner, 10/27/2016. /// /// The files to process. /// T about the parent folder holding the files. /// The action to invoke for each file. /// Any exceptions. /// True, use parallel processing. //////////////////////////////////////////////////////////////////////////////////////////////////// private static void DoFileAction( IEnumerable files, T metadata, DoCallerFileAction fileAction, ConcurrentQueue exceptions, bool useParallelProcessing) { Task.WhenAll(files.Select(async file => { await FileAction(file, metadata, fileAction, exceptions); })); } //////////////////////////////////////////////////////////////////////////////////////////////////// /// Invokes the user supplied action with the file. /// /// Mgardner, 10/27/2016. /// /// The file to process. /// T about the parent folder holding the file. /// The action to invoke on the file. /// Any exceptions. /// /// A Task<bool> //////////////////////////////////////////////////////////////////////////////////////////////////// private static async Task FileAction( Alphaleonis.Win32.Filesystem.FileInfo file, T metadata, DoCallerFileAction fileAction, ConcurrentQueue exceptions) { try { if (fileAction != null) { await fileAction(file, metadata); } } catch (Exception ex) { Logger.Error(ex); // Store the exception and continue with the loop. exceptions.Enqueue(ex); } // FIXME: Return actual status. return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Performs an action on a folder either with parallel processing, or sequentially. /// /// /// Mgardner, 10/27/2016. /// /// The folders to process. /// The filter for the files to process. /// T about the parent folder holding the folders. /// The action to invoke for each folder. /// The action to invoke for each file. /// Recurse subfolders. /// Any exceptions. /// True, use parallel processing. //////////////////////////////////////////////////////////////////////////////////////////////////// private static void DoFolderAction( IEnumerable folders, string filter, T metadata, DoCallerFolderAction folderAction, DoCallerFileAction fileAction, bool recursive, ConcurrentQueue exceptions, bool useParallelProcessing) { if (useParallelProcessing) { Parallel.ForEach(folders, new ParallelOptions { }, (folder) => { FolderAction(folder, filter, metadata, folderAction, fileAction, recursive, exceptions, useParallelProcessing); } ); } else { foreach (var folder in folders) { FolderAction(folder, filter, metadata, folderAction, fileAction, recursive, exceptions, useParallelProcessing); } } } //////////////////////////////////////////////////////////////////////////////////////////////////// /// Invokes the user supplied action with the file. /// /// Mgardner, 10/27/2016. /// /// The file to process. /// The filter for files. /// T about the parent folder holding the file. /// The user supplied action to perform for each folder. /// The action to invoke on the file. /// Recurse subfolders. /// Any exceptions. /// True to use parallel processing. //////////////////////////////////////////////////////////////////////////////////////////////////// private static void FolderAction( Alphaleonis.Win32.Filesystem.DirectoryInfo folder, string filter, T metadata, DoCallerFolderAction folderAction, DoCallerFileAction fileAction, bool recursive, ConcurrentQueue exceptions, bool useParallelProcessing) { try { if ((folderAction != null) && folderAction(folder, metadata)) { ScanDirectory(folder, filter, metadata, folderAction, fileAction, recursive, useParallelProcessing); } } catch (Exception ex) { Logger.Error(ex); // Store the exception and continue with the loop. exceptions.Enqueue(ex); } } #endregion #region IDISPOSABLE IMPLEMENTATION //////////////////////////////////////////////////////////////////////////////////////////////////// /// /// Implement the only method in IDisposable. It calls the virtual Dispose() and suppresses /// finalization. /// /// /// Mgardner, 10/27/2016. //////////////////////////////////////////////////////////////////////////////////////////////////// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } //////////////////////////////////////////////////////////////////////////////////////////////////// /// This method performs the clean-up work. /// /// This method will be implemented in sealed classes, too. /// /// . //////////////////////////////////////////////////////////////////////////////////////////////////// private void Dispose(bool isDisposing) { // Don't dispose more than once! if (!_alreadyDisposed) { if (isDisposing) { // Dispose of MANAGED resources by calling their // Dispose() method. //Api = null; //if (_YearsToDmsFolderIds != null) //{ // _YearsToDmsFolderIds.Dispose(); // _YearsToDmsFolderIds = null; //} // Dispose of UNMANAGED resources here and set the disposed flag. //if (nativeResource != IntPtr.Zero) //{ // Marshal.FreeHGlobal(nativeResource); // nativeResource = IntPtr.Zero; //} // Indicate that disposing has been completed. _alreadyDisposed = true; } } // Tell the base class to free its resources because // it is responsible for calling GC.SuppressFinalize(). // base.Dispose(isDisposing); } #endregion } }