/* * Copyright 2016 The Cartographer Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System.IO.Compression; using System.Text; using System.Text.Json; namespace CartographerSharp.IO; /// /// A simple writer of a compressed sequence of protocol buffer messages to a file. /// The format is not intended to be compatible with any other format used outside of Cartographer. /// public class ProtoStreamWriter : IProtoStreamWriter, IDisposable { // First eight bytes to identify our proto stream format. private const ulong Magic = 0x7b1d1f7b5bf501db; private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = false, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.Never }; private readonly FileStream _fileStream; private bool _disposed; private bool _magicWritten; public ProtoStreamWriter(string filename) { if (string.IsNullOrEmpty(filename)) throw new ArgumentException("Filename cannot be null or empty", nameof(filename)); _fileStream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read); WriteMagic(); } private void WriteMagic() { WriteSizeAsLittleEndian(Magic); _magicWritten = true; } private void WriteSizeAsLittleEndian(ulong size) { for (int i = 0; i < 8; i++) { _fileStream.WriteByte((byte)(size & 0xff)); size >>= 8; } } private void Write(string uncompressedData) { if (!_magicWritten) { WriteMagic(); } // Compress using GZip byte[] uncompressedBytes = Encoding.UTF8.GetBytes(uncompressedData); byte[] compressedBytes; using (var memoryStream = new MemoryStream()) { using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Compress, leaveOpen: true)) { gzipStream.Write(uncompressedBytes, 0, uncompressedBytes.Length); } compressedBytes = memoryStream.ToArray(); } // Write compressed size WriteSizeAsLittleEndian((ulong)compressedBytes.Length); // Write compressed data _fileStream.Write(compressedBytes, 0, compressedBytes.Length); } /// /// Serializes, compresses and writes the proto to the file. /// public void WriteProto(T proto) { ArgumentNullException.ThrowIfNull(proto, nameof(proto)); // Serialize to JSON (we use System.Text.Json instead of Google.Protobuf) string json = JsonSerializer.Serialize(proto, JsonOptions); Write(json); } /// /// This should be called to check whether writing was successful. /// public bool Close() { if (_disposed) return false; _fileStream.Flush(); _fileStream.Close(); _disposed = true; // Prevent Dispose() from calling Close() again (double-close would throw on Flush) return true; } public void Dispose() { if (!_disposed) { Close(); _fileStream?.Dispose(); _disposed = true; } GC.SuppressFinalize(this); } }