1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-05-17 23:16:11 +08:00
Files
kubo/core/coreapi/object.go
Łukasz Magiera 7c2aa0e9a9 gx: update go-unixfs to propagate archive changes
License: MIT
Signed-off-by: Łukasz Magiera <magik6k@gmail.com>
2019-01-30 20:58:32 +01:00

374 lines
7.9 KiB
Go

package coreapi
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
caopts "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
"github.com/ipfs/go-ipfs/dagutils"
"github.com/ipfs/go-ipfs/pin"
ft "gx/ipfs/QmQ1JnYpnzkaurjW1yxkQxC2w3K1PorNE1nv1vaP5Le7sq/go-unixfs"
cid "gx/ipfs/QmR8BauakNcBa3RbE4nbQu76PDiJgoQgz8AJdhJuiU4TAw/go-cid"
ipld "gx/ipfs/QmRL22E4paat7ky7vx9MLpR97JHHbFPrg3ytFQw6qp1y1s/go-ipld-format"
dag "gx/ipfs/Qmb2UEG2TAeVrEJSjqsZF7Y2he7wRDkrdt6c3bECxwZf4k/go-merkledag"
)
const inputLimit = 2 << 20
type ObjectAPI CoreAPI
type Link struct {
Name, Hash string
Size uint64
}
type Node struct {
Links []Link
Data string
}
func (api *ObjectAPI) New(ctx context.Context, opts ...caopts.ObjectNewOption) (ipld.Node, error) {
options, err := caopts.ObjectNewOptions(opts...)
if err != nil {
return nil, err
}
var n ipld.Node
switch options.Type {
case "empty":
n = new(dag.ProtoNode)
case "unixfs-dir":
n = ft.EmptyDirNode()
}
err = api.dag.Add(ctx, n)
if err != nil {
return nil, err
}
return n, nil
}
func (api *ObjectAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.ObjectPutOption) (coreiface.ResolvedPath, error) {
options, err := caopts.ObjectPutOptions(opts...)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(io.LimitReader(src, inputLimit+10))
if err != nil {
return nil, err
}
var dagnode *dag.ProtoNode
switch options.InputEnc {
case "json":
node := new(Node)
err = json.Unmarshal(data, node)
if err != nil {
return nil, err
}
// check that we have data in the Node to add
// otherwise we will add the empty object without raising an error
if nodeEmpty(node) {
return nil, errors.New("no data or links in this node")
}
dagnode, err = deserializeNode(node, options.DataType)
if err != nil {
return nil, err
}
case "protobuf":
dagnode, err = dag.DecodeProtobuf(data)
case "xml":
node := new(Node)
err = xml.Unmarshal(data, node)
if err != nil {
return nil, err
}
// check that we have data in the Node to add
// otherwise we will add the empty object without raising an error
if nodeEmpty(node) {
return nil, errors.New("no data or links in this node")
}
dagnode, err = deserializeNode(node, options.DataType)
if err != nil {
return nil, err
}
default:
return nil, errors.New("unknown object encoding")
}
if err != nil {
return nil, err
}
if options.Pin {
defer api.blockstore.PinLock().Unlock()
}
err = api.dag.Add(ctx, dagnode)
if err != nil {
return nil, err
}
if options.Pin {
api.pinning.PinWithMode(dagnode.Cid(), pin.Recursive)
err = api.pinning.Flush()
if err != nil {
return nil, err
}
}
return coreiface.IpfsPath(dagnode.Cid()), nil
}
func (api *ObjectAPI) Get(ctx context.Context, path coreiface.Path) (ipld.Node, error) {
return api.core().ResolveNode(ctx, path)
}
func (api *ObjectAPI) Data(ctx context.Context, path coreiface.Path) (io.Reader, error) {
nd, err := api.core().ResolveNode(ctx, path)
if err != nil {
return nil, err
}
pbnd, ok := nd.(*dag.ProtoNode)
if !ok {
return nil, dag.ErrNotProtobuf
}
return bytes.NewReader(pbnd.Data()), nil
}
func (api *ObjectAPI) Links(ctx context.Context, path coreiface.Path) ([]*ipld.Link, error) {
nd, err := api.core().ResolveNode(ctx, path)
if err != nil {
return nil, err
}
links := nd.Links()
out := make([]*ipld.Link, len(links))
for n, l := range links {
out[n] = (*ipld.Link)(l)
}
return out, nil
}
func (api *ObjectAPI) Stat(ctx context.Context, path coreiface.Path) (*coreiface.ObjectStat, error) {
nd, err := api.core().ResolveNode(ctx, path)
if err != nil {
return nil, err
}
stat, err := nd.Stat()
if err != nil {
return nil, err
}
out := &coreiface.ObjectStat{
Cid: nd.Cid(),
NumLinks: stat.NumLinks,
BlockSize: stat.BlockSize,
LinksSize: stat.LinksSize,
DataSize: stat.DataSize,
CumulativeSize: stat.CumulativeSize,
}
return out, nil
}
func (api *ObjectAPI) AddLink(ctx context.Context, base coreiface.Path, name string, child coreiface.Path, opts ...caopts.ObjectAddLinkOption) (coreiface.ResolvedPath, error) {
options, err := caopts.ObjectAddLinkOptions(opts...)
if err != nil {
return nil, err
}
baseNd, err := api.core().ResolveNode(ctx, base)
if err != nil {
return nil, err
}
childNd, err := api.core().ResolveNode(ctx, child)
if err != nil {
return nil, err
}
basePb, ok := baseNd.(*dag.ProtoNode)
if !ok {
return nil, dag.ErrNotProtobuf
}
var createfunc func() *dag.ProtoNode
if options.Create {
createfunc = ft.EmptyDirNode
}
e := dagutils.NewDagEditor(basePb, api.dag)
err = e.InsertNodeAtPath(ctx, name, childNd, createfunc)
if err != nil {
return nil, err
}
nnode, err := e.Finalize(ctx, api.dag)
if err != nil {
return nil, err
}
return coreiface.IpfsPath(nnode.Cid()), nil
}
func (api *ObjectAPI) RmLink(ctx context.Context, base coreiface.Path, link string) (coreiface.ResolvedPath, error) {
baseNd, err := api.core().ResolveNode(ctx, base)
if err != nil {
return nil, err
}
basePb, ok := baseNd.(*dag.ProtoNode)
if !ok {
return nil, dag.ErrNotProtobuf
}
e := dagutils.NewDagEditor(basePb, api.dag)
err = e.RmLink(ctx, link)
if err != nil {
return nil, err
}
nnode, err := e.Finalize(ctx, api.dag)
if err != nil {
return nil, err
}
return coreiface.IpfsPath(nnode.Cid()), nil
}
func (api *ObjectAPI) AppendData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.ResolvedPath, error) {
return api.patchData(ctx, path, r, true)
}
func (api *ObjectAPI) SetData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.ResolvedPath, error) {
return api.patchData(ctx, path, r, false)
}
func (api *ObjectAPI) patchData(ctx context.Context, path coreiface.Path, r io.Reader, appendData bool) (coreiface.ResolvedPath, error) {
nd, err := api.core().ResolveNode(ctx, path)
if err != nil {
return nil, err
}
pbnd, ok := nd.(*dag.ProtoNode)
if !ok {
return nil, dag.ErrNotProtobuf
}
data, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
if appendData {
data = append(pbnd.Data(), data...)
}
pbnd.SetData(data)
err = api.dag.Add(ctx, pbnd)
if err != nil {
return nil, err
}
return coreiface.IpfsPath(pbnd.Cid()), nil
}
func (api *ObjectAPI) Diff(ctx context.Context, before coreiface.Path, after coreiface.Path) ([]coreiface.ObjectChange, error) {
beforeNd, err := api.core().ResolveNode(ctx, before)
if err != nil {
return nil, err
}
afterNd, err := api.core().ResolveNode(ctx, after)
if err != nil {
return nil, err
}
changes, err := dagutils.Diff(ctx, api.dag, beforeNd, afterNd)
if err != nil {
return nil, err
}
out := make([]coreiface.ObjectChange, len(changes))
for i, change := range changes {
out[i] = coreiface.ObjectChange{
Type: change.Type,
Path: change.Path,
}
if change.Before.Defined() {
out[i].Before = coreiface.IpfsPath(change.Before)
}
if change.After.Defined() {
out[i].After = coreiface.IpfsPath(change.After)
}
}
return out, nil
}
func (api *ObjectAPI) core() coreiface.CoreAPI {
return (*CoreAPI)(api)
}
func deserializeNode(nd *Node, dataFieldEncoding string) (*dag.ProtoNode, error) {
dagnode := new(dag.ProtoNode)
switch dataFieldEncoding {
case "text":
dagnode.SetData([]byte(nd.Data))
case "base64":
data, err := base64.StdEncoding.DecodeString(nd.Data)
if err != nil {
return nil, err
}
dagnode.SetData(data)
default:
return nil, fmt.Errorf("unkown data field encoding")
}
links := make([]*ipld.Link, len(nd.Links))
for i, link := range nd.Links {
c, err := cid.Decode(link.Hash)
if err != nil {
return nil, err
}
links[i] = &ipld.Link{
Name: link.Name,
Size: link.Size,
Cid: c,
}
}
dagnode.SetLinks(links)
return dagnode, nil
}
func nodeEmpty(node *Node) bool {
return node.Data == "" && len(node.Links) == 0
}