mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-04-21 05:39:42 +00:00
Port Reports namespace from ST
This commit is contained in:
@@ -85,6 +85,7 @@ Below are a list of the included namespaces with links to their README files:
|
||||
- [SabreTools.Metadata.DatFiles](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Metadata.DatFiles)
|
||||
- [SabreTools.Metadata.DatItems](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Metadata.Datitems)
|
||||
- [SabreTools.Metadata.Filter](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Metadata.Filter)
|
||||
- [SabreTools.Metadata.Reports](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Metadata.Reports)
|
||||
- [SabreTools.ObjectIdentifier](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.ObjectIdentifier)
|
||||
- [SabreTools.Serialization.CrossModel](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Serialization.CrossModel)
|
||||
- [SabreTools.Serialization.Readers](https://github.com/SabreTools/SabreTools.Serialization/tree/main/SabreTools.Serialization.Readers)
|
||||
|
||||
89
SabreTools.Metadata.Reports/BaseReport.cs
Normal file
89
SabreTools.Metadata.Reports/BaseReport.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Logging;
|
||||
using SabreTools.Metadata.DatFiles;
|
||||
|
||||
#pragma warning disable IDE0290 // Use primary constructor
|
||||
namespace SabreTools.Metadata.Reports
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for a report output format
|
||||
/// </summary>
|
||||
public abstract class BaseReport
|
||||
{
|
||||
#region Logging
|
||||
|
||||
/// <summary>
|
||||
/// Logging object
|
||||
/// </summary>
|
||||
protected readonly Logger _logger = new();
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Set of DatStatistics objects to use for formatting
|
||||
/// </summary>
|
||||
protected List<DatStatistics> _statistics;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new report from the filename
|
||||
/// </summary>
|
||||
/// <param name="statsList">List of statistics objects to set</param>
|
||||
public BaseReport(List<DatStatistics> statsList)
|
||||
{
|
||||
_statistics = statsList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create and open an output file for writing direct from a set of statistics
|
||||
/// </summary>
|
||||
/// <param name="outfile">Name of the file to write to</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="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 bool WriteToFile(string? outfile, bool baddumpCol, bool nodumpCol, bool throwOnError = false)
|
||||
{
|
||||
InternalStopwatch watch = new($"Writing statistics to '{outfile}");
|
||||
|
||||
try
|
||||
{
|
||||
// Try to create the output file
|
||||
FileStream stream = File.Create(outfile ?? string.Empty);
|
||||
if (stream is null)
|
||||
{
|
||||
_logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write to the stream
|
||||
bool result = WriteToStream(stream, baddumpCol, nodumpCol, throwOnError);
|
||||
|
||||
// Dispose of the stream
|
||||
stream.Dispose();
|
||||
}
|
||||
catch (Exception ex) when (!throwOnError)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
watch.Stop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a set of statistics to an input stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream to write to</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="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 abstract bool WriteToStream(Stream stream, bool baddumpCol, bool nodumpCol, bool throwOnError = false);
|
||||
}
|
||||
}
|
||||
38
SabreTools.Metadata.Reports/Enums.cs
Normal file
38
SabreTools.Metadata.Reports/Enums.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace SabreTools.Metadata.Reports
|
||||
{
|
||||
/// <summary>
|
||||
/// Determine which format to output Stats to
|
||||
/// </summary>
|
||||
public enum StatReportFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Only output to the console
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Console-formatted
|
||||
/// </summary>
|
||||
Textfile,
|
||||
|
||||
/// <summary>
|
||||
/// ClrMamePro HTML
|
||||
/// </summary>
|
||||
HTML,
|
||||
|
||||
/// <summary>
|
||||
/// Comma-Separated Values (Standardized)
|
||||
/// </summary>
|
||||
CSV,
|
||||
|
||||
/// <summary>
|
||||
/// Semicolon-Separated Values (Standardized)
|
||||
/// </summary>
|
||||
SSV,
|
||||
|
||||
/// <summary>
|
||||
/// Tab-Separated Values (Standardized)
|
||||
/// </summary>
|
||||
TSV,
|
||||
}
|
||||
}
|
||||
20
SabreTools.Metadata.Reports/Formats/CommaSeparatedValue.cs
Normal file
20
SabreTools.Metadata.Reports/Formats/CommaSeparatedValue.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using SabreTools.Metadata.DatFiles;
|
||||
|
||||
namespace SabreTools.Metadata.Reports.Formats
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a comma-separated value file
|
||||
/// </summary>
|
||||
public sealed class CommaSeparatedValue : SeparatedValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new report from the filename
|
||||
/// </summary>
|
||||
/// <param name="statsList">List of statistics objects to set</param>
|
||||
public CommaSeparatedValue(List<DatStatistics> statsList) : base(statsList)
|
||||
{
|
||||
_delim = ',';
|
||||
}
|
||||
}
|
||||
}
|
||||
329
SabreTools.Metadata.Reports/Formats/Html.cs
Normal file
329
SabreTools.Metadata.Reports/Formats/Html.cs
Normal file
@@ -0,0 +1,329 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD
|
||||
using System.Net;
|
||||
#endif
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Metadata.DatFiles;
|
||||
using SabreTools.Text.Extensions;
|
||||
using ItemStatus = SabreTools.Data.Models.Metadata.ItemStatus;
|
||||
using ItemType = SabreTools.Data.Models.Metadata.ItemType;
|
||||
|
||||
#pragma warning disable IDE0290 // Use primary constructor
|
||||
namespace SabreTools.Metadata.Reports.Formats
|
||||
{
|
||||
/// <summary>
|
||||
/// HTML report format
|
||||
/// </summary>
|
||||
/// TODO: Make output standard width, without making the entire thing a table
|
||||
public class Html : BaseReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new report from the filename
|
||||
/// </summary>
|
||||
/// <param name="statsList">List of statistics objects to set</param>
|
||||
public Html(List<DatStatistics> statsList)
|
||||
: base(statsList)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool WriteToStream(Stream stream, bool baddumpCol, bool nodumpCol, bool throwOnError = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
XmlTextWriter xtw = new(stream, Encoding.UTF8)
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
IndentChar = '\t',
|
||||
Indentation = 1
|
||||
};
|
||||
|
||||
// Write out the header
|
||||
WriteHeader(xtw, baddumpCol, nodumpCol);
|
||||
|
||||
// Now process each of the statistics
|
||||
for (int i = 0; i < _statistics.Count; i++)
|
||||
{
|
||||
// Get the current statistic
|
||||
DatStatistics stat = _statistics[i];
|
||||
|
||||
// If we have a directory statistic
|
||||
if (stat.IsDirectory)
|
||||
{
|
||||
WriteMidSeparator(xtw, baddumpCol, nodumpCol);
|
||||
WriteIndividual(xtw, stat, baddumpCol, nodumpCol);
|
||||
|
||||
// If we have anything but the last value, write the separator
|
||||
if (i < _statistics.Count - 1)
|
||||
{
|
||||
WriteFooterSeparator(xtw, baddumpCol, nodumpCol);
|
||||
WriteMidHeader(xtw, baddumpCol, nodumpCol);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a normal statistic
|
||||
else
|
||||
{
|
||||
WriteIndividual(xtw, stat, baddumpCol, nodumpCol);
|
||||
}
|
||||
}
|
||||
|
||||
WriteFooter(xtw);
|
||||
#if NET452_OR_GREATER
|
||||
xtw.Dispose();
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex) when (!throwOnError)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write out the header to the stream, if any exists
|
||||
/// </summary>
|
||||
/// <param name="xtw">XmlTextWriter to write to</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>
|
||||
private static void WriteHeader(XmlTextWriter xtw, bool baddumpCol, bool nodumpCol)
|
||||
{
|
||||
xtw.WriteDocType("html", null, null, null);
|
||||
xtw.WriteStartElement("html");
|
||||
|
||||
xtw.WriteStartElement("header");
|
||||
xtw.WriteElementString("title", "DAT Statistics Report");
|
||||
xtw.WriteElementString("style", @"
|
||||
body {
|
||||
background-color: lightgray;
|
||||
}
|
||||
.dir {
|
||||
color: #0088FF;
|
||||
}");
|
||||
xtw.WriteEndElement(); // header
|
||||
|
||||
xtw.WriteStartElement("body");
|
||||
|
||||
xtw.WriteElementString("h2", $"DAT Statistics Report ({DateTime.Now:d})");
|
||||
|
||||
xtw.WriteStartElement("table");
|
||||
xtw.WriteAttributeString("border", "1");
|
||||
xtw.WriteAttributeString("cellpadding", "5");
|
||||
xtw.WriteAttributeString("cellspacing", "0");
|
||||
xtw.Flush();
|
||||
|
||||
// Now write the mid header for those who need it
|
||||
WriteMidHeader(xtw, baddumpCol, nodumpCol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write out the mid-header to the stream, if any exists
|
||||
/// </summary>
|
||||
/// <param name="xtw">XmlTextWriter to write to</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>
|
||||
private static void WriteMidHeader(XmlTextWriter xtw, bool baddumpCol, bool nodumpCol)
|
||||
{
|
||||
xtw.WriteStartElement("tr");
|
||||
xtw.WriteAttributeString("bgcolor", "gray");
|
||||
|
||||
xtw.WriteElementString("th", "File Name");
|
||||
|
||||
xtw.WriteStartElement("th");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString("Total Size");
|
||||
xtw.WriteEndElement(); // th
|
||||
|
||||
xtw.WriteStartElement("th");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString("Games");
|
||||
xtw.WriteEndElement(); // th
|
||||
|
||||
xtw.WriteStartElement("th");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString("Roms");
|
||||
xtw.WriteEndElement(); // th
|
||||
|
||||
xtw.WriteStartElement("th");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString("Disks");
|
||||
xtw.WriteEndElement(); // th
|
||||
|
||||
xtw.WriteStartElement("th");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString("# with CRC");
|
||||
xtw.WriteEndElement(); // th
|
||||
|
||||
xtw.WriteStartElement("th");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString("# with MD5");
|
||||
xtw.WriteEndElement(); // th
|
||||
|
||||
xtw.WriteStartElement("th");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString("# with SHA-1");
|
||||
xtw.WriteEndElement(); // th
|
||||
|
||||
xtw.WriteStartElement("th");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString("# with SHA-256");
|
||||
xtw.WriteEndElement(); // th
|
||||
|
||||
if (baddumpCol)
|
||||
{
|
||||
xtw.WriteStartElement("th");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString("Baddumps");
|
||||
xtw.WriteEndElement(); // th
|
||||
}
|
||||
|
||||
if (nodumpCol)
|
||||
{
|
||||
xtw.WriteStartElement("th");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString("Nodumps");
|
||||
xtw.WriteEndElement(); // th
|
||||
}
|
||||
|
||||
xtw.WriteEndElement(); // tr
|
||||
xtw.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a single set of statistics
|
||||
/// </summary>
|
||||
/// <param name="xtw">XmlTextWriter to write to</param>
|
||||
/// <param name="stat">DatStatistics object to write out</param>
|
||||
/// <param name="baddumpCol">True if baddumps should be included in output, false ot
|
||||
/// herwise</param>
|
||||
/// <param name="nodumpCol">True if nodumps should be included in output, false otherwise</param>
|
||||
private static void WriteIndividual(XmlTextWriter xtw, DatStatistics stat, bool baddumpCol, bool nodumpCol)
|
||||
{
|
||||
bool isDirectory = stat.DisplayName!.StartsWith("DIR: ");
|
||||
|
||||
xtw.WriteStartElement("tr");
|
||||
if (isDirectory)
|
||||
xtw.WriteAttributeString("class", "dir");
|
||||
|
||||
#if NET20 || NET35
|
||||
xtw.WriteElementString("td", isDirectory ? stat.DisplayName.Remove(0, 5) : stat.DisplayName);
|
||||
#else
|
||||
xtw.WriteElementString("td", isDirectory ? WebUtility.HtmlEncode(stat.DisplayName.Remove(0, 5)) : WebUtility.HtmlEncode(stat.DisplayName));
|
||||
#endif
|
||||
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString(NumberHelper.GetBytesReadable(stat.TotalSize));
|
||||
xtw.WriteEndElement(); // td
|
||||
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString(stat.MachineCount.ToString());
|
||||
xtw.WriteEndElement(); // td
|
||||
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString(stat.GetItemCount(ItemType.Rom).ToString());
|
||||
xtw.WriteEndElement(); // td
|
||||
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString(stat.GetItemCount(ItemType.Disk).ToString());
|
||||
xtw.WriteEndElement(); // td
|
||||
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString(stat.GetHashCount(HashType.CRC32).ToString());
|
||||
xtw.WriteEndElement(); // td
|
||||
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString(stat.GetHashCount(HashType.MD5).ToString());
|
||||
xtw.WriteEndElement(); // td
|
||||
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString(stat.GetHashCount(HashType.SHA1).ToString());
|
||||
xtw.WriteEndElement(); // td
|
||||
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString(stat.GetHashCount(HashType.SHA256).ToString());
|
||||
xtw.WriteEndElement(); // td
|
||||
|
||||
if (baddumpCol)
|
||||
{
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString(stat.GetStatusCount(ItemStatus.BadDump).ToString());
|
||||
xtw.WriteEndElement(); // td
|
||||
}
|
||||
|
||||
if (nodumpCol)
|
||||
{
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("align", "right");
|
||||
xtw.WriteString(stat.GetStatusCount(ItemStatus.Nodump).ToString());
|
||||
xtw.WriteEndElement(); // td
|
||||
}
|
||||
|
||||
xtw.WriteEndElement(); // tr
|
||||
xtw.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write out the separator to the stream, if any exists
|
||||
/// </summary>
|
||||
/// <param name="xtw">XmlTextWriter to write to</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>
|
||||
private static void WriteMidSeparator(XmlTextWriter xtw, bool baddumpCol, bool nodumpCol)
|
||||
{
|
||||
xtw.WriteStartElement("tr");
|
||||
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("colspan", baddumpCol && nodumpCol ? "12" : (baddumpCol ^ nodumpCol ? "11" : "10"));
|
||||
xtw.WriteEndElement(); // td
|
||||
|
||||
xtw.WriteEndElement(); // tr
|
||||
xtw.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write out the footer-separator to the stream, if any exists
|
||||
/// </summary>
|
||||
/// <param name="xtw">XmlTextWriter to write to</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>
|
||||
private static void WriteFooterSeparator(XmlTextWriter xtw, bool baddumpCol, bool nodumpCol)
|
||||
{
|
||||
xtw.WriteStartElement("tr");
|
||||
xtw.WriteAttributeString("border", "0");
|
||||
|
||||
xtw.WriteStartElement("td");
|
||||
xtw.WriteAttributeString("colspan", baddumpCol && nodumpCol ? "12" : (baddumpCol ^ nodumpCol ? "11" : "10"));
|
||||
xtw.WriteEndElement(); // td
|
||||
|
||||
xtw.WriteEndElement(); // tr
|
||||
xtw.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write out the footer to the stream, if any exists
|
||||
/// </summary>
|
||||
/// <param name="xtw">XmlTextWriter to write to</param>
|
||||
private static void WriteFooter(XmlTextWriter xtw)
|
||||
{
|
||||
xtw.WriteEndElement(); // table
|
||||
xtw.WriteEndElement(); // body
|
||||
xtw.WriteEndElement(); // html
|
||||
xtw.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using SabreTools.Metadata.DatFiles;
|
||||
|
||||
namespace SabreTools.Metadata.Reports.Formats
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a semicolon-separated value file
|
||||
/// </summary>
|
||||
public sealed class SemicolonSeparatedValue : SeparatedValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new report from the filename
|
||||
/// </summary>
|
||||
/// <param name="statsList">List of statistics objects to set</param>
|
||||
public SemicolonSeparatedValue(List<DatStatistics> statsList) : base(statsList)
|
||||
{
|
||||
_delim = ';';
|
||||
}
|
||||
}
|
||||
}
|
||||
148
SabreTools.Metadata.Reports/Formats/SeparatedValue.cs
Normal file
148
SabreTools.Metadata.Reports/Formats/SeparatedValue.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Metadata.DatFiles;
|
||||
using SabreTools.Text.SeparatedValue;
|
||||
using ItemStatus = SabreTools.Data.Models.Metadata.ItemStatus;
|
||||
using ItemType = SabreTools.Data.Models.Metadata.ItemType;
|
||||
|
||||
#pragma warning disable IDE0290 // Use primary constructor
|
||||
namespace SabreTools.Metadata.Reports.Formats
|
||||
{
|
||||
/// <summary>
|
||||
/// Separated-Value report format
|
||||
/// </summary>
|
||||
public abstract class SeparatedValue : BaseReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the delimiter between fields
|
||||
/// </summary>
|
||||
protected char _delim;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new report from the filename
|
||||
/// </summary>
|
||||
/// <param name="statsList">List of statistics objects to set</param>
|
||||
public SeparatedValue(List<DatStatistics> statsList)
|
||||
: base(statsList)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool WriteToStream(Stream stream, bool baddumpCol, bool nodumpCol, bool throwOnError = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
Writer svw = new(stream, Encoding.UTF8)
|
||||
{
|
||||
Separator = _delim,
|
||||
Quotes = true,
|
||||
};
|
||||
|
||||
// Write out the header
|
||||
WriteHeader(svw, baddumpCol, nodumpCol);
|
||||
|
||||
// Now process each of the statistics
|
||||
for (int i = 0; i < _statistics.Count; i++)
|
||||
{
|
||||
// Get the current statistic
|
||||
DatStatistics stat = _statistics[i];
|
||||
|
||||
// If we have a directory statistic
|
||||
if (stat.IsDirectory)
|
||||
{
|
||||
WriteIndividual(svw, stat, baddumpCol, nodumpCol);
|
||||
|
||||
// If we have anything but the last value, write the separator
|
||||
if (i < _statistics.Count - 1)
|
||||
WriteFooterSeparator(svw);
|
||||
}
|
||||
|
||||
// If we have a normal statistic
|
||||
else
|
||||
{
|
||||
WriteIndividual(svw, stat, baddumpCol, nodumpCol);
|
||||
}
|
||||
}
|
||||
|
||||
svw.Dispose();
|
||||
}
|
||||
catch (Exception ex) when (!throwOnError)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write out the header to the stream, if any exists
|
||||
/// </summary>
|
||||
/// <param name="svw">Writer to write to</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>
|
||||
private static void WriteHeader(Writer svw, bool baddumpCol, bool nodumpCol)
|
||||
{
|
||||
string[] headers =
|
||||
[
|
||||
"File Name",
|
||||
"Total Size",
|
||||
"Games",
|
||||
"Roms",
|
||||
"Disks",
|
||||
"# with CRC",
|
||||
"# with MD5",
|
||||
"# with SHA-1",
|
||||
"# with SHA-256",
|
||||
"# with SHA-384",
|
||||
"# with SHA-512",
|
||||
baddumpCol ? "BadDumps" : string.Empty,
|
||||
nodumpCol ? "Nodumps" : string.Empty,
|
||||
];
|
||||
svw.WriteHeader(headers);
|
||||
svw.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a single set of statistics
|
||||
/// </summary>
|
||||
/// <param name="svw">Writer to write to</param>
|
||||
/// <param name="stat">DatStatistics object to write out</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>
|
||||
private static void WriteIndividual(Writer svw, DatStatistics stat, bool baddumpCol, bool nodumpCol)
|
||||
{
|
||||
string[] values =
|
||||
[
|
||||
stat.DisplayName!,
|
||||
stat.TotalSize.ToString(),
|
||||
stat.MachineCount.ToString(),
|
||||
stat.GetItemCount(ItemType.Rom).ToString(),
|
||||
stat.GetItemCount(ItemType.Disk).ToString(),
|
||||
stat.GetHashCount(HashType.CRC32).ToString(),
|
||||
stat.GetHashCount(HashType.MD5).ToString(),
|
||||
stat.GetHashCount(HashType.SHA1).ToString(),
|
||||
stat.GetHashCount(HashType.SHA256).ToString(),
|
||||
stat.GetHashCount(HashType.SHA384).ToString(),
|
||||
stat.GetHashCount(HashType.SHA512).ToString(),
|
||||
baddumpCol ? stat.GetStatusCount(ItemStatus.BadDump).ToString() : string.Empty,
|
||||
nodumpCol ? stat.GetStatusCount(ItemStatus.Nodump).ToString() : string.Empty,
|
||||
];
|
||||
svw.WriteValues(values);
|
||||
svw.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write out the footer-separator to the stream, if any exists
|
||||
/// </summary>
|
||||
/// <param name="svw">Writer to write to</param>
|
||||
private static void WriteFooterSeparator(Writer svw)
|
||||
{
|
||||
svw.WriteString("\n");
|
||||
svw.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
20
SabreTools.Metadata.Reports/Formats/TabSeparatedValue.cs
Normal file
20
SabreTools.Metadata.Reports/Formats/TabSeparatedValue.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using SabreTools.Metadata.DatFiles;
|
||||
|
||||
namespace SabreTools.Metadata.Reports.Formats
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a tab-separated value file
|
||||
/// </summary>
|
||||
public sealed class TabSeparatedValue : SeparatedValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new report from the filename
|
||||
/// </summary>
|
||||
/// <param name="statsList">List of statistics objects to set</param>
|
||||
public TabSeparatedValue(List<DatStatistics> statsList) : base(statsList)
|
||||
{
|
||||
_delim = '\t';
|
||||
}
|
||||
}
|
||||
}
|
||||
117
SabreTools.Metadata.Reports/Formats/Textfile.cs
Normal file
117
SabreTools.Metadata.Reports/Formats/Textfile.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.Hashing;
|
||||
using SabreTools.Metadata.DatFiles;
|
||||
using SabreTools.Text.Extensions;
|
||||
using ItemStatus = SabreTools.Data.Models.Metadata.ItemStatus;
|
||||
using ItemType = SabreTools.Data.Models.Metadata.ItemType;
|
||||
|
||||
#pragma warning disable IDE0290 // Use primary constructor
|
||||
namespace SabreTools.Metadata.Reports.Formats
|
||||
{
|
||||
/// <summary>
|
||||
/// Textfile report format
|
||||
/// </summary>
|
||||
public class Textfile : BaseReport
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new report from the filename
|
||||
/// </summary>
|
||||
/// <param name="statsList">List of statistics objects to set</param>
|
||||
public Textfile(List<DatStatistics> statsList)
|
||||
: base(statsList)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool WriteToStream(Stream stream, bool baddumpCol, bool nodumpCol, bool throwOnError = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
StreamWriter sw = new(stream);
|
||||
|
||||
// Now process each of the statistics
|
||||
for (int i = 0; i < _statistics.Count; i++)
|
||||
{
|
||||
// Get the current statistic
|
||||
DatStatistics stat = _statistics[i];
|
||||
|
||||
// If we have a directory statistic
|
||||
if (stat.IsDirectory)
|
||||
{
|
||||
WriteIndividual(sw, stat, baddumpCol, nodumpCol);
|
||||
|
||||
// If we have anything but the last value, write the separator
|
||||
if (i < _statistics.Count - 1)
|
||||
WriteFooterSeparator(sw);
|
||||
}
|
||||
|
||||
// If we have a normal statistic
|
||||
else
|
||||
{
|
||||
WriteIndividual(sw, stat, baddumpCol, nodumpCol);
|
||||
}
|
||||
}
|
||||
|
||||
sw.Dispose();
|
||||
}
|
||||
catch (Exception ex) when (!throwOnError)
|
||||
{
|
||||
_logger.Error(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a single set of statistics
|
||||
/// </summary>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
/// <param name="stat">DatStatistics object to write out</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>
|
||||
private static void WriteIndividual(StreamWriter sw, DatStatistics stat, bool baddumpCol, bool nodumpCol)
|
||||
{
|
||||
var line = new StringBuilder();
|
||||
|
||||
line.AppendLine($"'{stat.DisplayName}':");
|
||||
line.AppendLine($"--------------------------------------------------");
|
||||
line.AppendLine($" Uncompressed size: {NumberHelper.GetBytesReadable(stat!.TotalSize)}");
|
||||
line.AppendLine($" Games found: {stat.MachineCount}");
|
||||
line.AppendLine($" Roms found: {stat.GetItemCount(ItemType.Rom)}");
|
||||
line.AppendLine($" Disks found: {stat.GetItemCount(ItemType.Disk)}");
|
||||
line.AppendLine($" Roms with CRC-32: {stat.GetHashCount(HashType.CRC32)}");
|
||||
line.AppendLine($" Roms with MD5: {stat.GetHashCount(HashType.MD5)}");
|
||||
line.AppendLine($" Roms with SHA-1: {stat.GetHashCount(HashType.SHA1)}");
|
||||
line.AppendLine($" Roms with SHA-256: {stat.GetHashCount(HashType.SHA256)}");
|
||||
line.AppendLine($" Roms with SHA-384: {stat.GetHashCount(HashType.SHA384)}");
|
||||
line.AppendLine($" Roms with SHA-512: {stat.GetHashCount(HashType.SHA512)}");
|
||||
|
||||
if (baddumpCol)
|
||||
line.AppendLine($" Roms with BadDump status: {stat.GetStatusCount(ItemStatus.BadDump)}");
|
||||
|
||||
if (nodumpCol)
|
||||
line.AppendLine($" Roms with Nodump status: {stat.GetStatusCount(ItemStatus.Nodump)}");
|
||||
|
||||
// For spacing between DATs
|
||||
line.AppendLine();
|
||||
line.AppendLine();
|
||||
|
||||
sw.Write(line.ToString());
|
||||
sw.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write out the footer-separator to the stream, if any exists
|
||||
/// </summary>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
private static void WriteFooterSeparator(StreamWriter sw)
|
||||
{
|
||||
sw.Write("\n");
|
||||
sw.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
SabreTools.Metadata.Reports/README.MD
Normal file
3
SabreTools.Metadata.Reports/README.MD
Normal file
@@ -0,0 +1,3 @@
|
||||
# SabreTools.Metadata.Reports
|
||||
|
||||
This library contains report generating functionality used by metadata format processing.
|
||||
@@ -0,0 +1,44 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>NU1902;NU1903</NoWarn>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>2.3.0</Version>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Description>Report functionality for metadata file processing</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2016-2026</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Serialization</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>metadata dat datfile report</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.Data.Models\SabreTools.Data.Models.csproj" />
|
||||
<ProjectReference Include="..\SabreTools.Metadata.DatFiles\SabreTools.Metadata.DatFiles.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.Hashing" Version="[2.0.0]" />
|
||||
<PackageReference Include="SabreTools.IO" Version="[2.0.0]" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Metadata.DatFile
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Metadata.DatFiles.Test", "SabreTools.Metadata.DatFiles.Test\SabreTools.Metadata.DatFiles.Test.csproj", "{EFF85ED6-27D6-4076-A935-9792D2975078}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Metadata.Reports", "SabreTools.Metadata.Reports\SabreTools.Metadata.Reports.csproj", "{6CDE2071-784B-4072-9EAE-33F0AE00566C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -321,6 +323,18 @@ Global
|
||||
{EFF85ED6-27D6-4076-A935-9792D2975078}.Release|x64.Build.0 = Release|Any CPU
|
||||
{EFF85ED6-27D6-4076-A935-9792D2975078}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{EFF85ED6-27D6-4076-A935-9792D2975078}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6CDE2071-784B-4072-9EAE-33F0AE00566C}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
<Compile Include="..\SabreTools.Metadata.DatItems\*.cs" />
|
||||
<Compile Include="..\SabreTools.Metadata.DatItems\Formats\*.cs" />
|
||||
<Compile Include="..\SabreTools.Metadata.Filter\*.cs" />
|
||||
<Compile Include="..\SabreTools.Metadata.Reports\*.cs" />
|
||||
<Compile Include="..\SabreTools.Metadata.Reports\Formats\*.cs" />
|
||||
<Compile Include="..\SabreTools.ObjectIdentifier\*.cs" />
|
||||
<Compile Include="..\SabreTools.Serialization.CrossModel\*.cs" Exclude="..\SabreTools.Serialization.CrossModel\ExtensionAttribute.cs" />
|
||||
<Compile Include="..\SabreTools.Serialization.Readers\*.cs" />
|
||||
|
||||
@@ -84,6 +84,7 @@ if [ $NO_BUILD = false ]; then
|
||||
dotnet pack SabreTools.Metadata.DatFiles/SabreTools.Metadata.DatFiles.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.Metadata.DatItems/SabreTools.Metadata.DatItems.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.Metadata.Filter/SabreTools.Metadata.Filter.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.Metadata.Reports/SabreTools.Metadata.Reports.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.ObjectIdentifier/SabreTools.ObjectIdentifier.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.Serialization.CrossModel/SabreTools.Serialization.CrossModel.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.Serialization.Readers/SabreTools.Serialization.Readers.csproj --output $BUILD_FOLDER
|
||||
|
||||
@@ -75,6 +75,7 @@ if (!$NO_BUILD.IsPresent) {
|
||||
dotnet pack SabreTools.Metadata.DatFiles\SabreTools.Metadata.DatFiles.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.Metadata.DatItems\SabreTools.Metadata.DatItems.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.Metadata.Filter\SabreTools.Metadata.Filter.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.Metadata.Reports\SabreTools.Metadata.Reports.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.ObjectIdentifier\SabreTools.ObjectIdentifier.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.Serialization.CrossModel\SabreTools.Serialization.CrossModel.csproj --output $BUILD_FOLDER
|
||||
dotnet pack SabreTools.Serialization.Readers\SabreTools.Serialization.Readers.csproj --output $BUILD_FOLDER
|
||||
|
||||
Reference in New Issue
Block a user