# HG changeset patch # User Peter Sanchez # Date 1692277088 21600 # Thu Aug 17 06:58:08 2023 -0600 # Node ID e36c6d1c255993e4263f780f9a1ee92d1e9ee3b4 # Parent a03742dcfd4a8fe16919eb0d959c853c7aa795e2 Adding FileBuffer type diff --git a/storage/filebuffer.go b/storage/filebuffer.go new file mode 100644 --- /dev/null +++ b/storage/filebuffer.go @@ -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 +}