1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-08-06 11:31:54 +08:00
Files
kubo/merkledag/node.go
Jeromy 01aee44679 merkledag: change 'Node' to be an interface
Also change existing 'Node' type to 'ProtoNode' and use that most
everywhere for now. As we move forward with the integration we will try
and use the Node interface in more places that we're currently using
ProtoNode.

License: MIT
Signed-off-by: Jeromy <why@ipfs.io>
2016-10-12 08:16:03 -07:00

322 lines
6.9 KiB
Go

package merkledag
import (
"fmt"
"context"
cid "gx/ipfs/QmXUuRadqDq5BuFWzVU6VuKaSjTcNm1gNCtLvvP1TJCW4z/go-cid"
mh "gx/ipfs/QmYDds3421prZgqKbLpEK7T9Aa2eVdQ7o3YarX1LVLdP2J/go-multihash"
key "gx/ipfs/QmYEoKZXHoAToWfhGF3vryhMn3WWhE1o2MasQ8uzY5iDi9/go-key"
)
var ErrLinkNotFound = fmt.Errorf("no link by that name")
// Node represents a node in the IPFS Merkle DAG.
// nodes have opaque data and a set of navigable links.
type ProtoNode struct {
links []*Link
data []byte
// cache encoded/marshaled value
encoded []byte
cached *cid.Cid
}
// NodeStat is a statistics object for a Node. Mostly sizes.
type NodeStat struct {
Hash string
NumLinks int // number of links in link table
BlockSize int // size of the raw, encoded data
LinksSize int // size of the links segment
DataSize int // size of the data segment
CumulativeSize int // cumulative size of object and its references
}
func (ns NodeStat) String() string {
f := "NodeStat{NumLinks: %d, BlockSize: %d, LinksSize: %d, DataSize: %d, CumulativeSize: %d}"
return fmt.Sprintf(f, ns.NumLinks, ns.BlockSize, ns.LinksSize, ns.DataSize, ns.CumulativeSize)
}
// Link represents an IPFS Merkle DAG Link between Nodes.
type Link struct {
// utf string name. should be unique per object
Name string // utf8
// cumulative size of target object
Size uint64
// multihash of the target object
Cid *cid.Cid
}
type LinkSlice []*Link
func (ls LinkSlice) Len() int { return len(ls) }
func (ls LinkSlice) Swap(a, b int) { ls[a], ls[b] = ls[b], ls[a] }
func (ls LinkSlice) Less(a, b int) bool { return ls[a].Name < ls[b].Name }
// MakeLink creates a link to the given node
func MakeLink(n Node) (*Link, error) {
s, err := n.Size()
if err != nil {
return nil, err
}
return &Link{
Size: s,
Cid: n.Cid(),
}, nil
}
// GetNode returns the MDAG Node that this link points to
func (l *Link) GetNode(ctx context.Context, serv DAGService) (Node, error) {
return serv.Get(ctx, l.Cid)
}
func NodeWithData(d []byte) *ProtoNode {
return &ProtoNode{data: d}
}
// AddNodeLink adds a link to another node.
func (n *ProtoNode) AddNodeLink(name string, that *ProtoNode) error {
n.encoded = nil
lnk, err := MakeLink(that)
lnk.Name = name
if err != nil {
return err
}
n.AddRawLink(name, lnk)
return nil
}
// AddNodeLinkClean adds a link to another node. without keeping a reference to
// the child node
func (n *ProtoNode) AddNodeLinkClean(name string, that Node) error {
n.encoded = nil
lnk, err := MakeLink(that)
if err != nil {
return err
}
n.AddRawLink(name, lnk)
return nil
}
// AddRawLink adds a copy of a link to this node
func (n *ProtoNode) AddRawLink(name string, l *Link) error {
n.encoded = nil
n.links = append(n.links, &Link{
Name: name,
Size: l.Size,
Cid: l.Cid,
})
return nil
}
// Remove a link on this node by the given name
func (n *ProtoNode) RemoveNodeLink(name string) error {
n.encoded = nil
good := make([]*Link, 0, len(n.links))
var found bool
for _, l := range n.links {
if l.Name != name {
good = append(good, l)
} else {
found = true
}
}
n.links = good
if !found {
return ErrNotFound
}
return nil
}
// Return a copy of the link with given name
func (n *ProtoNode) GetNodeLink(name string) (*Link, error) {
for _, l := range n.links {
if l.Name == name {
return &Link{
Name: l.Name,
Size: l.Size,
Cid: l.Cid,
}, nil
}
}
return nil, ErrLinkNotFound
}
var ErrNotProtobuf = fmt.Errorf("expected protobuf dag node")
func (n *ProtoNode) GetLinkedProtoNode(ctx context.Context, ds DAGService, name string) (*ProtoNode, error) {
nd, err := n.GetLinkedNode(ctx, ds, name)
if err != nil {
return nil, err
}
pbnd, ok := nd.(*ProtoNode)
if !ok {
return nil, ErrNotProtobuf
}
return pbnd, nil
}
func (n *ProtoNode) GetLinkedNode(ctx context.Context, ds DAGService, name string) (Node, error) {
lnk, err := n.GetNodeLink(name)
if err != nil {
return nil, err
}
return lnk.GetNode(ctx, ds)
}
// Copy returns a copy of the node.
// NOTE: Does not make copies of Node objects in the links.
func (n *ProtoNode) Copy() *ProtoNode {
nnode := new(ProtoNode)
if len(n.data) > 0 {
nnode.data = make([]byte, len(n.data))
copy(nnode.data, n.data)
}
if len(n.links) > 0 {
nnode.links = make([]*Link, len(n.links))
copy(nnode.links, n.links)
}
return nnode
}
func (n *ProtoNode) RawData() []byte {
out, _ := n.EncodeProtobuf(false)
return out
}
func (n *ProtoNode) Data() []byte {
return n.data
}
func (n *ProtoNode) SetData(d []byte) {
n.encoded = nil
n.cached = nil
n.data = d
}
// UpdateNodeLink return a copy of the node with the link name set to point to
// that. If a link of the same name existed, it is removed.
func (n *ProtoNode) UpdateNodeLink(name string, that *ProtoNode) (*ProtoNode, error) {
newnode := n.Copy()
err := newnode.RemoveNodeLink(name)
err = nil // ignore error
err = newnode.AddNodeLink(name, that)
return newnode, err
}
// Size returns the total size of the data addressed by node,
// including the total sizes of references.
func (n *ProtoNode) Size() (uint64, error) {
b, err := n.EncodeProtobuf(false)
if err != nil {
return 0, err
}
s := uint64(len(b))
for _, l := range n.links {
s += l.Size
}
return s, nil
}
// Stat returns statistics on the node.
func (n *ProtoNode) Stat() (*NodeStat, error) {
enc, err := n.EncodeProtobuf(false)
if err != nil {
return nil, err
}
cumSize, err := n.Size()
if err != nil {
return nil, err
}
return &NodeStat{
Hash: n.Key().B58String(),
NumLinks: len(n.links),
BlockSize: len(enc),
LinksSize: len(enc) - len(n.data), // includes framing.
DataSize: len(n.data),
CumulativeSize: int(cumSize),
}, nil
}
func (n *ProtoNode) Key() key.Key {
return key.Key(n.Multihash())
}
func (n *ProtoNode) Loggable() map[string]interface{} {
return map[string]interface{}{
"node": n.String(),
}
}
func (n *ProtoNode) Cid() *cid.Cid {
h := n.Multihash()
return cid.NewCidV0(h)
}
func (n *ProtoNode) String() string {
return n.Cid().String()
}
// Multihash hashes the encoded data of this node.
func (n *ProtoNode) Multihash() mh.Multihash {
// NOTE: EncodeProtobuf generates the hash and puts it in n.cached.
_, err := n.EncodeProtobuf(false)
if err != nil {
// Note: no possibility exists for an error to be returned through here
panic(err)
}
return n.cached.Hash()
}
func (n *ProtoNode) Links() []*Link {
return n.links
}
func (n *ProtoNode) SetLinks(links []*Link) {
n.links = links
}
func (n *ProtoNode) Resolve(path []string) (*Link, []string, error) {
if len(path) == 0 {
return nil, nil, fmt.Errorf("end of path, no more links to resolve")
}
lnk, err := n.GetNodeLink(path[0])
if err != nil {
return nil, nil, err
}
return lnk, path[1:], nil
}
func (n *ProtoNode) Tree() []string {
out := make([]string, 0, len(n.links))
for _, lnk := range n.links {
out = append(out, lnk.Name)
}
return out
}