LFInteractive Docs
  • Programming Documentation
  • C#
    • Installing Visual Studio
    • Understanding C#
      • Namespaces
      • Classes
      • Enum
      • Variables
        • Static Types
        • Primitive Types
        • Access Modifiers
        • Variables vs Properties
        • Nullable Variables
      • Getters and Setters
      • Solution vs Project
      • Struct vs Class
      • Coding Conventions
      • Tasks and Async
        • Parallel Tasks
      • Methods
      • PreProcessor Statements
    • Creating Your First Console App
      • Class Library
        • Models
          • File Model
          • Result Model
        • Controllers
          • File Controller
          • File System Controller
      • Console App
        • Nuget Packages
        • Main Method
    • Minecraft.NET
      • Minecraft.NET Library
      • Modrinth.NET Library
      • CurseForge.NET Library
      • Fabric.NET Library
    • Common Lib
      • Strings
      • Advanced Network Client
      • Advanced Timer
      • Advanced File Info
      • Configuration File
      • Application Config
      • Database File
      • Crypt Class
  • C++
    • Networking
      • Windows Socket (Client)
    • cclip
    • VCPKG
    • spdlog
      • Getting Started
      • Patterns
Powered by GitBook
On this page
  • Create Class
  • Methods
  • Directory Iterator
  • Overview
  • Scan
  • Overview
  • Print
  • Overview
  • Save
  • Overview
  • Open File
  • Overview
  • Overview

Was this helpful?

Edit on GitHub
  1. C#
  2. Creating Your First Console App
  3. Class Library
  4. Controllers

File System Controller

Create Class

Create a static class called FileSystemController in the Controller namespace

See: Classes

namespace Library.Controller;
public static class FileSystemController
{}

Methods

  • Directory Iterator

  • Scan

  • Print

  • Save Result

  • Save

  • Open File

Directory Iterator

create a public static async method named DirectoryIterator that takes a String as a parameter and returns Task<(ConcurrentDictionary<string, FileModel> Model, int FileCount)>

private static async Task<(ConcurrentDictionary<string, FileModel> Model, int FileCount)> DirectoryIterator(string path)

create a Concurrent Dictionary with a String and a FileModel, Concurrent Dictionaries are Dictionaries that are thread safe.

ConcurrentDictionary<string, FileModel> fileData = new();

create an array of all files.

string[] entries = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);

Create a Parallel foreach loop through all file entries, a Parallel foreach is a foreach loop that splits the given array into many smaller arrays and creates a Thread around them, then process's the array. See: Parallel Tasks

Parallel.ForEach(entries, entry =>
{
});

Create a Nullable string named extension See: Nullable Variables

string? extension = Path.GetExtension(entry);
if (extension != null)
{
    // Do Stuff
}
FileModel instance = FileController.GetFile(entry);

Check if the file data already contains the instance extension and if it does append the instance to it

if (fileData.ContainsKey(extension))
{
    // Appends the result of "model" to "instance"
    lock (fileData)
    {
        fileData[extension] = fileData[extension] with
        {
            Lines = fileData[extension].Lines + instance.Lines,
            Bytes = fileData[extension].Bytes + instance.Bytes,
            Characters = fileData[extension].Characters + instance.Characters,
            NumberOfFiles = fileData[extension].NumberOfFiles + instance.NumberOfFiles,
        };
    }
}

otherwise create a new entry

else
{
    fileData[extension] = instance;
}

Finally iterate the file count

fileCount++;

Extra Note: You may want to wrap everything in a try catch

Overview

private static async Task<(ConcurrentDictionary<string, FileModel> Model, int FileCount)> DirectoryIterator(string path)
{
    ConcurrentDictionary<string, FileModel> fileData = new();
    string[] entries = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
    int fileCount = 0;
    List<Task> tasks = new();
    Parallel.ForEach(entries, entry =>
    {
        string extension = Path.GetExtension(entry);
        if (extension != null)
        {
            Log.Debug("Processing file: {FILE}", entry);
            try
            {
                // Path is a file
                FileModel instance = FileController.GetFile(entry);
                fileCount++;
                if (fileData.ContainsKey(extension))
                {
                    // Appends the result of "model" to "instance"
                    lock (fileData)
                    {
                        fileData[extension] = fileData[extension] with
                        {
                            Lines = fileData[extension].Lines + instance.Lines,
                            Bytes = fileData[extension].Bytes + instance.Bytes,
                            Characters = fileData[extension].Characters + instance.Characters,
                            NumberOfFiles = fileData[extension].NumberOfFiles + instance.NumberOfFiles,
                        };
                    }
                }
                else
                {
                    //fileData.TryAdd(extension, instance);
                    fileData[extension] = instance;
                }
            }
            catch (Exception e)
            {
                // Throws an error if the file could NOT be accessed.
                Log.Error("Unable to read file: {PATH}\n{EXCEPTION}", entry, e);
            }
        }
    });

    await Task.WhenAll(tasks);

    Log.Debug("Returning batch: {SIZE} - {FILES}", fileData.Count, fileCount);
    return (fileData, fileCount);
}

