object.go 7.38 KB
Newer Older
1 2 3 4 5
package coreapi

import (
	"bytes"
	"context"
6 7 8 9 10
	"encoding/base64"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
11 12 13
	"io"
	"io/ioutil"

Jakub Sztandera's avatar
Jakub Sztandera committed
14
	cid "github.com/ipfs/go-cid"
15
	"github.com/ipfs/go-ipfs-pinner"
Jakub Sztandera's avatar
Jakub Sztandera committed
16 17
	ipld "github.com/ipfs/go-ipld-format"
	dag "github.com/ipfs/go-merkledag"
18
	"github.com/ipfs/go-merkledag/dagutils"
Jakub Sztandera's avatar
Jakub Sztandera committed
19 20 21
	ft "github.com/ipfs/go-unixfs"
	coreiface "github.com/ipfs/interface-go-ipfs-core"
	caopts "github.com/ipfs/interface-go-ipfs-core/options"
22
	ipath "github.com/ipfs/interface-go-ipfs-core/path"
23 24
)

Łukasz Magiera's avatar
Łukasz Magiera committed
25 26
const inputLimit = 2 << 20

27
type ObjectAPI CoreAPI
28

Łukasz Magiera's avatar
Łukasz Magiera committed
29 30 31 32 33 34 35 36 37 38
type Link struct {
	Name, Hash string
	Size       uint64
}

type Node struct {
	Links []Link
	Data  string
}

39
func (api *ObjectAPI) New(ctx context.Context, opts ...caopts.ObjectNewOption) (ipld.Node, error) {
Łukasz Magiera's avatar
Łukasz Magiera committed
40 41 42 43
	options, err := caopts.ObjectNewOptions(opts...)
	if err != nil {
		return nil, err
	}
44

45
	var n ipld.Node
Łukasz Magiera's avatar
Łukasz Magiera committed
46 47 48 49 50
	switch options.Type {
	case "empty":
		n = new(dag.ProtoNode)
	case "unixfs-dir":
		n = ft.EmptyDirNode()
51 52
	default:
		return nil, fmt.Errorf("unknown node type: %s", options.Type)
Łukasz Magiera's avatar
Łukasz Magiera committed
53 54
	}

55
	err = api.dag.Add(ctx, n)
56 57 58
	if err != nil {
		return nil, err
	}
Łukasz Magiera's avatar
Łukasz Magiera committed
59
	return n, nil
60 61
}

62
func (api *ObjectAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.ObjectPutOption) (ipath.Resolved, error) {
63 64 65 66 67
	options, err := caopts.ObjectPutOptions(opts...)
	if err != nil {
		return nil, err
	}

Łukasz Magiera's avatar
Łukasz Magiera committed
68 69 70 71 72 73 74 75 76
	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)
77 78 79
		decoder := json.NewDecoder(bytes.NewReader(data))
		decoder.DisallowUnknownFields()
		err = decoder.Decode(node)
Łukasz Magiera's avatar
Łukasz Magiera committed
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
		if err != nil {
			return nil, err
		}

		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
		}

		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
	}

112
	if options.Pin {
113
		defer api.blockstore.PinLock().Unlock()
114 115
	}

116
	err = api.dag.Add(ctx, dagnode)
Łukasz Magiera's avatar
Łukasz Magiera committed
117 118 119 120
	if err != nil {
		return nil, err
	}

121
	if options.Pin {
122
		api.pinning.PinWithMode(dagnode.Cid(), pin.Recursive)
123
		err = api.pinning.Flush(ctx)
124 125 126 127 128
		if err != nil {
			return nil, err
		}
	}

129
	return ipath.IpfsPath(dagnode.Cid()), nil
130 131
}

132
func (api *ObjectAPI) Get(ctx context.Context, path ipath.Path) (ipld.Node, error) {
133 134 135
	return api.core().ResolveNode(ctx, path)
}

136
func (api *ObjectAPI) Data(ctx context.Context, path ipath.Path) (io.Reader, error) {
137 138 139 140 141 142 143 144 145 146 147 148 149
	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
}

150
func (api *ObjectAPI) Links(ctx context.Context, path ipath.Path) ([]*ipld.Link, error) {
151 152 153 154 155 156
	nd, err := api.core().ResolveNode(ctx, path)
	if err != nil {
		return nil, err
	}

	links := nd.Links()
157
	out := make([]*ipld.Link, len(links))
158
	for n, l := range links {
159
		out[n] = (*ipld.Link)(l)
160 161 162 163 164
	}

	return out, nil
}

165
func (api *ObjectAPI) Stat(ctx context.Context, path ipath.Path) (*coreiface.ObjectStat, error) {
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
	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
}

