diff --git a/SabreTools.DatFiles/Formats/OfflineList.Writer.cs b/SabreTools.DatFiles/Formats/OfflineList.Writer.cs index c844c807..9bbc9c7b 100644 --- a/SabreTools.DatFiles/Formats/OfflineList.Writer.cs +++ b/SabreTools.DatFiles/Formats/OfflineList.Writer.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Xml; +using System.Linq; using SabreTools.Core; using SabreTools.DatItems; using SabreTools.DatItems.Formats; -using SabreTools.IO; namespace SabreTools.DatFiles.Formats { @@ -53,62 +50,13 @@ namespace SabreTools.DatFiles.Formats 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) + var datafile = CreateDat(ignoreblanks); + if (!Serialization.OfflineList.SerializeToFile(datafile, outfile)) { - logger.Warning($"File '{outfile}' could not be created for writing! Please check to see if the file is writable"); + logger.Warning($"File '{outfile}' could not be written! See the log for more details."); return false; } - - XmlTextWriter xtw = new(fs, new UTF8Encoding(false)) - { - Formatting = Formatting.Indented, - IndentChar = '\t', - Indentation = 1 - }; - - // Write out the header - WriteHeader(xtw); - - // 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]; - - // Check for a "null" item - datItem = ProcessNullifiedItem(datItem); - - // Write out the item if we're not ignoring - if (!ShouldIgnore(datItem, ignoreblanks)) - WriteDatItem(xtw, datItem); - - // Set the new data to compare against - lastgame = datItem.Machine.Name; - } - } - - // Write the file footer out - WriteFooter(xtw); - - logger.User($"'{outfile}' written!{Environment.NewLine}"); - xtw.Dispose(); - fs.Dispose(); } catch (Exception ex) when (!throwOnError) { @@ -116,220 +64,353 @@ namespace SabreTools.DatFiles.Formats return false; } + logger.User($"'{outfile}' written!{Environment.NewLine}"); return true; } + #region Converters + /// - /// Write out DAT header using the supplied StreamWriter - /// - /// XmlTextWriter to output to - private void WriteHeader(XmlTextWriter xtw) + /// Create a Dat from the current internal information + /// + /// True if blank roms should be skipped on output, false otherwise + private Models.OfflineList.Dat CreateDat(bool ignoreblanks) { - xtw.WriteStartDocument(false); - - xtw.WriteStartElement("dat"); - xtw.WriteAttributeString("xsi", "xmlns", "http://www.w3.org/2001/XMLSchema-instance"); - xtw.WriteAttributeString("noNamespaceSchemaLocation", "xsi", "datas.xsd"); - - xtw.WriteStartElement("configuration"); - xtw.WriteRequiredElementString("datName", Header.Name); - xtw.WriteElementString("datVersion", Items.TotalCount.ToString()); - xtw.WriteRequiredElementString("system", Header.System); - xtw.WriteRequiredElementString("screenshotsWidth", Header.ScreenshotsWidth); - xtw.WriteRequiredElementString("screenshotsHeight", Header.ScreenshotsHeight); - - if (Header.Infos != null) + var dat = new Models.OfflineList.Dat { - xtw.WriteStartElement("infos"); + NoNamespaceSchemaLocation = "datas.xsd", + Configuration = CreateConfiguration(), + Games = CreateGames(ignoreblanks), + GUI = CreateGUI(), + }; - foreach (var info in Header.Infos) - { - xtw.WriteStartElement(info.Name); - xtw.WriteAttributeString("visible", info.Visible?.ToString()); - xtw.WriteAttributeString("inNamingOption", info.InNamingOption?.ToString()); - xtw.WriteAttributeString("default", info.Default?.ToString()); - xtw.WriteEndElement(); - } - - // End infos - xtw.WriteEndElement(); - } - - if (Header.CanOpen != null) - { - xtw.WriteStartElement("canOpen"); - - foreach (string extension in Header.CanOpen) - { - xtw.WriteElementString("extension", extension); - } - - // End canOpen - xtw.WriteEndElement(); - } - - xtw.WriteStartElement("newDat"); - xtw.WriteRequiredElementString("datVersionURL", Header.Url); - - xtw.WriteStartElement("datUrl"); - xtw.WriteAttributeString("fileName", $"{Header.FileName ?? string.Empty}.zip"); - xtw.WriteString(Header.Url); - xtw.WriteEndElement(); - - xtw.WriteRequiredElementString("imURL", Header.Url); - - // End newDat - xtw.WriteEndElement(); - - xtw.WriteStartElement("search"); - - xtw.WriteStartElement("to"); - xtw.WriteAttributeString("value", "location"); - xtw.WriteAttributeString("default", "true"); - xtw.WriteAttributeString("auto", "true"); - xtw.WriteEndElement(); - - xtw.WriteStartElement("to"); - xtw.WriteAttributeString("value", "romSize"); - xtw.WriteAttributeString("default", "true"); - xtw.WriteAttributeString("auto", "false"); - xtw.WriteEndElement(); - - xtw.WriteStartElement("to"); - xtw.WriteAttributeString("value", "languages"); - xtw.WriteAttributeString("default", "true"); - xtw.WriteAttributeString("auto", "true"); - xtw.WriteEndElement(); - - xtw.WriteStartElement("to"); - xtw.WriteAttributeString("value", "saveType"); - xtw.WriteAttributeString("default", "false"); - xtw.WriteAttributeString("auto", "false"); - xtw.WriteEndElement(); - - xtw.WriteStartElement("to"); - xtw.WriteAttributeString("value", "publisher"); - xtw.WriteAttributeString("default", "false"); - xtw.WriteAttributeString("auto", "true"); - xtw.WriteEndElement(); - - xtw.WriteStartElement("to"); - xtw.WriteAttributeString("value", "sourceRom"); - xtw.WriteAttributeString("default", "false"); - xtw.WriteAttributeString("auto", "true"); - xtw.WriteEndElement(); - - // End search - xtw.WriteEndElement(); - - xtw.WriteRequiredElementString("romTitle", Header.RomTitle ?? "%u - %n"); - - // End configuration - xtw.WriteEndElement(); - - xtw.WriteStartElement("games"); - - xtw.Flush(); + return dat; } /// - /// Write out DatItem using the supplied StreamWriter - /// - /// XmlTextWriter to output to - /// DatItem object to be output - /// True if the data was written, false on error - private void WriteDatItem(XmlTextWriter xtw, DatItem datItem) + /// Create a Configuration from the current internal information + /// + private Models.OfflineList.Configuration? CreateConfiguration() { - // Pre-process the item name - ProcessItemName(datItem, true); + // If we don't have a header, we can't do anything + if (this.Header == null) + return null; - // Build the state - xtw.WriteStartElement("game"); - xtw.WriteElementString("imageNumber", "1"); - xtw.WriteElementString("releaseNumber", "1"); - xtw.WriteRequiredElementString("title", datItem.GetName() ?? string.Empty); - xtw.WriteElementString("saveType", "None"); - - if (datItem.ItemType == ItemType.Rom) + var configuration = new Models.OfflineList.Configuration { - var rom = datItem as Rom; - xtw.WriteRequiredElementString("romSize", rom.Size?.ToString()); - } + DatName = Header.Name, + //ImFolder = Header.ImFolder; // TODO: Add to internal model + DatVersion = Header.Version, + System = Header.System, + ScreenshotsWidth = Header.ScreenshotsWidth, + ScreenshotsHeight = Header.ScreenshotsHeight, + Infos = CreateInfos(), + CanOpen = CreateCanOpen(), + NewDat = CreateNewDat(), + Search = CreateSearch(), + RomTitle = Header.RomTitle, + }; - xtw.WriteRequiredElementString("publisher", datItem.Machine.Publisher); - xtw.WriteElementString("location", "0"); - xtw.WriteElementString("sourceRom", "None"); - xtw.WriteElementString("language", "0"); - - if (datItem.ItemType == ItemType.Rom) - { - var rom = datItem as Rom; - string tempext = "." + rom.Name.GetNormalizedExtension(); - - xtw.WriteStartElement("files"); - if (!string.IsNullOrWhiteSpace(rom.CRC)) - { - xtw.WriteStartElement("romCRC"); - xtw.WriteRequiredAttributeString("extension", tempext); - xtw.WriteString(rom.CRC?.ToUpperInvariant()); - xtw.WriteEndElement(); - } - - // End files - xtw.WriteEndElement(); - } - - xtw.WriteElementString("im1CRC", "00000000"); - xtw.WriteElementString("im2CRC", "00000000"); - xtw.WriteRequiredElementString("comment", datItem.Machine.Comment); - xtw.WriteRequiredElementString("duplicateID", datItem.Machine.CloneOf); - - // End game - xtw.WriteEndElement(); - - xtw.Flush(); + return configuration; } /// - /// Write out DAT footer using the supplied StreamWriter - /// - /// XmlTextWriter to output to - /// True if the data was written, false on error - private void WriteFooter(XmlTextWriter xtw) + /// Create a Infos from the current internal information + /// + private Models.OfflineList.Infos? CreateInfos() { - // End games - xtw.WriteEndElement(); + // If we don't have infos, we can't do anything + if (!Header.InfosSpecified) + return null; - xtw.WriteStartElement("gui"); + var infos = new Models.OfflineList.Infos(); + foreach (var info in Header.Infos) + { + switch (info.Name) + { + case "title": + infos.Title = new Models.OfflineList.Title + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; - xtw.WriteStartElement("images"); - xtw.WriteAttributeString("width", "487"); - xtw.WriteAttributeString("height", "162"); + case "location": + infos.Location = new Models.OfflineList.Location + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; - xtw.WriteStartElement("image"); - xtw.WriteAttributeString("x", "0"); - xtw.WriteAttributeString("y", "0"); - xtw.WriteAttributeString("width", "240"); - xtw.WriteAttributeString("height", "160"); - xtw.WriteEndElement(); + case "publisher": + infos.Publisher = new Models.OfflineList.Publisher + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; - xtw.WriteStartElement("image"); - xtw.WriteAttributeString("x", "245"); - xtw.WriteAttributeString("y", "0"); - xtw.WriteAttributeString("width", "240"); - xtw.WriteAttributeString("height", "160"); - xtw.WriteEndElement(); + case "sourceRom": + infos.SourceRom = new Models.OfflineList.SourceRom + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; - // End images - xtw.WriteEndElement(); + case "saveType": + infos.SaveType = new Models.OfflineList.SaveType + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; - // End gui - xtw.WriteEndElement(); + case "romSize": + infos.RomSize = new Models.OfflineList.RomSize + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; - // End dat - xtw.WriteEndElement(); + case "releaseNumber": + infos.ReleaseNumber = new Models.OfflineList.ReleaseNumber + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; - xtw.Flush(); + case "languageNumber": + infos.LanguageNumber = new Models.OfflineList.LanguageNumber + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; + + case "comment": + infos.Comment = new Models.OfflineList.Comment + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; + + case "romCRC": + infos.RomCRC = new Models.OfflineList.RomCRC + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; + + case "im1CRC": + infos.Im1CRC = new Models.OfflineList.Im1CRC + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; + + case "im2CRC": + infos.Im2CRC = new Models.OfflineList.Im2CRC + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; + + case "languages": + infos.Languages = new Models.OfflineList.Languages + { + Visible = info.Visible?.ToString(), + InNamingOption = info.InNamingOption?.ToString(), + Default = info.Default?.ToString(), + }; + break; + } + } + + return infos; } + + /// + /// Create a CanOpen from the current internal information + /// + private Models.OfflineList.CanOpen? CreateCanOpen() + { + // If we don't have a canopen, we can't do anything + if (!Header.CanOpenSpecified) + return null; + + var canOpen = new Models.OfflineList.CanOpen + { + Extension = Header.CanOpen.ToArray(), + }; + + return canOpen; + } + + /// + /// Create a NewDat from the current internal information + /// + private Models.OfflineList.NewDat? CreateNewDat() + { + // If we don't have a Header, we can't do anything + if (Header == null) + return null; + + var newDat = new Models.OfflineList.NewDat + { + DatVersionUrl = Header.Url, + //DatUrl = Header.DatUrl; // TODO: Add to internal model + //ImUrl = Header.ImUrl; // TODO: Add to internal model + }; + + return newDat; + } + + /// + /// Create a Search from the current internal information + /// + private Models.OfflineList.Search? CreateSearch() + { + // If we don't have a Header, we can't do anything + if (Header == null) + return null; + + // TODO: Add to internal model + return null; + } + + /// + /// Create a Games from the current internal information + /// + /// True if blank roms should be skipped on output, false otherwise + private Models.OfflineList.Games? 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; + var game = CreateGame(machine); + + // Create holders for all item types + var romCRCs = new List(); + + // Loop through and convert the items to respective lists + for (int index = 0; index < items.Count; index++) + { + // Get the item + var item = items[index]; + + // Check for a "null" item + item = ProcessNullifiedItem(item); + + // Skip if we're ignoring the item + if (ShouldIgnore(item, ignoreblanks)) + continue; + + switch (item) + { + case Rom rom: + romCRCs.Add(CreateRomCRC(rom)); + break; + } + } + + // Assign the values to the game + game.Files = new Models.OfflineList.Files { RomCRC = romCRCs.ToArray() }; + + // Add the game to the list + games.Add(game); + } + + return new Models.OfflineList.Games { Game = games.ToArray() }; + } + + /// + /// Create a Machine from the current internal information + /// + private Models.OfflineList.Game? CreateGame(Machine machine) + { + // If we don't have a machine, we can't do anything + if (machine == null) + return null; + + var game = new Models.OfflineList.Game + { + //ImageNumber = machine.ImageNumber, // TODO: Add to internal model + //ReleaseNumber = machine.ReleaseNumber, // TODO: Add to internal model + Title = machine.Name, + //SaveType = machine.SaveType, // TODO: Add to internal model + Publisher = machine.Publisher, + //Location = machine.Location, // TODO: Add to internal model + //SourceRom = machine.SourceRom, // TODO: Add to internal model + //Language = machine.Language, // TODO: Add to internal model + //Im1CRC = machine.Im1CRC, // TODO: Add to internal model + //Im2CRC = machine.Im2CRC, // TODO: Add to internal model + Comment = machine.Comment, + DuplicateID = machine.CloneOf, + }; + + return game; + } + + /// + /// Create a RomCRC from the current Rom DatItem + /// + private static Models.OfflineList.FileRomCRC CreateRomCRC(Rom item) + { + var romCRC = new Models.OfflineList.FileRomCRC + { + Content = item.CRC, + }; + + return romCRC; + } + + /// + /// Create a GUI from the current internal information + /// + private Models.OfflineList.GUI? CreateGUI() + { + // If we don't have a header, we can't do anything + if (this.Header == null) + return null; + + // TODO: Add to internal model + return null; + } + + #endregion } }