From 706eb7c0a6478963f808070ae83132819e8fd090 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 27 Jul 2020 01:39:32 -0700 Subject: [PATCH] Stats in dictionary --- RombaSharp/RombaSharp.Help.cs | 2 +- SabreTools.Library/DatFiles/DatFile.cs | 14 +- SabreTools.Library/DatFiles/DatStats.cs | 612 ------------------ SabreTools.Library/DatFiles/ItemDictionary.cs | 606 ++++++++++++++++- SabreTools.Library/DatFiles/OfflineList.cs | 2 +- SabreTools.Library/Reports/BaseReport.cs | 4 +- SabreTools/SabreTools.Help.cs | 2 +- 7 files changed, 599 insertions(+), 643 deletions(-) delete mode 100644 SabreTools.Library/DatFiles/DatStats.cs diff --git a/RombaSharp/RombaSharp.Help.cs b/RombaSharp/RombaSharp.Help.cs index 0227fec2..280833e4 100644 --- a/RombaSharp/RombaSharp.Help.cs +++ b/RombaSharp/RombaSharp.Help.cs @@ -679,7 +679,7 @@ structure according to the original DAT master directory tree structure."; } // Now output the stats for all inputs - DatStats.OutputStats(Inputs, "rombasharp-datstats", null /* outDir */, true /* single */, true /* baddumpCol */, true /* nodumpCol */, StatReportFormat.Textfile); + ItemDictionary.OutputStats(Inputs, "rombasharp-datstats", null /* outDir */, true /* single */, true /* baddumpCol */, true /* nodumpCol */, StatReportFormat.Textfile); } } diff --git a/SabreTools.Library/DatFiles/DatFile.cs b/SabreTools.Library/DatFiles/DatFile.cs index c6090ec3..9da845f8 100644 --- a/SabreTools.Library/DatFiles/DatFile.cs +++ b/SabreTools.Library/DatFiles/DatFile.cs @@ -1739,7 +1739,7 @@ namespace SabreTools.Library.DatFiles #region Perform setup // If the DAT is not populated and inverse is not set, inform the user and quit - if (Items.Statistics.Count == 0 && !inverse) + if (Items.TotalCount == 0 && !inverse) { Globals.Logger.User("No entries were found to rebuild, exiting..."); return false; @@ -1919,7 +1919,7 @@ namespace SabreTools.Library.DatFiles #region Perform setup // If the DAT is not populated and inverse is not set, inform the user and quit - if (Items.Statistics.Count == 0 && !inverse) + if (Items.TotalCount == 0 && !inverse) { Globals.Logger.User("No entries were found to rebuild, exiting..."); return false; @@ -2595,7 +2595,7 @@ namespace SabreTools.Library.DatFiles private bool SplitByExtension(string outDir, List extA, List extB) { // If roms is empty, return false - if (Items.Statistics.Count == 0) + if (Items.TotalCount == 0) return false; // Make sure all of the extensions don't have a dot at the beginning @@ -3000,7 +3000,7 @@ namespace SabreTools.Library.DatFiles public bool Write(string outDir = null, bool norename = true, bool stats = false, bool ignoreblanks = false, bool overwrite = true) { // If there's nothing there, abort - if (Items.Statistics.Count == 0) + if (Items.TotalCount == 0) { Globals.Logger.User("There were no items to write out!"); return false; @@ -3053,13 +3053,13 @@ namespace SabreTools.Library.DatFiles // Output initial statistics, for kicks if (stats) { - if (Items.Statistics.RomCount + Items.Statistics.DiskCount == 0) + if (Items.RomCount + Items.DiskCount == 0) Items.RecalculateStats(); Items.BucketBy(BucketedBy.Game, DedupeType.None, norename: true); var consoleOutput = BaseReport.Create(StatReportFormat.None, null, true, true); - consoleOutput.ReplaceStatistics(DatHeader.FileName, Items.Keys.Count(), Items.Statistics); + consoleOutput.ReplaceStatistics(DatHeader.FileName, Items.Keys.Count(), Items); } // Bucket and dedupe according to the flag @@ -3072,7 +3072,7 @@ namespace SabreTools.Library.DatFiles Items.BucketBy(BucketedBy.Game, DedupeType.None, norename: norename); // Output the number of items we're going to be writing - Globals.Logger.User($"A total of {Items.Statistics.Count} items will be written out to '{DatHeader.FileName}'"); + Globals.Logger.User($"A total of {Items.TotalCount} items will be written out to '{DatHeader.FileName}'"); // Get the outfile names Dictionary outfiles = DatHeader.CreateOutFileNames(outDir, overwrite); diff --git a/SabreTools.Library/DatFiles/DatStats.cs b/SabreTools.Library/DatFiles/DatStats.cs deleted file mode 100644 index 597906b9..00000000 --- a/SabreTools.Library/DatFiles/DatStats.cs +++ /dev/null @@ -1,612 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; - -using SabreTools.Library.Data; -using SabreTools.Library.DatItems; -using SabreTools.Library.Reports; -using SabreTools.Library.Tools; - -namespace SabreTools.Library.DatFiles -{ - /// - /// Represents statistical data associated with a DAT - /// - public class DatStats - { - #region Private instance variables - - /// - /// Object used to lock stats updates - /// - private readonly object _lockObject = new object(); - - #endregion - - #region Publicly facing variables - - /// - /// Statistics writing format - /// - public StatReportFormat ReportFormat { get; set; } = StatReportFormat.None; - - /// - /// Statistics output file name - /// - public string ReportName { get; set; } = "report"; - - /// - /// Overall item count - /// - public long Count { get; set; } = 0; - - /// - /// Number of Archive items - /// - public long ArchiveCount { get; set; } = 0; - - /// - /// Number of BiosSet items - /// - public long BiosSetCount { get; set; } = 0; - - /// - /// Number of Disk items - /// - public long DiskCount { get; set; } = 0; - - /// - /// Number of Release items - /// - public long ReleaseCount { get; set; } = 0; - - /// - /// Number of Rom items - /// - public long RomCount { get; set; } = 0; - - /// - /// Number of Sample items - /// - public long SampleCount { get; set; } = 0; - - /// - /// Number of machines - /// - /// Special count only used by statistics output - public long GameCount { get; set; } = 0; - - /// - /// Total uncompressed size - /// - public long TotalSize { get; set; } = 0; - - /// - /// Number of items with a CRC hash - /// - public long CRCCount { get; set; } = 0; - - /// - /// Number of items with an MD5 hash - /// - public long MD5Count { get; set; } = 0; - -#if NET_FRAMEWORK - /// - /// Number of items with a RIPEMD160 hash - /// - public long RIPEMD160Count { get; set; } = 0; -#endif - - /// - /// Number of items with a SHA-1 hash - /// - public long SHA1Count { get; set; } = 0; - - /// - /// Number of items with a SHA-256 hash - /// - public long SHA256Count { get; set; } = 0; - - /// - /// Number of items with a SHA-384 hash - /// - public long SHA384Count { get; set; } = 0; - - /// - /// Number of items with a SHA-512 hash - /// - public long SHA512Count { get; set; } = 0; - - /// - /// Number of items with the baddump status - /// - public long BaddumpCount { get; set; } = 0; - - /// - /// Number of items with the good status - /// - public long GoodCount { get; set; } = 0; - - /// - /// Number of items with the nodump status - /// - public long NodumpCount { get; set; } = 0; - - /// - /// Number of items with the verified status - /// - public long VerifiedCount { get; set; } = 0; - - #endregion - - #region Instance Methods - - #region Updates - - /// - /// Add to the statistics given a DatItem - /// - /// Item to add info from - public void AddItem(DatItem item) - { - // No matter what the item is, we increate the count - lock (_lockObject) - { - this.Count += 1; - - // Now we do different things for each item type - - switch (item.ItemType) - { - case ItemType.Archive: - this.ArchiveCount += 1; - break; - case ItemType.BiosSet: - this.BiosSetCount += 1; - break; - case ItemType.Disk: - this.DiskCount += 1; - if (((Disk)item).ItemStatus != ItemStatus.Nodump) - { - this.MD5Count += (string.IsNullOrWhiteSpace(((Disk)item).MD5) ? 0 : 1); -#if NET_FRAMEWORK - this.RIPEMD160Count += (string.IsNullOrWhiteSpace(((Disk)item).RIPEMD160) ? 0 : 1); -#endif - this.SHA1Count += (string.IsNullOrWhiteSpace(((Disk)item).SHA1) ? 0 : 1); - this.SHA256Count += (string.IsNullOrWhiteSpace(((Disk)item).SHA256) ? 0 : 1); - this.SHA384Count += (string.IsNullOrWhiteSpace(((Disk)item).SHA384) ? 0 : 1); - this.SHA512Count += (string.IsNullOrWhiteSpace(((Disk)item).SHA512) ? 0 : 1); - } - - this.BaddumpCount += (((Disk)item).ItemStatus == ItemStatus.BadDump ? 1 : 0); - this.GoodCount += (((Disk)item).ItemStatus == ItemStatus.Good ? 1 : 0); - this.NodumpCount += (((Disk)item).ItemStatus == ItemStatus.Nodump ? 1 : 0); - this.VerifiedCount += (((Disk)item).ItemStatus == ItemStatus.Verified ? 1 : 0); - break; - case ItemType.Release: - this.ReleaseCount += 1; - break; - case ItemType.Rom: - this.RomCount += 1; - if (((Rom)item).ItemStatus != ItemStatus.Nodump) - { - this.TotalSize += ((Rom)item).Size; - this.CRCCount += (string.IsNullOrWhiteSpace(((Rom)item).CRC) ? 0 : 1); - this.MD5Count += (string.IsNullOrWhiteSpace(((Rom)item).MD5) ? 0 : 1); -#if NET_FRAMEWORK - this.RIPEMD160Count += (string.IsNullOrWhiteSpace(((Rom)item).RIPEMD160) ? 0 : 1); -#endif - this.SHA1Count += (string.IsNullOrWhiteSpace(((Rom)item).SHA1) ? 0 : 1); - this.SHA256Count += (string.IsNullOrWhiteSpace(((Rom)item).SHA256) ? 0 : 1); - this.SHA384Count += (string.IsNullOrWhiteSpace(((Rom)item).SHA384) ? 0 : 1); - this.SHA512Count += (string.IsNullOrWhiteSpace(((Rom)item).SHA512) ? 0 : 1); - } - - this.BaddumpCount += (((Rom)item).ItemStatus == ItemStatus.BadDump ? 1 : 0); - this.GoodCount += (((Rom)item).ItemStatus == ItemStatus.Good ? 1 : 0); - this.NodumpCount += (((Rom)item).ItemStatus == ItemStatus.Nodump ? 1 : 0); - this.VerifiedCount += (((Rom)item).ItemStatus == ItemStatus.Verified ? 1 : 0); - break; - case ItemType.Sample: - this.SampleCount += 1; - break; - } - } - } - - /// - /// Add statistics from another DatStats object - /// - /// DatStats object to add from - public void AddStats(DatStats stats) - { - this.Count += stats.Count; - - this.ArchiveCount += stats.ArchiveCount; - this.BiosSetCount += stats.BiosSetCount; - this.DiskCount += stats.DiskCount; - this.ReleaseCount += stats.ReleaseCount; - this.RomCount += stats.RomCount; - this.SampleCount += stats.SampleCount; - - this.GameCount += stats.GameCount; - - this.TotalSize += stats.TotalSize; - - // Individual hash counts - this.CRCCount += stats.CRCCount; - this.MD5Count += stats.MD5Count; -#if NET_FRAMEWORK - this.RIPEMD160Count += stats.RIPEMD160Count; -#endif - this.SHA1Count += stats.SHA1Count; - this.SHA256Count += stats.SHA256Count; - this.SHA384Count += stats.SHA384Count; - this.SHA512Count += stats.SHA512Count; - - // Individual status counts - this.BaddumpCount += stats.BaddumpCount; - this.GoodCount += stats.GoodCount; - this.NodumpCount += stats.NodumpCount; - this.VerifiedCount += stats.VerifiedCount; - } - - /// - /// Get the highest-order BucketedBy value that represents the statistics - /// - public BucketedBy GetBestAvailable() - { - // If all items are supposed to have a SHA-512, we bucket by that - if (RomCount + DiskCount - NodumpCount == SHA512Count) - return BucketedBy.SHA512; - - // If all items are supposed to have a SHA-384, we bucket by that - else if (RomCount + DiskCount - NodumpCount == SHA384Count) - return BucketedBy.SHA384; - - // If all items are supposed to have a SHA-256, we bucket by that - else if (RomCount + DiskCount - NodumpCount == SHA256Count) - return BucketedBy.SHA256; - - // If all items are supposed to have a SHA-1, we bucket by that - else if (RomCount + DiskCount - NodumpCount == SHA1Count) - return BucketedBy.SHA1; - -#if NET_FRAMEWORK - // If all items are supposed to have a RIPEMD160, we bucket by that - else if (RomCount + DiskCount - NodumpCount == RIPEMD160Count) - return BucketedBy.RIPEMD160; -#endif - - // If all items are supposed to have a MD5, we bucket by that - else if (RomCount + DiskCount - NodumpCount == MD5Count) - return BucketedBy.MD5; - - // Otherwise, we bucket by CRC - else - return BucketedBy.CRC; - } - - /// - /// Remove from the statistics given a DatItem - /// - /// Item to remove info for - public void RemoveItem(DatItem item) - { - // If we have a null item, we can't do anything - if (item == null) - return; - - // No matter what the item is, we increate the count - lock (_lockObject) - { - this.Count -= 1; - - // Now we do different things for each item type - - switch (item.ItemType) - { - case ItemType.Archive: - this.ArchiveCount -= 1; - break; - case ItemType.BiosSet: - this.BiosSetCount -= 1; - break; - case ItemType.Disk: - this.DiskCount -= 1; - if (((Disk)item).ItemStatus != ItemStatus.Nodump) - { - this.MD5Count -= (string.IsNullOrWhiteSpace(((Disk)item).MD5) ? 0 : 1); -#if NET_FRAMEWORK - this.RIPEMD160Count -= (string.IsNullOrWhiteSpace(((Disk)item).RIPEMD160) ? 0 : 1); -#endif - this.SHA1Count -= (string.IsNullOrWhiteSpace(((Disk)item).SHA1) ? 0 : 1); - this.SHA256Count -= (string.IsNullOrWhiteSpace(((Disk)item).SHA256) ? 0 : 1); - this.SHA384Count -= (string.IsNullOrWhiteSpace(((Disk)item).SHA384) ? 0 : 1); - this.SHA512Count -= (string.IsNullOrWhiteSpace(((Disk)item).SHA512) ? 0 : 1); - } - - this.BaddumpCount -= (((Disk)item).ItemStatus == ItemStatus.BadDump ? 1 : 0); - this.GoodCount -= (((Disk)item).ItemStatus == ItemStatus.Good ? 1 : 0); - this.NodumpCount -= (((Disk)item).ItemStatus == ItemStatus.Nodump ? 1 : 0); - this.VerifiedCount -= (((Disk)item).ItemStatus == ItemStatus.Verified ? 1 : 0); - break; - case ItemType.Release: - this.ReleaseCount -= 1; - break; - case ItemType.Rom: - this.RomCount -= 1; - if (((Rom)item).ItemStatus != ItemStatus.Nodump) - { - this.TotalSize -= ((Rom)item).Size; - this.CRCCount -= (string.IsNullOrWhiteSpace(((Rom)item).CRC) ? 0 : 1); - this.MD5Count -= (string.IsNullOrWhiteSpace(((Rom)item).MD5) ? 0 : 1); -#if NET_FRAMEWORK - this.RIPEMD160Count -= (string.IsNullOrWhiteSpace(((Rom)item).RIPEMD160) ? 0 : 1); -#endif - this.SHA1Count -= (string.IsNullOrWhiteSpace(((Rom)item).SHA1) ? 0 : 1); - this.SHA256Count -= (string.IsNullOrWhiteSpace(((Rom)item).SHA256) ? 0 : 1); - this.SHA384Count -= (string.IsNullOrWhiteSpace(((Rom)item).SHA384) ? 0 : 1); - this.SHA512Count -= (string.IsNullOrWhiteSpace(((Rom)item).SHA512) ? 0 : 1); - } - - this.BaddumpCount -= (((Rom)item).ItemStatus == ItemStatus.BadDump ? 1 : 0); - this.GoodCount -= (((Rom)item).ItemStatus == ItemStatus.Good ? 1 : 0); - this.NodumpCount -= (((Rom)item).ItemStatus == ItemStatus.Nodump ? 1 : 0); - this.VerifiedCount -= (((Rom)item).ItemStatus == ItemStatus.Verified ? 1 : 0); - break; - case ItemType.Sample: - this.SampleCount -= 1; - break; - } - } - } - - /// - /// Reset all statistics - /// - public void Reset() - { - this.Count = 0; - - this.ArchiveCount = 0; - this.BiosSetCount = 0; - this.DiskCount = 0; - this.ReleaseCount = 0; - this.RomCount = 0; - this.SampleCount = 0; - - this.GameCount = 0; - - this.TotalSize = 0; - - this.CRCCount = 0; - this.MD5Count = 0; -#if NET_FRAMEWORK - this.RIPEMD160Count = 0; -#endif - this.SHA1Count = 0; - this.SHA256Count = 0; - this.SHA384Count = 0; - this.SHA512Count = 0; - - this.BaddumpCount = 0; - this.GoodCount = 0; - this.NodumpCount = 0; - this.VerifiedCount = 0; - } - - #endregion - - #region Writing - - /// - /// Output the stats for a list of input dats as files in a human-readable format - /// - /// List of input files and folders - /// Name of the output file - /// True if single DAT stats are output, false otherwise - /// True if baddumps should be included in output, false otherwise - /// True if nodumps should be included in output, false otherwise - /// Set the statistics output format to use - public static void OutputStats( - List inputs, - string reportName, - string outDir, - bool single, - bool baddumpCol, - bool nodumpCol, - StatReportFormat statDatFormat) - { - // If there's no output format, set the default - if (statDatFormat == StatReportFormat.None) - statDatFormat = StatReportFormat.Textfile; - - // Get the proper output file name - if (string.IsNullOrWhiteSpace(reportName)) - reportName = "report"; - - // Get the proper output directory name - outDir = DirectoryExtensions.Ensure(outDir); - - // Get the dictionary of desired output report names - Dictionary outputs = CreateOutStatsNames(outDir, statDatFormat, reportName); - - // Make sure we have all files and then order them - List files = DirectoryExtensions.GetFilesOnly(inputs); - files = files - .OrderBy(i => Path.GetDirectoryName(i.CurrentPath)) - .ThenBy(i => Path.GetFileName(i.CurrentPath)) - .ToList(); - - // Get all of the writers that we need - List reports = outputs.Select(kvp => BaseReport.Create(kvp.Key, kvp.Value, baddumpCol, nodumpCol)).ToList(); - - // Write the header, if any - reports.ForEach(report => report.WriteHeader()); - - // Init all total variables - DatStats totalStats = new DatStats(); - - // Init directory-level variables - string lastdir = null; - string basepath = null; - DatStats dirStats = new DatStats(); - - // Now process each of the input files - foreach (ParentablePath file in files) - { - // Get the directory for the current file - string thisdir = Path.GetDirectoryName(file.CurrentPath); - basepath = Path.GetDirectoryName(Path.GetDirectoryName(file.CurrentPath)); - - // If we don't have the first file and the directory has changed, show the previous directory stats and reset - if (lastdir != null && thisdir != lastdir) - { - // Output separator if needed - reports.ForEach(report => report.WriteMidSeparator()); - - DatFile lastdirdat = DatFile.Create(); - - reports.ForEach(report => report.ReplaceStatistics($"DIR: {WebUtility.HtmlEncode(lastdir)}", dirStats.GameCount, dirStats)); - reports.ForEach(report => report.Write()); - - // Write the mid-footer, if any - reports.ForEach(report => report.WriteFooterSeparator()); - - // Write the header, if any - reports.ForEach(report => report.WriteMidHeader()); - - // Reset the directory stats - dirStats.Reset(); - } - - Globals.Logger.Verbose($"Beginning stat collection for '{file}'", false); - List games = new List(); - DatFile datdata = DatFile.CreateAndParse(file.CurrentPath); - datdata.Items.BucketBy(BucketedBy.Game, DedupeType.None, norename: true); - - // Output single DAT stats (if asked) - Globals.Logger.User($"Adding stats for file '{file}'\n", false); - if (single) - { - reports.ForEach(report => report.ReplaceStatistics(datdata.DatHeader.FileName, datdata.Items.Keys.Count, datdata.Items.Statistics)); - reports.ForEach(report => report.Write()); - } - - // Add single DAT stats to dir - dirStats.AddStats(datdata.Items.Statistics); - dirStats.GameCount += datdata.Items.Keys.Count(); - - // Add single DAT stats to totals - totalStats.AddStats(datdata.Items.Statistics); - totalStats.GameCount += datdata.Items.Keys.Count(); - - // Make sure to assign the new directory - lastdir = thisdir; - } - - // Output the directory stats one last time - reports.ForEach(report => report.WriteMidSeparator()); - - if (single) - { - reports.ForEach(report => report.ReplaceStatistics($"DIR: {WebUtility.HtmlEncode(lastdir)}", dirStats.GameCount, dirStats)); - reports.ForEach(report => report.Write()); - } - - // Write the mid-footer, if any - reports.ForEach(report => report.WriteFooterSeparator()); - - // Write the header, if any - reports.ForEach(report => report.WriteMidHeader()); - - // Reset the directory stats - dirStats.Reset(); - - // Output total DAT stats - reports.ForEach(report => report.ReplaceStatistics("DIR: All DATs", totalStats.GameCount, totalStats)); - reports.ForEach(report => report.Write()); - - // Output footer if needed - reports.ForEach(report => report.WriteFooter()); - - Globals.Logger.User(@" -Please check the log folder if the stats scrolled offscreen", false); - } - - /// - /// Get the proper extension for the stat output format - /// - /// Output path to use - /// StatDatFormat to get the extension for - /// Name of the input file to use - /// Dictionary of output formats mapped to file names - private static Dictionary CreateOutStatsNames(string outDir, StatReportFormat statDatFormat, string reportName, bool overwrite = true) - { - Dictionary output = new Dictionary(); - - // 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)); - - if (statDatFormat.HasFlag(StatReportFormat.Textfile)) - output.Add(StatReportFormat.Textfile, CreateOutStatsNamesHelper(outDir, ".txt", reportName, overwrite)); - - if (statDatFormat.HasFlag(StatReportFormat.CSV)) - output.Add(StatReportFormat.CSV, CreateOutStatsNamesHelper(outDir, ".csv", reportName, overwrite)); - - if (statDatFormat.HasFlag(StatReportFormat.HTML)) - output.Add(StatReportFormat.HTML, CreateOutStatsNamesHelper(outDir, ".html", reportName, overwrite)); - - if (statDatFormat.HasFlag(StatReportFormat.SSV)) - output.Add(StatReportFormat.SSV, CreateOutStatsNamesHelper(outDir, ".ssv", reportName, overwrite)); - - if (statDatFormat.HasFlag(StatReportFormat.TSV)) - output.Add(StatReportFormat.TSV, CreateOutStatsNamesHelper(outDir, ".tsv", reportName, overwrite)); - - return output; - } - - /// - /// Help generating the outstats name - /// - /// Output directory - /// Extension to use for the file - /// Name of the input file to use - /// True if we ignore existing files, false otherwise - /// String containing the new filename - 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; - } - - - #endregion - - #endregion // Instance Methods - } -} diff --git a/SabreTools.Library/DatFiles/ItemDictionary.cs b/SabreTools.Library/DatFiles/ItemDictionary.cs index cb707885..329eae59 100644 --- a/SabreTools.Library/DatFiles/ItemDictionary.cs +++ b/SabreTools.Library/DatFiles/ItemDictionary.cs @@ -1,10 +1,14 @@ using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net; using System.Threading.Tasks; using SabreTools.Library.Data; using SabreTools.Library.DatItems; +using SabreTools.Library.Reports; +using SabreTools.Library.Tools; using NaturalSort; namespace SabreTools.Library.DatFiles @@ -35,6 +39,8 @@ namespace SabreTools.Library.DatFiles #region Publically available fields + #region Keys + /// /// Get the keys from the file dictionary /// @@ -58,13 +64,119 @@ namespace SabreTools.Library.DatFiles } } + #endregion + + #region Statistics + /// - /// DatStats object for reporting + /// Overall item count /// - public DatStats Statistics { get; private set; } + public long TotalCount { get; private set; } = 0; + + /// + /// Number of Archive items + /// + public long ArchiveCount { get; private set; } = 0; + + /// + /// Number of BiosSet items + /// + public long BiosSetCount { get; private set; } = 0; + + /// + /// Number of Disk items + /// + public long DiskCount { get; private set; } = 0; + + /// + /// Number of Release items + /// + public long ReleaseCount { get; private set; } = 0; + + /// + /// Number of Rom items + /// + public long RomCount { get; private set; } = 0; + + /// + /// Number of Sample items + /// + public long SampleCount { get; private set; } = 0; + + /// + /// Number of machines + /// + /// Special count only used by statistics output + public long GameCount { get; private set; } = 0; + + /// + /// Total uncompressed size + /// + public long TotalSize { get; private set; } = 0; + + /// + /// Number of items with a CRC hash + /// + public long CRCCount { get; private set; } = 0; + + /// + /// Number of items with an MD5 hash + /// + public long MD5Count { get; private set; } = 0; + +#if NET_FRAMEWORK + /// + /// Number of items with a RIPEMD160 hash + /// + public long RIPEMD160Count { get; private set; } = 0; +#endif + + /// + /// Number of items with a SHA-1 hash + /// + public long SHA1Count { get; private set; } = 0; + + /// + /// Number of items with a SHA-256 hash + /// + public long SHA256Count { get; private set; } = 0; + + /// + /// Number of items with a SHA-384 hash + /// + public long SHA384Count { get; private set; } = 0; + + /// + /// Number of items with a SHA-512 hash + /// + public long SHA512Count { get; private set; } = 0; + + /// + /// Number of items with the baddump status + /// + public long BaddumpCount { get; private set; } = 0; + + /// + /// Number of items with the good status + /// + public long GoodCount { get; private set; } = 0; + + /// + /// Number of items with the nodump status + /// + public long NodumpCount { get; private set; } = 0; + + /// + /// Number of items with the verified status + /// + public long VerifiedCount { get; private set; } = 0; #endregion + #endregion + + #region Instsnce methods + #region Accessors /// @@ -112,7 +224,7 @@ namespace SabreTools.Library.DatFiles items[key].Add(value); // Now update the statistics - Statistics.AddItem(value); + AddItemStatistics(value); } } @@ -145,7 +257,7 @@ namespace SabreTools.Library.DatFiles // Now update the statistics foreach (DatItem item in value) { - Statistics.AddItem(item); + AddItemStatistics(item); } } } @@ -203,7 +315,7 @@ namespace SabreTools.Library.DatFiles // Remove the statistics first foreach (DatItem item in items[key]) { - Statistics.RemoveItem(item); + RemoveItemStatistics(item); } // Remove the key from the dictionary @@ -222,7 +334,7 @@ namespace SabreTools.Library.DatFiles return false; // Remove the statistics first - Statistics.RemoveItem(value); + RemoveItemStatistics(value); return items[key].Remove(value); } @@ -236,6 +348,110 @@ namespace SabreTools.Library.DatFiles bucketedBy = newBucket; } + /// + /// Add to the statistics given a DatItem + /// + /// Item to add info from + private void AddItemStatistics(DatItem item) + { + // No matter what the item is, we increate the count + TotalCount += 1; + + // Now we do different things for each item type + switch (item.ItemType) + { + case ItemType.Archive: + ArchiveCount += 1; + break; + case ItemType.BiosSet: + BiosSetCount += 1; + break; + case ItemType.Disk: + DiskCount += 1; + if (((Disk)item).ItemStatus != ItemStatus.Nodump) + { + MD5Count += (string.IsNullOrWhiteSpace(((Disk)item).MD5) ? 0 : 1); +#if NET_FRAMEWORK + RIPEMD160Count += (string.IsNullOrWhiteSpace(((Disk)item).RIPEMD160) ? 0 : 1); +#endif + SHA1Count += (string.IsNullOrWhiteSpace(((Disk)item).SHA1) ? 0 : 1); + SHA256Count += (string.IsNullOrWhiteSpace(((Disk)item).SHA256) ? 0 : 1); + SHA384Count += (string.IsNullOrWhiteSpace(((Disk)item).SHA384) ? 0 : 1); + SHA512Count += (string.IsNullOrWhiteSpace(((Disk)item).SHA512) ? 0 : 1); + } + + BaddumpCount += (((Disk)item).ItemStatus == ItemStatus.BadDump ? 1 : 0); + GoodCount += (((Disk)item).ItemStatus == ItemStatus.Good ? 1 : 0); + NodumpCount += (((Disk)item).ItemStatus == ItemStatus.Nodump ? 1 : 0); + VerifiedCount += (((Disk)item).ItemStatus == ItemStatus.Verified ? 1 : 0); + break; + case ItemType.Release: + ReleaseCount += 1; + break; + case ItemType.Rom: + RomCount += 1; + if (((Rom)item).ItemStatus != ItemStatus.Nodump) + { + TotalSize += ((Rom)item).Size; + CRCCount += (string.IsNullOrWhiteSpace(((Rom)item).CRC) ? 0 : 1); + MD5Count += (string.IsNullOrWhiteSpace(((Rom)item).MD5) ? 0 : 1); +#if NET_FRAMEWORK + RIPEMD160Count += (string.IsNullOrWhiteSpace(((Rom)item).RIPEMD160) ? 0 : 1); +#endif + SHA1Count += (string.IsNullOrWhiteSpace(((Rom)item).SHA1) ? 0 : 1); + SHA256Count += (string.IsNullOrWhiteSpace(((Rom)item).SHA256) ? 0 : 1); + SHA384Count += (string.IsNullOrWhiteSpace(((Rom)item).SHA384) ? 0 : 1); + SHA512Count += (string.IsNullOrWhiteSpace(((Rom)item).SHA512) ? 0 : 1); + } + + BaddumpCount += (((Rom)item).ItemStatus == ItemStatus.BadDump ? 1 : 0); + GoodCount += (((Rom)item).ItemStatus == ItemStatus.Good ? 1 : 0); + NodumpCount += (((Rom)item).ItemStatus == ItemStatus.Nodump ? 1 : 0); + VerifiedCount += (((Rom)item).ItemStatus == ItemStatus.Verified ? 1 : 0); + break; + case ItemType.Sample: + SampleCount += 1; + break; + } + } + + /// + /// Add statistics from another DatStats object + /// + /// DatStats object to add from + private void AddStatistics(ItemDictionary stats) + { + TotalCount += stats.Count; + + ArchiveCount += stats.ArchiveCount; + BiosSetCount += stats.BiosSetCount; + DiskCount += stats.DiskCount; + ReleaseCount += stats.ReleaseCount; + RomCount += stats.RomCount; + SampleCount += stats.SampleCount; + + GameCount += stats.GameCount; + + TotalSize += stats.TotalSize; + + // Individual hash counts + CRCCount += stats.CRCCount; + MD5Count += stats.MD5Count; +#if NET_FRAMEWORK + RIPEMD160Count += stats.RIPEMD160Count; +#endif + SHA1Count += stats.SHA1Count; + SHA256Count += stats.SHA256Count; + SHA384Count += stats.SHA384Count; + SHA512Count += stats.SHA512Count; + + // Individual status counts + BaddumpCount += stats.BaddumpCount; + GoodCount += stats.GoodCount; + NodumpCount += stats.NodumpCount; + VerifiedCount += stats.VerifiedCount; + } + /// /// Ensure the key exists in the items dictionary /// @@ -247,6 +463,78 @@ namespace SabreTools.Library.DatFiles items.Add(key, new List()); } + /// + /// Remove from the statistics given a DatItem + /// + /// Item to remove info for + public void RemoveItemStatistics(DatItem item) + { + // If we have a null item, we can't do anything + if (item == null) + return; + + // No matter what the item is, we increate the count + TotalCount -= 1; + + // Now we do different things for each item type + + switch (item.ItemType) + { + case ItemType.Archive: + ArchiveCount -= 1; + break; + case ItemType.BiosSet: + BiosSetCount -= 1; + break; + case ItemType.Disk: + DiskCount -= 1; + if (((Disk)item).ItemStatus != ItemStatus.Nodump) + { + MD5Count -= (string.IsNullOrWhiteSpace(((Disk)item).MD5) ? 0 : 1); +#if NET_FRAMEWORK + RIPEMD160Count -= (string.IsNullOrWhiteSpace(((Disk)item).RIPEMD160) ? 0 : 1); +#endif + SHA1Count -= (string.IsNullOrWhiteSpace(((Disk)item).SHA1) ? 0 : 1); + SHA256Count -= (string.IsNullOrWhiteSpace(((Disk)item).SHA256) ? 0 : 1); + SHA384Count -= (string.IsNullOrWhiteSpace(((Disk)item).SHA384) ? 0 : 1); + SHA512Count -= (string.IsNullOrWhiteSpace(((Disk)item).SHA512) ? 0 : 1); + } + + BaddumpCount -= (((Disk)item).ItemStatus == ItemStatus.BadDump ? 1 : 0); + GoodCount -= (((Disk)item).ItemStatus == ItemStatus.Good ? 1 : 0); + NodumpCount -= (((Disk)item).ItemStatus == ItemStatus.Nodump ? 1 : 0); + VerifiedCount -= (((Disk)item).ItemStatus == ItemStatus.Verified ? 1 : 0); + break; + case ItemType.Release: + ReleaseCount -= 1; + break; + case ItemType.Rom: + RomCount -= 1; + if (((Rom)item).ItemStatus != ItemStatus.Nodump) + { + TotalSize -= ((Rom)item).Size; + CRCCount -= (string.IsNullOrWhiteSpace(((Rom)item).CRC) ? 0 : 1); + MD5Count -= (string.IsNullOrWhiteSpace(((Rom)item).MD5) ? 0 : 1); +#if NET_FRAMEWORK + RIPEMD160Count -= (string.IsNullOrWhiteSpace(((Rom)item).RIPEMD160) ? 0 : 1); +#endif + SHA1Count -= (string.IsNullOrWhiteSpace(((Rom)item).SHA1) ? 0 : 1); + SHA256Count -= (string.IsNullOrWhiteSpace(((Rom)item).SHA256) ? 0 : 1); + SHA384Count -= (string.IsNullOrWhiteSpace(((Rom)item).SHA384) ? 0 : 1); + SHA512Count -= (string.IsNullOrWhiteSpace(((Rom)item).SHA512) ? 0 : 1); + } + + BaddumpCount -= (((Rom)item).ItemStatus == ItemStatus.BadDump ? 1 : 0); + GoodCount -= (((Rom)item).ItemStatus == ItemStatus.Good ? 1 : 0); + NodumpCount -= (((Rom)item).ItemStatus == ItemStatus.Nodump ? 1 : 0); + VerifiedCount -= (((Rom)item).ItemStatus == ItemStatus.Verified ? 1 : 0); + break; + case ItemType.Sample: + SampleCount -= 1; + break; + } + } + #endregion #region Constructors @@ -259,8 +547,6 @@ namespace SabreTools.Library.DatFiles bucketedBy = BucketedBy.Default; mergedBy = DedupeType.None; items = new Dictionary>(); - - Statistics = new DatStats(); } #endregion @@ -281,15 +567,15 @@ namespace SabreTools.Library.DatFiles return; // If the sorted type isn't the same, we want to sort the dictionary accordingly - if (this.bucketedBy != bucketBy) + if (bucketedBy != bucketBy) { Globals.Logger.User($"Organizing roms by {bucketBy}"); // Set the sorted type - this.bucketedBy = bucketBy; + bucketedBy = bucketBy; // Reset the merged type since this might change the merge - this.mergedBy = DedupeType.None; + mergedBy = DedupeType.None; // First do the initial sort of all of the roms inplace List oldkeys = Keys.ToList(); @@ -326,12 +612,12 @@ namespace SabreTools.Library.DatFiles } // If the merge type isn't the same, we want to merge the dictionary accordingly - if (this.mergedBy != dedupeType) + if (mergedBy != dedupeType) { Globals.Logger.User($"Deduping roms by {dedupeType}"); // Set the sorted type - this.mergedBy = dedupeType; + mergedBy = dedupeType; Parallel.ForEach(Keys, Globals.ParallelOptions, key => { @@ -394,7 +680,7 @@ namespace SabreTools.Library.DatFiles List output = new List(); // Check for an empty rom list first - if (Statistics.Count == 0) + if (TotalCount == 0) return output; // We want to get the proper key for the DatItem @@ -442,7 +728,7 @@ namespace SabreTools.Library.DatFiles public bool HasDuplicates(DatItem datItem, bool sorted = false) { // Check for an empty rom list first - if (Statistics.Count == 0) + if (TotalCount == 0) return false; // We want to get the proper key for the DatItem @@ -463,10 +749,10 @@ namespace SabreTools.Library.DatFiles public void RecalculateStats() { // Wipe out any stats already there - Statistics.Reset(); + ResetStatistics(); // If we have a blank Dat in any way, return - if (this == null || Statistics.Count == 0) + if (this == null || TotalCount == 0) return; // Loop through and add @@ -475,7 +761,7 @@ namespace SabreTools.Library.DatFiles List datItems = items[key]; foreach (DatItem item in datItems) { - Statistics.AddItem(item); + AddItemStatistics(item); } }); } @@ -492,6 +778,76 @@ namespace SabreTools.Library.DatFiles } } + /// + /// Get the highest-order BucketedBy value that represents the statistics + /// + private BucketedBy GetBestAvailable() + { + // If all items are supposed to have a SHA-512, we bucket by that + if (RomCount + DiskCount - NodumpCount == SHA512Count) + return BucketedBy.SHA512; + + // If all items are supposed to have a SHA-384, we bucket by that + else if (RomCount + DiskCount - NodumpCount == SHA384Count) + return BucketedBy.SHA384; + + // If all items are supposed to have a SHA-256, we bucket by that + else if (RomCount + DiskCount - NodumpCount == SHA256Count) + return BucketedBy.SHA256; + + // If all items are supposed to have a SHA-1, we bucket by that + else if (RomCount + DiskCount - NodumpCount == SHA1Count) + return BucketedBy.SHA1; + +#if NET_FRAMEWORK + // If all items are supposed to have a RIPEMD160, we bucket by that + else if (RomCount + DiskCount - NodumpCount == RIPEMD160Count) + return BucketedBy.RIPEMD160; +#endif + + // If all items are supposed to have a MD5, we bucket by that + else if (RomCount + DiskCount - NodumpCount == MD5Count) + return BucketedBy.MD5; + + // Otherwise, we bucket by CRC + else + return BucketedBy.CRC; + } + + /// + /// Reset all statistics + /// + private void ResetStatistics() + { + TotalCount = 0; + + ArchiveCount = 0; + BiosSetCount = 0; + DiskCount = 0; + ReleaseCount = 0; + RomCount = 0; + SampleCount = 0; + + GameCount = 0; + + TotalSize = 0; + + CRCCount = 0; + MD5Count = 0; +#if NET_FRAMEWORK + RIPEMD160Count = 0; +#endif + SHA1Count = 0; + SHA256Count = 0; + SHA384Count = 0; + SHA512Count = 0; + + BaddumpCount = 0; + GoodCount = 0; + NodumpCount = 0; + VerifiedCount = 0; + } + /// /// Sort the input DAT and get the key to be used by the item /// @@ -502,7 +858,7 @@ namespace SabreTools.Library.DatFiles { // If we're not already sorted, take care of it if (!sorted) - BucketBy(Statistics.GetBestAvailable(), DedupeType.None); + BucketBy(GetBestAvailable(), DedupeType.None); // Now that we have the sorted type, we get the proper key return datItem.GetKey(bucketedBy); @@ -559,5 +915,217 @@ namespace SabreTools.Library.DatFiles } #endregion + + #endregion + + #region Static methods + + #region Writing + + /// + /// Output the stats for a list of input dats as files in a human-readable format + /// + /// List of input files and folders + /// Name of the output file + /// True if single DAT stats are output, false otherwise + /// True if baddumps should be included in output, false otherwise + /// True if nodumps should be included in output, false otherwise + /// Set the statistics output format to use + public static void OutputStats( + List inputs, + string reportName, + string outDir, + bool single, + bool baddumpCol, + bool nodumpCol, + StatReportFormat statDatFormat) + { + // If there's no output format, set the default + if (statDatFormat == StatReportFormat.None) + statDatFormat = StatReportFormat.Textfile; + + // Get the proper output file name + if (string.IsNullOrWhiteSpace(reportName)) + reportName = "report"; + + // Get the proper output directory name + outDir = DirectoryExtensions.Ensure(outDir); + + // Get the dictionary of desired output report names + Dictionary outputs = CreateOutStatsNames(outDir, statDatFormat, reportName); + + // Make sure we have all files and then order them + List files = DirectoryExtensions.GetFilesOnly(inputs); + files = files + .OrderBy(i => Path.GetDirectoryName(i.CurrentPath)) + .ThenBy(i => Path.GetFileName(i.CurrentPath)) + .ToList(); + + // Get all of the writers that we need + List reports = outputs.Select(kvp => BaseReport.Create(kvp.Key, kvp.Value, baddumpCol, nodumpCol)).ToList(); + + // Write the header, if any + reports.ForEach(report => report.WriteHeader()); + + // Init all total variables + ItemDictionary totalStats = new ItemDictionary(); + + // Init directory-level variables + string lastdir = null; + string basepath = null; + ItemDictionary dirStats = new ItemDictionary(); + + // Now process each of the input files + foreach (ParentablePath file in files) + { + // Get the directory for the current file + string thisdir = Path.GetDirectoryName(file.CurrentPath); + basepath = Path.GetDirectoryName(Path.GetDirectoryName(file.CurrentPath)); + + // If we don't have the first file and the directory has changed, show the previous directory stats and reset + if (lastdir != null && thisdir != lastdir) + { + // Output separator if needed + reports.ForEach(report => report.WriteMidSeparator()); + + DatFile lastdirdat = DatFile.Create(); + + reports.ForEach(report => report.ReplaceStatistics($"DIR: {WebUtility.HtmlEncode(lastdir)}", dirStats.GameCount, dirStats)); + reports.ForEach(report => report.Write()); + + // Write the mid-footer, if any + reports.ForEach(report => report.WriteFooterSeparator()); + + // Write the header, if any + reports.ForEach(report => report.WriteMidHeader()); + + // Reset the directory stats + dirStats.ResetStatistics(); + } + + Globals.Logger.Verbose($"Beginning stat collection for '{file}'", false); + List games = new List(); + DatFile datdata = DatFile.CreateAndParse(file.CurrentPath); + datdata.Items.BucketBy(BucketedBy.Game, DedupeType.None, norename: true); + + // Output single DAT stats (if asked) + Globals.Logger.User($"Adding stats for file '{file}'\n", false); + if (single) + { + reports.ForEach(report => report.ReplaceStatistics(datdata.DatHeader.FileName, datdata.Items.Keys.Count, datdata.Items)); + reports.ForEach(report => report.Write()); + } + + // Add single DAT stats to dir + dirStats.AddStatistics(datdata.Items); + dirStats.GameCount += datdata.Items.Keys.Count(); + + // Add single DAT stats to totals + totalStats.AddStatistics(datdata.Items); + totalStats.GameCount += datdata.Items.Keys.Count(); + + // Make sure to assign the new directory + lastdir = thisdir; + } + + // Output the directory stats one last time + reports.ForEach(report => report.WriteMidSeparator()); + + if (single) + { + reports.ForEach(report => report.ReplaceStatistics($"DIR: {WebUtility.HtmlEncode(lastdir)}", dirStats.GameCount, dirStats)); + reports.ForEach(report => report.Write()); + } + + // Write the mid-footer, if any + reports.ForEach(report => report.WriteFooterSeparator()); + + // Write the header, if any + reports.ForEach(report => report.WriteMidHeader()); + + // Reset the directory stats + dirStats.ResetStatistics(); + + // Output total DAT stats + reports.ForEach(report => report.ReplaceStatistics("DIR: All DATs", totalStats.GameCount, totalStats)); + reports.ForEach(report => report.Write()); + + // Output footer if needed + reports.ForEach(report => report.WriteFooter()); + + Globals.Logger.User(@" +Please check the log folder if the stats scrolled offscreen", false); + } + + /// + /// Get the proper extension for the stat output format + /// + /// Output path to use + /// StatDatFormat to get the extension for + /// Name of the input file to use + /// Dictionary of output formats mapped to file names + private static Dictionary CreateOutStatsNames(string outDir, StatReportFormat statDatFormat, string reportName, bool overwrite = true) + { + Dictionary output = new Dictionary(); + + // 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)); + + if (statDatFormat.HasFlag(StatReportFormat.Textfile)) + output.Add(StatReportFormat.Textfile, CreateOutStatsNamesHelper(outDir, ".txt", reportName, overwrite)); + + if (statDatFormat.HasFlag(StatReportFormat.CSV)) + output.Add(StatReportFormat.CSV, CreateOutStatsNamesHelper(outDir, ".csv", reportName, overwrite)); + + if (statDatFormat.HasFlag(StatReportFormat.HTML)) + output.Add(StatReportFormat.HTML, CreateOutStatsNamesHelper(outDir, ".html", reportName, overwrite)); + + if (statDatFormat.HasFlag(StatReportFormat.SSV)) + output.Add(StatReportFormat.SSV, CreateOutStatsNamesHelper(outDir, ".ssv", reportName, overwrite)); + + if (statDatFormat.HasFlag(StatReportFormat.TSV)) + output.Add(StatReportFormat.TSV, CreateOutStatsNamesHelper(outDir, ".tsv", reportName, overwrite)); + + return output; + } + + /// + /// Help generating the outstats name + /// + /// Output directory + /// Extension to use for the file + /// Name of the input file to use + /// True if we ignore existing files, false otherwise + /// String containing the new filename + 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; + } + + #endregion + + #endregion } } diff --git a/SabreTools.Library/DatFiles/OfflineList.cs b/SabreTools.Library/DatFiles/OfflineList.cs index 72a87b95..9b56e7d4 100644 --- a/SabreTools.Library/DatFiles/OfflineList.cs +++ b/SabreTools.Library/DatFiles/OfflineList.cs @@ -837,7 +837,7 @@ namespace SabreTools.Library.DatFiles xtw.WriteStartElement("configuration"); xtw.WriteElementString("datName", DatHeader.Name); - xtw.WriteElementString("datVersion", Items.Statistics.Count.ToString()); + xtw.WriteElementString("datVersion", Items.TotalCount.ToString()); xtw.WriteElementString("system", "none"); xtw.WriteElementString("screenshotsWidth", "240"); xtw.WriteElementString("screenshotsHeight", "160"); diff --git a/SabreTools.Library/Reports/BaseReport.cs b/SabreTools.Library/Reports/BaseReport.cs index e1cbb780..096a95e1 100644 --- a/SabreTools.Library/Reports/BaseReport.cs +++ b/SabreTools.Library/Reports/BaseReport.cs @@ -15,7 +15,7 @@ namespace SabreTools.Library.Reports { protected string _name; protected long _machineCount; - protected DatStats _stats; + protected ItemDictionary _stats; protected StreamWriter _writer; protected bool _baddumpCol; @@ -104,7 +104,7 @@ namespace SabreTools.Library.Reports /// /// Replace the statistics that is being output /// - public void ReplaceStatistics(string datName, long machineCount, DatStats datStats) + public void ReplaceStatistics(string datName, long machineCount, ItemDictionary datStats) { _name = datName; _machineCount = machineCount; diff --git a/SabreTools/SabreTools.Help.cs b/SabreTools/SabreTools.Help.cs index 77b6e8b1..88a14307 100644 --- a/SabreTools/SabreTools.Help.cs +++ b/SabreTools/SabreTools.Help.cs @@ -3250,7 +3250,7 @@ The stats that are outputted are as follows: filename = Path.GetFileName(filename); } - DatStats.OutputStats( + ItemDictionary.OutputStats( Inputs, filename, outputDir,