188
func (api *ObjectAPI) AddLink(ctx context.Context, base ipath.Path, name string, child ipath.Path, opts ...caopts.ObjectAddLinkOption) (ipath.Resolved, error) {
Łukasz Magiera's avatar
Łukasz Magiera committed
189 190 191 192 193 194
	options, err := caopts.ObjectAddLinkOptions(opts...)
	if err != nil {
		return nil, err
	}

	baseNd, err := api.core().ResolveNode(ctx, base)
195 196 197 198 199 200 201 202 203
	if err != nil {
		return nil, err
	}

	childNd, err := api.core().ResolveNode(ctx, child)
	if err != nil {
		return nil, err
	}

Łukasz Magiera's avatar
Łukasz Magiera committed
204
	basePb, ok := baseNd.(*dag.ProtoNode)
205 206 207 208 209
	if !ok {
		return nil, dag.ErrNotProtobuf
	}

	var createfunc func() *dag.ProtoNode
Łukasz Magiera's avatar
Łukasz Magiera committed
210
	if options.Create {
211 212 213
		createfunc = ft.EmptyDirNode
	}

214
	e := dagutils.NewDagEditor(basePb, api.dag)
215 216 217 218 219 220

	err = e.InsertNodeAtPath(ctx, name, childNd, createfunc)
	if err != nil {
		return nil, err
	}

221
	nnode, err := e.Finalize(ctx, api.dag)
222 223 224 225
	if err != nil {
		return nil, err
	}

226
	return ipath.IpfsPath(nnode.Cid()), nil
227 228
}

229
func (api *ObjectAPI) RmLink(ctx context.Context, base ipath.Path, link string) (ipath.Resolved, error) {
Łukasz Magiera's avatar
Łukasz Magiera committed
230
	baseNd, err := api.core().ResolveNode(ctx, base)
231 232 233 234
	if err != nil {
		return nil, err
	}

Łukasz Magiera's avatar
Łukasz Magiera committed
235
	basePb, ok := baseNd.(*dag.ProtoNode)
236 237 238 239
	if !ok {
		return nil, dag.ErrNotProtobuf
	}

240
	e := dagutils.NewDagEditor(basePb, api.dag)
241 242 243 244 245 246

	err = e.RmLink(ctx, link)
	if err != nil {
		return nil, err
	}

247
	nnode, err := e.Finalize(ctx, api.dag)
248 249 250 251
	if err != nil {
		return nil, err
	}

252
	return ipath.IpfsPath(nnode.Cid()), nil
253 254
}

255
func (api *ObjectAPI) AppendData(ctx context.Context, path ipath.Path, r io.Reader) (ipath.Resolved, error) {
256 257 258
	return api.patchData(ctx, path, r, true)
}

259
func (api *ObjectAPI) SetData(ctx context.Context, path ipath.Path, r io.Reader) (ipath.Resolved, error) {
260 261 262
	return api.patchData(ctx, path, r, false)
}

263
func (api *ObjectAPI) patchData(ctx context.Context, path ipath.Path, r io.Reader, appendData bool) (ipath.Resolved, error) {
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
	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)

284
	err = api.dag.Add(ctx, pbnd)
285 286 287 288
	if err != nil {
		return nil, err
	}

289
	return ipath.IpfsPath(pbnd.Cid()), nil
290 291
}

292
func (api *ObjectAPI) Diff(ctx context.Context, before ipath.Path, after ipath.Path) ([]coreiface.ObjectChange, error) {
293 294 295 296 297 298 299 300 301 302
	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
	}

303
	changes, err := dagutils.Diff(ctx, api.dag, beforeNd, afterNd)
304 305 306 307 308 309 310
	if err != nil {
		return nil, err
	}

	out := make([]coreiface.ObjectChange, len(changes))
	for i, change := range changes {
		out[i] = coreiface.ObjectChange{
311
			Type: coreiface.ChangeType(change.Type),
312 313 314
			Path: change.Path,
		}

315
		if change.Before.Defined() {
316
			out[i].Before = ipath.IpfsPath(change.Before)
317 318
		}

319
		if change.After.Defined() {
320
			out[i].After = ipath.IpfsPath(change.After)
321 322 323 324 325 326
		}
	}

	return out, nil
}

327
func (api *ObjectAPI) core() coreiface.CoreAPI {
328
	return (*CoreAPI)(api)
329
}
Łukasz Magiera's avatar
Łukasz Magiera committed
330 331 332 333 334 335 336

func deserializeNode(nd *Node, dataFieldEncoding string) (*dag.ProtoNode, error) {
	dagnode := new(dag.ProtoNode)
	switch dataFieldEncoding {
	case "text":
		dagnode.SetData([]byte(nd.Data))
	case "base64":
Steven Allen's avatar
Steven Allen committed
337 338 339 340
		data, err := base64.StdEncoding.DecodeString(nd.Data)
		if err != nil {
			return nil, err
		}
Łukasz Magiera's avatar
Łukasz Magiera committed
341 342
		dagnode.SetData(data)
	default:
flowed's avatar
flowed committed
343
		return nil, fmt.Errorf("unknown data field encoding")
Łukasz Magiera's avatar
Łukasz Magiera committed
344 345
	}

Steven Allen's avatar
Steven Allen committed
346
	links := make([]*ipld.Link, len(nd.Links))
Łukasz Magiera's avatar
Łukasz Magiera committed
347 348 349 350 351
	for i, link := range nd.Links {
		c, err := cid.Decode(link.Hash)
		if err != nil {
			return nil, err
		}
Steven Allen's avatar
Steven Allen committed
352
		links[i] = &ipld.Link{
Łukasz Magiera's avatar
Łukasz Magiera committed
353 354 355 356 357
			Name: link.Name,
			Size: link.Size,
			Cid:  c,
		}
	}
Steven Allen's avatar
Steven Allen committed
358
	dagnode.SetLinks(links)
Łukasz Magiera's avatar
Łukasz Magiera committed
359 360 361

	return dagnode, nil
}