blob: 16af4cf2a260fd02f448055cfb09a5468b032d0b [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package localblobstore is the interface to a local blob store.
// Implementations include fs_cablobstore.
//
// Expected use
// ============
// These examples assume that bs, bsS (sender) and bsR (receiver) are blobstores.
//
// Writing blobs
// bw, err := bs.NewBlobWriter(ctx, "") // For a new blob, implementation picks blob name.
// if err == nil {
// blobName := bw.Name() // Get name the implementation picked.
// ... use bw.AppendFragment() to append data to the blob...
// ... and/or bw.AppendBlob() to append data that's in another existing blob...
// err = bw.Close()
// }
//
// Resume writing a blob that was partially written due to a crash (not yet finalized).
// bw, err := bs.ResumeBlobWriter(ctx, name)
// if err == nil {
// size := bw.Size() // The store has this many bytes from the blob.
// ... write the remaining data using bwAppendFragment() and/or bw.AppendBlob()...
// err = bw.Close()
// }
//
// Reading blobs
// br, err := bs.NewBlobReader(ctx, name)
// if err == nil {
// ... read bytes with br.ReadAt() or br.Read(), perhas with br.Seek()...
// err = br.Close()
// }
//
// Transferring blobs from one store to another:
// See example in localblobstore_transfer_test.go
// Summary:
// - The sender sends the chunksum of the blob from BlobReader's Hash().
// - The receiver checks whether it already has the blob, with the same
// checksum.
// - If the receiver does not have the blob, the sender sends the list of chunk
// hashes in the blob using BlobChunkStream().
// - The receiver uses RecipeStreamFromChunkStream() with the chunk hash stream
// from the sender, and tells the sender the chunk hashes of the chunks it
// needs.
// - The sender uses LookupChunk() to find the data for each chunk the receiver
// needs, and sends it to the receiver.
// - The receiver applies the recipe steps, with the actual chunkj data from
// the sender and its own local data.
package localblobstore
import "v.io/v23/context"
// A BlobStore represents a simple, content-addressable store.
type BlobStore interface {
// NewBlobReader() returns a pointer to a newly allocated BlobReader on
// the specified blobName. BlobReaders should not be used concurrently
// by multiple threads. Returned handles should be closed with
// Close().
NewBlobReader(ctx *context.T, blobName string) (br BlobReader, err error)
// NewBlobWriter() returns a pointer to a newly allocated BlobWriter on
// a newly created blob. If "name" is non-empty, its is used to name
// the blob, and it must be in the format of a name returned by this
// interface (probably by another instance on another device).
// Otherwise, otherwise a new name is created, which can be found using
// the Name() method. It is an error to attempt to overwrite a blob
// that already exists in this blob store. BlobWriters should not be
// used concurrently by multiple threads. The returned handle should
// be closed with either the Close() or CloseWithoutFinalize() method
// to avoid leaking file handles.
NewBlobWriter(ctx *context.T, name string) (bw BlobWriter, err error)
// ResumeBlobWriter() returns a pointer to a newly allocated BlobWriter on
// an old, but unfinalized blob name.
ResumeBlobWriter(ctx *context.T, blobName string) (bw BlobWriter, err error)
// DeleteBlob() deletes the named blob from the BlobStore.
DeleteBlob(ctx *context.T, blobName string) (err error)
// GC() removes old temp files and content-addressed blocks that are no
// longer referenced by any blob. It may be called concurrently with
// other calls to GC(), and with uses of BlobReaders and BlobWriters.
GC(ctx *context.T) error
// BlobChunkStream() returns a ChunkStream that can be used to read the
// ordered list of content hashes of chunks in blob blobName. It is
// expected that this list will be presented to
// RecipeStreamFromChunkStream() on another device, to create a recipe
// for transmitting the blob efficiently to that other device.
BlobChunkStream(ctx *context.T, blobName string) ChunkStream
// RecipeStreamFromChunkStream() returns a pointer to a RecipeStream
// that allows the client to iterate over each RecipeStep needed to
// create the blob formed by the chunks in chunkStream. It is expected
// that this will be called on a receiving device, and be given a
// ChunkStream from a sending device, to yield a recipe for efficient
// chunk transfer. RecipeStep values with non-nil Chunk fields need
// the chunk from the sender; once the data is returned is can be
// written with BlobWriter.AppendFragment(). Those with blob
// references can be written locally with BlobWriter.AppendBlob().
RecipeStreamFromChunkStream(ctx *context.T, chunkStream ChunkStream) RecipeStream
// LookupChunk() returns the location of a chunk with the specified chunk
// hash within the store. It is expected that chunk hashes from
// RecipeStep entries from RecipeStreamFromChunkStream() will be mapped
// to blob Location values on the sender for transmission to the
// receiver.
LookupChunk(ctx *context.T, chunkHash []byte) (loc Location, err error)
// ListBlobIds() returns an iterator that can be used to enumerate the
// blobs in a BlobStore. Expected use is:
//
// iter := bs.ListBlobIds(ctx)
// for iter.Advance() {
// // Process iter.Value() here.
// }
// if iter.Err() != nil {
// // The loop terminated early due to an error.
// }
ListBlobIds(ctx *context.T) (iter Stream)
// ListCAIds() returns an iterator that can be used to enumerate the
// content-addressable fragments in a BlobStore. Expected use is:
//
// iter := bs.ListCAIds(ctx)
// for iter.Advance() {
// // Process iter.Value() here.
// }
// if iter.Err() != nil {
// // The loop terminated early due to an error.
// }
ListCAIds(ctx *context.T) (iter Stream)
// Root() returns the name of the root directory where the BlobStore is stored.
Root() string
}
// A Location describes chunk's location within a blob. It is returned by
// BlobStore.LookupChunk().
type Location struct {
BlobName string // name of blob
Offset int64 // byte offset of chunk within blob
Size int64 // size of chunk
}
// A BlobReader allows a blob to be read using the standard ReadAt(), Read(),
// and Seek() calls. A BlobReader can be created with NewBlobReader(), and
// should be closed with the Close() method to avoid leaking file handles.
type BlobReader interface {
// ReadAt() fills b[] with up to len(b) bytes of data starting at
// position "at" within the blob that the BlobReader indicates, and
// returns the number of bytes read.
ReadAt(b []byte, at int64) (n int, err error)
// Read() fills b[] with up to len(b) bytes of data starting at the
// current seek position of the BlobReader within the blob that the
// BlobReader indicates, and then both returns the number of bytes read
// and advances the BlobReader's seek position by that amount.
Read(b []byte) (n int, err error)
// Seek() sets the seek position of the BlobReader to offset if
// whence==0, offset+current_seek_position if whence==1, and
// offset+end_of_blob if whence==2, and then returns the current seek
// position.
Seek(offset int64, whence int) (result int64, err error)
// Close() indicates that the client will perform no further operations
// on the BlobReader. It releases any resources held by the
// BlobReader.
Close() error
// Name() returns the BlobReader's name.
Name() string
// Size() returns the BlobReader's size.
Size() int64
// IsFinalized() returns whether the BlobReader has been finalized.
IsFinalized() bool
// Hash() returns the BlobReader's hash. It may be nil if the blob is
// not finalized.
Hash() []byte
}
// A BlockOrFile represents a vector of bytes, and contains either a data
// block (as a []byte), or a (file name, size, offset) triple.
type BlockOrFile struct {
Block []byte // If FileName is empty, the bytes represented.
FileName string // If non-empty, the name of the file containing the bytes.
Size int64 // If FileName is non-empty, the number of bytes (or -1 for "all")
Offset int64 // If FileName is non-empty, the offset of the relevant bytes within the file.
}
// A BlobWriter allows a blob to be written. If a blob has not yet been
// finalized, it also allows that blob to be extended. A BlobWriter may be
// created with NewBlobWriter(), and should be closed with Close() or
// CloseWithoutFinalize().
type BlobWriter interface {
// AppendBlob() adds a (substring of a) pre-existing blob to the blob
// being written by the BlobWriter. The fragments of the pre-existing
// blob are not physically copied; they are referenced by both blobs.
AppendBlob(blobName string, size int64, offset int64) (err error)
// AppendFragment() appends a fragment to the blob being written by the
// BlobWriter, where the fragment is composed of the byte vectors
// described by the elements of item[]. The fragment is copied into
// the blob store.
AppendFragment(item ...BlockOrFile) (err error)
// Close() finalizes the BlobWriter, and indicates that the client will
// perform no further append operations on the BlobWriter. Any
// internal open file handles are closed.
Close() (err error)
// CloseWithoutFinalize() indicates that the client will perform no
// further append operations on the BlobWriter, but does not finalize
// the blob. Any internal open file handles are closed. Clients are
// expected to need this operation infrequently.
CloseWithoutFinalize() (err error)
// Name() returns the BlobWriter's name.
Name() string
// Size() returns the BlobWriter's size.
Size() int64
// IsFinalized() returns whether the BlobWriter has been finalized.
IsFinalized() bool
// Hash() returns the BlobWriter's hash, reflecting the bytes written so far.
Hash() []byte
}
// A Stream represents an iterator that allows the client to enumerate
// all the blobs or fragments in a BlobStore.
//
// The interfaces Stream, ChunkStream, RecipeStream all have four calls,
// and differ only in the Value() call.
type Stream interface {
// Advance() stages an item so that it may be retrieved via Value().
// Returns true iff there is an item to retrieve. Advance() must be
// called before Value() is called. The caller is expected to read
// until Advance() returns false, or to call Cancel().
Advance() bool
// Value() returns the item that was staged by Advance(). May panic if
// Advance() returned false or was not called. Never blocks.
Value() (name string)
// Err() returns any error encountered by Advance. Never blocks.
Err() error
// Cancel() indicates that the client wishes to cease reading from the stream.
// It causes the next call to Advance() to return false. Never blocks.
// It may be called concurrently with other calls on the stream.
Cancel()
}
// A ChunkStream represents an iterator that allows the client to enumerate
// the chunks in a blob. See the comments for Stream for usage.
type ChunkStream interface {
Advance() bool
// Value() returns the chunkHash that was staged by Advance(). May
// panic if Advance() returned false or was not called. Never blocks.
// The result may share storage with buf[] if it is large enough;
// otherwise, a new buffer is allocated. It is legal to call with
// buf==nil.
Value(buf []byte) (chunkHash []byte)
Err() error
Cancel()
}
// A RecipeStep describes one piece of a recipe for making a blob.
// The step consists either of appending the chunk with content hash Chunk and size Size,
// or (if Chunk==nil) the Size bytes from Blob, starting at Offset.
type RecipeStep struct {
Chunk []byte
Blob string
Size int64
Offset int64
}
// A RecipeStream represents an iterator that allows the client to obtain the
// steps needed to construct a blob with a given ChunkStream, attempting to
// reuse data in existing blobs. See the comments for Stream for usage.
type RecipeStream interface {
Advance() bool
// Value() returns the RecipeStep that was staged by Advance(). May panic if
// Advance() returned false or was not called. Never blocks.
Value() RecipeStep
Err() error
Cancel()
}