go-hep.org/x/hep@v0.38.1/sio/record.go (about)

     1  // Copyright ©2017 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 sio
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"reflect"
    12  )
    13  
    14  // recordHeader describes the on-disk record (header part)
    15  type recordHeader struct {
    16  	Len uint32
    17  	Typ uint32
    18  }
    19  
    20  // recordData describes the on-disk record (payload part)
    21  type recordData struct {
    22  	Options uint32
    23  	DataLen uint32 // length of compressed record data
    24  	UCmpLen uint32 // length of uncompressed record data
    25  	NameLen uint32 // length of record name
    26  }
    27  
    28  // Record manages blocks of data
    29  type Record struct {
    30  	name    string         // record name
    31  	unpack  bool           // whether to unpack incoming records
    32  	options uint32         // options (flag word)
    33  	bindex  map[string]int // index of connected blocks
    34  	bnames  []string       // connected blocks names
    35  	blocks  []Block        // connected blocks
    36  }
    37  
    38  // Name returns the name of this record
    39  func (rec *Record) Name() string {
    40  	return rec.name
    41  }
    42  
    43  // Unpack returns whether to unpack incoming records
    44  func (rec *Record) Unpack() bool {
    45  	return rec.unpack
    46  }
    47  
    48  // SetUnpack sets whether to unpack incoming records
    49  func (rec *Record) SetUnpack(unpack bool) {
    50  	rec.unpack = unpack
    51  }
    52  
    53  // Compress returns the compression flag
    54  func (rec *Record) Compress() bool {
    55  	return rec.options&optCompress != 0
    56  }
    57  
    58  // SetCompress sets or resets the compression flag
    59  func (rec *Record) SetCompress(compress bool) {
    60  	rec.options &= optNotCompress
    61  	if compress {
    62  		rec.options |= optCompress
    63  	}
    64  }
    65  
    66  // Options returns the options of this record.
    67  func (rec *Record) Options() uint32 {
    68  	return rec.options
    69  }
    70  
    71  // Disconnect disconnects all blocks previously connected to this
    72  // Record (for reading or writing.)
    73  func (rec *Record) Disconnect() {
    74  	rec.bnames = rec.bnames[:0]
    75  	rec.bindex = make(map[string]int)
    76  	rec.blocks = rec.blocks[:0]
    77  }
    78  
    79  // Connect connects a Block to this Record (for reading or writing)
    80  func (rec *Record) Connect(name string, ptr any) error {
    81  	var err error
    82  	iblk, ok := rec.bindex[name]
    83  	if !ok {
    84  		iblk = len(rec.blocks)
    85  		rec.bnames = append(rec.bnames, name)
    86  		rec.blocks = append(rec.blocks, nil)
    87  		rec.bindex[name] = iblk
    88  		//return fmt.Errorf("sio.Record: Block name [%s] already connected", name)
    89  		//return ErrBlockConnected
    90  	}
    91  	var block Block
    92  	switch ptr := ptr.(type) {
    93  	case Block:
    94  		block = ptr
    95  	case Codec:
    96  		rt := reflect.TypeOf(ptr)
    97  		var vers uint32
    98  		if ptr, ok := ptr.(Versioner); ok {
    99  			vers = ptr.VersionSio()
   100  		}
   101  		block = &userBlock{
   102  			blk:     ptr,
   103  			version: vers,
   104  			name:    rt.Name(),
   105  		}
   106  
   107  	default:
   108  		rt := reflect.TypeOf(ptr)
   109  		if rt.Kind() != reflect.Ptr {
   110  			return fmt.Errorf("sio: Connect needs a pointer to a block of data")
   111  		}
   112  		var vers uint32
   113  		if ptr, ok := ptr.(Versioner); ok {
   114  			vers = ptr.VersionSio()
   115  		}
   116  		block = &genericBlock{
   117  			rt:      rt,
   118  			rv:      reflect.ValueOf(ptr),
   119  			version: vers,
   120  			name:    rt.Name(),
   121  		}
   122  	}
   123  	rec.blocks[iblk] = block
   124  	return err
   125  }
   126  
   127  // read reads a record
   128  func (rec *Record) read(r *reader) error {
   129  	var err error
   130  	// fmt.Printf("::: reading record [%s]... [%d]\n", rec.name, r.Len())
   131  	type fixlink struct {
   132  		link Linker
   133  		vers uint32
   134  	}
   135  	var linkers []fixlink
   136  	// loop until data has been depleted
   137  	for r.Len() > 0 {
   138  		beg := r.Len()
   139  		// read block header
   140  		var hdr blockHeader
   141  		err = bread(r, &hdr)
   142  		if err != nil {
   143  			return err
   144  		}
   145  		if hdr.Typ != blkMarker {
   146  			// fmt.Printf("*** err record[%s]: noblockmarker\n", rec.name)
   147  			return ErrRecordNoBlockMarker
   148  		}
   149  
   150  		var data blockData
   151  		err = bread(r, &data)
   152  		if err != nil {
   153  			return err
   154  		}
   155  		r.ver = data.Version
   156  
   157  		var cbuf bytes.Buffer
   158  		nlen := align4U32(data.NameLen)
   159  		n, err := io.CopyN(&cbuf, r, int64(nlen))
   160  		if err != nil {
   161  			// fmt.Printf(">>> err:%v\n", err)
   162  			return err
   163  		}
   164  		if n != int64(nlen) {
   165  			return ErrBlockShortRead
   166  		}
   167  		iblk, ok := rec.bindex[string(cbuf.Bytes()[:data.NameLen])]
   168  		if ok {
   169  			blk := rec.blocks[iblk]
   170  			// fmt.Printf("### %q\n", buf.String())
   171  			err = blk.UnmarshalSio(r)
   172  			end := r.Len()
   173  			if err != nil {
   174  				// fmt.Printf("*** error unmarshaling record=%q block=%q: %v\n", rec.name, name, err)
   175  				return err
   176  			}
   177  			if beg-end != int(hdr.Len) {
   178  				/*
   179  					if true {
   180  						var typ any
   181  						switch blk := blk.(type) {
   182  						case *userBlock:
   183  							typ = blk.blk
   184  						case *genericBlock:
   185  							typ = blk.rv.Interface()
   186  						}
   187  						log.Printf("record %q block %q (%T) (beg-end=%d-%d=%d != %d)", rec.Name(), name, typ, beg, end, beg-end, int(hdr.Len))
   188  					} else {
   189  				*/
   190  				return ErrBlockShortRead
   191  			}
   192  			// fmt.Printf(">>> read record=%q block=%q (buf=%d)\n", rec.name, name, buf.Len())
   193  			if ublk, ok := blk.(*userBlock); ok {
   194  				if link, ok := ublk.blk.(Linker); ok {
   195  					linkers = append(linkers, fixlink{link, data.Version})
   196  				}
   197  			}
   198  		}
   199  
   200  		// check whether there is still something to be read.
   201  		// if there is, check whether there is a block-marker
   202  		if r.Len() > 0 {
   203  			next := bytes.Index(r.Bytes(), blkMarkerBeg)
   204  			if next > 0 {
   205  				pos := next - 4 // sizeof mark-block
   206  				r.Next(pos)     // drain the buffer until next block
   207  			} else {
   208  				// drain the whole buffer
   209  				r.Next(r.Len())
   210  			}
   211  		}
   212  	}
   213  	r.relocate()
   214  	for _, fix := range linkers {
   215  		err = fix.link.LinkSio(fix.vers)
   216  		if err != nil {
   217  			return err
   218  		}
   219  	}
   220  
   221  	// fmt.Printf("::: reading record [%s]... [done]\n", rec.name)
   222  	return err
   223  }
   224  
   225  func (rec *Record) write(w *writer) error {
   226  	var (
   227  		err  error
   228  		work = make([]byte, 16*1024*1024)
   229  	)
   230  	for i, k := range rec.bnames {
   231  		blk := rec.blocks[i]
   232  		bhdr := blockHeader{
   233  			Typ: blkMarker,
   234  		}
   235  
   236  		bdata := blockData{
   237  			Version: blk.VersionSio(),
   238  			NameLen: uint32(len(k)),
   239  		}
   240  
   241  		wblk := newWriterFrom(w)
   242  		wblk.ver = bdata.Version
   243  
   244  		err = blk.MarshalSio(wblk)
   245  		if err != nil {
   246  			return err
   247  		}
   248  
   249  		bhdr.Len = uint32(blockHeaderSize) + uint32(blockDataSize) +
   250  			align4U32(bdata.NameLen) + uint32(wblk.Len())
   251  
   252  		// fmt.Printf("blockHeader: %v\n", bhdr)
   253  		// fmt.Printf("blockData:   %v (%s)\n", bdata, k)
   254  
   255  		err = bwrite(w, &bhdr)
   256  		if err != nil {
   257  			return err
   258  		}
   259  
   260  		err = bwrite(w, &bdata)
   261  		if err != nil {
   262  			return err
   263  		}
   264  
   265  		_, err = w.Write([]byte(k))
   266  		if err != nil {
   267  			return err
   268  		}
   269  		padlen := align4U32(bdata.NameLen) - bdata.NameLen
   270  		if padlen > 0 {
   271  			_, err = w.Write(make([]byte, int(padlen)))
   272  			if err != nil {
   273  				return err
   274  			}
   275  		}
   276  
   277  		_, err := io.CopyBuffer(w, wblk.buf, work)
   278  		if err != nil {
   279  			return err
   280  		}
   281  		w.ids = wblk.ids
   282  		w.tag = wblk.tag
   283  		w.ptr = wblk.ptr
   284  	}
   285  	return err
   286  }