package storage import ( "bytes" "errors" "hash/crc64" "io" "os" "path/filepath" ) // FileGetter is the interface for getting a stream of a file payload, // addressed by name/filename. Presumably, the names will be scoped to relative // file paths. type FileGetter interface { // Get returns a stream for the provided file path Get(filename string) (output io.ReadCloser, err error) } // FilePutter is the interface for storing a stream of a file payload, // addressed by name/filename. type FilePutter interface { // Put returns the size of the stream received, and the crc64 checksum for // the provided stream Put(filename string, input io.Reader) (size int64, checksum []byte, err error) } // FileGetPutter is the interface that groups both Getting and Putting file // payloads. type FileGetPutter interface { FileGetter FilePutter } // NewPathFileGetter returns a FileGetter that is for files relative to path // relpath. func NewPathFileGetter(relpath string) FileGetter { return &pathFileGetter{root: relpath} } type pathFileGetter struct { root string } func (pfg pathFileGetter) Get(filename string) (io.ReadCloser, error) { return os.Open(filepath.Join(pfg.root, filename)) } type bufferFileGetPutter struct { files map[string][]byte } func (bfgp bufferFileGetPutter) Get(name string) (io.ReadCloser, error) { if _, ok := bfgp.files[name]; !ok { return nil, errors.New("no such file") } b := bytes.NewBuffer(bfgp.files[name]) return &readCloserWrapper{b}, nil } func (bfgp *bufferFileGetPutter) Put(name string, r io.Reader) (int64, []byte, error) { crc := crc64.New(CRCTable) buf := bytes.NewBuffer(nil) cw := io.MultiWriter(crc, buf) i, err := io.Copy(cw, r) if err != nil { return 0, nil, err } bfgp.files[name] = buf.Bytes() return i, crc.Sum(nil), nil } type readCloserWrapper struct { io.Reader } func (w *readCloserWrapper) Close() error { return nil } // NewBufferFileGetPutter is a simple in-memory FileGetPutter // // Implication is this is memory intensive... // Probably best for testing or light weight cases. func NewBufferFileGetPutter() FileGetPutter { return &bufferFileGetPutter{ files: map[string][]byte{}, } } // NewDiscardFilePutter is a bit bucket FilePutter func NewDiscardFilePutter() FilePutter { return &bitBucketFilePutter{} } type bitBucketFilePutter struct { buffer [32 * 1024]byte // 32 kB is the buffer size currently used by io.Copy, as of August 2021. } func (bbfp *bitBucketFilePutter) Put(name string, r io.Reader) (int64, []byte, error) { c := crc64.New(CRCTable) i, err := io.CopyBuffer(c, r, bbfp.buffer[:]) return i, c.Sum(nil), err } // CRCTable is the default table used for crc64 sum calculations var CRCTable = crc64.MakeTable(crc64.ISO)