diff --git a/RombaSharp/Features/Diffdat.cs b/RombaSharp/Features/Diffdat.cs index 1a097494..00a36594 100644 --- a/RombaSharp/Features/Diffdat.cs +++ b/RombaSharp/Features/Diffdat.cs @@ -55,7 +55,7 @@ in -old DAT file. Ignores those entries in -old that are not in -new."; return; } - // Get the DatTool for parsing + // Get the DatTool for opeations DatTool dt = new DatTool(); // Create the encapsulating datfile @@ -67,7 +67,7 @@ in -old DAT file. Ignores those entries in -old that are not in -new."; // Diff against the new datfile DatFile intDat = dt.CreateAndParse(newdat); datfile.DiffAgainst(intDat, false); - intDat.Write(outdat); + dt.Write(intDat, outdat); } } } diff --git a/RombaSharp/Features/Dir2Dat.cs b/RombaSharp/Features/Dir2Dat.cs index 4381cd54..76f21b3c 100644 --- a/RombaSharp/Features/Dir2Dat.cs +++ b/RombaSharp/Features/Dir2Dat.cs @@ -56,7 +56,7 @@ namespace RombaSharp.Features datfile.Header.Description = description; dt.PopulateFromDir(datfile, source, asFiles: TreatAsFile.NonArchive); datfile.ApplyCleaning(new Cleaner() { ExcludeFields = Hash.DeepHashes.AsFields() }); - datfile.Write(outdat); + dt.Write(datfile, outdat); } } } diff --git a/RombaSharp/Features/EDiffdat.cs b/RombaSharp/Features/EDiffdat.cs index 81738788..c4db2976 100644 --- a/RombaSharp/Features/EDiffdat.cs +++ b/RombaSharp/Features/EDiffdat.cs @@ -59,7 +59,7 @@ namespace RombaSharp.Features // Diff against the new datfile DatFile intDat = dt.CreateAndParse(newdat); datfile.DiffAgainst(intDat, false); - intDat.Write(outdat); + dt.Write(intDat, outdat); } } } diff --git a/SabreTools.DatFiles/DatFile.Splitting.cs b/SabreTools.DatFiles/DatFile.Splitting.cs index df1b3947..6f5d661c 100644 --- a/SabreTools.DatFiles/DatFile.Splitting.cs +++ b/SabreTools.DatFiles/DatFile.Splitting.cs @@ -299,7 +299,8 @@ namespace SabreTools.DatFiles newDatFile.Header.Type = null; // Write out the temporary DAT to the proper directory - newDatFile.Write(outDir); + DatTool dt = new DatTool(); + dt.Write(newDatFile, outDir); } /// diff --git a/SabreTools.DatFiles/DatFile.Writing.cs b/SabreTools.DatFiles/DatFile.Writing.cs deleted file mode 100644 index dbf234da..00000000 --- a/SabreTools.DatFiles/DatFile.Writing.cs +++ /dev/null @@ -1,439 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -using SabreTools.Core; -using SabreTools.DatFiles.Reports; -using SabreTools.DatItems; -using SabreTools.IO; - -// This file represents all methods related to writing to a file -namespace SabreTools.DatFiles -{ - public abstract partial class DatFile - { - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Set the output directory (current directory on null) - /// True if files should be overwritten (default), false if they should be renamed instead - /// True if blank roms should be skipped on output, false otherwise (default) - /// True if quotes are assumed in supported types (default), false otherwise - /// 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 bool Write( - string outDir, - bool overwrite = true, - bool ignoreblanks = false, - bool quotes = true, - bool throwOnError = false) - { - // If we have nothing writable, abort - if (!HasWritable()) - { - logger.User("There were no items to write out!"); - return false; - } - - // Ensure the output directory is set and created - outDir = DirectoryExtensions.Ensure(outDir, create: true); - - // If the DAT has no output format, default to XML - if (Header.DatFormat == 0) - { - logger.Verbose("No DAT format defined, defaulting to XML"); - Header.DatFormat = DatFormat.Logiqx; - } - - // Make sure that the three essential fields are filled in - EnsureHeaderFields(); - - // Bucket roms by game name, if not already - Items.BucketBy(Field.Machine_Name, DedupeType.None); - - // Output the number of items we're going to be writing - logger.User($"A total of {Items.TotalCount - Items.RemovedCount} items will be written out to '{Header.FileName}'"); - - // Get the outfile names - Dictionary outfiles = Header.CreateOutFileNames(outDir, overwrite); - - try - { - // Write out all required formats - Parallel.ForEach(outfiles.Keys, Globals.ParallelOptions, datFormat => - { - string outfile = outfiles[datFormat]; - try - { - Create(datFormat, this, quotes)?.WriteToFile(outfile, ignoreblanks, throwOnError); - } - catch (Exception ex) - { - logger.Error(ex, $"Datfile {outfile} could not be written out"); - if (throwOnError) throw ex; - } - - }); - } - catch (Exception ex) - { - logger.Error(ex); - if (throwOnError) throw ex; - return false; - } - - return true; - } - - /// - /// Write the stats out to console for the current DatFile - /// - public void WriteStatsToConsole() - { - if (Items.RomCount + Items.DiskCount == 0) - Items.RecalculateStats(); - - Items.BucketBy(Field.Machine_Name, DedupeType.None, norename: true); - - var consoleOutput = BaseReport.Create(StatReportFormat.None, null, true, true); - consoleOutput.ReplaceStatistics(Header.FileName, Items.Keys.Count(), Items); - } - - /// - /// Create and open an output file for writing direct from a dictionary - /// - /// Name of the file to write to - /// True if blank roms should be skipped on output, false otherwise (default) - /// 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 abstract bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false); - - /// - /// Create a prefix or postfix from inputs - /// - /// DatItem to create a prefix/postfix for - /// True for prefix, false for postfix - /// Sanitized string representing the postfix or prefix - protected string CreatePrefixPostfix(DatItem item, bool prefix) - { - // Initialize strings - string fix = string.Empty, - game = item.Machine.Name, - name = item.GetName() ?? item.ItemType.ToString(), - crc = string.Empty, - md5 = string.Empty, - ripemd160 = string.Empty, - sha1 = string.Empty, - sha256 = string.Empty, - sha384 = string.Empty, - sha512 = string.Empty, - size = string.Empty, - spamsum = string.Empty; - - // If we have a prefix - if (prefix) - fix = Header.Prefix + (Header.Quotes ? "\"" : string.Empty); - - // If we have a postfix - else - fix = (Header.Quotes ? "\"" : string.Empty) + Header.Postfix; - - // Ensure we have the proper values for replacement - if (item.ItemType == ItemType.Disk) - { - md5 = (item as Disk).MD5 ?? string.Empty; - sha1 = (item as Disk).SHA1 ?? string.Empty; - } - else if (item.ItemType == ItemType.Media) - { - md5 = (item as Media).MD5 ?? string.Empty; - sha1 = (item as Media).SHA1 ?? string.Empty; - sha256 = (item as Media).SHA256 ?? string.Empty; - spamsum = (item as Media).SpamSum ?? string.Empty; - } - else if (item.ItemType == ItemType.Rom) - { - crc = (item as Rom).CRC ?? string.Empty; - md5 = (item as Rom).MD5 ?? string.Empty; -#if NET_FRAMEWORK - ripemd160 = (item as Rom).RIPEMD160 ?? string.Empty; -#endif - sha1 = (item as Rom).SHA1 ?? string.Empty; - sha256 = (item as Rom).SHA256 ?? string.Empty; - sha384 = (item as Rom).SHA384 ?? string.Empty; - sha512 = (item as Rom).SHA512 ?? string.Empty; - size = (item as Rom).Size?.ToString() ?? string.Empty; - spamsum = (item as Rom).SpamSum ?? string.Empty; - } - - // Now do bulk replacement where possible - fix = fix - .Replace("%game%", game) - .Replace("%machine%", game) - .Replace("%name%", name) - .Replace("%manufacturer%", item.Machine.Manufacturer ?? string.Empty) - .Replace("%publisher%", item.Machine.Publisher ?? string.Empty) - .Replace("%category%", item.Machine.Category ?? string.Empty) - .Replace("%crc%", crc) - .Replace("%md5%", md5) - .Replace("%ripemd160%", ripemd160) - .Replace("%sha1%", sha1) - .Replace("%sha256%", sha256) - .Replace("%sha384%", sha384) - .Replace("%sha512%", sha512) - .Replace("%size%", size) - .Replace("%spamsum%", spamsum); - - // TODO: Add GameName logic here too? - // TODO: Figure out what I meant by the above ^ - - return fix; - } - - /// - /// Process an item and correctly set the item name - /// - /// DatItem to update - /// True if the Quotes flag should be ignored, false otherwise - /// True if the UseRomName should be always on (default), false otherwise - protected void ProcessItemName(DatItem item, bool forceRemoveQuotes, bool forceRomName = true) - { - string name = item.GetName() ?? string.Empty; - - // Backup relevant values and set new ones accordingly - bool quotesBackup = Header.Quotes; - bool useRomNameBackup = Header.UseRomName; - if (forceRemoveQuotes) - Header.Quotes = false; - - if (forceRomName) - Header.UseRomName = true; - - // Create the proper Prefix and Postfix - string pre = CreatePrefixPostfix(item, true); - string post = CreatePrefixPostfix(item, false); - - // If we're in Depot mode, take care of that instead - if (Header.OutputDepot?.IsActive == true) - { - if (item.ItemType == ItemType.Disk) - { - Disk disk = item as Disk; - - // We can only write out if there's a SHA-1 - if (!string.IsNullOrWhiteSpace(disk.SHA1)) - { - name = PathExtensions.GetDepotPath(disk.SHA1, Header.OutputDepot.Depth).Replace('\\', '/'); - item.SetFields(new Dictionary { [Field.DatItem_Name] = $"{pre}{name}{post}" } ); - } - } - else if (item.ItemType == ItemType.Media) - { - Media media = item as Media; - - // We can only write out if there's a SHA-1 - if (!string.IsNullOrWhiteSpace(media.SHA1)) - { - name = PathExtensions.GetDepotPath(media.SHA1, Header.OutputDepot.Depth).Replace('\\', '/'); - item.SetFields(new Dictionary { [Field.DatItem_Name] = $"{pre}{name}{post}" }); - } - } - else if (item.ItemType == ItemType.Rom) - { - Rom rom = item as Rom; - - // We can only write out if there's a SHA-1 - if (!string.IsNullOrWhiteSpace(rom.SHA1)) - { - name = PathExtensions.GetDepotPath(rom.SHA1, Header.OutputDepot.Depth).Replace('\\', '/'); - item.SetFields(new Dictionary { [Field.DatItem_Name] = $"{pre}{name}{post}" }); - } - } - - return; - } - - if (!string.IsNullOrWhiteSpace(Header.ReplaceExtension) || Header.RemoveExtension) - { - if (Header.RemoveExtension) - Header.ReplaceExtension = string.Empty; - - string dir = Path.GetDirectoryName(name); - dir = dir.TrimStart(Path.DirectorySeparatorChar); - name = Path.Combine(dir, Path.GetFileNameWithoutExtension(name) + Header.ReplaceExtension); - } - - if (!string.IsNullOrWhiteSpace(Header.AddExtension)) - name += Header.AddExtension; - - if (Header.UseRomName && Header.GameName) - name = Path.Combine(item.Machine.Name, name); - - // Now assign back the item name - item.SetFields(new Dictionary { [Field.DatItem_Name] = pre + name + post }); - - // Restore all relevant values - if (forceRemoveQuotes) - Header.Quotes = quotesBackup; - - if (forceRomName) - Header.UseRomName = useRomNameBackup; - } - - /// - /// Process any DatItems that are "null", usually created from directory population - /// - /// DatItem to check for "null" status - /// Cleaned DatItem - protected DatItem ProcessNullifiedItem(DatItem datItem) - { - // If we don't have a Rom, we can ignore it - if (datItem.ItemType != ItemType.Rom) - return datItem; - - // Cast for easier parsing - Rom rom = datItem as Rom; - - // If the Rom has "null" characteristics, ensure all fields - if (rom.Size == null && rom.CRC == "null") - { - logger.Verbose($"Empty folder found: {datItem.Machine.Name}"); - - rom.Name = (rom.Name == "null" ? "-" : rom.Name); - rom.Size = Constants.SizeZero; - rom.CRC = rom.CRC == "null" ? Constants.CRCZero : null; - rom.MD5 = rom.MD5 == "null" ? Constants.MD5Zero : null; -#if NET_FRAMEWORK - rom.RIPEMD160 = rom.RIPEMD160 == "null" ? Constants.RIPEMD160Zero : null; -#endif - rom.SHA1 = rom.SHA1 == "null" ? Constants.SHA1Zero : null; - rom.SHA256 = rom.SHA256 == "null" ? Constants.SHA256Zero : null; - rom.SHA384 = rom.SHA384 == "null" ? Constants.SHA384Zero : null; - rom.SHA512 = rom.SHA512 == "null" ? Constants.SHA512Zero : null; - rom.SpamSum = rom.SpamSum == "null" ? Constants.SpamSumZero : null; - } - - return rom; - } - - /// - /// Get supported types for write - /// - /// List of supported types for writing - protected virtual ItemType[] GetSupportedTypes() - { - return Enum.GetValues(typeof(ItemType)) as ItemType[]; - } - - /// - /// Get if a machine contains any writable items - /// - /// DatItems to check - /// True if the machine contains at least one writable item, false otherwise - /// Empty machines are kept with this - protected bool ContainsWritable(List datItems) - { - // Empty machines are considered writable - if (datItems == null || datItems.Count == 0) - return true; - - foreach (DatItem datItem in datItems) - { - if (GetSupportedTypes().Contains(datItem.ItemType)) - return true; - } - - return false; - } - - /// - /// Get if an item should be ignored on write - /// - /// DatItem to check - /// True if blank roms should be skipped on output, false otherwise - /// True if the item should be skipped on write, false otherwise - protected bool ShouldIgnore(DatItem datItem, bool ignoreBlanks) - { - // If the item is supposed to be removed, we ignore - if (datItem.Remove) - return true; - - // If we have the Blank dat item, we ignore - if (datItem.ItemType == ItemType.Blank) - return true; - - // If we're ignoring blanks and we have a Rom - if (ignoreBlanks && datItem.ItemType == ItemType.Rom) - { - Rom rom = datItem as Rom; - - // If we have a 0-size or blank rom, then we ignore - if (rom.Size == 0 || rom.Size == null) - return true; - } - - // If we have an item type not in the list of supported values - if (!GetSupportedTypes().Contains(datItem.ItemType)) - return true; - - return false; - } - - /// - /// Ensure that FileName, Name, and Description are filled with some value - /// - private void EnsureHeaderFields() - { - // Empty FileName - if (string.IsNullOrWhiteSpace(Header.FileName)) - { - if (string.IsNullOrWhiteSpace(Header.Name) && string.IsNullOrWhiteSpace(Header.Description)) - Header.FileName = Header.Name = Header.Description = "Default"; - - else if (string.IsNullOrWhiteSpace(Header.Name) && !string.IsNullOrWhiteSpace(Header.Description)) - Header.FileName = Header.Name = Header.Description; - - else if (!string.IsNullOrWhiteSpace(Header.Name) && string.IsNullOrWhiteSpace(Header.Description)) - Header.FileName = Header.Description = Header.Name; - - else if (!string.IsNullOrWhiteSpace(Header.Name) && !string.IsNullOrWhiteSpace(Header.Description)) - Header.FileName = Header.Description; - } - - // Filled FileName - else - { - if (string.IsNullOrWhiteSpace(Header.Name) && string.IsNullOrWhiteSpace(Header.Description)) - Header.Name = Header.Description = Header.FileName; - - else if (string.IsNullOrWhiteSpace(Header.Name) && !string.IsNullOrWhiteSpace(Header.Description)) - Header.Name = Header.Description; - - else if (!string.IsNullOrWhiteSpace(Header.Name) && string.IsNullOrWhiteSpace(Header.Description)) - Header.Description = Header.Name; - } - } - - /// - /// Get if the DatFile has any writable items - /// - /// True if there are any writable items, false otherwise - private bool HasWritable() - { - // Force a statistics recheck, just in case - Items.RecalculateStats(); - - // If there's nothing there, abort - if (Items.TotalCount == 0) - return false; - - // If every item is removed, abort - if (Items.TotalCount == Items.RemovedCount) - return false; - - return true; - } - } -} \ No newline at end of file diff --git a/SabreTools.DatFiles/DatFile.cs b/SabreTools.DatFiles/DatFile.cs index 844fe402..94579196 100644 --- a/SabreTools.DatFiles/DatFile.cs +++ b/SabreTools.DatFiles/DatFile.cs @@ -1,11 +1,14 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Serialization; using SabreTools.Core; using SabreTools.DatFiles.Formats; +using SabreTools.DatFiles.Reports; using SabreTools.DatItems; +using SabreTools.IO; using SabreTools.Logging; using Newtonsoft.Json; @@ -86,7 +89,7 @@ namespace SabreTools.DatFiles return new ClrMamePro(baseDat, quotes); case DatFormat.CSV: - return new SeparatedValue(baseDat, ','); + return new Formats.SeparatedValue(baseDat, ','); case DatFormat.DOSCenter: return new DosCenter(baseDat); @@ -154,10 +157,10 @@ namespace SabreTools.DatFiles return new Formats.SoftwareList(baseDat); case DatFormat.SSV: - return new SeparatedValue(baseDat, ';'); + return new Formats.SeparatedValue(baseDat, ';'); case DatFormat.TSV: - return new SeparatedValue(baseDat, '\t'); + return new Formats.SeparatedValue(baseDat, '\t'); // We use new-style Logiqx as a backup for generic DatFile case null: @@ -375,5 +378,303 @@ namespace SabreTools.DatFiles #endregion #endregion + + #region Writing + + /// + /// Write the stats out to console for the current DatFile + /// + public void WriteStatsToConsole() + { + if (Items.RomCount + Items.DiskCount == 0) + Items.RecalculateStats(); + + Items.BucketBy(Field.Machine_Name, DedupeType.None, norename: true); + + var consoleOutput = BaseReport.Create(StatReportFormat.None, null, true, true); + consoleOutput.ReplaceStatistics(Header.FileName, Items.Keys.Count(), Items); + } + + /// + /// Create and open an output file for writing direct from a dictionary + /// + /// Name of the file to write to + /// True if blank roms should be skipped on output, false otherwise (default) + /// 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 abstract bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false); + + /// + /// Create a prefix or postfix from inputs + /// + /// DatItem to create a prefix/postfix for + /// True for prefix, false for postfix + /// Sanitized string representing the postfix or prefix + protected string CreatePrefixPostfix(DatItem item, bool prefix) + { + // Initialize strings + string fix = string.Empty, + game = item.Machine.Name, + name = item.GetName() ?? item.ItemType.ToString(), + crc = string.Empty, + md5 = string.Empty, + ripemd160 = string.Empty, + sha1 = string.Empty, + sha256 = string.Empty, + sha384 = string.Empty, + sha512 = string.Empty, + size = string.Empty, + spamsum = string.Empty; + + // If we have a prefix + if (prefix) + fix = Header.Prefix + (Header.Quotes ? "\"" : string.Empty); + + // If we have a postfix + else + fix = (Header.Quotes ? "\"" : string.Empty) + Header.Postfix; + + // Ensure we have the proper values for replacement + if (item.ItemType == ItemType.Disk) + { + md5 = (item as Disk).MD5 ?? string.Empty; + sha1 = (item as Disk).SHA1 ?? string.Empty; + } + else if (item.ItemType == ItemType.Media) + { + md5 = (item as Media).MD5 ?? string.Empty; + sha1 = (item as Media).SHA1 ?? string.Empty; + sha256 = (item as Media).SHA256 ?? string.Empty; + spamsum = (item as Media).SpamSum ?? string.Empty; + } + else if (item.ItemType == ItemType.Rom) + { + crc = (item as Rom).CRC ?? string.Empty; + md5 = (item as Rom).MD5 ?? string.Empty; +#if NET_FRAMEWORK + ripemd160 = (item as Rom).RIPEMD160 ?? string.Empty; +#endif + sha1 = (item as Rom).SHA1 ?? string.Empty; + sha256 = (item as Rom).SHA256 ?? string.Empty; + sha384 = (item as Rom).SHA384 ?? string.Empty; + sha512 = (item as Rom).SHA512 ?? string.Empty; + size = (item as Rom).Size?.ToString() ?? string.Empty; + spamsum = (item as Rom).SpamSum ?? string.Empty; + } + + // Now do bulk replacement where possible + fix = fix + .Replace("%game%", game) + .Replace("%machine%", game) + .Replace("%name%", name) + .Replace("%manufacturer%", item.Machine.Manufacturer ?? string.Empty) + .Replace("%publisher%", item.Machine.Publisher ?? string.Empty) + .Replace("%category%", item.Machine.Category ?? string.Empty) + .Replace("%crc%", crc) + .Replace("%md5%", md5) + .Replace("%ripemd160%", ripemd160) + .Replace("%sha1%", sha1) + .Replace("%sha256%", sha256) + .Replace("%sha384%", sha384) + .Replace("%sha512%", sha512) + .Replace("%size%", size) + .Replace("%spamsum%", spamsum); + + // TODO: Add GameName logic here too? + // TODO: Figure out what I meant by the above ^ + + return fix; + } + + /// + /// Process an item and correctly set the item name + /// + /// DatItem to update + /// True if the Quotes flag should be ignored, false otherwise + /// True if the UseRomName should be always on (default), false otherwise + protected void ProcessItemName(DatItem item, bool forceRemoveQuotes, bool forceRomName = true) + { + string name = item.GetName() ?? string.Empty; + + // Backup relevant values and set new ones accordingly + bool quotesBackup = Header.Quotes; + bool useRomNameBackup = Header.UseRomName; + if (forceRemoveQuotes) + Header.Quotes = false; + + if (forceRomName) + Header.UseRomName = true; + + // Create the proper Prefix and Postfix + string pre = CreatePrefixPostfix(item, true); + string post = CreatePrefixPostfix(item, false); + + // If we're in Depot mode, take care of that instead + if (Header.OutputDepot?.IsActive == true) + { + if (item.ItemType == ItemType.Disk) + { + Disk disk = item as Disk; + + // We can only write out if there's a SHA-1 + if (!string.IsNullOrWhiteSpace(disk.SHA1)) + { + name = PathExtensions.GetDepotPath(disk.SHA1, Header.OutputDepot.Depth).Replace('\\', '/'); + item.SetFields(new Dictionary { [Field.DatItem_Name] = $"{pre}{name}{post}" } ); + } + } + else if (item.ItemType == ItemType.Media) + { + Media media = item as Media; + + // We can only write out if there's a SHA-1 + if (!string.IsNullOrWhiteSpace(media.SHA1)) + { + name = PathExtensions.GetDepotPath(media.SHA1, Header.OutputDepot.Depth).Replace('\\', '/'); + item.SetFields(new Dictionary { [Field.DatItem_Name] = $"{pre}{name}{post}" }); + } + } + else if (item.ItemType == ItemType.Rom) + { + Rom rom = item as Rom; + + // We can only write out if there's a SHA-1 + if (!string.IsNullOrWhiteSpace(rom.SHA1)) + { + name = PathExtensions.GetDepotPath(rom.SHA1, Header.OutputDepot.Depth).Replace('\\', '/'); + item.SetFields(new Dictionary { [Field.DatItem_Name] = $"{pre}{name}{post}" }); + } + } + + return; + } + + if (!string.IsNullOrWhiteSpace(Header.ReplaceExtension) || Header.RemoveExtension) + { + if (Header.RemoveExtension) + Header.ReplaceExtension = string.Empty; + + string dir = Path.GetDirectoryName(name); + dir = dir.TrimStart(Path.DirectorySeparatorChar); + name = Path.Combine(dir, Path.GetFileNameWithoutExtension(name) + Header.ReplaceExtension); + } + + if (!string.IsNullOrWhiteSpace(Header.AddExtension)) + name += Header.AddExtension; + + if (Header.UseRomName && Header.GameName) + name = Path.Combine(item.Machine.Name, name); + + // Now assign back the item name + item.SetFields(new Dictionary { [Field.DatItem_Name] = pre + name + post }); + + // Restore all relevant values + if (forceRemoveQuotes) + Header.Quotes = quotesBackup; + + if (forceRomName) + Header.UseRomName = useRomNameBackup; + } + + /// + /// Process any DatItems that are "null", usually created from directory population + /// + /// DatItem to check for "null" status + /// Cleaned DatItem + protected DatItem ProcessNullifiedItem(DatItem datItem) + { + // If we don't have a Rom, we can ignore it + if (datItem.ItemType != ItemType.Rom) + return datItem; + + // Cast for easier parsing + Rom rom = datItem as Rom; + + // If the Rom has "null" characteristics, ensure all fields + if (rom.Size == null && rom.CRC == "null") + { + logger.Verbose($"Empty folder found: {datItem.Machine.Name}"); + + rom.Name = (rom.Name == "null" ? "-" : rom.Name); + rom.Size = Constants.SizeZero; + rom.CRC = rom.CRC == "null" ? Constants.CRCZero : null; + rom.MD5 = rom.MD5 == "null" ? Constants.MD5Zero : null; +#if NET_FRAMEWORK + rom.RIPEMD160 = rom.RIPEMD160 == "null" ? Constants.RIPEMD160Zero : null; +#endif + rom.SHA1 = rom.SHA1 == "null" ? Constants.SHA1Zero : null; + rom.SHA256 = rom.SHA256 == "null" ? Constants.SHA256Zero : null; + rom.SHA384 = rom.SHA384 == "null" ? Constants.SHA384Zero : null; + rom.SHA512 = rom.SHA512 == "null" ? Constants.SHA512Zero : null; + rom.SpamSum = rom.SpamSum == "null" ? Constants.SpamSumZero : null; + } + + return rom; + } + + /// + /// Get supported types for write + /// + /// List of supported types for writing + protected virtual ItemType[] GetSupportedTypes() + { + return Enum.GetValues(typeof(ItemType)) as ItemType[]; + } + + /// + /// Get if a machine contains any writable items + /// + /// DatItems to check + /// True if the machine contains at least one writable item, false otherwise + /// Empty machines are kept with this + protected bool ContainsWritable(List datItems) + { + // Empty machines are considered writable + if (datItems == null || datItems.Count == 0) + return true; + + foreach (DatItem datItem in datItems) + { + if (GetSupportedTypes().Contains(datItem.ItemType)) + return true; + } + + return false; + } + + /// + /// Get if an item should be ignored on write + /// + /// DatItem to check + /// True if blank roms should be skipped on output, false otherwise + /// True if the item should be skipped on write, false otherwise + protected bool ShouldIgnore(DatItem datItem, bool ignoreBlanks) + { + // If the item is supposed to be removed, we ignore + if (datItem.Remove) + return true; + + // If we have the Blank dat item, we ignore + if (datItem.ItemType == ItemType.Blank) + return true; + + // If we're ignoring blanks and we have a Rom + if (ignoreBlanks && datItem.ItemType == ItemType.Rom) + { + Rom rom = datItem as Rom; + + // If we have a 0-size or blank rom, then we ignore + if (rom.Size == 0 || rom.Size == null) + return true; + } + + // If we have an item type not in the list of supported values + if (!GetSupportedTypes().Contains(datItem.ItemType)) + return true; + + return false; + } + + #endregion } } diff --git a/SabreTools.DatFiles/DatTool.Verifying.cs b/SabreTools.DatFiles/DatTool.Verifying.cs index 1aaf79e3..1567136b 100644 --- a/SabreTools.DatFiles/DatTool.Verifying.cs +++ b/SabreTools.DatFiles/DatTool.Verifying.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using SabreTools.Core; +using SabreTools.DatFiles.Reports; using SabreTools.DatItems; using SabreTools.FileTypes; using SabreTools.IO; diff --git a/SabreTools.DatFiles/DatTool.Writing.cs b/SabreTools.DatFiles/DatTool.Writing.cs new file mode 100644 index 00000000..e6b500a7 --- /dev/null +++ b/SabreTools.DatFiles/DatTool.Writing.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using SabreTools.Core; +using SabreTools.IO; + +// This file represents all methods related to writing to a file +namespace SabreTools.DatFiles +{ + // TODO: Re-evaluate if these should be made static instead of instanced + public partial class DatTool + { + /// + /// 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 (default), false if they should be renamed instead + /// True if blank roms should be skipped on output, false otherwise (default) + /// True if quotes are assumed in supported types (default), false otherwise + /// 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 bool Write( + DatFile datFile, + string outDir, + bool overwrite = true, + bool ignoreblanks = false, + bool quotes = true, + bool throwOnError = false) + { + // If we have nothing writable, abort + if (!HasWritable(datFile)) + { + logger.User("There were no items to write out!"); + return false; + } + + // Ensure the output directory is set and created + outDir = DirectoryExtensions.Ensure(outDir, create: true); + + // If the DAT has no output format, default to XML + if (datFile.Header.DatFormat == 0) + { + logger.Verbose("No DAT format defined, defaulting to XML"); + datFile.Header.DatFormat = DatFormat.Logiqx; + } + + // Make sure that the three essential fields are filled in + EnsureHeaderFields(); + + // Bucket roms by game name, if not already + datFile.Items.BucketBy(Field.Machine_Name, DedupeType.None); + + // Output the number of items we're going to be writing + logger.User($"A total of {datFile.Items.TotalCount - datFile.Items.RemovedCount} items will be written out to '{datFile.Header.FileName}'"); + + // Get the outfile names + Dictionary outfiles = datFile.Header.CreateOutFileNames(outDir, overwrite); + + try + { + // Write out all required formats + Parallel.ForEach(outfiles.Keys, Globals.ParallelOptions, datFormat => + { + string outfile = outfiles[datFormat]; + try + { + DatFile.Create(datFormat, datFile, quotes)?.WriteToFile(outfile, ignoreblanks, throwOnError); + } + catch (Exception ex) + { + logger.Error(ex, $"Datfile {outfile} could not be written out"); + if (throwOnError) throw ex; + } + + }); + } + catch (Exception ex) + { + logger.Error(ex); + if (throwOnError) throw ex; + return false; + } + + return true; + } + + /// + /// Ensure that FileName, Name, and Description are filled with some value + /// + /// Current DatFile object to write from + private void EnsureHeaderFields(DatFile datFile) + { + // Empty FileName + if (string.IsNullOrWhiteSpace(datFile.Header.FileName)) + { + if (string.IsNullOrWhiteSpace(datFile.Header.Name) && string.IsNullOrWhiteSpace(datFile.Header.Description)) + datFile.Header.FileName = datFile.Header.Name = datFile.Header.Description = "Default"; + + else if (string.IsNullOrWhiteSpace(datFile.Header.Name) && !string.IsNullOrWhiteSpace(datFile.Header.Description)) + datFile.Header.FileName = datFile.Header.Name = datFile.Header.Description; + + else if (!string.IsNullOrWhiteSpace(datFile.Header.Name) && string.IsNullOrWhiteSpace(datFile.Header.Description)) + datFile.Header.FileName = datFile.Header.Description = datFile.Header.Name; + + else if (!string.IsNullOrWhiteSpace(datFile.Header.Name) && !string.IsNullOrWhiteSpace(datFile.Header.Description)) + datFile.Header.FileName = datFile.Header.Description; + } + + // Filled FileName + else + { + if (string.IsNullOrWhiteSpace(datFile.Header.Name) && string.IsNullOrWhiteSpace(datFile.Header.Description)) + datFile.Header.Name = datFile.Header.Description = datFile.Header.FileName; + + else if (string.IsNullOrWhiteSpace(datFile.Header.Name) && !string.IsNullOrWhiteSpace(datFile.Header.Description)) + datFile.Header.Name = datFile.Header.Description; + + else if (!string.IsNullOrWhiteSpace(datFile.Header.Name) && string.IsNullOrWhiteSpace(datFile.Header.Description)) + datFile.Header.Description = datFile.Header.Name; + } + } + + /// + /// Get if the DatFile has any writable items + /// + /// Current DatFile object to write from + /// True if there are any writable items, false otherwise + private bool HasWritable(DatFile datFile) + { + // Force a statistics recheck, just in case + datFile.Items.RecalculateStats(); + + // If there's nothing there, abort + if (datFile.Items.TotalCount == 0) + return false; + + // If every item is removed, abort + if (datFile.Items.TotalCount == datFile.Items.RemovedCount) + return false; + + return true; + } + } +} \ No newline at end of file diff --git a/SabreTools/Features/Batch.cs b/SabreTools/Features/Batch.cs index 2d619c18..5be28f12 100644 --- a/SabreTools/Features/Batch.cs +++ b/SabreTools/Features/Batch.cs @@ -406,7 +406,7 @@ Reset the internal state: reset();"; } // Write out the dat with the current state - datFile.Write(outputDirectory, overwrite: overwrite.Value); + dt.Write(datFile, outputDirectory, overwrite: overwrite.Value); break; // Reset the internal state diff --git a/SabreTools/Features/DatFromDir.cs b/SabreTools/Features/DatFromDir.cs index 4856aacb..5b5ec253 100644 --- a/SabreTools/Features/DatFromDir.cs +++ b/SabreTools/Features/DatFromDir.cs @@ -107,7 +107,7 @@ namespace SabreTools.Features datdata.ApplyCleaning(Cleaner); // Write out the file - datdata.Write(OutputDir); + dt.Write(datdata, OutputDir); } else { diff --git a/SabreTools/Features/Sort.cs b/SabreTools/Features/Sort.cs index ade5e9aa..06a8dd9c 100644 --- a/SabreTools/Features/Sort.cs +++ b/SabreTools/Features/Sort.cs @@ -117,7 +117,7 @@ namespace SabreTools.Features datdata.Header.Name = $"fixDAT_{Header.Name}"; datdata.Header.Description = $"fixDAT_{Header.Description}"; datdata.Items.ClearMarked(); - datdata.Write(OutputDir); + dt.Write(datdata, OutputDir); } } } @@ -158,7 +158,7 @@ namespace SabreTools.Features datdata.Header.Name = $"fixDAT_{Header.Name}"; datdata.Header.Description = $"fixDAT_{Header.Description}"; datdata.Items.ClearMarked(); - datdata.Write(OutputDir); + dt.Write(datdata, OutputDir); } } } diff --git a/SabreTools/Features/Split.cs b/SabreTools/Features/Split.cs index 37f73f15..0dcc32d7 100644 --- a/SabreTools/Features/Split.cs +++ b/SabreTools/Features/Split.cs @@ -71,8 +71,8 @@ namespace SabreTools.Features InternalStopwatch watch = new InternalStopwatch("Outputting extension-split DATs"); // Output both possible DatFiles - extADat.Write(OutputDir); - extBDat.Write(OutputDir); + dt.Write(extADat, OutputDir); + dt.Write(extBDat, OutputDir); watch.Stop(); } @@ -87,7 +87,7 @@ namespace SabreTools.Features // Loop through each type DatFile Parallel.ForEach(typeDats.Keys, Globals.ParallelOptions, itemType => { - typeDats[itemType].Write(OutputDir); + dt.Write(typeDats[itemType], OutputDir); }); watch.Stop(); @@ -111,8 +111,8 @@ namespace SabreTools.Features InternalStopwatch watch = new InternalStopwatch("Outputting size-split DATs"); // Output both possible DatFiles - lessThan.Write(OutputDir); - greaterThan.Write(OutputDir); + dt.Write(lessThan, OutputDir); + dt.Write(greaterThan, OutputDir); watch.Stop(); } @@ -127,7 +127,7 @@ namespace SabreTools.Features // Loop through each type DatFile Parallel.ForEach(typeDats.Keys, Globals.ParallelOptions, itemType => { - typeDats[itemType].Write(OutputDir); + dt.Write(typeDats[itemType], OutputDir); }); watch.Stop(); diff --git a/SabreTools/Features/Update.cs b/SabreTools/Features/Update.cs index 37ecff41..a19fc349 100644 --- a/SabreTools/Features/Update.cs +++ b/SabreTools/Features/Update.cs @@ -179,7 +179,7 @@ namespace SabreTools.Features string realOutDir = inputPath.GetOutputPath(OutputDir, GetBoolean(features, InplaceValue)); // Try to output the file, overwriting only if it's not in the current directory - datFile.Write(realOutDir, overwrite: GetBoolean(features, InplaceValue)); + dt.Write(datFile, realOutDir, overwrite: GetBoolean(features, InplaceValue)); }); return; @@ -219,7 +219,7 @@ namespace SabreTools.Features DatFile dupeData = userInputDat.DiffDuplicates(inputPaths); InternalStopwatch watch = new InternalStopwatch("Outputting duplicate DAT"); - dupeData.Write(OutputDir, overwrite: false); + dt.Write(dupeData, OutputDir, overwrite: false); watch.Stop(); } @@ -229,7 +229,7 @@ namespace SabreTools.Features DatFile outerDiffData = userInputDat.DiffNoDuplicates(inputPaths); InternalStopwatch watch = new InternalStopwatch("Outputting no duplicate DAT"); - outerDiffData.Write(OutputDir, overwrite: false); + dt.Write(outerDiffData, OutputDir, overwrite: false); watch.Stop(); } @@ -247,7 +247,7 @@ namespace SabreTools.Features string path = inputPaths[j].GetOutputPath(OutputDir, GetBoolean(features, InplaceValue)); // Try to output the file - datFiles[j].Write(path, overwrite: GetBoolean(features, InplaceValue)); + dt.Write(datFiles[j], path, overwrite: GetBoolean(features, InplaceValue)); }); watch.Stop(); @@ -283,7 +283,7 @@ namespace SabreTools.Features string path = inputPaths[j].GetOutputPath(OutputDir, GetBoolean(features, InplaceValue)); // Try to output the file - datFiles[j].Write(path, overwrite: GetBoolean(features, InplaceValue)); + dt.Write(datFiles[j], path, overwrite: GetBoolean(features, InplaceValue)); }); watch.Stop(); @@ -310,7 +310,7 @@ namespace SabreTools.Features // Finally output the diffed DatFile string interOutDir = inputPath.GetOutputPath(OutputDir, GetBoolean(features, InplaceValue)); - repDat.Write(interOutDir, overwrite: GetBoolean(features, InplaceValue)); + dt.Write(repDat, interOutDir, overwrite: GetBoolean(features, InplaceValue)); }); } @@ -335,7 +335,7 @@ namespace SabreTools.Features // Finally output the replaced DatFile string interOutDir = inputPath.GetOutputPath(OutputDir, GetBoolean(features, InplaceValue)); - repDat.Write(interOutDir, overwrite: GetBoolean(features, InplaceValue)); + dt.Write(repDat, interOutDir, overwrite: GetBoolean(features, InplaceValue)); }); } @@ -347,7 +347,7 @@ namespace SabreTools.Features if (string.Equals(userInputDat.Header.Type, "SuperDAT", StringComparison.OrdinalIgnoreCase)) userInputDat.ApplySuperDAT(inputPaths); - userInputDat.Write(OutputDir); + dt.Write(userInputDat, OutputDir); } } } diff --git a/SabreTools/Features/Verify.cs b/SabreTools/Features/Verify.cs index 68d712d3..3b5987d1 100644 --- a/SabreTools/Features/Verify.cs +++ b/SabreTools/Features/Verify.cs @@ -95,7 +95,7 @@ namespace SabreTools.Features // Now write out if there are any items left datdata.WriteStatsToConsole(); - datdata.Write(OutputDir); + dt.Write(datdata, OutputDir); } } // Otherwise, process all DATs into the same output @@ -144,7 +144,7 @@ namespace SabreTools.Features // Now write out if there are any items left datdata.WriteStatsToConsole(); - datdata.Write(OutputDir); + dt.Write(datdata, OutputDir); } } }