Skip to content

Commit

Permalink
Merge pull request #4548 from ipfs/feat/coreapi/block
Browse files Browse the repository at this point in the history
coreapi: Block API
  • Loading branch information
whyrusleeping authored Feb 3, 2018
2 parents d1a3fcc + e77b938 commit d58da74
Show file tree
Hide file tree
Showing 5 changed files with 422 additions and 0 deletions.
136 changes: 136 additions & 0 deletions core/coreapi/block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package coreapi

import (
"bytes"
"context"
"errors"
"fmt"
"io"
"io/ioutil"

util "github.com/ipfs/go-ipfs/blocks/blockstore/util"
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
caopts "github.com/ipfs/go-ipfs/core/coreapi/interface/options"

mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
blocks "gx/ipfs/Qmej7nf81hi2x2tvjRBF3mcp74sQyuDH4VMYDGd1YtXjb2/go-block-format"
)

type BlockAPI struct {
*CoreAPI
*caopts.BlockOptions
}

type BlockStat struct {
path coreiface.Path
size int
}

func (api *BlockAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.BlockPutOption) (coreiface.Path, error) {
settings, err := caopts.BlockPutOptions(opts...)
if err != nil {
return nil, err
}

data, err := ioutil.ReadAll(src)
if err != nil {
return nil, err
}

var pref cid.Prefix
pref.Version = 1

formatval, ok := cid.Codecs[settings.Codec]
if !ok {
return nil, fmt.Errorf("unrecognized format: %s", settings.Codec)
}
if settings.Codec == "v0" && settings.MhType == mh.SHA2_256 {
pref.Version = 0
}
pref.Codec = formatval

pref.MhType = settings.MhType
pref.MhLength = settings.MhLength

bcid, err := pref.Sum(data)
if err != nil {
return nil, err
}

b, err := blocks.NewBlockWithCid(data, bcid)
if err != nil {
return nil, err
}

err = api.node.Blocks.AddBlock(b)
if err != nil {
return nil, err
}

return ParseCid(b.Cid()), nil
}

func (api *BlockAPI) Get(ctx context.Context, p coreiface.Path) (io.Reader, error) {
b, err := api.node.Blocks.GetBlock(ctx, p.Cid())
if err != nil {
return nil, err
}

return bytes.NewReader(b.RawData()), nil
}

func (api *BlockAPI) Rm(ctx context.Context, p coreiface.Path, opts ...caopts.BlockRmOption) error {
settings, err := caopts.BlockRmOptions(opts...)
if err != nil {
return err
}
cids := []*cid.Cid{p.Cid()}
o := util.RmBlocksOpts{Force: settings.Force}

out, err := util.RmBlocks(api.node.Blockstore, api.node.Pinning, cids, o)
if err != nil {
return err
}

select {
case res, ok := <-out:
if !ok {
return nil
}

remBlock, ok := res.(*util.RemovedBlock)
if !ok {
return errors.New("got unexpected output from util.RmBlocks")
}

if remBlock.Error != "" {
return errors.New(remBlock.Error)
}
return nil
case <-ctx.Done():
return ctx.Err()
}

return nil
}

func (api *BlockAPI) Stat(ctx context.Context, p coreiface.Path) (coreiface.BlockStat, error) {
b, err := api.node.Blocks.GetBlock(ctx, p.Cid())
if err != nil {
return nil, err
}

return &BlockStat{
path: ParseCid(b.Cid()),
size: len(b.RawData()),
}, nil
}

func (bs *BlockStat) Size() int {
return bs.size
}

func (bs *BlockStat) Path() coreiface.Path {
return bs.path
}
167 changes: 167 additions & 0 deletions core/coreapi/block_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package coreapi_test

import (
"context"
"io/ioutil"
"strings"
"testing"

mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
)

func TestBlockPut(t *testing.T) {
ctx := context.Background()
_, api, err := makeAPI(ctx)
if err != nil {
t.Error(err)
}

res, err := api.Block().Put(ctx, strings.NewReader(`Hello`))
if err != nil {
t.Error(err)
}

if res.Cid().String() != "QmPyo15ynbVrSTVdJL9th7JysHaAbXt9dM9tXk1bMHbRtk" {
t.Errorf("got wrong cid: %s", res.Cid().String())
}
}

func TestBlockPutFormat(t *testing.T) {
ctx := context.Background()
_, api, err := makeAPI(ctx)
if err != nil {
t.Error(err)
}

res, err := api.Block().Put(ctx, strings.NewReader(`Hello`), api.Block().WithFormat("cbor"))
if err != nil {
t.Error(err)
}

if res.Cid().String() != "zdpuAn4amuLWo8Widi5v6VQpuo2dnpnwbVE3oB6qqs7mDSeoa" {
t.Errorf("got wrong cid: %s", res.Cid().String())
}
}

func TestBlockPutHash(t *testing.T) {
ctx := context.Background()
_, api, err := makeAPI(ctx)
if err != nil {
t.Error(err)
}

res, err := api.Block().Put(ctx, strings.NewReader(`Hello`), api.Block().WithHash(mh.KECCAK_512, -1))
if err != nil {
t.Error(err)
}

if res.Cid().String() != "zBurKB9YZkcDf6xa53WBE8CFX4ydVqAyf9KPXBFZt5stJzEstaS8Hukkhu4gwpMtc1xHNDbzP7sPtQKyWsP3C8fbhkmrZ" {
t.Errorf("got wrong cid: %s", res.Cid().String())
}
}

