diff --git a/SabreTools.DatFiles/Formats/DosCenter.Reader.cs b/SabreTools.DatFiles/Formats/DosCenter.Reader.cs new file mode 100644 index 00000000..2ef61262 --- /dev/null +++ b/SabreTools.DatFiles/Formats/DosCenter.Reader.cs @@ -0,0 +1,163 @@ +using System; +using System.Linq; +using SabreTools.Core.Tools; +using SabreTools.DatItems; +using SabreTools.DatItems.Formats; + +namespace SabreTools.DatFiles.Formats +{ + /// + /// Represents parsing and writing of a DosCenter DAT + /// + internal partial class DosCenter : DatFile + { + /// + public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false) + { + try + { + // Deserialize the input file + var metadataFile = Serialization.DosCenter.Deserialize(filename); + + // Convert the header to the internal format + ConvertHeader(metadataFile?.DosCenter, keep); + + // Convert the game data to the internal format + ConvertGames(metadataFile?.Game, filename, indexId, statsOnly); + } + catch (Exception ex) when (!throwOnError) + { + string message = $"'{filename}' - An error occurred during parsing"; + logger.Error(ex, message); + } + } + + #region Converters + + /// + /// Convert header information + /// + /// Deserialized model to convert + /// True if full pathnames are to be kept, false otherwise (default) + private void ConvertHeader(Models.DosCenter.DosCenter? doscenter, bool keep) + { + // If the header is missing, we can't do anything + if (doscenter == null) + return; + + Header.Name ??= doscenter.Name; + Header.Description ??= doscenter.Description; + Header.Version ??= doscenter.Version; + Header.Date ??= doscenter.Date; + Header.Author ??= doscenter.Author; + Header.Homepage ??= doscenter.Homepage; + Header.Comment ??= doscenter.Comment; + + // Handle implied SuperDAT + if (doscenter.Name.Contains(" - SuperDAT") && keep) + Header.Type ??= "SuperDAT"; + } + + /// + /// Convert games information + /// + /// Array of deserialized models to convert + /// Name of the file to be parsed + /// Index ID for the DAT + /// True to only add item statistics while parsing, false otherwise + private void ConvertGames(Models.DosCenter.Game[]? games, string filename, int indexId, bool statsOnly) + { + // If the game array is missing, we can't do anything + if (games == null || !games.Any()) + return; + + // Loop through the games and add + foreach (var game in games) + { + ConvertGame(game, filename, indexId, statsOnly); + } + } + + /// + /// Convert game information + /// + /// Deserialized model to convert + /// Name of the file to be parsed + /// Index ID for the DAT + /// True to only add item statistics while parsing, false otherwise + private void ConvertGame(Models.DosCenter.Game game, string filename, int indexId, bool statsOnly) + { + // If the game is missing, we can't do anything + if (game == null) + return; + + // Create the machine for copying information + string machineName = game.Name.Trim('"'); + if (machineName.EndsWith(".zip")) + machineName = System.IO.Path.GetFileNameWithoutExtension(machineName); + + var machine = new Machine { Name = machineName }; + + // Check if there are any items + bool containsItems = false; + + // Loop through each type of item + ConvertFiles(game.File, machine, filename, indexId, statsOnly, ref containsItems); + + // If we had no items, create a Blank placeholder + if (!containsItems) + { + var blank = new Blank + { + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + blank.CopyMachineInformation(machine); + ParseAddHelper(blank, statsOnly); + } + } + + /// + /// Convert Rom information + /// + /// Array of deserialized models to convert + /// Prefilled machine to use + /// Name of the file to be parsed + /// Index ID for the DAT + /// True to only add item statistics while parsing, false otherwise + /// True if there were any items in the array, false otherwise + private void ConvertFiles(Models.DosCenter.File[]? files, Machine machine, string filename, int indexId, bool statsOnly, ref bool containsItems) + { + // If the files array is missing, we can't do anything + if (files == null || !files.Any()) + return; + + containsItems = true; + foreach (var rom in files) + { + var item = new Rom + { + Name = rom.Name, + Size = Utilities.CleanLong(rom.Size), + CRC = rom.CRC, + Date = rom.Date, + + Source = new Source + { + Index = indexId, + Name = filename, + }, + }; + + item.CopyMachineInformation(machine); + ParseAddHelper(item, statsOnly); + } + } + + #endregion + } +} diff --git a/SabreTools.DatFiles/Formats/DosCenter.Writer.cs b/SabreTools.DatFiles/Formats/DosCenter.Writer.cs new file mode 100644 index 00000000..b787f2f0 --- /dev/null +++ b/SabreTools.DatFiles/Formats/DosCenter.Writer.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SabreTools.Core; +using SabreTools.DatItems; +using SabreTools.DatItems.Formats; + +namespace SabreTools.DatFiles.Formats +{ + /// + /// Represents parsing and writing of a DosCenter DAT + /// + internal partial class DosCenter : DatFile + { + /// + protected override ItemType[] GetSupportedTypes() + { + return new ItemType[] + { + ItemType.Rom + }; + } + + /// + protected override List GetMissingRequiredFields(DatItem datItem) + { + List missingFields = new(); + + // Check item name + if (string.IsNullOrWhiteSpace(datItem.GetName())) + missingFields.Add(DatItemField.Name); + + switch (datItem) + { + case Rom rom: + if (!rom.SizeSpecified) + missingFields.Add(DatItemField.Size); + // if (string.IsNullOrWhiteSpace(rom.Date)) + // missingFields.Add(DatItemField.Date); + if (string.IsNullOrWhiteSpace(rom.CRC)) + missingFields.Add(DatItemField.CRC); + break; + } + + return missingFields; + } + + /// + public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false) + { + try + { + logger.User($"Writing to '{outfile}'..."); + + var metadataFile = CreateMetadataFile(ignoreblanks); + if (!Serialization.DosCenter.SerializeToFile(metadataFile, outfile)) + { + logger.Warning($"File '{outfile}' could not be written! See the log for more details."); + return false; + } + } + catch (Exception ex) when (!throwOnError) + { + logger.Error(ex); + return false; + } + + return true; + } + + #region Converters + + /// + /// Create a MetadataFile from the current internal information + /// + /// True if blank roms should be skipped on output, false otherwise + private Models.DosCenter.MetadataFile CreateMetadataFile(bool ignoreblanks) + { + var metadataFile = new Models.DosCenter.MetadataFile + { + DosCenter = CreateDosCenter(), + Game = CreateGames(ignoreblanks) + }; + return metadataFile; + } + + /// + /// Create a DosCenter from the current internal information + /// + private Models.DosCenter.DosCenter? CreateDosCenter() + { + // If we don't have a header, we can't do anything + if (this.Header == null) + return null; + + var clrMamePro = new Models.DosCenter.DosCenter + { + Name = Header.Name, + Description = Header.Description, + Version = Header.Version, + Date = Header.Date, + Author = Header.Author, + Homepage = Header.Homepage, + Comment = Header.Comment, + }; + + return clrMamePro; + } + + /// + /// Create an array of GameBase from the current internal information + /// + /// True if blank roms should be skipped on output, false otherwise + private Models.DosCenter.Game[]? CreateGames(bool ignoreblanks) + { + // If we don't have items, we can't do anything + if (this.Items == null || !this.Items.Any()) + return null; + + // Create a list of hold the games + var games = new List(); + + // Loop through the sorted items and create games for them + foreach (string key in Items.SortedKeys) + { + var items = Items.FilteredItems(key); + if (items == null || !items.Any()) + continue; + + // Get the first item for game information + var machine = items[0].Machine; + + // We re-add the missing parts of the game name + var game = new Models.DosCenter.Game + { + Name = $"\"{machine.Name}.zip\"" + }; + + // Create holders for all item types + var files = new List(); + + // Loop through and convert the items to respective lists + foreach (var item in items) + { + // Skip if we're ignoring the item + if (ShouldIgnore(item, ignoreblanks)) + continue; + + switch (item) + { + case Rom rom: + files.Add(CreateFile(rom)); + break; + } + } + + // Assign the values to the game + game.File = files.ToArray(); + + // Add the game to the list + games.Add(game); + } + + return games.ToArray(); + } + + /// + /// Create a File from the current Rom DatItem + /// + private static Models.DosCenter.File CreateFile(Rom item) + { + var rom = new Models.DosCenter.File + { + Name = item.Name, + Size = item.Size?.ToString(), + CRC = item.CRC, + Date = item.Date, + }; + return rom; + } + + #endregion + } +} diff --git a/SabreTools.DatFiles/Formats/DosCenter.cs b/SabreTools.DatFiles/Formats/DosCenter.cs index f3033d43..380a619e 100644 --- a/SabreTools.DatFiles/Formats/DosCenter.cs +++ b/SabreTools.DatFiles/Formats/DosCenter.cs @@ -1,22 +1,9 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using SabreTools.Core; -using SabreTools.Core.Tools; -using SabreTools.DatItems; -using SabreTools.DatItems.Formats; -using SabreTools.IO; -using SabreTools.IO.Readers; -using SabreTools.IO.Writers; - -namespace SabreTools.DatFiles.Formats +namespace SabreTools.DatFiles.Formats { /// - /// Represents parsing and writing of a DosCenter DAT + /// Represents a DosCenter DAT /// - internal class DosCenter : DatFile + internal partial class DosCenter : DatFile { /// /// Constructor designed for casting a base DatFile @@ -26,414 +13,5 @@ namespace SabreTools.DatFiles.Formats : base(datFile) { } - - /// - public override void ParseFile(string filename, int indexId, bool keep, bool statsOnly = false, bool throwOnError = false) - { - // Open a file reader - Encoding enc = filename.GetEncoding(); - ClrMameProReader cmpr = new(System.IO.File.OpenRead(filename), enc) - { - DosCenter = true - }; - - while (!cmpr.EndOfStream) - { - try - { - cmpr.ReadNextLine(); - - // Ignore everything not top-level - if (cmpr.RowType != CmpRowType.TopLevel) - continue; - - // Switch on the top-level name - switch (cmpr.TopLevel.ToLowerInvariant()) - { - // Header values - case "doscenter": - ReadHeader(cmpr); - break; - - // Sets - case "game": - ReadGame(cmpr, statsOnly, filename, indexId); - break; - - default: - break; - } - } - catch (Exception ex) when (!throwOnError) - { - string message = $"'{filename}' - There was an error parsing line {cmpr.LineNumber} '{cmpr.CurrentLine}'"; - logger.Error(ex, message); - } - } - - cmpr.Dispose(); - } - - /// - /// Read header information - /// - /// ClrMameProReader to use to parse the header - private void ReadHeader(ClrMameProReader cmpr) - { - // If there's no subtree to the header, skip it - if (cmpr == null || cmpr.EndOfStream) - return; - - // While we don't hit an end element or end of stream - while (!cmpr.EndOfStream) - { - cmpr.ReadNextLine(); - - // Ignore comments, internal items, and nothingness - if (cmpr.RowType == CmpRowType.None || cmpr.RowType == CmpRowType.Comment || cmpr.RowType == CmpRowType.Internal) - continue; - - // If we reached the end of a section, break - if (cmpr.RowType == CmpRowType.EndTopLevel) - break; - - // If the standalone value is null, we skip - if (cmpr.Standalone == null) - continue; - - string itemKey = cmpr.Standalone?.Key.ToLowerInvariant().TrimEnd(':'); - string itemVal = cmpr.Standalone?.Value; - - // For all other cases - switch (itemKey) - { - case "name": - Header.Name ??= itemVal; - break; - case "description": - Header.Description ??= itemVal; - break; - case "version": - Header.Version ??= itemVal; - break; - case "date": - Header.Date ??= itemVal; - break; - case "author": - Header.Author ??= itemVal; - break; - case "homepage": - Header.Homepage ??= itemVal; - break; - case "comment": - Header.Comment ??= itemVal; - break; - } - } - } - - /// - /// Read set information - /// - /// ClrMameProReader to use to parse the header - /// True to only add item statistics while parsing, false otherwise - /// Name of the file to be parsed - /// Index ID for the DAT - private void ReadGame(ClrMameProReader cmpr, bool statsOnly, string filename, int indexId) - { - // Prepare all internal variables - bool containsItems = false; - Machine machine = new() - { - MachineType = MachineType.None, - }; - - // If there's no subtree to the header, skip it - if (cmpr == null || cmpr.EndOfStream) - return; - - // While we don't hit an end element or end of stream - while (!cmpr.EndOfStream) - { - cmpr.ReadNextLine(); - - // Ignore comments and nothingness - if (cmpr.RowType == CmpRowType.None || cmpr.RowType == CmpRowType.Comment) - continue; - - // If we reached the end of a section, break - if (cmpr.RowType == CmpRowType.EndTopLevel) - break; - - // Handle any standalone items - if (cmpr.RowType == CmpRowType.Standalone && cmpr.Standalone != null) - { - string itemKey = cmpr.Standalone?.Key.ToLowerInvariant(); - string itemVal = cmpr.Standalone?.Value; - - switch (itemKey) - { - case "name": - machine.Name = (itemVal.ToLowerInvariant().EndsWith(".zip") ? itemVal.Remove(itemVal.Length - 4) : itemVal); - machine.Description = (itemVal.ToLowerInvariant().EndsWith(".zip") ? itemVal.Remove(itemVal.Length - 4) : itemVal); - break; - } - } - - // Handle any internal items - else if (cmpr.RowType == CmpRowType.Internal - && string.Equals(cmpr.InternalName, "file", StringComparison.OrdinalIgnoreCase) - && cmpr.Internal != null) - { - containsItems = true; - - // Create the proper DatItem based on the type - Rom item = DatItem.Create(ItemType.Rom) as Rom; - - // Then populate it with information - item.CopyMachineInformation(machine); - item.Source = new Source - { - Index = indexId, - Name = filename, - }; - - // Loop through all of the attributes - foreach (var kvp in cmpr.Internal) - { - string attrKey = kvp.Key; - string attrVal = kvp.Value; - - switch (attrKey) - { - //If the item is empty, we automatically skip it because it's a fluke - case "": - continue; - - // Regular attributes - case "name": - item.Name = attrVal; - break; - - case "size": - item.Size = Utilities.CleanLong(attrVal); - break; - - case "crc": - item.CRC = attrVal; - break; - case "date": - item.Date = attrVal; - break; - } - } - - // Now process and add the rom - ParseAddHelper(item, statsOnly); - } - } - - // If no items were found for this machine, add a Blank placeholder - if (!containsItems) - { - Blank blank = new() - { - Source = new Source - { - Index = indexId, - Name = filename, - }, - }; - - blank.CopyMachineInformation(machine); - - // Now process and add the rom - ParseAddHelper(blank, statsOnly); - } - } - - /// - protected override ItemType[] GetSupportedTypes() - { - return new ItemType[] { ItemType.Rom }; - } - - /// - protected override List GetMissingRequiredFields(DatItem datItem) - { - // TODO: Check required fields - return null; - } - - /// - public override bool WriteToFile(string outfile, bool ignoreblanks = false, bool throwOnError = false) - { - try - { - logger.User($"Writing to '{outfile}'..."); - FileStream fs = System.IO.File.Create(outfile); - - // If we get back null for some reason, just log and return - if (fs == null) - { - logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable"); - return false; - } - - ClrMameProWriter cmpw = new(fs, new UTF8Encoding(false)) - { - Quotes = false - }; - - // Write out the header - WriteHeader(cmpw); - - // Write out each of the machines and roms - string lastgame = null; - - // Use a sorted list of games to output - foreach (string key in Items.SortedKeys) - { - ConcurrentList datItems = Items.FilteredItems(key); - - // If this machine doesn't contain any writable items, skip - if (!ContainsWritable(datItems)) - continue; - - // Resolve the names in the block - datItems = DatItem.ResolveNames(datItems); - - for (int index = 0; index < datItems.Count; index++) - { - DatItem datItem = datItems[index]; - - List newsplit = datItem.Machine.Name.Split('\\').ToList(); - - // If we have a different game and we're not at the start of the list, output the end of last item - if (lastgame != null && lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) - WriteEndGame(cmpw); - - // If we have a new game, output the beginning of the new item - if (lastgame == null || lastgame.ToLowerInvariant() != datItem.Machine.Name.ToLowerInvariant()) - WriteStartGame(cmpw, datItem); - - // Check for a "null" item - datItem = ProcessNullifiedItem(datItem); - - // Write out the item if we're not ignoring - if (!ShouldIgnore(datItem, ignoreblanks)) - WriteDatItem(cmpw, datItem); - - // Set the new data to compare against - lastgame = datItem.Machine.Name; - } - } - - // Write the file footer out - WriteFooter(cmpw); - - logger.User($"'{outfile}' written!{Environment.NewLine}"); - cmpw.Dispose(); - fs.Dispose(); - } - catch (Exception ex) when (!throwOnError) - { - logger.Error(ex); - return false; - } - - return true; - } - - /// - /// Write out DAT header using the supplied StreamWriter - /// - /// ClrMameProWriter to output to - private void WriteHeader(ClrMameProWriter cmpw) - { - // Build the state - cmpw.WriteStartElement("DOSCenter"); - - cmpw.WriteRequiredStandalone("Name:", Header.Name, false); - cmpw.WriteRequiredStandalone("Description:", Header.Description, false); - cmpw.WriteRequiredStandalone("Version:", Header.Version, false); - cmpw.WriteRequiredStandalone("Date:", Header.Date, false); - cmpw.WriteRequiredStandalone("Author:", Header.Author, false); - cmpw.WriteRequiredStandalone("Homepage:", Header.Homepage, false); - cmpw.WriteRequiredStandalone("Comment:", Header.Comment, false); - - cmpw.WriteEndElement(); - - cmpw.Flush(); - } - - /// - /// Write out Game start using the supplied StreamWriter - /// - /// ClrMameProWriter to output to - /// DatItem object to be output - private void WriteStartGame(ClrMameProWriter cmpw, DatItem datItem) - { - // No game should start with a path separator - datItem.Machine.Name = datItem.Machine.Name.TrimStart(Path.DirectorySeparatorChar); - - // Build the state - cmpw.WriteStartElement("game"); - cmpw.WriteRequiredStandalone("name", $"{datItem.Machine.Name}.zip", true); - - cmpw.Flush(); - } - - /// - /// Write out Game end using the supplied StreamWriter - /// - /// ClrMameProWriter to output to - private void WriteEndGame(ClrMameProWriter cmpw) - { - // End game - cmpw.WriteEndElement(); - - cmpw.Flush(); - } - - /// - /// Write out DatItem using the supplied StreamWriter - /// - /// ClrMameProWriter to output to - /// DatItem object to be output - private void WriteDatItem(ClrMameProWriter cmpw, DatItem datItem) - { - // Pre-process the item name - ProcessItemName(datItem, true); - - // Build the state - switch (datItem.ItemType) - { - case ItemType.Rom: - var rom = datItem as Rom; - cmpw.WriteStartElement("file"); - cmpw.WriteRequiredAttributeString("name", rom.Name); - cmpw.WriteOptionalAttributeString("size", rom.Size?.ToString()); - cmpw.WriteOptionalAttributeString("date", rom.Date); - cmpw.WriteOptionalAttributeString("crc", rom.CRC?.ToLowerInvariant()); - cmpw.WriteEndElement(); - break; - } - - cmpw.Flush(); - } - - /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// ClrMameProWriter to output to - /// True if the data was written, false on error - private void WriteFooter(ClrMameProWriter cmpw) - { - // End game - cmpw.WriteEndElement(); - - cmpw.Flush(); - } } } diff --git a/SabreTools.Models/DosCenter/File.cs b/SabreTools.Models/DosCenter/File.cs index 5003a7c9..8c6f7375 100644 --- a/SabreTools.Models/DosCenter/File.cs +++ b/SabreTools.Models/DosCenter/File.cs @@ -4,16 +4,16 @@ namespace SabreTools.Models.DosCenter public class File { /// name, attribute - public string? Name { get; set; } + public string Name { get; set; } /// size, attribute, numeric - public string? Size { get; set; } + public string Size { get; set; } /// crc, attribute - public string? CRC { get; set; } + public string CRC { get; set; } /// date, attribute - public string? Date { get; set; } + public string Date { get; set; } #region DO NOT USE IN PRODUCTION diff --git a/SabreTools.Models/DosCenter/DatFile.cs b/SabreTools.Models/DosCenter/MetadataFile.cs similarity index 92% rename from SabreTools.Models/DosCenter/DatFile.cs rename to SabreTools.Models/DosCenter/MetadataFile.cs index 571b37ee..48f2a6e9 100644 --- a/SabreTools.Models/DosCenter/DatFile.cs +++ b/SabreTools.Models/DosCenter/MetadataFile.cs @@ -1,6 +1,6 @@ namespace SabreTools.Models.DosCenter { - public class DatFile + public class MetadataFile { /// doscenter public DosCenter? DosCenter { get; set; } diff --git a/SabreTools.Serialization/DosCenter.cs b/SabreTools.Serialization/DosCenter.Deserializer.cs similarity index 81% rename from SabreTools.Serialization/DosCenter.cs rename to SabreTools.Serialization/DosCenter.Deserializer.cs index c40e1002..aa668de5 100644 --- a/SabreTools.Serialization/DosCenter.cs +++ b/SabreTools.Serialization/DosCenter.Deserializer.cs @@ -7,16 +7,16 @@ using SabreTools.Models.DosCenter; namespace SabreTools.Serialization { /// - /// Serializer for DosCenter metadata files + /// Deserializer for DosCenter metadata files /// - public class DosCenter + public partial class DosCenter { /// /// Deserializes a DosCenter metadata file to the defined type /// /// Path to the file to deserialize /// Deserialized data on success, null on failure - public static DatFile? Deserialize(string path) + public static MetadataFile? Deserialize(string path) { try { @@ -35,7 +35,7 @@ namespace SabreTools.Serialization /// /// Stream to deserialize /// Deserialized data on success, null on failure - public static DatFile? Deserialize(Stream? stream) + public static MetadataFile? Deserialize(Stream? stream) { try { @@ -45,7 +45,7 @@ namespace SabreTools.Serialization // Setup the reader and output var reader = new ClrMameProReader(stream, Encoding.UTF8) { DosCenter = true }; - var dat = new DatFile(); + var dat = new MetadataFile(); // Loop through and parse out the values string lastTopLevel = reader.TopLevel; @@ -166,9 +166,6 @@ namespace SabreTools.Serialization // If we're in a file block else if (reader.TopLevel == "game" && reader.RowType == CmpRowType.Internal) { - // Create the block - var file = new Models.DosCenter.File(); - // If we have an unknown type, log it if (reader.InternalName != "file") { @@ -176,32 +173,9 @@ namespace SabreTools.Serialization continue; } - foreach (var kvp in reader.Internal) - { - switch (kvp.Key?.ToLowerInvariant()) - { - case "name": - file.Name = kvp.Value; - break; - case "size": - file.Size = kvp.Value; - break; - case "crc": - file.CRC = kvp.Value; - break; - case "date": - file.Date = kvp.Value; - break; - default: - fileAdditional.Add(item: reader.CurrentLine); - break; - } - } - - // Add the file to the list - file.ADDITIONAL_ELEMENTS = fileAdditional.ToArray(); + // Create the file and add to the list + var file = CreateFile(reader); files.Add(file); - fileAdditional.Clear(); } else @@ -221,5 +195,40 @@ namespace SabreTools.Serialization return default; } } + + /// + /// Create a File object from the current reader context + /// + /// ClrMameProReader representing the metadata file + /// File object created from the reader context + private static Models.DosCenter.File CreateFile(ClrMameProReader reader) + { + var itemAdditional = new List(); + var file = new Models.DosCenter.File(); + foreach (var kvp in reader.Internal) + { + switch (kvp.Key?.ToLowerInvariant()) + { + case "name": + file.Name = kvp.Value; + break; + case "size": + file.Size = kvp.Value; + break; + case "crc": + file.CRC = kvp.Value; + break; + case "date": + file.Date = kvp.Value; + break; + default: + itemAdditional.Add(item: reader.CurrentLine); + break; + } + } + + file.ADDITIONAL_ELEMENTS = itemAdditional.ToArray(); + return file; + } } } \ No newline at end of file diff --git a/SabreTools.Serialization/DosCenter.Serializer.cs b/SabreTools.Serialization/DosCenter.Serializer.cs new file mode 100644 index 00000000..7004da15 --- /dev/null +++ b/SabreTools.Serialization/DosCenter.Serializer.cs @@ -0,0 +1,149 @@ +using System.IO; +using System.Linq; +using System.Text; +using SabreTools.IO.Writers; +using SabreTools.Models.DosCenter; +namespace SabreTools.Serialization +{ + /// + /// Deserializer for DosCenter metadata files + /// + public partial class DosCenter + { + /// + /// Serializes the defined type to a DosCenter metadata file + /// + /// Data to serialize + /// Path to the file to serialize to + /// True on successful serialization, false otherwise + public static bool SerializeToFile(MetadataFile? metadataFile, string path) + { + using var stream = SerializeToStream(metadataFile); + if (stream == null) + return false; + + using var fs = System.IO.File.OpenWrite(path); + stream.Seek(0, SeekOrigin.Begin); + stream.CopyTo(fs); + return true; + } + + /// + /// Serializes the defined type to a stream + /// + /// Data to serialize + /// Stream containing serialized data on success, null otherwise + public static Stream? SerializeToStream(MetadataFile? metadataFile) + { + // If the metadata file is null + if (metadataFile == null) + return null; + + // Setup the writer and output + var stream = new MemoryStream(); + var writer = new ClrMameProWriter(stream, Encoding.UTF8) + { + Quotes = false, + }; + + // Write the header, if it exists + WriteHeader(metadataFile.DosCenter, writer); + + // Write out the games, if they exist + WriteGames(metadataFile.Game, writer); + + // Return the stream + return stream; + } + + /// + /// Write header information to the current writer + /// + /// DosCenter representing the header information + /// ClrMameProWriter representing the output + private static void WriteHeader(Models.DosCenter.DosCenter? header, ClrMameProWriter writer) + { + // If the header information is missing, we can't do anything + if (header == null) + return; + + writer.WriteStartElement("DOSCenter"); + + writer.WriteOptionalStandalone("Name:", header.Name); + writer.WriteOptionalStandalone("Description:", header.Description); + writer.WriteOptionalStandalone("Version:", header.Version); + writer.WriteOptionalStandalone("Date:", header.Date); + writer.WriteOptionalStandalone("Author:", header.Author); + writer.WriteOptionalStandalone("Homepage:", header.Homepage); + writer.WriteOptionalStandalone("Comment:", header.Comment); + + writer.WriteEndElement(); // doscenter + writer.Flush(); + } + + /// + /// Write games information to the current writer + /// + /// Array of Game objects representing the games information + /// ClrMameProWriter representing the output + private static void WriteGames(Game[]? games, ClrMameProWriter writer) + { + // If the games information is missing, we can't do anything + if (games == null || !games.Any()) + return; + + // Loop through and write out the games + foreach (var game in games) + { + WriteGame(game, writer); + writer.Flush(); + } + } + + /// + /// Write game information to the current writer + /// + /// Game object representing the game information + /// ClrMameProWriter representing the output + private static void WriteGame(Game game, ClrMameProWriter writer) + { + // If the game information is missing, we can't do anything + if (game == null) + return; + + writer.WriteStartElement("game"); + + // Write the standalone values + writer.WriteRequiredStandalone("name", game.Name, throwOnError: true); + + // Write the item values + WriteFiles(game.File, writer); + + writer.WriteEndElement(); // game + } + + /// + /// Write files information to the current writer + /// + /// Array of File objects to write + /// ClrMameProWriter representing the output + private static void WriteFiles(Models.DosCenter.File[]? files, ClrMameProWriter writer) + { + // If the array is missing, we can't do anything + if (files == null) + return; + + foreach (var file in files) + { + writer.WriteStartElement("file"); + + writer.WriteRequiredAttributeString("name", file.Name, throwOnError: true); + writer.WriteRequiredAttributeString("size", file.Size, throwOnError: true); + writer.WriteOptionalAttributeString("date", file.Date); + writer.WriteRequiredAttributeString("crc", file.CRC.ToUpperInvariant(), throwOnError: true); + + writer.WriteEndElement(); // file + } + } + } +} \ No newline at end of file