Scan

create a public static async method named Scan that takes a String as a parameter and returns Task<ResultModel>

public static async Task<ResultModel> Scan(string path)

Start a stopwatch

Stopwatch stopwatch = Stopwatch.StartNew();

Create a variable for model and file count

(ConcurrentDictionary<string, FileModel> models, int fileCount) = await DirectoryIterator(path);

Stop the stopwatch

stopwatch.Stop();

Then create and return a ResultModel See: Result Model

ResultModel model = new()
{
    Items = models.Values.OrderByDescending(i => i.Lines).ToArray(),
    NumberOfExtensions = models.Count,
    Duration = stopwatch.Elapsed,
    NumberOfFiles = fileCount
};
return model;

Overview

public static async Task<ResultModel> Scan(string path)
{
    Log.Information("Starting scan of: '{PATH}'", path);
    Stopwatch stopwatch = Stopwatch.StartNew();
    (ConcurrentDictionary<string, FileModel> models, int fileCount) = await DirectoryIterator(path);

    stopwatch.Stop();
    ResultModel model = new()
    {
        Items = models.Values.OrderByDescending(i => i.Lines).ToArray(),
        NumberOfExtensions = models.Count,
        Duration = stopwatch.Elapsed,
        NumberOfFiles = fileCount
    };
    Log.Information("Scan Completed after {TIME}", model.Duration);

    return model;
}

Print

create a public static method named Print that takes a ResultModel as a parameter and returns void

public static void Print(ResultModel scanResult)

Loop through each item in scanResult

foreach (FileModel model in scanResult.Items)

Overview

public static void Print(ResultModel scanResult)
{
    Console.ForegroundColor = ConsoleColor.Green;
    foreach (FileModel model in scanResult.Items)
    {
        Console.WriteLine($"{model.Extension}:");
        Console.ForegroundColor = ConsoleColor.Cyan;
        Console.WriteLine($"\t- {model.Lines:N0} Line(s)");
        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.WriteLine($"\t- {model.Characters:N0} Character(s)");
        Console.ForegroundColor = ConsoleColor.Magenta;
        Console.WriteLine($"\t- {model.NumberOfFiles:N0} File(s)");
        Console.ForegroundColor = ConsoleColor.DarkYellow;
        Console.WriteLine($"\t- {CLFileMath.AdjustedFileSize(model.Bytes)}");
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine($"\t- {model.Encoding.EncodingName} Encoding(s)");
    }
    Console.ResetColor();
}

Save

Create a file stream and a stream writer to write the scanResult

See: Result Model

