using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Threading; using System.Threading.Tasks; using Tools; namespace napdump { [Flags] public enum WritingOption : uint { /// /// Serialize dump IBInfos /// Dump = 1 << 0, /// /// Serialize as binary IBInfos /// Binary = 1 << 1, /// /// Serialize as text IBInfos /// Text = 1<<2, /// /// (flag) GZ compress IBInfos /// Gz = 1 << 3, /// /// (flag) File system serialise format /// Fs = 1 << 4, /// /// Archive serialise format /// Ar = 1 << 5, } public static class BinWriter { public static WritingOption Options { get; private set; } public static void Initialise(WritingOption opt) { Options = opt; } public static async Task SaveOptions(Stream to) { await to.WriteValueUnmanagedAsync((uint)Options); } public static async Task LoadOptions(Stream from, CancellationToken token = default) { var opt = (WritingOption)await from.ReadValueUnmanagedAsync(token); Initialise(opt); return opt; } public static async Task WriteEntry(Stream to, T toWrite, CancellationToken token=default) where T : ISerializable, IBinaryWritable, ITextWritable { var Options = BinWriter.Options; if (Options.HasFlag(WritingOption.Gz)) to = new GZipStream(to, CompressionLevel.Optimal); try { if (Options.HasFlag(WritingOption.Dump)) { var binf = new BinaryFormatter(); await Task.Yield(); token.ThrowIfCancellationRequested(); binf.Serialize(to, toWrite); } else if (Options.HasFlag(WritingOption.Binary)) { await to.WriteStringAsync(toWrite.GetType().AssemblyQualifiedName, token); await toWrite.WriteBinaryAsync(to, token); } else if (Options.HasFlag(WritingOption.Text)) { using (var textWriter = new StreamWriter(to)) { await textWriter.WriteLineAsync($"Type={toWrite.GetType().AssemblyQualifiedName}".AsMemory(), token); await textWriter.WriteLineAsync(new ReadOnlyMemory(), token); await toWrite.WriteTextAsync(textWriter, token); } } } finally { if (Options.HasFlag(WritingOption.Gz)) await to.DisposeAsync(); } } public static async Task ReadEntry(Stream from, CancellationToken token = default) { var Options = BinWriter.Options; if (Options.HasFlag(WritingOption.Gz)) from = new GZipStream(from, CompressionMode.Decompress); try { object value = default; if (Options.HasFlag(WritingOption.Dump)) { var binf = new BinaryFormatter(); await Task.Yield(); token.ThrowIfCancellationRequested(); value= binf.Deserialize(from); } else if (Options.HasFlag(WritingOption.Binary)) { string typeName = await from.ReadStringAsync(token); Type type = Type.GetType(typeName); value = Activator.CreateInstance(type); await ((value as IBinaryWritable)?.ReadBinaryAsync(from, token) ?? throw new InvalidDataException("Bad type definition: " + type.FullName + " does not implement IBinaryWritable")); } else if (Options.HasFlag(WritingOption.Text)) { using (var reader = new StreamReader(from)) { var line0 = await reader.ReadLineAsync(); token.ThrowIfCancellationRequested(); var def = line0.Split(new char[] { '=' }, 2); await reader.ReadLineAsync(); token.ThrowIfCancellationRequested(); if (def.Length != 2||def[0].Trim().ToLower()!="type") throw new InvalidDataException("Bad type definition"); Type type = Type.GetType(def[1].Trim()); value = Activator.CreateInstance(type); await ((value as ITextWritable)?.ReadTextAsync(reader, token) ?? throw new InvalidDataException("Bad type definition: "+type.FullName+" does not implement ITextWritable")); } } return value; } finally { if (Options.HasFlag(WritingOption.Gz)) await from.DisposeAsync(); } } } public interface IBinaryWritable { ValueTask WriteBinaryAsync(Stream to, CancellationToken cancel); ValueTask ReadBinaryAsync(Stream from, CancellationToken cancel); } public interface ITextWritable { ValueTask WriteTextAsync(StreamWriter to, CancellationToken cancel); ValueTask ReadTextAsync(StreamReader from, CancellationToken cancel); } }