/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 * * https://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; using System.Collections.Generic; using System.Globalization; using System.IO; using Avro.Generic; using Avro.IO; namespace Avro.File { /// /// Stores in a file a sequence of data conforming to a schema. The schema is stored in the file /// with the data. Each datum in a file is of the same schema. Data is written with a /// . Data is grouped into blocks. A synchronization marker is /// written between blocks, so that files may be split. Blocks may be compressed. Extensible /// metadata is stored at the end of the file. Files may be appended to. /// /// Type of datum to write to the file. public class DataFileWriter : IFileWriter { private Schema _schema; private Codec _codec; private Stream _stream; private bool _leaveOpen; private MemoryStream _blockStream; private MemoryStream _compressedBlockStream; private Encoder _encoder, _blockEncoder; private DatumWriter _writer; private byte[] _syncData; private bool _isOpen; private bool _headerWritten; private int _blockCount; private int _syncInterval; private IDictionary _metaData; /// /// Open a new writer instance to write /// to a file path, using a Null codec /// /// Datum writer to use. /// Path to the file. /// A new file writer. public static IFileWriter OpenWriter(DatumWriter writer, string path) { return OpenWriter(writer, new FileStream(path, FileMode.Create), Codec.CreateCodec(Codec.Type.Null)); } /// /// Open a new writer instance to write /// to an output stream, using a Null codec /// /// Datum writer to use. /// Stream to write to. /// A new file writer. public static IFileWriter OpenWriter(DatumWriter writer, Stream outStream) { return OpenWriter(writer, outStream, Codec.CreateCodec(Codec.Type.Null)); } /// /// Open a new writer instance to write /// to an output stream, using a Null codec /// /// Datum writer to use. /// Stream to write to. /// Leave the stream open after disposing the object /// A new file writer. public static IFileWriter OpenWriter(DatumWriter writer, Stream outStream, bool leaveOpen) { return OpenWriter(writer, outStream, Codec.CreateCodec(Codec.Type.Null), leaveOpen); } /// /// Open a new writer instance to write /// to a file path with a specified codec /// /// Datum writer to use. /// Path to the file. /// Codec to use when writing. /// A new file writer. public static IFileWriter OpenWriter(DatumWriter writer, string path, Codec codec) { return OpenWriter(writer, new FileStream(path, FileMode.Create), codec); } /// /// Open a new writer instance to write /// to an output stream with a specified codec /// /// Datum writer to use. /// Stream to write to. /// Codec to use when writing. /// A new file writer. public static IFileWriter OpenWriter(DatumWriter writer, Stream outStream, Codec codec) { return new DataFileWriter(writer).Create(writer.Schema, outStream, codec, false); } /// /// Open a new writer instance to write /// to an output stream with a specified codec /// /// Datum writer to use. /// Stream to write to. /// Codec to use when writing. /// Leave the stream open after disposing the object /// A new file writer. public static IFileWriter OpenWriter(DatumWriter writer, Stream outStream, Codec codec, bool leaveOpen) { return new DataFileWriter(writer).Create(writer.Schema, outStream, codec, leaveOpen); } /// /// Open a new writer instance to append to a file path. /// /// Datum writer to use. /// Path to the file. /// A new file writer. public static IFileWriter OpenAppendWriter(DatumWriter writer, string path) { return new DataFileWriter(writer).AppendTo(path); } /// /// Open a new writer instance to append to an output stream. /// Both in and out streams must point to the same file. /// /// Datum writer to use. /// reading the existing file. /// stream to write to, positioned at the end of the existing file. /// A new file writer. public static IFileWriter OpenAppendWriter(DatumWriter writer, Stream inStream, Stream outStream) { if (!inStream.CanRead) { throw new AvroRuntimeException($"{nameof(inStream)} must have Read access"); } if (!outStream.CanWrite) { throw new AvroRuntimeException($"{nameof(outStream)} must have Write access"); } return new DataFileWriter(writer).AppendTo(inStream, outStream); } private DataFileWriter(DatumWriter writer) { _writer = writer; _syncInterval = DataFileConstants.DefaultSyncInterval; } /// public bool IsReservedMeta(string key) { return key.StartsWith(DataFileConstants.MetaDataReserved, StringComparison.Ordinal); } /// public void SetMeta(string key, byte[] value) { if (IsReservedMeta(key)) { throw new AvroRuntimeException("Cannot set reserved meta key: " + key); } _metaData.Add(key, value); } /// public void SetMeta(string key, long value) { try { SetMeta(key, GetByteValue(value.ToString(CultureInfo.InvariantCulture))); } catch (Exception e) { throw new AvroRuntimeException(e.Message, e); } } /// public void SetMeta(string key, string value) { try { SetMeta(key, GetByteValue(value)); } catch (Exception e) { throw new AvroRuntimeException(e.Message, e); } } /// public void SetSyncInterval(int syncInterval) { if (syncInterval < 32 || syncInterval > (1 << 30)) { throw new AvroRuntimeException("Invalid sync interval value: " + syncInterval); } _syncInterval = syncInterval; } /// public void Append(T datum) { AssertOpen(); EnsureHeader(); long usedBuffer = _blockStream.Position; try { _writer.Write(datum, _blockEncoder); } catch (Exception e) { _blockStream.Position = usedBuffer; throw new AvroRuntimeException("Error appending datum to writer", e); } _blockCount++; WriteIfBlockFull(); } private IFileWriter AppendTo(string path) { using (var inStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { var outStream = new FileStream(path, FileMode.Append); return AppendTo(inStream, outStream); } // outStream does not need to be closed here. It will be closed by invoking Close() // of this writer. } private IFileWriter AppendTo(Stream inStream, Stream outStream) { using (var dataFileReader = DataFileReader.OpenReader(inStream)) { var header = dataFileReader.GetHeader(); _schema = header.Schema; _syncData = header.SyncData; _metaData = header.MetaData; } if (_metaData.TryGetValue(DataFileConstants.MetaDataCodec, out byte[] codecBytes)) { string codec = System.Text.Encoding.UTF8.GetString(codecBytes); _codec = Codec.CreateCodecFromString(codec); } else { _codec = Codec.CreateCodec(Codec.Type.Null); } _headerWritten = true; _stream = outStream; _stream.Seek(0, SeekOrigin.End); Init(); return this; } private void EnsureHeader() { if (!_headerWritten) { WriteHeader(); _headerWritten = true; } } /// public void Flush() { EnsureHeader(); SyncInternal(); } /// public long Sync() { SyncInternal(); return _stream.Position; } private void SyncInternal() { AssertOpen(); WriteBlock(); } /// public void Close() { EnsureHeader(); Flush(); _stream.Flush(); if (!_leaveOpen) _stream.Close(); _blockStream.Dispose(); _compressedBlockStream.Dispose(); _isOpen = false; } private void WriteHeader() { _encoder.WriteFixed(DataFileConstants.Magic); WriteMetaData(); WriteSyncData(); } private void Init() { _blockCount = 0; _encoder = new BinaryEncoder(_stream); _blockStream = new MemoryStream(); _blockEncoder = new BinaryEncoder(_blockStream); _compressedBlockStream = new MemoryStream(); if (_codec == null) _codec = Codec.CreateCodec(Codec.Type.Null); _isOpen = true; } private void AssertOpen() { if (!_isOpen) throw new AvroRuntimeException("Cannot complete operation: avro file/stream not open"); } private IFileWriter Create(Schema schema, Stream outStream, Codec codec, bool leaveOpen) { _codec = codec; _stream = outStream; _metaData = new Dictionary(); _schema = schema; _leaveOpen = leaveOpen; Init(); return this; } private void WriteMetaData() { // Add sync, code & schema to metadata GenerateSyncData(); //SetMetaInternal(DataFileConstants.MetaDataSync, _syncData); - Avro 1.5.4 C SetMetaInternal(DataFileConstants.MetaDataCodec, GetByteValue(_codec.GetName())); SetMetaInternal(DataFileConstants.MetaDataSchema, GetByteValue(_schema.ToString())); // write metadata int size = _metaData.Count; _encoder.WriteInt(size); foreach (KeyValuePair metaPair in _metaData) { _encoder.WriteString(metaPair.Key); _encoder.WriteBytes(metaPair.Value); } _encoder.WriteMapEnd(); } private void WriteIfBlockFull() { if (BufferInUse() >= _syncInterval) WriteBlock(); } private long BufferInUse() { return _blockStream.Position; } private void WriteBlock() { if (_blockCount > 0) { // write count _encoder.WriteLong(_blockCount); // write data _codec.Compress(_blockStream, _compressedBlockStream); _encoder.WriteBytes(_compressedBlockStream.GetBuffer(), 0, (int)_compressedBlockStream.Length); // write sync marker _encoder.WriteFixed(_syncData); // reset / re-init block _blockCount = 0; _blockStream.SetLength(0); } } private void WriteSyncData() { _encoder.WriteFixed(_syncData); } private void GenerateSyncData() { _syncData = new byte[16]; Random random = new Random(); random.NextBytes(_syncData); } private void SetMetaInternal(string key, byte[] value) { _metaData.Add(key, value); } private byte[] GetByteValue(string value) { return System.Text.Encoding.UTF8.GetBytes(value); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases resources associated with this . /// /// /// True if called from ; false otherwise. /// protected virtual void Dispose(bool disposing) { Close(); if (disposing && !_leaveOpen) _stream.Dispose(); } } }