func TestBlockGet(t *testing.T) {
ctx := context.Background()
_, api, err := makeAPI(ctx)
if err != nil {
t.Error(err)
}

res, err := api.Block().Put(ctx, strings.NewReader(`Hello`), api.Block().WithHash(mh.KECCAK_512, -1))
if err != nil {
t.Error(err)
}

r, err := api.Block().Get(ctx, res)
if err != nil {
t.Error(err)
}

d, err := ioutil.ReadAll(r)
if err != nil {
t.Error(err)
}

if string(d) != "Hello" {
t.Error("didn't get correct data back")
}
}

func TestBlockRm(t *testing.T) {
ctx := context.Background()
_, api, err := makeAPI(ctx)
if err != nil {
t.Error(err)
}

res, err := api.Block().Put(ctx, strings.NewReader(`Hello`))
if err != nil {
t.Error(err)
}

r, err := api.Block().Get(ctx, res)
if err != nil {
t.Error(err)
}

d, err := ioutil.ReadAll(r)
if err != nil {
t.Error(err)
}

if string(d) != "Hello" {
t.Error("didn't get correct data back")
}

err = api.Block().Rm(ctx, res)
if err != nil {
t.Error(err)
}

_, err = api.Block().Get(ctx, res)
if err == nil {
t.Error("expected err to exist")
}
if err.Error() != "blockservice: key not found" {
t.Errorf("unexpected error; %s", err.Error())
}

err = api.Block().Rm(ctx, res)
if err == nil {
t.Error("expected err to exist")
}
if err.Error() != "blockstore: block not found" {
t.Errorf("unexpected error; %s", err.Error())
}

err = api.Block().Rm(ctx, res, api.Block().WithForce(true))
if err != nil {
t.Error(err)
}
}

func TestBlockStat(t *testing.T) {
ctx := context.Background()
_, api, err := makeAPI(ctx)
if err != nil {
t.Error(err)
}

res, err := api.Block().Put(ctx, strings.NewReader(`Hello`))
if err != nil {
t.Error(err)
}

stat, err := api.Block().Stat(ctx, res)
if err != nil {
t.Error(err)
}

if stat.Path().String() != res.String() {
t.Error("paths don't match")
}

if stat.Size() != len("Hello") {
t.Error("length doesn't match")
}
}
4 changes: 4 additions & 0 deletions core/coreapi/coreapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ func (api *CoreAPI) Unixfs() coreiface.UnixfsAPI {
return (*UnixfsAPI)(api)
}

func (api *CoreAPI) Block() coreiface.BlockAPI {
return &BlockAPI{api, nil}
}

// Dag returns the DagAPI interface backed by the go-ipfs node
func (api *CoreAPI) Dag() coreiface.DagAPI {
return &DagAPI{api, nil}
Expand Down
43 changes: 43 additions & 0 deletions core/coreapi/interface/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,25 @@ type Key interface {
Path() Path
}

type BlockStat interface {
Size() int
Path() Path
}

// CoreAPI defines an unified interface to IPFS for Go programs.
type CoreAPI interface {
// Unixfs returns an implementation of Unixfs API.
Unixfs() UnixfsAPI

// Block returns an implementation of Block API.
Block() BlockAPI

// Dag returns an implementation of Dag API.
Dag() DagAPI

// Name returns an implementation of Name API.
Name() NameAPI

// Key returns an implementation of Key API.
Key() KeyAPI

Expand All @@ -87,6 +98,38 @@ type UnixfsAPI interface {
Ls(context.Context, Path) ([]*Link, error)
}

// BlockAPI specifies the interface to the block layer
type BlockAPI interface {
// Put imports raw block data, hashing it using specified settings.
Put(context.Context, io.Reader, ...options.BlockPutOption) (Path, error)

// WithFormat is an option for Put which specifies the multicodec to use to
// serialize the object. Default is "v0"
WithFormat(codec string) options.BlockPutOption

// WithHash is an option for Put which specifies the multihash settings to use
// when hashing the object. Default is mh.SHA2_256 (0x12).
// If mhLen is set to -1, default length for the hash will be used
WithHash(mhType uint64, mhLen int) options.BlockPutOption

// Get attempts to resolve the path and return a reader for data in the block
Get(context.Context, Path) (io.Reader, error)

// Rm removes the block specified by the path from local blockstore.
// By default an error will be returned if the block can't be found locally.
//
// NOTE: If the specified block is pinned it won't be removed and no error
// will be returned
Rm(context.Context, Path, ...options.BlockRmOption) error

// WithForce is an option for Rm which, when set to true, will ignore
// non-existing blocks
WithForce(force bool) options.BlockRmOption

// Stat returns information on
Stat(context.Context, Path) (BlockStat, error)
}

// DagAPI specifies the interface to IPLD
type DagAPI interface {
// Put inserts data using specified format and input encoding.
Expand Down
Loading

0 comments on commit d58da74

Please sign in to comment.