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 }