go-hep.org/x/hep@v0.38.1/sio/stream.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  	"compress/flate"
    10  	"compress/zlib"
    11  	"encoding/binary"
    12  	"fmt"
    13  	"io"
    14  	"os"
    15  	"unsafe"
    16  )
    17  
    18  // Open opens and connects a RIO stream to a file for reading
    19  func Open(fname string) (*Stream, error) {
    20  	var stream *Stream
    21  	var err error
    22  
    23  	f, err := os.Open(fname)
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  
    28  	stream = &Stream{
    29  		name: fname,
    30  		f:    f,
    31  		recs: make(map[string]*Record),
    32  	}
    33  
    34  	return stream, err
    35  }
    36  
    37  // Create opens and connects a RIO stream to a file for writing
    38  func Create(fname string) (*Stream, error) {
    39  	var stream *Stream
    40  	var err error
    41  
    42  	f, err := os.Create(fname)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	stream = &Stream{
    48  		name: fname,
    49  		f:    f,
    50  		recs: make(map[string]*Record),
    51  	}
    52  
    53  	return stream, err
    54  }
    55  
    56  // Stream manages operations of a single RIO stream.
    57  type Stream struct {
    58  	name string   // stream name
    59  	f    *os.File // file handle
    60  
    61  	recpos  int64 // start position of last record read
    62  	complvl int   // compression level
    63  
    64  	recs map[string]*Record // records to read/write
    65  }
    66  
    67  // Fd returns the integer Unix file descriptor referencing the underlying open file.
    68  func (stream *Stream) Fd() uintptr {
    69  	return stream.f.Fd()
    70  }
    71  
    72  // Close closes a stream and the underlying file
    73  func (stream *Stream) Close() error {
    74  	return stream.f.Close()
    75  }
    76  
    77  // Stat returns the FileInfo structure describing underlying file. If there is an
    78  // error, it will be of type *os.PathError.
    79  func (stream *Stream) Stat() (os.FileInfo, error) {
    80  	return stream.f.Stat()
    81  }
    82  
    83  // Sync commits the current contents of the stream to stable storage.
    84  func (stream *Stream) Sync() error {
    85  	return stream.f.Sync()
    86  }
    87  
    88  // Name returns the stream name
    89  func (stream *Stream) Name() string {
    90  	return stream.name
    91  }
    92  
    93  // FileName returns the name of the file connected to that stream
    94  func (stream *Stream) FileName() string {
    95  	return stream.f.Name()
    96  }
    97  
    98  // Mode returns the stream mode (as os.FileMode)
    99  func (stream *Stream) Mode() (os.FileMode, error) {
   100  	var mode os.FileMode
   101  	fi, err := stream.f.Stat()
   102  	if err != nil {
   103  		return mode, err
   104  	}
   105  
   106  	return fi.Mode(), nil
   107  }
   108  
   109  // SetCompressionLevel sets the (zlib) compression level
   110  func (stream *Stream) SetCompressionLevel(lvl int) {
   111  	if lvl < 0 {
   112  		stream.complvl = flate.DefaultCompression
   113  	} else if lvl > 9 {
   114  		stream.complvl = flate.BestCompression
   115  	} else {
   116  		stream.complvl = lvl
   117  	}
   118  }
   119  
   120  // CurPos returns the current position in the file
   121  //
   122  //	-1 if error
   123  func (stream *Stream) CurPos() int64 {
   124  	pos, err := stream.f.Seek(0, 1)
   125  	if err != nil {
   126  		return -1
   127  	}
   128  	return pos
   129  }
   130  
   131  // Seek sets the offset for the next Read or Write on the stream to offset,
   132  // interpreted according to whence:  0 means relative to the origin of the
   133  // file, 1 means relative to the current offset, and 2 means relative to
   134  // the end. It returns the new offset and an error, if any.
   135  func (stream *Stream) Seek(offset int64, whence int) (int64, error) {
   136  	return stream.f.Seek(offset, whence)
   137  }
   138  
   139  // Record adds a Record to the list of records to read/write or
   140  // returns the Record with that name.
   141  func (stream *Stream) Record(name string) *Record {
   142  	rec, dup := stream.recs[name]
   143  	if dup {
   144  		return rec
   145  	}
   146  	rec = &Record{
   147  		name:   name,
   148  		unpack: false,
   149  		bindex: make(map[string]int),
   150  	}
   151  	stream.recs[name] = rec
   152  	return stream.recs[name]
   153  }
   154  
   155  // HasRecord returns whether a Record with name n has been added to this Stream
   156  func (stream *Stream) HasRecord(n string) bool {
   157  	_, ok := stream.recs[n]
   158  	return ok
   159  }
   160  
   161  // DelRecord removes the Record with name n from this Stream.
   162  // DelRecord is a no-op if such a Record was not known to the Stream.
   163  func (stream *Stream) DelRecord(n string) {
   164  	delete(stream.recs, n)
   165  }
   166  
   167  // Records returns the list of Records currently attached to this Stream.
   168  func (stream *Stream) Records() []*Record {
   169  	recs := make([]*Record, 0, len(stream.recs))
   170  	for _, rec := range stream.recs {
   171  		recs = append(recs, rec)
   172  	}
   173  	return recs
   174  }
   175  
   176  //func (stream *Stream) dump() {
   177  //	fmt.Printf("=========== stream [%s] ============\n", stream.name)
   178  //	fmt.Printf("::: records: (%d)\n", len(stream.recs))
   179  //	for k, rec := range stream.recs {
   180  //		fmt.Printf("::: %s: %v\n", k, rec)
   181  //	}
   182  //}
   183  
   184  // ReadRecord reads the next record
   185  func (stream *Stream) ReadRecord() (*Record, error) {
   186  	var err error
   187  	var record *Record
   188  
   189  	// fmt.Printf("~~~ Read()... ~~~~~~~~~~~~~~~~~~\n")
   190  	// defer fmt.Printf("~~~ Read()... ~~~~~~~~~~~~~~~~~~ [done]\n")
   191  
   192  	stream.recpos = -1
   193  
   194  	requested := false
   195  	// loop over records until a requested one turns up
   196  	for !requested {
   197  
   198  		stream.recpos = stream.CurPos()
   199  		// fmt.Printf(">>> recpos=%d <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n", stream.recpos)
   200  
   201  		// interpret: 1) length of the record header
   202  		//            2) record marker
   203  		var rechdr recordHeader
   204  		err = stream.read(&rechdr)
   205  		if err != nil {
   206  			return nil, err
   207  		}
   208  		//fmt.Printf(">>> buf=%v\n", buf[:])
   209  		//fmt.Printf(">>> rechdr=%v\n", rechdr)
   210  
   211  		if rechdr.Typ != recMarker {
   212  			return nil, ErrStreamNoRecMarker
   213  		}
   214  
   215  		var (
   216  			curpos  int64
   217  			recdata recordData
   218  		)
   219  		err = stream.read(&recdata)
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  		// fmt.Printf(">>> rec=%v\n", recdata)
   224  		buf := make([]byte, align4U32(recdata.NameLen))
   225  		_, err = io.ReadFull(stream.f, buf)
   226  		if err != nil {
   227  			return nil, err
   228  		}
   229  		recname := string(buf[:recdata.NameLen])
   230  		// fmt.Printf(">>> name=[%s]\n", recname)
   231  		record = stream.Record(recname)
   232  		record.options = recdata.Options
   233  		requested = record.Unpack()
   234  
   235  		// if the record is not interesting, go to next record.
   236  		// skip over any padding bytes inserted to make the next record header
   237  		// start on a 4-bytes boundary in the file
   238  		if !requested {
   239  			recdata.DataLen = align4U32(recdata.DataLen)
   240  			curpos, err = stream.Seek(int64(recdata.DataLen), 1)
   241  			if curpos != int64(recdata.DataLen+rechdr.Len)+stream.recpos {
   242  				return nil, io.ErrUnexpectedEOF
   243  			}
   244  			if err != nil {
   245  				return nil, err
   246  			}
   247  			continue
   248  		}
   249  
   250  		// extract the compression bit from the options word
   251  		compress := record.Compress()
   252  		if !compress {
   253  			// read the rest of the record data.
   254  			// note that uncompressed data is *ALWAYS* aligned to a 4-bytes boundary
   255  			// in the file, so no pad skipping is necessary
   256  			buf = make([]byte, recdata.DataLen)
   257  			_, err = io.ReadFull(stream.f, buf)
   258  			if err != nil {
   259  				return nil, err
   260  			}
   261  
   262  		} else {
   263  			// read the compressed record data
   264  			cbuf := make([]byte, recdata.DataLen)
   265  			_, err = io.ReadFull(stream.f, cbuf)
   266  			if err != nil {
   267  				return nil, err
   268  			}
   269  
   270  			// handle padding bytes that may have been inserted to make the next
   271  			// record header start on a 4-bytes boundary in the file.
   272  			padlen := align4U32(recdata.DataLen) - recdata.DataLen
   273  			if padlen > 0 {
   274  				_, err = stream.Seek(int64(padlen), 1)
   275  				if err != nil {
   276  					return nil, err
   277  				}
   278  			}
   279  
   280  			unzip, err := zlib.NewReader(bytes.NewBuffer(cbuf))
   281  			if err != nil {
   282  				return nil, err
   283  			}
   284  			buf = make([]byte, recdata.UCmpLen)
   285  			nb, err := io.ReadFull(unzip, buf)
   286  			unzip.Close()
   287  			if err != nil {
   288  				return nil, err
   289  			}
   290  			if nb != len(buf) {
   291  				return nil, io.ErrUnexpectedEOF
   292  			}
   293  			//stream.recpos = recstart
   294  		}
   295  		recbuf := newReader(buf)
   296  		//fmt.Printf("::: recbuf: %d buf:%d\n", recbuf.Len(), len(buf))
   297  		err = record.read(recbuf)
   298  		if err != nil {
   299  			return record, err
   300  		}
   301  	}
   302  	return record, err
   303  }
   304  
   305  func (stream *Stream) WriteRecord(record *Record) error {
   306  	var err error
   307  	// fmt.Printf("~~~ Write(%v)...\n", record.Name())
   308  	// defer fmt.Printf("~~~ Write(%v)... [done]\n", record.Name())
   309  
   310  	rechdr := recordHeader{
   311  		Len: 0,
   312  		Typ: recMarker,
   313  	}
   314  	recdata := recordData{
   315  		Options: record.options,
   316  		DataLen: 0,
   317  		UCmpLen: 0,
   318  		NameLen: uint32(len(record.name)),
   319  	}
   320  
   321  	rechdr.Len = uint32(unsafe.Sizeof(rechdr)) + uint32(unsafe.Sizeof(recdata)) +
   322  		align4U32(uint32(recdata.NameLen))
   323  
   324  	buf := newWriter()
   325  	err = record.write(buf)
   326  	if err != nil {
   327  		return err
   328  	}
   329  
   330  	ucmplen := uint32(buf.Len())
   331  	recdata.UCmpLen = ucmplen
   332  	recdata.DataLen = ucmplen
   333  
   334  	if record.Compress() {
   335  		var b bytes.Buffer
   336  		zip, err := zlib.NewWriterLevel(&b, stream.complvl)
   337  		if err != nil {
   338  			return err
   339  		}
   340  		_, err = zip.Write(buf.Bytes())
   341  		if err != nil {
   342  			return err
   343  		}
   344  		err = zip.Close()
   345  		if err != nil {
   346  			return err
   347  		}
   348  		recdata.DataLen = align4U32(uint32(b.Len()))
   349  		if n := int(recdata.DataLen - uint32(b.Len())); n > 0 {
   350  			var tmp [4]byte
   351  			b.Write(tmp[:n])
   352  		}
   353  
   354  		buf.buf = &b
   355  	}
   356  
   357  	err = stream.write(&rechdr)
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	err = stream.write(&recdata)
   363  	if err != nil {
   364  		return err
   365  	}
   366  
   367  	_, err = stream.f.Write([]byte(record.name))
   368  	if err != nil {
   369  		return err
   370  	}
   371  
   372  	padlen := align4U32(recdata.NameLen) - recdata.NameLen
   373  	if padlen > 0 {
   374  		_, err = stream.f.Write(make([]byte, int(padlen)))
   375  		if err != nil {
   376  			return err
   377  		}
   378  	}
   379  
   380  	n := int64(buf.Len())
   381  	w, err := io.Copy(stream.f, buf.buf)
   382  	if err != nil {
   383  		return err
   384  	}
   385  
   386  	if n != w {
   387  		return fmt.Errorf("sio: written to few bytes (%d). expected (%d)", w, n)
   388  	}
   389  
   390  	return err
   391  }
   392  
   393  func (stream *Stream) read(data any) error {
   394  	return binary.Read(stream.f, binary.BigEndian, data)
   395  }
   396  
   397  func (stream *Stream) write(data any) error {
   398  	return binary.Write(stream.f, binary.BigEndian, data)
   399  }