Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

coreapi: Block API #4548

Merged
merged 5 commits into from
Feb 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -26,6 +26,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