@@ 0,0 1,216 @@
+// Taken and adapted from https://github.com/mattetti/filebuffer
+
+// This file is licensed as follows
+
+// The MIT License (MIT)
+
+// Copyright (c) 2016 Matt Aimonetti
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+// FileBuffer is a type implementing a few file like interfaces
+// backed by a byte buffer.
+// Implemented interfaces:
+//
+// * Reader
+// * ReaderAt
+// * Writer
+// * Seeker
+// * Closer
+
+package storage
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "os"
+)
+
+// 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
+}
+
+// New returns a new populated Buffer
+func New(b []byte) *FileBuffer {
+ return &FileBuffer{Buff: bytes.NewBuffer(b)}
+}
+
+// NewFromReader 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 NewFromReader(reader io.Reader) (*FileBuffer, error) {
+ data, err := io.ReadAll(reader)
+ if err != nil {
+ return nil, err
+ }
+ return New(data), nil
+}
+
+// Bytes returns the bytes available until the end of the buffer.
+func (f *FileBuffer) Bytes() []byte {
+ if f.isClosed || f.Index >= int64(f.Buff.Len()) {
+ return []byte{}
+ }
+ return f.Buff.Bytes()[f.Index:]
+}
+
+// String implements the Stringer interface
+func (f *FileBuffer) String() string {
+ 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(b []byte) (n int, err error) {
+ 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(p []byte, off int64) (n int, err error) {
+ 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(p []byte) (n int, err error) {
+ 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) {
+ 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() error {
+ f.isClosed = true
+ return nil
+}
+
+// Reopen is just a utility to reaccess the buffer after it's been closed.
+func (f *FileBuffer) Reopen() error {
+ if f.isClosed {
+ f.isClosed = false
+ }
+ return nil
+}
+
+// Reset will discard the data buffer
+func (f *FileBuffer) Reset() error {
+ f.Reopen()
+ f.Buff.Reset()
+ f.Seek(0, 0)
+ return nil
+}