diff --git a/CrossModel/SeparatedValue.Deserializer.cs b/CrossModel/SeparatedValue.Deserializer.cs new file mode 100644 index 00000000..c7dcc35a --- /dev/null +++ b/CrossModel/SeparatedValue.Deserializer.cs @@ -0,0 +1,141 @@ +using System.Collections.Generic; +using System.Linq; +using SabreTools.Models.SeparatedValue; + +namespace SabreTools.Serialization.CrossModel +{ + public partial class SeparatedValue : IModelSerializer + { + /// +#if NET48 + public MetadataFile Deserialize(Models.Metadata.MetadataFile obj) +#else + public MetadataFile? Deserialize(Models.Metadata.MetadataFile? obj) +#endif + { + if (obj == null) + return null; + + var header = obj.Read(Models.Metadata.MetadataFile.HeaderKey); + var metadataFile = header != null ? ConvertHeaderFromInternalModel(header) : new MetadataFile(); + + var machines = obj.Read(Models.Metadata.MetadataFile.MachineKey); + if (machines != null && machines.Any()) + { + metadataFile.Row = machines + .Where(m => m != null) + .SelectMany(ConvertMachineFromInternalModel) + .ToArray(); + } + + return metadataFile; + } + + /// + /// Convert from to + /// + private static MetadataFile ConvertHeaderFromInternalModel(Models.Metadata.Header item) + { + var metadataFile = new MetadataFile + { + Header = item.ReadStringArray(Models.Metadata.Header.HeaderKey), + }; + return metadataFile; + } + + /// + /// Convert from to an array of + /// + private static Row[] ConvertMachineFromInternalModel(Models.Metadata.Machine item) + { + var rowItems = new List(); + + var roms = item.Read(Models.Metadata.Machine.RomKey); + if (roms != null && roms.Any()) + { + rowItems.AddRange(roms + .Where(r => r != null) + .Select(rom => ConvertFromInternalModel(rom, item))); + } + + var disks = item.Read(Models.Metadata.Machine.DiskKey); + if (disks != null && disks.Any()) + { + rowItems.AddRange(disks + .Where(d => d != null) + .Select(disk => ConvertFromInternalModel(disk, item))); + } + + var media = item.Read(Models.Metadata.Machine.MediaKey); + if (media != null && media.Any()) + { + rowItems.AddRange(media + .Where(m => m != null) + .Select(medium => ConvertFromInternalModel(medium, item))); + } + + return rowItems.ToArray(); + } + + /// + /// Convert from to + /// + private static Row ConvertFromInternalModel(Models.Metadata.Disk item, Models.Metadata.Machine parent) + { + var row = new Row + { + GameName = parent.ReadString(Models.Metadata.Machine.NameKey), + Description = parent.ReadString(Models.Metadata.Machine.DescriptionKey), + Type = "disk", + DiskName = item.ReadString(Models.Metadata.Disk.NameKey), + MD5 = item.ReadString(Models.Metadata.Disk.MD5Key), + SHA1 = item.ReadString(Models.Metadata.Disk.SHA1Key), + Status = item.ReadString(Models.Metadata.Disk.StatusKey), + }; + return row; + } + + /// + /// Convert from to + /// + private static Row ConvertFromInternalModel(Models.Metadata.Media item, Models.Metadata.Machine parent) + { + var row = new Row + { + GameName = parent.ReadString(Models.Metadata.Machine.NameKey), + Description = parent.ReadString(Models.Metadata.Machine.DescriptionKey), + Type = "media", + DiskName = item.ReadString(Models.Metadata.Media.NameKey), + MD5 = item.ReadString(Models.Metadata.Media.MD5Key), + SHA1 = item.ReadString(Models.Metadata.Media.SHA1Key), + SHA256 = item.ReadString(Models.Metadata.Media.SHA256Key), + SpamSum = item.ReadString(Models.Metadata.Media.SpamSumKey), + }; + return row; + } + + /// + /// Convert from to + /// + private static Row ConvertFromInternalModel(Models.Metadata.Rom item, Models.Metadata.Machine parent) + { + var row = new Row + { + GameName = parent?.ReadString(Models.Metadata.Machine.NameKey), + Description = parent?.ReadString(Models.Metadata.Machine.DescriptionKey), + Type = "rom", + RomName = item.ReadString(Models.Metadata.Rom.NameKey), + Size = item.ReadString(Models.Metadata.Rom.SizeKey), + CRC = item.ReadString(Models.Metadata.Rom.CRCKey), + MD5 = item.ReadString(Models.Metadata.Rom.MD5Key), + SHA1 = item.ReadString(Models.Metadata.Rom.SHA1Key), + SHA256 = item.ReadString(Models.Metadata.Rom.SHA256Key), + SHA384 = item.ReadString(Models.Metadata.Rom.SHA384Key), + SHA512 = item.ReadString(Models.Metadata.Rom.SHA512Key), + SpamSum = item.ReadString(Models.Metadata.Rom.SpamSumKey), + Status = item.ReadString(Models.Metadata.Rom.StatusKey), + }; + return row; + } + } +} \ No newline at end of file diff --git a/CrossModel/SeparatedValue.Serializer.cs b/CrossModel/SeparatedValue.Serializer.cs new file mode 100644 index 00000000..831c2fd3 --- /dev/null +++ b/CrossModel/SeparatedValue.Serializer.cs @@ -0,0 +1,167 @@ +using System.Linq; +using SabreTools.Models.SeparatedValue; + +namespace SabreTools.Serialization.CrossModel +{ + public partial class SeparatedValue : IModelSerializer + { + /// +#if NET48 + public Models.Metadata.MetadataFile Serialize(MetadataFile obj) +#else + public Models.Metadata.MetadataFile? Serialize(MetadataFile? obj) +#endif + { + if (obj == null) + return null; + + var metadataFile = new Models.Metadata.MetadataFile + { + [Models.Metadata.MetadataFile.HeaderKey] = ConvertHeaderToInternalModel(obj), + }; + + if (obj?.Row != null && obj.Row.Any()) + metadataFile[Models.Metadata.MetadataFile.MachineKey] = obj.Row.Select(ConvertMachineToInternalModel).ToArray(); + + return metadataFile; + } + + /// + /// Convert from to + /// + private static Models.Metadata.Header ConvertHeaderToInternalModel(MetadataFile item) + { + var header = new Models.Metadata.Header + { + [Models.Metadata.Header.HeaderKey] = item.Header, + }; + + if (item.Row != null && item.Row.Any()) + { + var first = item.Row[0]; + //header[Models.Metadata.Header.FileNameKey] = first.FileName; // Not possible to map + header[Models.Metadata.Header.NameKey] = first.FileName; + header[Models.Metadata.Header.DescriptionKey] = first.Description; + } + + return header; + } + + /// + /// Convert from to + /// + private static Models.Metadata.Machine ConvertMachineToInternalModel(Row item) + { + var machine = new Models.Metadata.Machine + { + [Models.Metadata.Machine.NameKey] = item.GameName, + [Models.Metadata.Machine.DescriptionKey] = item.GameDescription, + }; + + var datItem = ConvertToInternalModel(item); + switch (datItem) + { + case Models.Metadata.Disk disk: + machine[Models.Metadata.Machine.DiskKey] = new Models.Metadata.Disk[] { disk }; + break; + + case Models.Metadata.Media media: + machine[Models.Metadata.Machine.MediaKey] = new Models.Metadata.Media[] { media }; + break; + + case Models.Metadata.Rom rom: + machine[Models.Metadata.Machine.RomKey] = new Models.Metadata.Rom[] { rom }; + break; + } + + return machine; + } + +#if NET48 + /// + /// Convert from to + /// + private static Models.Metadata.DatItem ConvertToInternalModel(Row item) + { + switch (item.Type) + { + case "disk": + return new Models.Metadata.Disk + { + [Models.Metadata.Disk.NameKey] = item.DiskName, + [Models.Metadata.Disk.MD5Key] = item.MD5, + [Models.Metadata.Disk.SHA1Key] = item.SHA1, + [Models.Metadata.Disk.StatusKey] = item.Status, + }; + + case "media": + return new Models.Metadata.Media + { + [Models.Metadata.Media.NameKey] = item.DiskName, + [Models.Metadata.Media.MD5Key] = item.MD5, + [Models.Metadata.Media.SHA1Key] = item.SHA1, + [Models.Metadata.Media.SHA256Key] = item.SHA256, + [Models.Metadata.Media.SpamSumKey] = item.SpamSum, + }; + + case "rom": + return new Models.Metadata.Rom + { + [Models.Metadata.Rom.NameKey] = item.RomName, + [Models.Metadata.Rom.SizeKey] = item.Size, + [Models.Metadata.Rom.CRCKey] = item.CRC, + [Models.Metadata.Rom.MD5Key] = item.MD5, + [Models.Metadata.Rom.SHA1Key] = item.SHA1, + [Models.Metadata.Rom.SHA256Key] = item.SHA256, + [Models.Metadata.Rom.SHA384Key] = item.SHA384, + [Models.Metadata.Rom.SHA512Key] = item.SHA512, + [Models.Metadata.Rom.SpamSumKey] = item.SpamSum, + [Models.Metadata.Rom.StatusKey] = item.Status, + }; + + default: + return null; + } + } +#else + /// + /// Convert from to + /// + private static Models.Metadata.DatItem? ConvertToInternalModel(Row item) + { + return item.Type switch + { + "disk" => new Models.Metadata.Disk + { + [Models.Metadata.Disk.NameKey] = item.DiskName, + [Models.Metadata.Disk.MD5Key] = item.MD5, + [Models.Metadata.Disk.SHA1Key] = item.SHA1, + [Models.Metadata.Disk.StatusKey] = item.Status, + }, + "media" => new Models.Metadata.Media + { + [Models.Metadata.Media.NameKey] = item.DiskName, + [Models.Metadata.Media.MD5Key] = item.MD5, + [Models.Metadata.Media.SHA1Key] = item.SHA1, + [Models.Metadata.Media.SHA256Key] = item.SHA256, + [Models.Metadata.Media.SpamSumKey] = item.SpamSum, + }, + "rom" => new Models.Metadata.Rom + { + [Models.Metadata.Rom.NameKey] = item.RomName, + [Models.Metadata.Rom.SizeKey] = item.Size, + [Models.Metadata.Rom.CRCKey] = item.CRC, + [Models.Metadata.Rom.MD5Key] = item.MD5, + [Models.Metadata.Rom.SHA1Key] = item.SHA1, + [Models.Metadata.Rom.SHA256Key] = item.SHA256, + [Models.Metadata.Rom.SHA384Key] = item.SHA384, + [Models.Metadata.Rom.SHA512Key] = item.SHA512, + [Models.Metadata.Rom.SpamSumKey] = item.SpamSum, + [Models.Metadata.Rom.StatusKey] = item.Status, + }, + _ => null, + }; + } +#endif + } +} \ No newline at end of file diff --git a/Files/SeparatedValue.Deserializer.cs b/Files/SeparatedValue.Deserializer.cs new file mode 100644 index 00000000..b4a99a05 --- /dev/null +++ b/Files/SeparatedValue.Deserializer.cs @@ -0,0 +1,20 @@ +using SabreTools.Models.SeparatedValue; + +namespace SabreTools.Serialization.Files +{ + public partial class SeparatedValue : IFileSerializer + { + /// +#if NET48 + public MetadataFile Deserialize(string path) +#else + public MetadataFile? Deserialize(string? path) +#endif + { + using (var stream = PathProcessor.OpenStream(path)) + { + return new Streams.SeparatedValue().Deserialize(stream); + } + } + } +} \ No newline at end of file diff --git a/Files/SeparatedValue.Serializer.cs b/Files/SeparatedValue.Serializer.cs new file mode 100644 index 00000000..c50cfd03 --- /dev/null +++ b/Files/SeparatedValue.Serializer.cs @@ -0,0 +1,27 @@ +using SabreTools.Models.SeparatedValue; + +namespace SabreTools.Serialization.Files +{ + public partial class SeparatedValue : IFileSerializer + { + /// +#if NET48 + public bool Serialize(MetadataFile obj, string path) +#else + public bool Serialize(MetadataFile? obj, string? path) +#endif + { + using (var stream = new Streams.SeparatedValue().Serialize(obj)) + { + if (stream == null) + return false; + + using (var fs = System.IO.File.OpenWrite(path)) + { + stream.CopyTo(fs); + return true; + } + } + } + } +} \ No newline at end of file diff --git a/SeparatedValue.cs b/SeparatedValue.cs new file mode 100644 index 00000000..3795f0ea --- /dev/null +++ b/SeparatedValue.cs @@ -0,0 +1,12 @@ +namespace SabreTools.Serialization +{ + /// + /// Represents separated-value variants + /// + public static class SeparatedValue + { + public const int HeaderWithoutExtendedHashesCount = 14; + + public const int HeaderWithExtendedHashesCount = 17; + } +} \ No newline at end of file diff --git a/Streams/SeparatedValue.Deserializer.cs b/Streams/SeparatedValue.Deserializer.cs new file mode 100644 index 00000000..a1a48127 --- /dev/null +++ b/Streams/SeparatedValue.Deserializer.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using SabreTools.IO.Readers; +using SabreTools.Models.SeparatedValue; + +namespace SabreTools.Serialization.Streams +{ + public partial class SeparatedValue : IStreamSerializer + { + /// +#if NET48 + public MetadataFile Deserialize(Stream data) => Deserialize(data, ','); +#else + public MetadataFile? Deserialize(Stream? data) => Deserialize(data, ','); +#endif + + /// +#if NET48 + public MetadataFile Deserialize(Stream data, char delim) +#else + public MetadataFile? Deserialize(Stream? data, char delim) +#endif + { + // If the stream is null + if (data == null) + return default; + + // Setup the reader and output + var reader = new SeparatedValueReader(data, Encoding.UTF8) + { + Header = true, + Separator = delim, + VerifyFieldCount = false, + }; + var dat = new MetadataFile(); + + // Read the header values first + if (!reader.ReadHeader() || reader.HeaderValues == null) + return null; + + dat.Header = reader.HeaderValues.ToArray(); + + // Loop through the rows and parse out values + var rows = new List(); + while (!reader.EndOfStream) + { + // If we have no next line + if (!reader.ReadNextLine() || reader.Line == null) + break; + + // Parse the line into a row +#if NET48 + Row row = null; +#else + Row? row = null; +#endif + if (reader.Line.Count < Serialization.SeparatedValue.HeaderWithExtendedHashesCount) + { + row = new Row + { + FileName = reader.Line[0], + InternalName = reader.Line[1], + Description = reader.Line[2], + GameName = reader.Line[3], + GameDescription = reader.Line[4], + Type = reader.Line[5], + RomName = reader.Line[6], + DiskName = reader.Line[7], + Size = reader.Line[8], + CRC = reader.Line[9], + MD5 = reader.Line[10], + SHA1 = reader.Line[11], + SHA256 = reader.Line[12], + Status = reader.Line[13], + }; + + // If we have additional fields + if (reader.Line.Count > Serialization.SeparatedValue.HeaderWithoutExtendedHashesCount) + row.ADDITIONAL_ELEMENTS = reader.Line.Skip(Serialization.SeparatedValue.HeaderWithoutExtendedHashesCount).ToArray(); + } + else + { + row = new Row + { + FileName = reader.Line[0], + InternalName = reader.Line[1], + Description = reader.Line[2], + GameName = reader.Line[3], + GameDescription = reader.Line[4], + Type = reader.Line[5], + RomName = reader.Line[6], + DiskName = reader.Line[7], + Size = reader.Line[8], + CRC = reader.Line[9], + MD5 = reader.Line[10], + SHA1 = reader.Line[11], + SHA256 = reader.Line[12], + SHA384 = reader.Line[13], + SHA512 = reader.Line[14], + SpamSum = reader.Line[15], + Status = reader.Line[16], + }; + + // If we have additional fields + if (reader.Line.Count > Serialization.SeparatedValue.HeaderWithExtendedHashesCount) + row.ADDITIONAL_ELEMENTS = reader.Line.Skip(Serialization.SeparatedValue.HeaderWithExtendedHashesCount).ToArray(); + } + rows.Add(row); + } + + // Assign the rows to the Dat and return + dat.Row = rows.ToArray(); + return dat; + } + } +} \ No newline at end of file diff --git a/Streams/SeparatedValue.Serializer.cs b/Streams/SeparatedValue.Serializer.cs new file mode 100644 index 00000000..66a6a816 --- /dev/null +++ b/Streams/SeparatedValue.Serializer.cs @@ -0,0 +1,128 @@ +using System.IO; +using System.Linq; +using System.Text; +using SabreTools.IO.Writers; +using SabreTools.Models.SeparatedValue; + +namespace SabreTools.Serialization.Streams +{ + public partial class SeparatedValue : IStreamSerializer + { + /// +#if NET48 + public Stream Serialize(MetadataFile obj) => Serialize(obj, ','); +#else + public Stream? Serialize(MetadataFile? obj) => Serialize(obj, ','); +#endif + + /// +#if NET48 + public Stream Serialize(MetadataFile obj, char delim) +#else + public Stream? Serialize(MetadataFile? obj, char delim) +#endif + { + // If the metadata file is null + if (obj == null) + return null; + + // Setup the writer and output + var stream = new MemoryStream(); + var writer = new SeparatedValueWriter(stream, Encoding.UTF8) { Separator = delim, Quotes = true }; + + // TODO: Include flag to write out long or short header + // Write the short header + WriteHeader(writer); + + // Write out the rows, if they exist + WriteRows(obj.Row, writer); + + // Return the stream + stream.Seek(0, SeekOrigin.Begin); + return stream; + } + + /// + /// Write header information to the current writer + /// + /// SeparatedValueWriter representing the output + private static void WriteHeader(SeparatedValueWriter writer) + { +#if NET48 + var headerArray = new string[] +#else + var headerArray = new string?[] +#endif + { + "File Name", + "Internal Name", + "Description", + "Game Name", + "Game Description", + "Type", + "Rom Name", + "Disk Name", + "Size", + "CRC", + "MD5", + "SHA1", + "SHA256", + //"SHA384", + //"SHA512", + //"SpamSum", + "Status", + }; + + writer.WriteHeader(headerArray); + writer.Flush(); + } + + /// + /// Write rows information to the current writer + /// + /// Array of Row objects representing the rows information + /// SeparatedValueWriter representing the output +#if NET48 + private static void WriteRows(Row[] rows, SeparatedValueWriter writer) +#else + private static void WriteRows(Row[]? rows, SeparatedValueWriter writer) +#endif + { + // If the games information is missing, we can't do anything + if (rows == null || !rows.Any()) + return; + + // Loop through and write out the rows + foreach (var row in rows) + { +#if NET48 + var rowArray = new string[] +#else + var rowArray = new string?[] +#endif + { + row.FileName, + row.InternalName, + row.Description, + row.GameName, + row.GameDescription, + row.Type, + row.RomName, + row.DiskName, + row.Size, + row.CRC, + row.MD5, + row.SHA1, + row.SHA256, + //row.SHA384, + //row.SHA512, + //row.SpamSum, + row.Status, + }; + + writer.WriteValues(rowArray); + writer.Flush(); + } + } + } +} \ No newline at end of file