2024-06-02 19:53:33 +00:00
|
|
|
package ioutils
|
2024-03-28 13:09:42 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"os"
|
2024-06-25 21:39:30 +00:00
|
|
|
"sync"
|
2024-06-02 19:53:33 +00:00
|
|
|
|
|
|
|
"github.com/royalcat/ctxio"
|
2024-03-28 13:09:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// FileBuffer implements interfaces implemented by files.
|
|
|
|
// The main purpose of this type is to have an in memory replacement for a
|
|
|
|
// file.
|
|
|
|
type FileBuffer struct {
|
|
|
|
// buff is the backing buffer
|
|
|
|
buff *bytes.Buffer
|
|
|
|
// index indicates where in the buffer we are at
|
|
|
|
index int64
|
|
|
|
isClosed bool
|
2024-06-25 21:39:30 +00:00
|
|
|
mu sync.RWMutex
|
2024-03-28 13:09:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var _ FileReader = (*FileBuffer)(nil)
|
2024-06-02 19:53:33 +00:00
|
|
|
var _ ctxio.Writer = (*FileBuffer)(nil)
|
2024-03-28 13:09:42 +00:00
|
|
|
|
|
|
|
// NewFileBuffer returns a new populated Buffer
|
|
|
|
func NewFileBuffer(b []byte) *FileBuffer {
|
|
|
|
return &FileBuffer{buff: bytes.NewBuffer(b)}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewFileBufferFromReader is a convenience method that returns a new populated Buffer
|
|
|
|
// whose contents are sourced from a supplied reader by loading it entirely
|
|
|
|
// into memory.
|
2024-06-02 19:53:33 +00:00
|
|
|
func NewFileBufferFromReader(ctx context.Context, reader ctxio.Reader) (*FileBuffer, error) {
|
|
|
|
data, err := ctxio.ReadAll(ctx, reader)
|
2024-03-28 13:09:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return NewFileBuffer(data), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewFileBufferFromReader is a convenience method that returns a new populated Buffer
|
|
|
|
// whose contents are sourced from a supplied reader by loading it entirely
|
|
|
|
// into memory.
|
|
|
|
func NewFileBufferFromIoReader(reader io.Reader) (*FileBuffer, error) {
|
|
|
|
data, err := io.ReadAll(reader)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return NewFileBuffer(data), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bytes returns the bytes available until the end of the buffer.
|
|
|
|
func (f *FileBuffer) Bytes() []byte {
|
2024-06-25 21:39:30 +00:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
|
|
|
|
2024-03-28 13:09:42 +00:00
|
|
|
if f.isClosed || f.index >= int64(f.buff.Len()) {
|
|
|
|
return []byte{}
|
|
|
|
}
|
2024-06-25 21:39:30 +00:00
|
|
|
return bytes.Clone(f.buff.Bytes()[f.index:])
|
2024-03-28 13:09:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// String implements the Stringer interface
|
|
|
|
func (f *FileBuffer) String() string {
|
2024-06-25 21:39:30 +00:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
|
|
|
|
2024-03-28 13:09:42 +00:00
|
|
|
return string(f.buff.Bytes()[f.index:])
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read implements io.Reader https://golang.org/pkg/io/#Reader
|
|
|
|
// Read reads up to len(p) bytes into p. It returns the number of bytes read (0 <= n <= len(p))
|
|
|
|
// and any error encountered. Even if Read returns n < len(p), it may use all of p as scratch
|
|
|
|
// space during the call. If some data is available but not len(p) bytes, Read conventionally
|
|
|
|
// returns what is available instead of waiting for more.
|
|
|
|
|
|
|
|
// When Read encounters an error or end-of-file condition after successfully reading n > 0 bytes,
|
|
|
|
// it returns the number of bytes read. It may return the (non-nil) error from the same call or
|
|
|
|
// return the error (and n == 0) from a subsequent call. An instance of this general case is
|
|
|
|
// that a Reader returning a non-zero number of bytes at the end of the input stream may return
|
|
|
|
// either err == EOF or err == nil. The next Read should return 0, EOF.
|
|
|
|
func (f *FileBuffer) Read(ctx context.Context, b []byte) (n int, err error) {
|
2024-06-25 21:39:30 +00:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
|
|
|
|
2024-03-28 13:09:42 +00:00
|
|
|
if f.isClosed {
|
|
|
|
return 0, os.ErrClosed
|
|
|
|
}
|
|
|
|
if len(b) == 0 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
if f.index >= int64(f.buff.Len()) {
|
|
|
|
return 0, io.EOF
|
|
|
|
}
|
|
|
|
n, err = bytes.NewBuffer(f.buff.Bytes()[f.index:]).Read(b)
|
|
|
|
f.index += int64(n)
|
|
|
|
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadAt implements io.ReaderAt https://golang.org/pkg/io/#ReaderAt
|
|
|
|
// ReadAt reads len(p) bytes into p starting at offset off in the underlying input source.
|
|
|
|
// It returns the number of bytes read (0 <= n <= len(p)) and any error encountered.
|
|
|
|
//
|
|
|
|
// When ReadAt returns n < len(p), it returns a non-nil error explaining why more bytes were not returned.
|
|
|
|
// In this respect, ReadAt is stricter than Read.
|
|
|
|
//
|
|
|
|
// Even if ReadAt returns n < len(p), it may use all of p as scratch space during the call.
|
|
|
|
// If some data is available but not len(p) bytes, ReadAt blocks until either all the data is available or an error occurs.
|
|
|
|
// In this respect ReadAt is different from Read.
|
|
|
|
//
|
|
|
|
// If the n = len(p) bytes returned by ReadAt are at the end of the input source,
|
|
|
|
// ReadAt may return either err == EOF or err == nil.
|
|
|
|
//
|
|
|
|
// If ReadAt is reading from an input source with a seek offset,
|
|
|
|
// ReadAt should not affect nor be affected by the underlying seek offset.
|
|
|
|
// Clients of ReadAt can execute parallel ReadAt calls on the same input source.
|
|
|
|
func (f *FileBuffer) ReadAt(ctx context.Context, p []byte, off int64) (n int, err error) {
|
2024-06-25 21:39:30 +00:00
|
|
|
f.mu.RLock()
|
|
|
|
defer f.mu.RUnlock()
|
|
|
|
|
2024-03-28 13:09:42 +00:00
|
|
|
if f.isClosed {
|
|
|
|
return 0, os.ErrClosed
|
|
|
|
}
|
|
|
|
if off < 0 {
|
|
|
|
return 0, errors.New("filebuffer.ReadAt: negative offset")
|
|
|
|
}
|
|
|
|
reqLen := len(p)
|
|
|
|
buffLen := int64(f.buff.Len())
|
|
|
|
if off >= buffLen {
|
|
|
|
return 0, io.EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
n = copy(p, f.buff.Bytes()[off:])
|
|
|
|
if n < reqLen {
|
|
|
|
err = io.EOF
|
|
|
|
}
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write implements io.Writer https://golang.org/pkg/io/#Writer
|
|
|
|
// by appending the passed bytes to the buffer unless the buffer is closed or index negative.
|
|
|
|
func (f *FileBuffer) Write(ctx context.Context, p []byte) (n int, err error) {
|
2024-06-25 21:39:30 +00:00
|
|
|
f.mu.Lock()
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
|
2024-03-28 13:09:42 +00:00
|
|
|
if f.isClosed {
|
|
|
|
return 0, os.ErrClosed
|
|
|
|
}
|
|
|
|
if f.index < 0 {
|
|
|
|
return 0, io.EOF
|
|
|
|
}
|
|
|
|
// we might have rewinded, let's reset the buffer before appending to it
|
|
|
|
idx := int(f.index)
|
|
|
|
buffLen := f.buff.Len()
|
|
|
|
if idx != buffLen && idx <= buffLen {
|
|
|
|
f.buff = bytes.NewBuffer(f.Bytes()[:f.index])
|
|
|
|
}
|
|
|
|
n, err = f.buff.Write(p)
|
|
|
|
|
|
|
|
f.index += int64(n)
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Seek implements io.Seeker https://golang.org/pkg/io/#Seeker
|
|
|
|
func (f *FileBuffer) Seek(offset int64, whence int) (idx int64, err error) {
|
2024-06-25 21:39:30 +00:00
|
|
|
f.mu.Lock()
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
|
2024-03-28 13:09:42 +00:00
|
|
|
if f.isClosed {
|
|
|
|
return 0, os.ErrClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
var abs int64
|
|
|
|
switch whence {
|
|
|
|
case 0:
|
|
|
|
abs = offset
|
|
|
|
case 1:
|
|
|
|
abs = int64(f.index) + offset
|
|
|
|
case 2:
|
|
|
|
abs = int64(f.buff.Len()) + offset
|
|
|
|
default:
|
|
|
|
return 0, errors.New("filebuffer.Seek: invalid whence")
|
|
|
|
}
|
|
|
|
if abs < 0 {
|
|
|
|
return 0, errors.New("filebuffer.Seek: negative position")
|
|
|
|
}
|
|
|
|
f.index = abs
|
|
|
|
return abs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close implements io.Closer https://golang.org/pkg/io/#Closer
|
|
|
|
// It closes the buffer, rendering it unusable for I/O. It returns an error, if any.
|
|
|
|
func (f *FileBuffer) Close(ctx context.Context) error {
|
2024-06-25 21:39:30 +00:00
|
|
|
f.mu.Lock()
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
|
2024-03-28 13:09:42 +00:00
|
|
|
f.isClosed = true
|
|
|
|
f.buff = nil
|
|
|
|
return nil
|
|
|
|
}
|