using System; using System.Collections.Generic; #if NET40_OR_GREATER || NETCOREAPP using System.Threading.Tasks; #endif using SabreTools.DatFiles; using SabreTools.DatItems; using SabreTools.IO.Extensions; using SabreTools.IO.Logging; using SabreTools.Reports; namespace SabreTools.DatTools { /// /// Helper methods for writing from DatFiles /// public class Writer { #region Private Constants /// /// Map of all formats to extensions, including "backup" extensions /// private static readonly Dictionary ExtensionMappings = new() { // .csv { DatFormat.CSV, new string[] { ".csv" } }, // .dat { DatFormat.ClrMamePro, new string[] { ".dat" } }, { DatFormat.RomCenter, new string[] { ".dat", ".rc.dat" } }, { DatFormat.DOSCenter, new string[] { ".dat", ".dc.dat" } }, // .json { DatFormat.SabreJSON, new string[] { ".json" } }, // .md2 { DatFormat.RedumpMD2, new string[] { ".md2" } }, // .md4 { DatFormat.RedumpMD4, new string[] { ".md4" } }, // .md5 { DatFormat.RedumpMD5, new string[] { ".md5" } }, // .sfv { DatFormat.RedumpSFV, new string[] { ".sfv" } }, // .sha1 { DatFormat.RedumpSHA1, new string[] { ".sha1" } }, // .sha256 { DatFormat.RedumpSHA256, new string[] { ".sha256" } }, // .sha384 { DatFormat.RedumpSHA384, new string[] { ".sha384" } }, // .sha512 { DatFormat.RedumpSHA512, new string[] { ".sha512" } }, // .spamsum { DatFormat.RedumpSpamSum, new string[] { ".spamsum" } }, // .ssv { DatFormat.SSV, new string[] { ".ssv" } }, // .tsv { DatFormat.TSV, new string[] { ".tsv" } }, // .txt { DatFormat.AttractMode, new string[] { ".txt" } }, { DatFormat.Listrom, new string[] { ".txt", ".lr.txt" } }, { DatFormat.MissFile, new string[] { ".txt", ".miss.txt" } }, { DatFormat.EverdriveSMDB, new string[] { ".txt", ".smdb.txt" } }, // .xml { DatFormat.Logiqx, new string[] { ".xml" } }, { DatFormat.LogiqxDeprecated, new string[] { ".xml", ".xml" } }, // Intentional duplicate { DatFormat.SabreXML, new string[] { ".xml", ".sd.xml" } }, { DatFormat.SoftwareList, new string[] { ".xml", ".sl.xml" } }, { DatFormat.Listxml, new string[] { ".xml", ".mame.xml" } }, { DatFormat.OfflineList, new string[] { ".xml", ".ol.xml" } }, { DatFormat.OpenMSX, new string[] { ".xml", ".msx.xml" } }, { DatFormat.ArchiveDotOrg, new string[] { ".xml", ".ado.xml" } }, }; #endregion #region Logging /// /// Logging object /// private static readonly Logger _staticLogger = new(); #endregion /// /// Create and open an output file for writing direct from a dictionary /// /// Current DatFile object to write from /// Set the output directory (current directory on null) /// True if the DAT was written correctly, false otherwise public static bool Write(DatFile datFile, string? outDir) => Write(datFile, outDir, overwrite: true, throwOnError: false); /// /// Create and open an output file for writing direct from a dictionary /// /// Current DatFile object to write from /// Set the output directory (current directory on null) /// True if files should be overwritten, false if they should be renamed instead /// True if the DAT was written correctly, false otherwise public static bool Write(DatFile datFile, string? outDir, bool overwrite) => Write(datFile, outDir, overwrite, throwOnError: false); /// /// Create and open an output file for writing direct from a dictionary /// /// Current DatFile object to write from /// Set the output directory (current directory on null) /// True if files should be overwritten, false if they should be renamed instead /// True if the error that is thrown should be thrown back to the caller, false otherwise /// True if the DAT was written correctly, false otherwise public static bool Write(DatFile datFile, string? outDir, bool overwrite, bool throwOnError) { // If we have nothing writable, abort if (!HasWritable(datFile)) { _staticLogger.User("There were no items to write out!"); return false; } // Ensure the output directory is set and created outDir = outDir.Ensure(create: true); InternalStopwatch watch = new($"Writing out internal dat to '{outDir}'"); // If the DAT has no output format, default to XML if (datFile.Header.GetFieldValue(DatHeader.DatFormatKey) == 0) { _staticLogger.Verbose("No DAT format defined, defaulting to XML"); datFile.Header.SetFieldValue(DatHeader.DatFormatKey, DatFormat.Logiqx); } // Make sure that the three essential fields are filled in EnsureHeaderFields(datFile); // Bucket roms by game name, if not already datFile.BucketBy(ItemKey.Machine); // Output the number of items we're going to be writing _staticLogger.User($"A total of {datFile.DatStatistics.TotalCount - datFile.DatStatistics.RemovedCount} items will be written out to '{datFile.Header.GetStringFieldValue(DatHeader.FileNameKey)}'"); // Get the outfile names Dictionary outfiles = DatHeader.CreateOutFileNames(datFile.Header, outDir!, overwrite); try { // Write out all required formats #if NET452_OR_GREATER || NETCOREAPP Parallel.ForEach(outfiles.Keys, Core.Globals.ParallelOptions, datFormat => #elif NET40_OR_GREATER Parallel.ForEach(outfiles.Keys, datFormat => #else foreach (var datFormat in outfiles.Keys) #endif { string outfile = outfiles[datFormat]; try { DatFile writingDatFile = DatFileTool.CreateDatFile(datFormat, datFile); writingDatFile.WriteToFile(outfile, ignoreblanks: true, throwOnError); } catch (Exception ex) when (!throwOnError) { _staticLogger.Error(ex, $"Datfile '{outfile}' could not be written out"); } #if NET40_OR_GREATER || NETCOREAPP }); #else } #endif } catch (Exception ex) when (!throwOnError) { _staticLogger.Error(ex); return false; } finally { watch.Stop(); } return true; } /// /// Write the stats out to console for the current DatFile /// /// Current DatFile object to write from public static void WriteStatsToConsole(DatFile datFile) { long diskCount = datFile.DatStatistics.GetItemCount(ItemType.Disk); long mediaCount = datFile.DatStatistics.GetItemCount(ItemType.Media); long romCount = datFile.DatStatistics.GetItemCount(ItemType.Rom); if (diskCount + mediaCount + romCount == 0) datFile.RecalculateStats(); datFile.BucketBy(ItemKey.Machine, norename: true); datFile.DatStatistics.DisplayName = datFile.Header.GetStringFieldValue(DatHeader.FileNameKey); datFile.DatStatistics.MachineCount = datFile.Items.SortedKeys.Length; List statsList = [ datFile.DatStatistics, ]; var consoleOutput = BaseReport.Create(StatReportFormat.None, statsList); consoleOutput!.WriteToFile(null, true, true); } /// /// Ensure that FileName, Name, and Description are filled with some value /// /// Current DatFile object to write from private static void EnsureHeaderFields(DatFile datFile) { // Empty FileName if (string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(DatHeader.FileNameKey))) { if (string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.NameKey)) && string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey))) { datFile.Header.SetFieldValue(DatHeader.FileNameKey, "Default"); datFile.Header.SetFieldValue(Models.Metadata.Header.NameKey, "Default"); datFile.Header.SetFieldValue(Models.Metadata.Header.DescriptionKey, "Default"); } else if (string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.NameKey)) && !string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey))) { datFile.Header.SetFieldValue(DatHeader.FileNameKey, datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey)); datFile.Header.SetFieldValue(Models.Metadata.Header.NameKey, datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey)); } else if (!string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.NameKey)) && string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey))) { datFile.Header.SetFieldValue(DatHeader.FileNameKey, datFile.Header.GetStringFieldValue(Models.Metadata.Header.NameKey)); datFile.Header.SetFieldValue(Models.Metadata.Header.DescriptionKey, datFile.Header.GetStringFieldValue(Models.Metadata.Header.NameKey)); } else if (!string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.NameKey)) && !string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey))) { datFile.Header.SetFieldValue(DatHeader.FileNameKey, datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey)); } } // Filled FileName else { if (string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.NameKey)) && string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey))) { datFile.Header.SetFieldValue(Models.Metadata.Header.NameKey, datFile.Header.GetStringFieldValue(DatHeader.FileNameKey)); datFile.Header.SetFieldValue(Models.Metadata.Header.DescriptionKey, datFile.Header.GetStringFieldValue(DatHeader.FileNameKey)); } else if (string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.NameKey)) && !string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey))) { datFile.Header.SetFieldValue(Models.Metadata.Header.NameKey, datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey)); } else if (!string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.NameKey)) && string.IsNullOrEmpty(datFile.Header.GetStringFieldValue(Models.Metadata.Header.DescriptionKey))) { datFile.Header.SetFieldValue(Models.Metadata.Header.DescriptionKey, datFile.Header.GetStringFieldValue(Models.Metadata.Header.NameKey)); } } } /// /// Get if the DatFile has any writable items /// /// Current DatFile object to write from /// True if there are any writable items, false otherwise private static bool HasWritable(DatFile datFile) { // Force a statistics recheck, just in case datFile.RecalculateStats(); // If there's nothing there, abort if (datFile.DatStatistics.TotalCount == 0) return false; // if (datFile.ItemsDB.DatStatistics.TotalCount == 0) // return false; // If every item is removed, abort if (datFile.DatStatistics.TotalCount == datFile.DatStatistics.RemovedCount) return false; // if (datFile.ItemsDB.DatStatistics.TotalCount == datFile.ItemsDB.DatStatistics.RemovedCount) // return false; return true; } } }