dagreader.go 3.05 KB
Newer Older
1 2 3 4 5 6 7
package merkledag

import (
	"bytes"
	"errors"
	"io"

8
	proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
9
	ft "github.com/jbenet/go-ipfs/importer/format"
10
	u "github.com/jbenet/go-ipfs/util"
11 12
)

Siraj Ravel's avatar
Siraj Ravel committed
13
var ErrIsDir = errors.New("this dag node is a directory")
14 15 16

// DagReader provides a way to easily read the data contained in a dag.
type DagReader struct {
17
	serv     *DAGService
18 19 20 21 22
	node     *Node
	position int
	buf      *bytes.Buffer
}

Jeromy's avatar
Jeromy committed
23 24
// NewDagReader creates a new reader object that reads the data represented by the given
// node, using the passed in DAGService for data retreival
25
func NewDagReader(n *Node, serv *DAGService) (io.Reader, error) {
26
	pb := new(ft.PBData)
27 28 29 30
	err := proto.Unmarshal(n.Data, pb)
	if err != nil {
		return nil, err
	}
31

32
	switch pb.GetType() {
33
	case ft.PBData_Directory:
Jeromy's avatar
Jeromy committed
34
		// Dont allow reading directories
35
		return nil, ErrIsDir
36
	case ft.PBData_File:
37
		return &DagReader{
Jeromy's avatar
Jeromy committed
38 39 40
			node: n,
			serv: serv,
			buf:  bytes.NewBuffer(pb.GetData()),
41
		}, nil
42
	case ft.PBData_Raw:
Jeromy's avatar
Jeromy committed
43
		// Raw block will just be a single level, return a byte buffer
44 45
		return bytes.NewBuffer(pb.GetData()), nil
	default:
Jeromy's avatar
Jeromy committed
46
		return nil, ft.ErrUnrecognizedType
47 48 49
	}
}

Jeromy's avatar
Jeromy committed
50 51
// Follows the next link in line and loads it from the DAGService,
// setting the next buffer to read from
52 53 54 55 56 57 58
func (dr *DagReader) precalcNextBuf() error {
	if dr.position >= len(dr.node.Links) {
		return io.EOF
	}
	nxtLink := dr.node.Links[dr.position]
	nxt := nxtLink.Node
	if nxt == nil {
59 60 61 62 63
		nxtNode, err := dr.serv.Get(u.Key(nxtLink.Hash))
		if err != nil {
			return err
		}
		nxt = nxtNode
64
	}
65
	pb := new(ft.PBData)
66 67 68 69 70 71 72
	err := proto.Unmarshal(nxt.Data, pb)
	if err != nil {
		return err
	}
	dr.position++

	switch pb.GetType() {
73
	case ft.PBData_Directory:
Jeromy's avatar
Jeromy committed
74
		return ft.ErrInvalidDirLocation
75
	case ft.PBData_File:
Jeromy's avatar
Jeromy committed
76 77
		//TODO: this *should* work, needs testing first
		//return NewDagReader(nxt, dr.serv)
78
		panic("Not yet handling different layers of indirection!")
79
	case ft.PBData_Raw:
80 81 82
		dr.buf = bytes.NewBuffer(pb.GetData())
		return nil
	default:
Jeromy's avatar
Jeromy committed
83
		return ft.ErrUnrecognizedType
84 85 86 87
	}
}

func (dr *DagReader) Read(b []byte) (int, error) {
Jeromy's avatar
Jeromy committed
88
	// If no cached buffer, load one
89 90 91 92 93 94 95 96
	if dr.buf == nil {
		err := dr.precalcNextBuf()
		if err != nil {
			return 0, err
		}
	}
	total := 0
	for {
Jeromy's avatar
Jeromy committed
97
		// Attempt to fill bytes from cached buffer
98 99 100
		n, err := dr.buf.Read(b[total:])
		total += n
		if err != nil {
Jeromy's avatar
Jeromy committed
101
			// EOF is expected
102 103 104 105
			if err != io.EOF {
				return total, err
			}
		}
Jeromy's avatar
Jeromy committed
106 107

		// If weve read enough bytes, return
108 109 110
		if total == len(b) {
			return total, nil
		}
Jeromy's avatar
Jeromy committed
111 112

		// Otherwise, load up the next block
113 114 115 116 117 118
		err = dr.precalcNextBuf()
		if err != nil {
			return total, err
		}
	}
}
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

/*
func (dr *DagReader) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	case os.SEEK_SET:
		for i := 0; i < len(dr.node.Links); i++ {
			nsize := dr.node.Links[i].Size - 8
			if offset > nsize {
				offset -= nsize
			} else {
				break
			}
		}
		dr.position = i
		err := dr.precalcNextBuf()
		if err != nil {
			return 0, err
		}
	case os.SEEK_CUR:
	case os.SEEK_END:
	default:
		return 0, errors.New("invalid whence")
	}
	return 0, nil
}
*/