Files
txqr/protocol/decode.go
T
Ivan Danyliuk 5a1f7301aa Fix cache issue
2018-11-11 14:53:18 +01:00

129 lines
2.9 KiB
Go

package protocol
import (
"fmt"
"strings"
)
// Decoder represents protocol decode.
type Decoder struct {
buffer []byte
complete bool
total int
frames []frameInfo
cache map[string]struct{}
progress int
}
// frameInfo represents the information about read frames.
// As frames can change size dynamically, we keep size info as well.
type frameInfo struct {
offset, size int
}
// NewDecoder creats and inits a new decoder.
func NewDecoder() *Decoder {
return &Decoder{
buffer: []byte{},
cache: make(map[string]struct{}),
}
}
// NewDecoderSize creats and inits a new decoder for the known size.
// Note, it doesn't limit the size of the input, but optimizes memory allocation.
func NewDecoderSize(size int) *Decoder {
return &Decoder{
buffer: make([]byte, size),
}
}
// DecodeChunk takes a single chunk of data and decodes it.
func (d *Decoder) DecodeChunk(data string) error {
if data == "" || len(data) < 4 {
return fmt.Errorf("invalid frame: \"%s\"", data)
}
idx := strings.IndexByte(data, '|')
if idx == -1 {
return fmt.Errorf("invalid frame: \"%s\"", data)
}
header := data[:idx]
// continuous QR reading often sends the same chunk in a row, skip it
if d.isCached(header) {
return nil
}
var offset, total int
_, err := fmt.Sscanf(header, "%d/%d", &offset, &total)
if err != nil {
return fmt.Errorf("invalid header: %v (%s)", err, header)
}
// allocate enough memory at first total read
if d.total == 0 {
d.buffer = make([]byte, total)
d.total = total
}
if total > d.total {
return fmt.Errorf("total changed during sequence, aborting")
}
payload := data[idx+1:]
size := len(payload)
// TODO(divan): optmize memory allocation
d.frames = append(d.frames, frameInfo{offset: offset, size: size})
copy(d.buffer[offset:offset+size], payload)
d.updateProgress()
return nil
}
// Data returns decoded data.
func (d *Decoder) Data() string {
return string(d.buffer)
}
// DataBytes returns decoded data as a byte slice.
func (d *Decoder) DataBytes() []byte {
return d.buffer
}
// Progress returns reading progress in percentage.
func (d *Decoder) Progress() int {
return d.progress
}
// IsCompleted reports whether the read was completed successfully or not.
func (d *Decoder) IsCompleted() bool {
return d.complete
}
// updateProgress updates progress and complete state of reading.
// FIXME(divan): this approach might give false negatives in extreme cases, like
// many dynamic changes of chunk sizes.
func (d *Decoder) updateProgress() {
var cur int
for _, frame := range d.frames {
cur += frame.size
}
d.progress = 100 * cur / d.total
d.complete = cur == d.total
}
// isCached takes the header of chunk data and see if it's been cached.
// If not, it caches it.
func (d *Decoder) isCached(header string) bool {
if _, ok := d.cache[header]; ok {
return true
}
// cache it
d.cache[header] = struct{}{}
return false
}