| // 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 it 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 |
| |
| // Close() closes the BlobStore. |
| Close() error |
| } |
| |
| // 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() |
| } |