Test Drive
That's probably enough talk about the design. Let's now take a look at the library in action. We've already seen the Windows Font file search, so now let's look at some of the other simple examples that are included with the recls 100% .NET distribution. (For brevity, I'm going to elide the command-line argument handling and other non-relevant aspects here. Check the distribution for the full program listings.)
FindEmptySubdirectories
This example (Listing 12) finds all the empty, accessible subdirectories of the current directory.
SearchOptions all = SearchOptions.IncludeHidden
| SearchOptions.IncludeSystem
| SearchOptions.IgnoreInaccessibleNodes;
foreach(IEntry directory in FileSearcher.Search(null, null
, SearchOptions.Directories | all))
{
bool fileFound = false;
foreach(IEntry file in FileSearcher.Search(directory.Path, null
, SearchOptions.Files | all))
{
fileFound = true;
break;
}
if(!fileFound)
{
Console.WriteLine(entry);
}
}
ShowImmediateSubdirectoriesTotalSizes
This example shows the total sizes of all immediate sub-directories. It is similar to the one above, but it does not recurse.
foreach(IEntry entry in FileSearcher.Search(null, null
, SearchOptions.Directories, 0))
{
Console.WriteLine("{0} : {1}", entry
, FileSearcher.CalculateDirectorySize(entry));
}
This is actually a really useful tool when you're trying to find where the space is being consumed on a drive. It can also be written in a single statement:
FileSearcher.Search(null, null,
SearchOptions.Directories, 0
).ForEach((e) => Console.WriteLine("{0} : {1}", e
, FileSearcher.CalculateDirectorySize(e)));
ListInaccessibleDirectories
This example (Listing 13) uses the exception handler to list all the inaccessible sub-directories.
FileSearcher.Search(null, null
, SearchOptions.Directories |
SearchOptions.IncludeHidden |
SearchOptions.IncludeSystem
, FileSearcher.UnrestrictedDepth,
(string directory, int depth) =>
{
Trace.WriteLine("searching " + directory + " [" + depth + "]");
return ProgressHandlerResult.Continue;
},
(path, x) =>
{
Console.WriteLine("could not access {0}: {1}", path, x.Message);
return ExceptionHandlerResult.ConsumeExceptionAndContinue;
}
).ForEach((e) => e = null);
The hidden and system flags are specified to ensure the best chance of running into inaccessible directories. For good measure, I have it perform some rudimentary diagnostic logging by specifying a progress handler that traces the directory and depth. Also, note the curious lambda expression in the ForEach() call. This is the best I could think of to give a no-op, since we don't need to do anything with the search results, just have it iterate over all the elements accessible in the IEnumerable<IEntry> instance returned from FileSearcher.Search().
When run on my work drive, I get the following output:
could not access H:\dev\bin\hidden\inaccessible\: Access to the path 'H:\dev\bin\hidden\inaccessible' is denied. could not access H:\dev\bin\hidden\inaccessible\: Access to the path 'H:\dev\bin\hidden\inaccessible' is denied. could not access H:\System Volume Information\: Access to the path 'H:\System Volume Information' is denied. could not access H:\System Volume Information\: Access to the path 'H:\System Volume Information' is denied.
DirectoryEntryCountFrequencyAnalysis
The final directory-oriented example (Listing 14) lists the number of files contained in each directory.
foreach(IEntry dir in FileSearcher.Search(null, null
, SearchOptions.Directories))
{
int n = 0;
foreach(IEntry file in FileSearcher.Search(dir.Path, null
, SearchOptions.Files, 0))
{
++n;
}
Console.WriteLine("{0} has {1} file(s)", dir.SearchRelativePath, n);
}
FindLargestMatchingFile
This example (Listing 15) finds the largest file matching the given pattern(s). I'm including the full listing to illustrate one way of processing command-line arguments into multi-part patterns. (Please note: it's not the best way of handling command-line arguments, but I didn't want to introduce any more dependencies or complexities into the examples.)
static void Main(string[] args)
{
string directory = null;
List<string> patterns = new List<string>();
foreach(string arg in args)
{
if(0 != arg.Length && '-' == arg[0])
{
switch(arg)
{
case "--help":
ShowUsageAndQuit(0);
break;
default:
Console.Error.WriteLine("FindLargestMatchingFile: unrecognised argument {0}; use --help for usage", arg);
break;
}
}
else
{
if(null == directory && arg.IndexOfAny(new char[] { '?', '*' }) < 0)
{
directory = arg;
}
else
{
if(arg.IndexOfAny(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar } ) >= 0)
{
Console.Error.WriteLine("invalid pattern: {0}", arg);
Environment.Exit(1);
}
else
{
patterns.Add(arg);
}
}
}
}
if(0 == patterns.Count)
{
patterns.Add(FileSearcher.WildcardsAll);
}
IEntry largest = null;
foreach(IEntry entry in FileSearcher.Search(directory
, String.Join("|", patterns.ToArray())
, SearchOptions.None))
{
if(null == largest || largest.Size < entry.Size)
{
largest = entry;
}
}
if(null == largest)
{
Console.Out.WriteLine("no matching entries found");
}
else
{
Console.Out.WriteLine("largest entry is {0}, which is {1} bytes"
, largest.SearchRelativePath, largest.Size);
}
}
FindCertainSmallExecutables (with LINQ)
This example (Listing 16) finds (the search-relative path of) executable modules that are smaller than 10k and read-only, and uses LINQ.
var files = FileSearcher.Search(null, "*.exe|*.dll", SearchOptions.Files);
var modules = from file in files
where file.Size < 10240 && file.IsReadOnly
select file.SearchRelativePath;
foreach(var module in modules)
{
Console.WriteLine("module: {0}", module);
}
StatAFile
The last example (Listing 17) illustrates the use of Stat() to elicit information about a single filesystem entity. Once again, I'll show the full listing.
static void Main(string[] args)
{
string path = Assembly.GetEntryAssembly().Location;
if(0 != args.Length)
{
path = args[0];
}
IEntry entry = FileSearcher.Stat(path);
if(null == entry)
{
Console.Error.WriteLine("file not found");
}
else
{
Console.WriteLine("{0,20}:\t{1}", "Path", entry.Path);
Console.WriteLine("{0,20}:\t{1}", "SearchRelativePath", entry.SearchRelativePath);
Console.WriteLine("{0,20}:\t{1}", "Drive", entry.Drive);
Console.WriteLine("{0,20}:\t{1}", "DirectoryPath", entry.DirectoryPath);
Console.WriteLine("{0,20}:\t{1}", "Directory", entry.Directory);
Console.WriteLine("{0,20}:\t{1}", "SearchDirectory", entry.SearchDirectory);
Console.WriteLine("{0,20}:\t{1}", "UncDrive", entry.UncDrive);
Console.WriteLine("{0,20}:\t{1}", "File", entry.File);
Console.WriteLine("{0,20}:\t{1}", "FileName", entry.FileName);
Console.WriteLine("{0,20}:\t{1}", "FileExtension", entry.FileExtension);
Console.WriteLine("{0,20}:\t{1}", "CreationTime", entry.CreationTime);
Console.WriteLine("{0,20}:\t{1}", "ModificationTime", entry.ModificationTime);
Console.WriteLine("{0,20}:\t{1}", "LastAccessTime", entry.LastAccessTime);
Console.WriteLine("{0,20}:\t{1}", "LastStatusChangeTime", entry.LastStatusChangeTime);
Console.WriteLine("{0,20}:\t{1}", "Size", entry.Size);
Console.WriteLine("{0,20}:\t{1}", "Attributes", entry.Attributes);
Console.WriteLine("{0,20}:\t{1}", "IsReadOnly", entry.IsReadOnly);
Console.WriteLine("{0,20}:\t{1}", "IsDirectory", entry.IsDirectory);
Console.WriteLine("{0,20}:\t{1}", "IsUnc", entry.IsUnc);
Console.WriteLine("{0,20}:\t[{1}]", "DirectoryParts", String.Join(", ", entry.DirectoryParts.ToArray())); // Assumes "using System.Linq"
}
}
When run on my development system, I get the following output:
Path: H:\freelibs\recls\100\recls.net\examples\StatASolutionFile\bin\Debug\StatASolutionFile.exe
SearchRelativePath: StatASolutionFile.exe
Drive: H:
DirectoryPath: H:\freelibs\recls\100\recls.net\examples\StatASolutionFile\bin\Debug\
Directory: \freelibs\recls\100\recls.net\examples\StatASolutionFile\bin\Debug\
SearchDirectory: H:\freelibs\recls\100\recls.net\examples\StatASolutionFile\bin\Debug\
UncDrive:
File: StatASolutionFile.exe
FileName: StatASolutionFile
FileExtension: .exe
CreationTime: 3/10/2009 6:53:23 AM
ModificationTime: 3/10/2009 8:05:59 AM
LastAccessTime: 3/10/2009 8:14:35 AM
LastStatusChangeTime: 3/10/2009 8:05:59 AM
Size: 7168
Attributes: Archive, Compressed
IsReadOnly: False
IsDirectory: False
IsUnc: False
DirectoryParts: [\, freelibs\, recls\, 100\, recls.net\, examples\, StatASolutionFile\, bin\, Debug\]



