/*
* 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
}
}