1
+package tar
2
+
3
+import (
4
+ "archive/tar"
5
+ "bufio"
6
+ "compress/gzip"
7
+ "fmt"
8
+ "io"
9
+ "path"
10
+ "time"
11
+
12
+ proto "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/gogo/protobuf/proto"
13
+ cxt "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
14
+
15
+ mdag "github.com/ipfs/go-ipfs/merkledag"
16
+ uio "github.com/ipfs/go-ipfs/unixfs/io"
17
+ upb "github.com/ipfs/go-ipfs/unixfs/pb"
18
+)
19
+
20
+// DefaultBufSize is the buffer size for gets. for now, 1MB, which is ~4 blocks.
21
+// TODO: does this need to be configurable?
22
+var DefaultBufSize = 1048576
23
+
24
+func DagArchive(ctx cxt.Context, nd *mdag.Node, name string, dag mdag.DAGService, compression int) (io.Reader, error) {
25
+
26
+ _, filename := path.Split(name)
27
+
28
+ // need to connect a writer to a reader
29
+ piper, pipew := io.Pipe()
30
+
31
+ // use a buffered writer to parallelize task
32
+ bufw := bufio.NewWriterSize(pipew, DefaultBufSize)
33
+
34
+ // construct the tar writer
35
+ w, err := NewWriter(bufw, dag, compression)
36
+ if err != nil {
37
+ return nil, err
38
+ }
39
+
40
+ // write all the nodes recursively
41
+ go func() {
42
+ if err := w.WriteNode(ctx, nd, filename); err != nil {
43
+ pipew.CloseWithError(err)
44
+ return
45
+ }
46
+
47
+ if err := bufw.Flush(); err != nil {
48
+ pipew.CloseWithError(err)
49
+ return
50
+ }
51
+
52
+ pipew.Close() // everything seems to be ok.
53
+ }()
54
+
55
+ return piper, nil
56
+}
57
+
58
+// Writer is a utility structure that helps to write
59
+// unixfs merkledag nodes as a tar archive format.
60
+// It wraps any io.Writer.
61
+type Writer struct {
62
+ Dag mdag.DAGService
63
+ TarW *tar.Writer
64
+}
65
+
66
+// NewWriter wraps given io.Writer.
67
+// compression determines whether to use gzip compression.
68
+func NewWriter(w io.Writer, dag mdag.DAGService, compression int) (*Writer, error) {
69
+
70
+ if compression != gzip.NoCompression {
71
+ var err error
72
+ w, err = gzip.NewWriterLevel(w, compression)
73
+ if err != nil {
74
+ return nil, err
75
+ }
76
+ }
77
+
78
+ return &Writer{
79
+ Dag: dag,
80
+ TarW: tar.NewWriter(w),
81
+ }, nil
82
+}
83
+
84
+func (w *Writer) WriteDir(ctx cxt.Context, nd *mdag.Node, fpath string) error {
85
+ if err := writeDirHeader(w.TarW, fpath); err != nil {
86
+ return err
87
+ }
88
+
89
+ for i, ng := range w.Dag.GetDAG(ctx, nd) {
90
+ child, err := ng.Get(ctx)
91
+ if err != nil {
92
+ return err
93
+ }
94
+
95
+ npath := path.Join(fpath, nd.Links[i].Name)
96
+ if err := w.WriteNode(ctx, child, npath); err != nil {
97
+ return err
98
+ }
99
+ }
100
+
101
+ return nil
102
+}
103
+
104
+func (w *Writer) WriteFile(ctx cxt.Context, nd *mdag.Node, fpath string) error {
105
+ pb := new(upb.Data)
106
+ if err := proto.Unmarshal(nd.Data, pb); err != nil {
107
+ return err
108
+ }
109
+
110
+ return w.writeFile(ctx, nd, pb, fpath)
111
+}
112
+
113
+func (w *Writer) writeFile(ctx cxt.Context, nd *mdag.Node, pb *upb.Data, fpath string) error {
114
+ if err := writeFileHeader(w.TarW, fpath, pb.GetFilesize()); err != nil {
115
+ return err
116
+ }
117
+
118
+ dagr, err := uio.NewDagReader(ctx, nd, w.Dag)
119
+ if err != nil {
120
+ return err
121
+ }
122
+
123
+ _, err = io.Copy(w.TarW, dagr)
124
+ if err != nil && err != io.EOF {
125
+ return err
126
+ }
127
+
128
+ return nil
129
+}
130
+
131
+func (w *Writer) WriteNode(ctx cxt.Context, nd *mdag.Node, fpath string) error {
132
+ pb := new(upb.Data)
133
+ if err := proto.Unmarshal(nd.Data, pb); err != nil {
134
+ return err
135
+ }
136
+
137
+ switch pb.GetType() {
138
+ case upb.Data_Directory:
139
+ return w.WriteDir(ctx, nd, fpath)
140
+ case upb.Data_File:
141
+ return w.writeFile(ctx, nd, pb, fpath)
142
+ default:
143
+ return fmt.Errorf("unixfs type not supported: %s", pb.GetType())
144
+ }
145
+}
146
+
147
+func (w *Writer) Close() error {
148
+ return w.TarW.Close()
149
+}
150
+
151
+func writeDirHeader(w *tar.Writer, fpath string) error {
152
+ return w.WriteHeader(&tar.Header{
153
+ Name: fpath,
154
+ Typeflag: tar.TypeDir,
155
+ Mode: 0777,
156
+ ModTime: time.Now(),
157
+ // TODO: set mode, dates, etc. when added to unixFS
158
+ })
159
+}
160
+
161
+func writeFileHeader(w *tar.Writer, fpath string, size uint64) error {
162
+ return w.WriteHeader(&tar.Header{
163
+ Name: fpath,
164
+ Size: int64(size),
165
+ Typeflag: tar.TypeReg,
166
+ Mode: 0644,
167
+ ModTime: time.Now(),
168
+ // TODO: set mode, dates, etc. when added to unixFS
169
+ })
170
+}