diff --git a/blockstore/blockstore.go b/blockstore/blockstore.go index 2414dbad0..7e8ff454b 100644 --- a/blockstore/blockstore.go +++ b/blockstore/blockstore.go @@ -1,7 +1,7 @@ package blockstore import ( - "github.com/ipfs/go-cid" + cid "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" @@ -36,12 +36,22 @@ type BatchDeleter interface { // hash function and returns them on get/has, ignoring the contents of the // blockstore. func WrapIDStore(bstore blockstore.Blockstore) Blockstore { - return blockstore.NewIdStore(bstore).(Blockstore) + if is, ok := bstore.(*idstore); ok { + // already wrapped + return is + } + + if bs, ok := bstore.(Blockstore); ok { + // we need to wrap our own becase we don't want to neuter the DeleteMany method + return NewIDStore(bs) + } + + return NewIDStore(Adapt(bstore)) } // FromDatastore creates a new blockstore backed by the given datastore. func FromDatastore(dstore ds.Batching) Blockstore { - return WrapIDStore(blockstore.NewBlockstore(dstore)) + return WrapIDStore(Adapt(blockstore.NewBlockstore(dstore))) } type adaptedBlockstore struct { diff --git a/blockstore/idstore.go b/blockstore/idstore.go new file mode 100644 index 000000000..ab6d8e105 --- /dev/null +++ b/blockstore/idstore.go @@ -0,0 +1,170 @@ +package blockstore + +import ( + "context" + "io" + + "golang.org/x/xerrors" + + blocks "github.com/ipfs/go-block-format" + cid "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" +) + +var _ Blockstore = (*idstore)(nil) + +type idstore struct { + bs Blockstore +} + +func NewIDStore(bs Blockstore) Blockstore { + return &idstore{bs: bs} +} + +func decodeCid(cid cid.Cid) (inline bool, data []byte, err error) { + dmh, err := mh.Decode(cid.Hash()) + if err != nil { + return false, nil, err + } + + if dmh.Code == mh.ID { + return true, dmh.Digest, nil + } + + return false, nil, err +} + +func (b *idstore) Has(cid cid.Cid) (bool, error) { + inline, _, err := decodeCid(cid) + if err != nil { + return false, xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return true, nil + } + + return b.bs.Has(cid) +} + +func (b *idstore) Get(cid cid.Cid) (blocks.Block, error) { + inline, data, err := decodeCid(cid) + if err != nil { + return nil, xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return blocks.NewBlockWithCid(data, cid) + } + + return b.bs.Get(cid) +} + +func (b *idstore) GetSize(cid cid.Cid) (int, error) { + inline, data, err := decodeCid(cid) + if err != nil { + return 0, xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return len(data), err + } + + return b.bs.GetSize(cid) +} + +func (b *idstore) View(cid cid.Cid, cb func([]byte) error) error { + inline, data, err := decodeCid(cid) + if err != nil { + return xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return cb(data) + } + + return b.bs.View(cid, cb) +} + +func (b *idstore) Put(blk blocks.Block) error { + inline, _, err := decodeCid(blk.Cid()) + if err != nil { + return xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return nil + } + + return b.bs.Put(blk) +} + +func (b *idstore) PutMany(blks []blocks.Block) error { + toPut := make([]blocks.Block, 0, len(blks)) + for _, blk := range blks { + inline, _, err := decodeCid(blk.Cid()) + if err != nil { + return xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + continue + } + toPut = append(toPut, blk) + } + + if len(toPut) > 0 { + return b.bs.PutMany(toPut) + } + + return nil +} + +func (b *idstore) DeleteBlock(cid cid.Cid) error { + inline, _, err := decodeCid(cid) + if err != nil { + return xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return nil + } + + return b.bs.DeleteBlock(cid) +} + +func (b *idstore) DeleteMany(cids []cid.Cid) error { + toDelete := make([]cid.Cid, 0, len(cids)) + for _, cid := range cids { + inline, _, err := decodeCid(cid) + if err != nil { + return xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + continue + } + toDelete = append(toDelete, cid) + } + + if len(toDelete) > 0 { + return b.bs.DeleteMany(toDelete) + } + + return nil +} + +func (b *idstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + return b.bs.AllKeysChan(ctx) +} + +func (b *idstore) HashOnRead(enabled bool) { + b.bs.HashOnRead(enabled) +} + +func (b *idstore) Close() error { + if c, ok := b.bs.(io.Closer); ok { + return c.Close() + } + return nil +}