2020-12-10 23:24:09 -08:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net;
|
2024-03-05 03:04:47 -05:00
|
|
|
#if NET40_OR_GREATER || NETCOREAPP
|
2021-02-18 11:13:11 -08:00
|
|
|
using System.Threading.Tasks;
|
2024-03-05 03:04:47 -05:00
|
|
|
#endif
|
2020-12-10 23:24:09 -08:00
|
|
|
using SabreTools.DatFiles;
|
|
|
|
|
using SabreTools.IO;
|
2024-04-24 13:45:38 -04:00
|
|
|
using SabreTools.IO.Extensions;
|
2024-10-24 00:36:44 -04:00
|
|
|
using SabreTools.IO.Logging;
|
2020-12-11 10:10:56 -08:00
|
|
|
using SabreTools.Reports;
|
2020-12-10 23:24:09 -08:00
|
|
|
|
|
|
|
|
namespace SabreTools.DatTools
|
|
|
|
|
{
|
2020-12-21 11:38:56 -08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Helper methods for dealing with DatFile statistics
|
|
|
|
|
/// </summary>
|
2024-02-28 19:19:50 -05:00
|
|
|
|
2020-12-10 23:24:09 -08:00
|
|
|
public class Statistics
|
|
|
|
|
{
|
|
|
|
|
#region Logging
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Logging object
|
|
|
|
|
/// </summary>
|
2025-01-08 16:59:44 -05:00
|
|
|
private static readonly Logger _staticLogger = new();
|
2020-12-10 23:24:09 -08:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2021-02-18 11:13:11 -08:00
|
|
|
/// Calculate statistics from a list of inputs
|
2020-12-10 23:24:09 -08:00
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="inputs">List of input files and folders</param>
|
|
|
|
|
/// <param name="single">True if single DAT stats are output, false otherwise</param>
|
2021-02-18 11:13:11 -08:00
|
|
|
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
|
|
|
|
|
public static List<DatStatistics> CalculateStatistics(List<string> inputs, bool single, bool throwOnError = false)
|
2020-12-10 23:24:09 -08:00
|
|
|
{
|
2021-02-18 11:13:11 -08:00
|
|
|
// Create the output list
|
2024-02-28 19:19:50 -05:00
|
|
|
List<DatStatistics> stats = [];
|
2020-12-10 23:24:09 -08:00
|
|
|
|
|
|
|
|
// Make sure we have all files and then order them
|
|
|
|
|
List<ParentablePath> files = PathTool.GetFilesOnly(inputs);
|
|
|
|
|
files = files
|
|
|
|
|
.OrderBy(i => Path.GetDirectoryName(i.CurrentPath))
|
|
|
|
|
.ThenBy(i => Path.GetFileName(i.CurrentPath))
|
|
|
|
|
.ToList();
|
|
|
|
|
|
2021-02-18 11:13:11 -08:00
|
|
|
// Init total
|
2024-10-24 01:51:04 -04:00
|
|
|
DatStatistics totalStats = new("DIR: All DATs", isDirectory: true);
|
2020-12-10 23:24:09 -08:00
|
|
|
|
|
|
|
|
// Init directory-level variables
|
2024-02-28 19:19:50 -05:00
|
|
|
string? lastdir = null;
|
2024-10-24 01:51:04 -04:00
|
|
|
DatStatistics dirStats = new(displayName: null, isDirectory: true);
|
2020-12-10 23:24:09 -08:00
|
|
|
|
|
|
|
|
// Now process each of the input files
|
|
|
|
|
foreach (ParentablePath file in files)
|
|
|
|
|
{
|
|
|
|
|
// Get the directory for the current file
|
2024-02-28 19:19:50 -05:00
|
|
|
string? thisdir = Path.GetDirectoryName(file.CurrentPath);
|
2020-12-10 23:24:09 -08:00
|
|
|
|
|
|
|
|
// If we don't have the first file and the directory has changed, show the previous directory stats and reset
|
2021-02-18 11:13:11 -08:00
|
|
|
if (lastdir != null && thisdir != lastdir && single)
|
2020-12-10 23:24:09 -08:00
|
|
|
{
|
2024-02-29 00:14:16 -05:00
|
|
|
#if NET20 || NET35
|
|
|
|
|
dirStats.DisplayName = $"DIR: {lastdir}";
|
|
|
|
|
#else
|
2021-02-18 11:13:11 -08:00
|
|
|
dirStats.DisplayName = $"DIR: {WebUtility.HtmlEncode(lastdir)}";
|
2024-02-29 00:14:16 -05:00
|
|
|
#endif
|
2024-03-13 01:22:59 -04:00
|
|
|
dirStats.MachineCount = dirStats.GameCount;
|
2021-02-18 11:13:11 -08:00
|
|
|
stats.Add(dirStats);
|
2024-10-24 01:51:04 -04:00
|
|
|
dirStats = new DatStatistics(displayName: null, isDirectory: true);
|
2020-12-10 23:24:09 -08:00
|
|
|
}
|
|
|
|
|
|
2023-04-19 16:39:58 -04:00
|
|
|
InternalStopwatch watch = new($"Collecting statistics for '{file.CurrentPath}'");
|
2021-02-18 11:13:11 -08:00
|
|
|
|
2024-02-28 19:19:50 -05:00
|
|
|
List<string> machines = [];
|
2021-02-18 11:13:11 -08:00
|
|
|
DatFile datdata = Parser.CreateAndParse(file.CurrentPath, statsOnly: true, throwOnError: throwOnError);
|
2020-12-10 23:24:09 -08:00
|
|
|
|
2021-02-18 11:13:11 -08:00
|
|
|
// Add single DAT stats (if asked)
|
2020-12-10 23:24:09 -08:00
|
|
|
if (single)
|
|
|
|
|
{
|
2025-01-12 23:15:30 -05:00
|
|
|
DatStatistics individualStats = datdata.DatStatistics;
|
2024-03-13 01:22:59 -04:00
|
|
|
individualStats.DisplayName = datdata.Header.GetStringFieldValue(DatHeader.FileNameKey);
|
2025-01-14 15:59:47 -05:00
|
|
|
individualStats.MachineCount = datdata.Items.SortedKeys.Length;
|
2021-02-18 11:13:11 -08:00
|
|
|
stats.Add(individualStats);
|
2020-12-10 23:24:09 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add single DAT stats to dir
|
2025-01-12 23:15:30 -05:00
|
|
|
dirStats.AddStatistics(datdata.DatStatistics);
|
2025-01-14 15:59:47 -05:00
|
|
|
dirStats.GameCount += datdata.Items.SortedKeys.Length;
|
2020-12-10 23:24:09 -08:00
|
|
|
|
|
|
|
|
// Add single DAT stats to totals
|
2025-01-12 23:15:30 -05:00
|
|
|
totalStats.AddStatistics(datdata.DatStatistics);
|
2025-01-14 15:59:47 -05:00
|
|
|
totalStats.GameCount += datdata.Items.SortedKeys.Length;
|
2020-12-10 23:24:09 -08:00
|
|
|
|
|
|
|
|
// Make sure to assign the new directory
|
|
|
|
|
lastdir = thisdir;
|
|
|
|
|
|
2021-02-18 11:13:11 -08:00
|
|
|
watch.Stop();
|
|
|
|
|
}
|
2020-12-10 23:24:09 -08:00
|
|
|
|
2021-02-18 11:13:11 -08:00
|
|
|
// Add last directory stats
|
2020-12-10 23:24:09 -08:00
|
|
|
if (single)
|
|
|
|
|
{
|
2024-02-29 00:14:16 -05:00
|
|
|
#if NET20 || NET35
|
|
|
|
|
dirStats.DisplayName = $"DIR: {lastdir}";
|
|
|
|
|
#else
|
2021-02-18 11:13:11 -08:00
|
|
|
dirStats.DisplayName = $"DIR: {WebUtility.HtmlEncode(lastdir)}";
|
2024-02-29 00:14:16 -05:00
|
|
|
#endif
|
2024-03-13 01:22:59 -04:00
|
|
|
dirStats.MachineCount = dirStats.GameCount;
|
2021-02-18 11:13:11 -08:00
|
|
|
stats.Add(dirStats);
|
2020-12-10 23:24:09 -08:00
|
|
|
}
|
|
|
|
|
|
2021-02-18 11:13:11 -08:00
|
|
|
// Add total DAT stats
|
2024-03-13 01:22:59 -04:00
|
|
|
totalStats.MachineCount = totalStats.GameCount;
|
2021-02-18 11:13:11 -08:00
|
|
|
stats.Add(totalStats);
|
2020-12-10 23:24:09 -08:00
|
|
|
|
2021-02-18 11:13:11 -08:00
|
|
|
return stats;
|
|
|
|
|
}
|
2020-12-10 23:24:09 -08:00
|
|
|
|
2021-02-18 11:13:11 -08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Output the stats for a list of input dats as files in a human-readable format
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="stats">List of pre-calculated statistics objects</param>
|
|
|
|
|
/// <param name="reportName">Name of the output file</param>
|
|
|
|
|
/// <param name="baddumpCol">True if baddumps should be included in output, false otherwise</param>
|
|
|
|
|
/// <param name="nodumpCol">True if nodumps should be included in output, false otherwise</param>
|
|
|
|
|
/// <param name="statDatFormat"> Set the statistics output format to use</param>
|
|
|
|
|
/// <param name="throwOnError">True if the error that is thrown should be thrown back to the caller, false otherwise</param>
|
|
|
|
|
/// <returns>True if the report was written correctly, false otherwise</returns>
|
|
|
|
|
public static bool Write(
|
|
|
|
|
List<DatStatistics> stats,
|
|
|
|
|
string reportName,
|
2024-03-05 20:26:38 -05:00
|
|
|
string? outDir,
|
2021-02-18 11:13:11 -08:00
|
|
|
bool baddumpCol,
|
|
|
|
|
bool nodumpCol,
|
|
|
|
|
StatReportFormat statDatFormat,
|
|
|
|
|
bool throwOnError = false)
|
|
|
|
|
{
|
|
|
|
|
// If there's no output format, set the default
|
|
|
|
|
if (statDatFormat == StatReportFormat.None)
|
|
|
|
|
{
|
2025-01-08 16:59:44 -05:00
|
|
|
_staticLogger.Verbose("No report format defined, defaulting to textfile");
|
2021-02-18 11:13:11 -08:00
|
|
|
statDatFormat = StatReportFormat.Textfile;
|
|
|
|
|
}
|
2020-12-10 23:24:09 -08:00
|
|
|
|
2021-02-18 11:13:11 -08:00
|
|
|
// Get the proper output file name
|
2024-02-29 00:14:16 -05:00
|
|
|
if (string.IsNullOrEmpty(reportName))
|
2021-02-18 11:13:11 -08:00
|
|
|
reportName = "report";
|
|
|
|
|
|
|
|
|
|
// Get the proper output directory name
|
2024-03-12 16:47:21 -04:00
|
|
|
outDir = outDir.Ensure();
|
2021-02-18 11:13:11 -08:00
|
|
|
|
2023-04-19 16:39:58 -04:00
|
|
|
InternalStopwatch watch = new($"Writing out report data to '{outDir}'");
|
2021-02-18 11:13:11 -08:00
|
|
|
|
|
|
|
|
// Get the dictionary of desired output report names
|
2024-03-05 20:26:38 -05:00
|
|
|
Dictionary<StatReportFormat, string> outfiles = CreateOutStatsNames(outDir!, statDatFormat, reportName);
|
2020-12-10 23:24:09 -08:00
|
|
|
|
2021-02-18 11:13:11 -08:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// Write out all required formats
|
2024-02-28 22:54:56 -05:00
|
|
|
#if NET452_OR_GREATER || NETCOREAPP
|
2024-10-24 05:58:03 -04:00
|
|
|
Parallel.ForEach(outfiles.Keys, Core.Globals.ParallelOptions, reportFormat =>
|
2024-02-28 22:54:56 -05:00
|
|
|
#elif NET40_OR_GREATER
|
|
|
|
|
Parallel.ForEach(outfiles.Keys, reportFormat =>
|
|
|
|
|
#else
|
|
|
|
|
foreach (var reportFormat in outfiles.Keys)
|
|
|
|
|
#endif
|
2021-02-18 11:13:11 -08:00
|
|
|
{
|
|
|
|
|
string outfile = outfiles[reportFormat];
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
BaseReport.Create(reportFormat, stats)?.WriteToFile(outfile, baddumpCol, nodumpCol, throwOnError);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex) when (!throwOnError)
|
|
|
|
|
{
|
2025-01-08 16:59:44 -05:00
|
|
|
_staticLogger.Error(ex, $"Report '{outfile}' could not be written out");
|
2021-02-18 11:13:11 -08:00
|
|
|
}
|
2024-02-28 21:59:13 -05:00
|
|
|
#if NET40_OR_GREATER || NETCOREAPP
|
2021-02-18 11:13:11 -08:00
|
|
|
});
|
2024-02-28 21:59:13 -05:00
|
|
|
#else
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2021-02-18 11:13:11 -08:00
|
|
|
}
|
|
|
|
|
catch (Exception ex) when (!throwOnError)
|
|
|
|
|
{
|
2025-01-08 16:59:44 -05:00
|
|
|
_staticLogger.Error(ex);
|
2021-02-18 11:13:11 -08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
watch.Stop();
|
|
|
|
|
}
|
2020-12-10 23:24:09 -08:00
|
|
|
|
2021-02-18 11:13:11 -08:00
|
|
|
return true;
|
2020-12-10 23:24:09 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get the proper extension for the stat output format
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="outDir">Output path to use</param>
|
|
|
|
|
/// <param name="statDatFormat">StatDatFormat to get the extension for</param>
|
|
|
|
|
/// <param name="reportName">Name of the input file to use</param>
|
|
|
|
|
/// <returns>Dictionary of output formats mapped to file names</returns>
|
|
|
|
|
private static Dictionary<StatReportFormat, string> CreateOutStatsNames(string outDir, StatReportFormat statDatFormat, string reportName, bool overwrite = true)
|
|
|
|
|
{
|
2024-02-28 19:19:50 -05:00
|
|
|
Dictionary<StatReportFormat, string> output = [];
|
2020-12-10 23:24:09 -08:00
|
|
|
|
|
|
|
|
// First try to create the output directory if we need to
|
|
|
|
|
if (!Directory.Exists(outDir))
|
|
|
|
|
Directory.CreateDirectory(outDir);
|
|
|
|
|
|
|
|
|
|
// Double check the outDir for the end delim
|
|
|
|
|
if (!outDir.EndsWith(Path.DirectorySeparatorChar.ToString()))
|
|
|
|
|
outDir += Path.DirectorySeparatorChar;
|
|
|
|
|
|
|
|
|
|
// For each output format, get the appropriate stream writer
|
|
|
|
|
output.Add(StatReportFormat.None, CreateOutStatsNamesHelper(outDir, ".null", reportName, overwrite));
|
|
|
|
|
|
2024-12-28 20:15:32 -05:00
|
|
|
#if NET20 || NET35
|
2024-02-28 22:54:56 -05:00
|
|
|
if ((statDatFormat & StatReportFormat.Textfile) != 0)
|
|
|
|
|
#else
|
2020-12-10 23:24:09 -08:00
|
|
|
if (statDatFormat.HasFlag(StatReportFormat.Textfile))
|
2024-02-28 22:54:56 -05:00
|
|
|
#endif
|
2020-12-10 23:24:09 -08:00
|
|
|
output.Add(StatReportFormat.Textfile, CreateOutStatsNamesHelper(outDir, ".txt", reportName, overwrite));
|
|
|
|
|
|
2024-12-28 20:15:32 -05:00
|
|
|
#if NET20 || NET35
|
2024-02-28 22:54:56 -05:00
|
|
|
if ((statDatFormat & StatReportFormat.CSV) != 0)
|
|
|
|
|
#else
|
2020-12-10 23:24:09 -08:00
|
|
|
if (statDatFormat.HasFlag(StatReportFormat.CSV))
|
2024-02-28 22:54:56 -05:00
|
|
|
#endif
|
2020-12-10 23:24:09 -08:00
|
|
|
output.Add(StatReportFormat.CSV, CreateOutStatsNamesHelper(outDir, ".csv", reportName, overwrite));
|
|
|
|
|
|
2024-12-28 20:15:32 -05:00
|
|
|
#if NET20 || NET35
|
2024-02-28 22:54:56 -05:00
|
|
|
if ((statDatFormat & StatReportFormat.HTML) != 0)
|
|
|
|
|
#else
|
2020-12-10 23:24:09 -08:00
|
|
|
if (statDatFormat.HasFlag(StatReportFormat.HTML))
|
2024-02-28 22:54:56 -05:00
|
|
|
#endif
|
2020-12-10 23:24:09 -08:00
|
|
|
output.Add(StatReportFormat.HTML, CreateOutStatsNamesHelper(outDir, ".html", reportName, overwrite));
|
|
|
|
|
|
2024-12-28 20:15:32 -05:00
|
|
|
#if NET20 || NET35
|
2024-02-28 22:54:56 -05:00
|
|
|
if ((statDatFormat & StatReportFormat.SSV) != 0)
|
|
|
|
|
#else
|
2020-12-10 23:24:09 -08:00
|
|
|
if (statDatFormat.HasFlag(StatReportFormat.SSV))
|
2024-02-28 22:54:56 -05:00
|
|
|
#endif
|
2020-12-10 23:24:09 -08:00
|
|
|
output.Add(StatReportFormat.SSV, CreateOutStatsNamesHelper(outDir, ".ssv", reportName, overwrite));
|
|
|
|
|
|
2024-12-28 20:15:32 -05:00
|
|
|
#if NET20 || NET35
|
2024-02-28 22:54:56 -05:00
|
|
|
if ((statDatFormat & StatReportFormat.TSV) != 0)
|
|
|
|
|
#else
|
2020-12-10 23:24:09 -08:00
|
|
|
if (statDatFormat.HasFlag(StatReportFormat.TSV))
|
2024-02-28 22:54:56 -05:00
|
|
|
#endif
|
2020-12-10 23:24:09 -08:00
|
|
|
output.Add(StatReportFormat.TSV, CreateOutStatsNamesHelper(outDir, ".tsv", reportName, overwrite));
|
|
|
|
|
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Help generating the outstats name
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="outDir">Output directory</param>
|
|
|
|
|
/// <param name="extension">Extension to use for the file</param>
|
|
|
|
|
/// <param name="reportName">Name of the input file to use</param>
|
|
|
|
|
/// <param name="overwrite">True if we ignore existing files, false otherwise</param>
|
|
|
|
|
/// <returns>String containing the new filename</returns>
|
|
|
|
|
private static string CreateOutStatsNamesHelper(string outDir, string extension, string reportName, bool overwrite)
|
|
|
|
|
{
|
|
|
|
|
string outfile = outDir + reportName + extension;
|
|
|
|
|
outfile = outfile.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", Path.DirectorySeparatorChar.ToString());
|
|
|
|
|
|
|
|
|
|
if (!overwrite)
|
|
|
|
|
{
|
|
|
|
|
int i = 1;
|
|
|
|
|
while (File.Exists(outfile))
|
|
|
|
|
{
|
|
|
|
|
outfile = $"{outDir}{reportName}_{i}{extension}";
|
|
|
|
|
outfile = outfile.Replace($"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}", Path.DirectorySeparatorChar.ToString());
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return outfile;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|