go-hep.org/x/hep@v0.38.1/rio/record.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  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"reflect"
    12  )
    13  
    14  // Record manages and describes blocks of data
    15  type Record struct {
    16  	unpack bool           // whether to unpack incoming/outcoming records
    17  	blocks []Block        // connected blocks
    18  	bmap   map[string]int // connected blocks
    19  
    20  	w *Writer
    21  	r *Reader
    22  
    23  	cw Compressor
    24  	xr Decompressor
    25  
    26  	raw rioRecord
    27  }
    28  
    29  func newRecord(name string, options Options) *Record {
    30  
    31  	rec := Record{
    32  		unpack: false,
    33  		blocks: make([]Block, 0, 2),
    34  		bmap:   make(map[string]int, 2),
    35  		raw: rioRecord{
    36  			Header: rioHeader{
    37  				Len:   0,
    38  				Frame: recFrame,
    39  			},
    40  			Options: options,
    41  			Name:    name,
    42  		},
    43  	}
    44  
    45  	return &rec
    46  }
    47  
    48  // Connect connects a Block to this Record (for reading or writing)
    49  func (rec *Record) Connect(name string, ptr any) error {
    50  	_, dup := rec.bmap[name]
    51  	if dup {
    52  		return fmt.Errorf("rio: block [%s] already connected to record [%s]", name, rec.Name())
    53  	}
    54  
    55  	version := Version(0)
    56  	switch t := ptr.(type) {
    57  	case Streamer:
    58  		version = t.RioVersion()
    59  	}
    60  
    61  	rec.bmap[name] = len(rec.blocks)
    62  	rec.blocks = append(
    63  		rec.blocks,
    64  		newBlock(name, version),
    65  	)
    66  	rec.blocks[rec.bmap[name]].typ = reflect.TypeOf(ptr)
    67  
    68  	return nil
    69  }
    70  
    71  // Block returns the block named name for reading or writing
    72  // Block returns nil if the block doesn't exist
    73  func (rec *Record) Block(name string) *Block {
    74  	i, ok := rec.bmap[name]
    75  	if !ok {
    76  		return nil
    77  	}
    78  	block := &rec.blocks[i]
    79  	return block
    80  }
    81  
    82  // Write writes data to the Writer, in the rio format
    83  func (rec *Record) Write() error {
    84  	var err error
    85  	xbuf := new(bytes.Buffer) // FIXME(sbinet): use a sync.Pool
    86  
    87  	for i := range rec.blocks {
    88  		block := &rec.blocks[i]
    89  		err = block.raw.RioMarshal(xbuf)
    90  		if err != nil {
    91  			return fmt.Errorf("rio: error writing block #%d (%s): %w", i, block.Name(), err)
    92  		}
    93  	}
    94  
    95  	xlen := xbuf.Len()
    96  
    97  	var cbuf *bytes.Buffer
    98  	switch {
    99  	case rec.Compress():
   100  		cbuf = new(bytes.Buffer)
   101  		switch {
   102  		case rec.cw == nil:
   103  			compr := rec.raw.Options.CompressorKind()
   104  			cw, err := compr.NewCompressor(cbuf, rec.raw.Options)
   105  			if err != nil {
   106  				return err
   107  			}
   108  			rec.cw = cw
   109  		default:
   110  			err = rec.cw.Reset(cbuf)
   111  			if err != nil {
   112  				return err
   113  			}
   114  		}
   115  		_, err = io.CopyBuffer(rec.cw, xbuf, make([]byte, 16*1024*1024))
   116  		if err != nil {
   117  			return fmt.Errorf("rio: error compressing blocks: %w", err)
   118  		}
   119  		err = rec.cw.Flush()
   120  		if err != nil {
   121  			return fmt.Errorf("rio: error compressing blocks: %w", err)
   122  		}
   123  
   124  	default:
   125  		cbuf = xbuf
   126  	}
   127  
   128  	clen := cbuf.Len()
   129  
   130  	rec.raw.Header.Len = uint32(clen)
   131  	rec.raw.CLen = uint32(clen)
   132  	rec.raw.XLen = uint32(xlen)
   133  
   134  	buf := new(bytes.Buffer)
   135  	err = rec.raw.RioMarshal(buf)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	err = rec.w.writeRecord(rec, buf.Bytes(), cbuf.Bytes())
   141  
   142  	return err
   143  }
   144  
   145  // Read reads data from the Reader, in the rio format
   146  func (rec *Record) Read() error {
   147  	return rec.readRecord(rec.r.r)
   148  }
   149  
   150  // readRecord reads data from the Reader r, in the rio format
   151  func (rec *Record) readRecord(r io.Reader) error {
   152  	err := rec.raw.RioUnmarshal(r)
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	clen := int64(rioAlignU32(rec.raw.CLen))
   158  	if !rec.unpack {
   159  		switch r := r.(type) {
   160  		case io.Seeker:
   161  			_, err = r.Seek(clen, 0)
   162  		default:
   163  			_, err = io.CopyN(io.Discard, r, clen)
   164  		}
   165  		return err
   166  	}
   167  
   168  	return rec.readBlocks(r)
   169  }
   170  
   171  // readBlocks reads the blocks data from the Reader
   172  func (rec *Record) readBlocks(r io.Reader) error {
   173  	var err error
   174  	clen := int64(rioAlignU32(rec.raw.CLen))
   175  
   176  	lr := &io.LimitedReader{
   177  		R: r,
   178  		N: clen,
   179  	}
   180  
   181  	// decompression
   182  	switch {
   183  	case rec.xr == nil:
   184  		compr := rec.raw.Options.CompressorKind()
   185  		xr, err := compr.NewDecompressor(lr)
   186  		if err != nil {
   187  			return err
   188  		}
   189  		rec.xr = xr
   190  		lr = &io.LimitedReader{
   191  			R: xr,
   192  			N: int64(rec.raw.XLen),
   193  		}
   194  
   195  	default:
   196  		err = rec.xr.Reset(lr)
   197  		if err != nil {
   198  			return err
   199  		}
   200  		lr = &io.LimitedReader{
   201  			R: rec.xr,
   202  			N: int64(rec.raw.XLen),
   203  		}
   204  	}
   205  
   206  	for lr.N > 0 {
   207  		blk := newBlock("", 0)
   208  		err = blk.raw.RioUnmarshal(lr)
   209  		if err == io.EOF {
   210  			err = nil
   211  			break
   212  		}
   213  		if err != nil {
   214  			return err
   215  		}
   216  		n := blk.Name()
   217  		if i, ok := rec.bmap[n]; ok {
   218  			rec.blocks[i] = blk
   219  		} else {
   220  			rec.bmap[n] = len(rec.blocks)
   221  			rec.blocks = append(rec.blocks, blk)
   222  		}
   223  	}
   224  
   225  	if lr.N > 0 {
   226  		return fmt.Errorf("rio: record read too few bytes (want=%d. got=%d)", clen, clen-lr.N)
   227  	}
   228  	return err
   229  }
   230  
   231  // Name returns the name of this record
   232  func (rec *Record) Name() string {
   233  	return rec.raw.Name
   234  }
   235  
   236  // Unpack returns whether to unpack incoming records
   237  func (rec *Record) Unpack() bool {
   238  	return rec.unpack
   239  }
   240  
   241  // SetUnpack sets whether to unpack incoming records
   242  func (rec *Record) SetUnpack(unpack bool) {
   243  	rec.unpack = unpack
   244  }
   245  
   246  // Compress returns the compression flag
   247  func (rec *Record) Compress() bool {
   248  	return CompressorKind((rec.raw.Options&gMaskCompr)>>16) != CompressNone
   249  }
   250  
   251  // Options returns the options of this record.
   252  func (rec *Record) Options() Options {
   253  	return rec.raw.Options
   254  }
   255  
   256  // EOF