125 lines
2.6 KiB
Go
125 lines
2.6 KiB
Go
|
package cache
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
|
||
|
"github.com/hashicorp/golang-lru/v2/expirable"
|
||
|
"github.com/pkg/errors"
|
||
|
"gitlab.com/wpetit/goweb/logger"
|
||
|
)
|
||
|
|
||
|
type readCacher struct {
|
||
|
reader io.ReadSeekCloser
|
||
|
cache *expirable.LRU[string, []byte]
|
||
|
key string
|
||
|
buffer bytes.Buffer
|
||
|
}
|
||
|
|
||
|
// Close implements io.ReadSeekCloser.
|
||
|
func (r *readCacher) Close() error {
|
||
|
if err := r.reader.Close(); err != nil {
|
||
|
return errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Read implements io.ReadSeekCloser.
|
||
|
func (r *readCacher) Read(p []byte) (n int, err error) {
|
||
|
length, err := r.reader.Read(p)
|
||
|
if err != nil {
|
||
|
if err == io.EOF {
|
||
|
if length > 0 {
|
||
|
if _, err := r.buffer.Write(p[:length]); err != nil {
|
||
|
logger.Error(context.Background(), "could not write to buffer", logger.CapturedE(errors.WithStack(err)))
|
||
|
return length, io.EOF
|
||
|
}
|
||
|
}
|
||
|
|
||
|
logger.Debug(context.Background(), "caching blob", logger.F("cacheKey", r.key))
|
||
|
r.cache.Add(r.key, r.buffer.Bytes())
|
||
|
|
||
|
return length, io.EOF
|
||
|
}
|
||
|
|
||
|
return length, errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
if length > 0 {
|
||
|
if _, err := r.buffer.Write(p[:length]); err != nil {
|
||
|
logger.Error(context.Background(), "could not write to buffer", logger.CapturedE(errors.WithStack(err)))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return length, nil
|
||
|
}
|
||
|
|
||
|
// Seek implements io.ReadSeekCloser.
|
||
|
func (r *readCacher) Seek(offset int64, whence int) (int64, error) {
|
||
|
length, err := r.reader.Seek(offset, whence)
|
||
|
if err != nil {
|
||
|
return length, errors.WithStack(err)
|
||
|
}
|
||
|
|
||
|
return length, nil
|
||
|
}
|
||
|
|
||
|
var _ io.ReadSeekCloser = &readCacher{}
|
||
|
|
||
|
type cachedReader struct {
|
||
|
buffer []byte
|
||
|
offset int64
|
||
|
}
|
||
|
|
||
|
// Read implements io.ReadSeekCloser.
|
||
|
func (r *cachedReader) Read(p []byte) (n int, err error) {
|
||
|
available := len(r.buffer) - int(r.offset)
|
||
|
if available == 0 {
|
||
|
return 0, io.EOF
|
||
|
}
|
||
|
|
||
|
size := len(p)
|
||
|
if size > available {
|
||
|
size = available
|
||
|
}
|
||
|
|
||
|
copy(p, r.buffer[r.offset:r.offset+int64(size)])
|
||
|
r.offset += int64(size)
|
||
|
|
||
|
return size, nil
|
||
|
}
|
||
|
|
||
|
// Close implements io.ReadSeekCloser.
|
||
|
func (r *cachedReader) Close() error {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Seek implements io.ReadSeekCloser.
|
||
|
func (r *cachedReader) Seek(offset int64, whence int) (int64, error) {
|
||
|
var newOffset int64
|
||
|
|
||
|
switch whence {
|
||
|
case io.SeekStart:
|
||
|
newOffset = offset
|
||
|
case io.SeekCurrent:
|
||
|
newOffset = r.offset + offset
|
||
|
case io.SeekEnd:
|
||
|
newOffset = int64(len(r.buffer)) + offset
|
||
|
default:
|
||
|
return 0, errors.Errorf("unknown seek whence '%d'", whence)
|
||
|
}
|
||
|
|
||
|
if newOffset > int64(len(r.buffer)) || newOffset < 0 {
|
||
|
return 0, fmt.Errorf("invalid offset %d", offset)
|
||
|
}
|
||
|
|
||
|
r.offset = newOffset
|
||
|
|
||
|
return newOffset, nil
|
||
|
}
|
||
|
|
||
|
var _ io.ReadSeekCloser = &cachedReader{}
|