~netlandish/gobwebs

f75501c211e3be963c8cf521d339a3750019526f — Peter Sanchez 1 year, 3 months ago 918e04d
Adding FileBuffer type
1 files changed, 216 insertions(+), 0 deletions(-)

A storage/filebuffer.go
A storage/filebuffer.go => storage/filebuffer.go +216 -0
@@ 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
}