using (FileStream fs = new(outputFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
    using StreamWriter writer = new(fs);
    writer.Write(scanResult);
}

Overview

public static void SaveResult(ResultModel scanResult, bool openWhenDone = true)
{
    Save(scanResult, Path.Combine(Path.GetTempPath(), Path.GetTempFileName() + ".json"), openWhenDone);
}

public static void Save(ResultModel scanResult, string outputFile, bool openWhenDone = true)
{
    using (FileStream fs = new(outputFile, FileMode.Create, FileAccess.Write, FileShare.None))
    {
        using StreamWriter writer = new(fs);
        writer.Write(scanResult);
    }
    Log.Information("Output to File: {FILE}", outputFile);
    if (openWhenDone)
    {
        OpenFile(outputFile);
    }
}

Open File

Create a method named OpenFile that takes in a String named file

public static void OpenFile(string file)

Next call Process.Start method with ProcessStartInfo with the file name of file and UseShellExecute set to true

Process.Start(
    new ProcessStartInfo()
    {
        FileName = file,
        UseShellExecute = true,
    });

Overview

public static void OpenFile(string file)
{
    Process.Start(
        new ProcessStartInfo()
        {
            FileName = file,
            UseShellExecute = true,
        });
}

Overview

using CLMath;
using Library.Model;
using Serilog;
using System.Collections.Concurrent;
using System.Diagnostics;

namespace Library.Controller;

/// <summary>
/// Provides methods for working with the file system.
/// </summary>
public static class FileSystemController
{  
    /// <summary>
   /// Prints the scan result to the console.
   /// </summary>
   /// <param name="scanResult">The scan result to print.</param>
    public static void Print(ResultModel scanResult)
    {
        Console.ForegroundColor = ConsoleColor.Green;
        foreach (FileModel model in scanResult.Items)
        {
            Console.WriteLine($"{model.Extension}:");
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WriteLine($"\t- {model.Lines:N0} Line(s)");
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine($"\t- {model.Characters:N0} Character(s)");
            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.WriteLine($"\t- {model.NumberOfFiles:N0} File(s)");
            Console.ForegroundColor = ConsoleColor.DarkYellow;
            Console.WriteLine($"\t- {CLFileMath.AdjustedFileSize(model.Bytes)}");
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine($"\t- {model.Encoding.EncodingName} Encoding(s)");
        }
        Console.ResetColor();
    }

    /// <summary>
    /// Saves the scan result to a temporary file and opens it.
    /// </summary>
    /// <param name="scanResult">The scan result to save.</param>
    /// <param name="openWhenDone">
    /// Indicates whether to open the file when done saving (default is true).
    /// </param>

    public static void SaveResult(ResultModel scanResult, bool openWhenDone = true)
    {
        Save(scanResult, Path.Combine(Path.GetTempPath(), Path.GetTempFileName() + ".json"), openWhenDone);
    }

    /// <summary>
    /// Saves the scan result to the specified file and opens it.
    /// </summary>
    /// <param name="scanResult">The scan result to save.</param>
    /// <param name="outputFile">The path of the output file.</param>
    /// <param name="openWhenDone">
    /// Indicates whether to open the file when done saving (default is true).
    /// </param>

    public static void Save(ResultModel scanResult, string outputFile, bool openWhenDone = true)
    {
        using (FileStream fs = new(outputFile, FileMode.Create, FileAccess.Write, FileShare.None))
        {
            using StreamWriter writer = new(fs);
            writer.Write(scanResult);
        }
        Log.Information("Output to File: {FILE}", outputFile);
        if (openWhenDone)
        {
            OpenFile(outputFile);
        }
    }

    /// <summary>
    /// Opens the specified file using the default associated application.
    /// </summary>
    /// <param name="file">The path of the file to open.</param>
    public static void OpenFile(string file)
    {
        Process.Start(
            new ProcessStartInfo()
            {
                FileName = file,
                UseShellExecute = true,
            });
    }

    /// <summary>
    /// Scans the specified directory and returns the scan result.
    /// </summary>
    /// <param name="path">The path of the directory to scan.</param>
    /// <returns>The scan result.</returns>
    public static async Task<ResultModel> Scan(string path)
    {
        Log.Information("Starting scan of: '{PATH}'", path);
        Stopwatch stopwatch = Stopwatch.StartNew();
        (ConcurrentDictionary<string, FileModel> models, int fileCount) = await DirectoryIterator(path);

        stopwatch.Stop();
        ResultModel model = new()
        {
            Items = models.Values.OrderByDescending(i => i.Lines).ToArray(),
            NumberOfExtensions = models.Count,
            Duration = stopwatch.Elapsed,
            NumberOfFiles = fileCount
        };
        Log.Information("Scan Completed after {TIME}", model.Duration);

        return model;
    }

    /// <summary>
    /// Iterates through the specified directory and collects file data.
    /// </summary>
    /// <param name="path">The path of the directory to iterate.</param>
    /// <returns>A tuple containing a dictionary of file models and the total number of files.</returns>
    private static async Task<(ConcurrentDictionary<string, FileModel> Model, int FileCount)> DirectoryIterator(string path)
    {
        ConcurrentDictionary<string, FileModel> fileData = new();
        string[] entries = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
        int fileCount = 0;
        List<Task> tasks = new();
        Parallel.ForEach(entries, entry =>
        {
            string? extension = Path.GetExtension(entry);
            if (extension != null)
            {
                Log.Debug("Processing file: {FILE}", entry);
                try
                {
                    // Path is a file
                    FileModel instance = FileController.GetFile(entry);
                    if (fileData.ContainsKey(extension))
                    {
                        // Appends the result of "model" to "instance"
                        lock (fileData)
                        {
                            fileData[extension] = fileData[extension] with
                            {
                                Lines = fileData[extension].Lines + instance.Lines,
                                Bytes = fileData[extension].Bytes + instance.Bytes,
                                Characters = fileData[extension].Characters + instance.Characters,
                                NumberOfFiles = fileData[extension].NumberOfFiles + instance.NumberOfFiles,
                            };
                        }
                    }
                    else
                    {
                        fileData[extension] = instance;
                    }
                    // Iterate the file count
                    fileCount++;
                }
                catch (Exception e)
                {
                    // Throws an error if the file could NOT be accessed.
                    Log.Error("Unable to read file: {PATH}\n{EXCEPTION}", entry, e);
                }
            }
        });

        await Task.WhenAll(tasks);

        Log.Debug("Returning batch: {SIZE} - {FILES}", fileData.Count, fileCount);
        return (fileData, fileCount);
    }
}
PreviousFile ControllerNextConsole App

Last updated 1 year ago

Was this helpful?

Now get the FileModel instance See:

Get File