go-hep.org/x/hep@v0.38.1/rio/writer.go (about)

     1  // Copyright ©2015 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package rio
     6  
     7  import (
     8  	"bufio"
     9  	"compress/flate"
    10  	"io"
    11  
    12  	riobin "codeberg.org/gonuts/binary"
    13  )
    14  
    15  type cwriter struct {
    16  	w *bufio.Writer
    17  	n int64
    18  }
    19  
    20  func (w *cwriter) Write(data []byte) (int, error) {
    21  	n, err := w.w.Write(data)
    22  	w.n += int64(n)
    23  	return n, err
    24  }
    25  
    26  func (w *cwriter) Flush() error {
    27  	return w.w.Flush()
    28  }
    29  
    30  // Writer is a rio write-only stream
    31  type Writer struct {
    32  	w *cwriter
    33  
    34  	options Options
    35  	version Version
    36  
    37  	recs    map[string]*Record
    38  	offsets map[string][]Span
    39  	closed  bool
    40  }
    41  
    42  // NewWriter returns a new write-only rio stream
    43  func NewWriter(w io.Writer) (*Writer, error) {
    44  	ww := &cwriter{bufio.NewWriter(w), 0}
    45  	// a rio stream starts with rio magic.
    46  	_, err := ww.Write(rioMagic[:])
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	return &Writer{
    52  		w:       ww,
    53  		options: NewOptions(CompressDefault, flate.DefaultCompression, 0),
    54  		version: 1,
    55  		recs:    make(map[string]*Record),
    56  		offsets: make(map[string][]Span),
    57  	}, nil
    58  }
    59  
    60  // SetCompressor enables compression and sets the compression method.
    61  func (w *Writer) SetCompressor(compr CompressorKind, lvl int) error {
    62  	var err error
    63  
    64  	// FIXME(sbinet) handle codec (gob|cbor|xdr|riobin|...)
    65  	codec := 0
    66  	w.options = NewOptions(compr, lvl, codec)
    67  
    68  	return err
    69  }
    70  
    71  // Record adds a Record to the list of records to write or
    72  // returns the Record with that name.
    73  func (w *Writer) Record(name string) *Record {
    74  	rec, ok := w.recs[name]
    75  	if !ok {
    76  		rec = newRecord(name, w.options)
    77  		rec.w = w
    78  		w.recs[name] = rec
    79  	}
    80  	return rec
    81  }
    82  
    83  // Close finishes writing the rio write-only stream.
    84  // It does not (and can not) close the underlying writer.
    85  func (w *Writer) Close() error {
    86  	if w.closed {
    87  		return nil
    88  	}
    89  	w.closed = true
    90  	pos := w.w.n
    91  	var meta Metadata
    92  	for _, rec := range w.recs {
    93  		var blocks []BlockDesc
    94  		for _, blk := range rec.blocks {
    95  			blocks = append(blocks, BlockDesc{blk.Name(), nameFromType(blk.typ)})
    96  		}
    97  		meta.Records = append(
    98  			meta.Records,
    99  			RecordDesc{
   100  				Name:   rec.Name(),
   101  				Blocks: blocks,
   102  			},
   103  		)
   104  	}
   105  	meta.Offsets = w.offsets
   106  
   107  	err := w.WriteValue(MetaRecord, &meta)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	ftr := rioFooter{
   113  		Header: rioHeader{
   114  			Len:   uint32(ftrSize),
   115  			Frame: ftrFrame,
   116  		},
   117  		Meta: pos,
   118  	}
   119  	err = ftr.RioMarshal(w.w)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	return w.w.Flush()
   124  }
   125  
   126  // writeRecord writes all the record data
   127  func (w *Writer) writeRecord(rec *Record, hdr, data []byte) error {
   128  	var err error
   129  	beg := w.w.n
   130  
   131  	_, err = w.w.Write(hdr)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	_, err = w.w.Write(data)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	n := rioAlignU32(rec.raw.Header.Len)
   142  	if n != rec.raw.Header.Len {
   143  		_, err = w.w.Write(make([]byte, int(n-rec.raw.Header.Len)))
   144  	}
   145  
   146  	end := w.w.n
   147  	w.offsets[rec.Name()] = append(w.offsets[rec.Name()], Span{beg, end - beg})
   148  	return err
   149  }
   150  
   151  // WriteValue writes a value to the stream.
   152  // The value is written to a record named `name` with one block `name`.
   153  func (w *Writer) WriteValue(name string, value any) error {
   154  	var err error
   155  
   156  	rec := w.Record(name)
   157  	err = rec.Connect(name, value)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	blk := rec.Block(name)
   163  	err = blk.Write(value)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	err = rec.Write()
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	return err
   174  }
   175  
   176  // encoder manages the encoding of data values into rioRecords
   177  type encoder struct {
   178  	w io.Writer
   179  }
   180  
   181  func (enc *encoder) Encode(v any) error {
   182  	switch v := v.(type) {
   183  	case Marshaler:
   184  		return v.RioMarshal(enc.w)
   185  	}
   186  
   187  	e := riobin.NewEncoder(enc.w)
   188  	e.Order = Endian
   189  	return e.Encode(v)
   190  }