Copy 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);
}
}