1
1
package commands
2
2
3
3
import (
4
- "bytes"
5
4
"fmt"
6
5
"io"
7
6
"text/tabwriter"
8
7
9
- cmds "github.com/ipfs/go-ipfs/commands"
8
+ cmdenv "github.com/ipfs/go-ipfs/core/ commands/cmdenv "
10
9
e "github.com/ipfs/go-ipfs/core/commands/e"
11
10
iface "github.com/ipfs/go-ipfs/core/coreapi/interface"
12
11
13
12
cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
14
13
ipld "gx/ipfs/QmR7TcHkR9nxkUorfi8XMTAMLUK7GiP64TWWBzY3aacc1o/go-ipld-format"
14
+ cmds "gx/ipfs/QmSXUokcP4TJpFfqozT69AVAYRtzXVMUjzQVkYX41R9Svs/go-ipfs-cmds"
15
15
merkledag "gx/ipfs/QmSei8kFMfqdJq7Q68d2LMnHbTWKKg2daA29ezUYFAUNgc/go-merkledag"
16
16
offline "gx/ipfs/QmT6dHGp3UYd3vUMpy7rzX2CXQv7HLcj42Vtq8qwwjgASb/go-ipfs-exchange-offline"
17
17
blockservice "gx/ipfs/QmWfhv1D18DRSiSm73r4QGcByspzPtxxRTcmHW3axFXZo8/go-blockservice"
@@ -21,24 +21,34 @@ import (
21
21
unixfspb "gx/ipfs/QmfB3oNXGGq9S4B2a9YeCajoATms3Zw2VvDm8fK7VeLSV8/go-unixfs/pb"
22
22
)
23
23
24
+ // LsLink contains printable data for a single ipld link in ls output
24
25
type LsLink struct {
25
26
Name , Hash string
26
27
Size uint64
27
28
Type unixfspb.Data_DataType
28
29
}
29
30
31
+ // LsObject is an element of LsOutput
32
+ // It can represent a whole directory, a directory header, one or more links,
33
+ // Or a the end of a directory
30
34
type LsObject struct {
31
- Hash string
32
- Links []LsLink
35
+ Hash string
36
+ Links []LsLink
37
+ HasHeader bool
38
+ HasLinks bool
39
+ HasFooter bool
33
40
}
34
41
42
+ // LsObject is a set of printable data for directories
35
43
type LsOutput struct {
36
- Objects []LsObject
44
+ MultipleFolders bool
45
+ Objects []LsObject
37
46
}
38
47
39
48
const (
40
49
lsHeadersOptionNameTime = "headers"
41
50
lsResolveTypeOptionName = "resolve-type"
51
+ lsStreamOptionName = "stream"
42
52
)
43
53
44
54
var LsCmd = & cmds.Command {
@@ -60,158 +70,225 @@ The JSON output contains type information.
60
70
Options : []cmdkit.Option {
61
71
cmdkit .BoolOption (lsHeadersOptionNameTime , "v" , "Print table headers (Hash, Size, Name)." ),
62
72
cmdkit .BoolOption (lsResolveTypeOptionName , "Resolve linked objects to find out their types." ).WithDefault (true ),
73
+ cmdkit .BoolOption (lsStreamOptionName , "s" , "Stream directory entries as they are found." ),
63
74
},
64
- Run : func (req cmds.Request , res cmds.Response ) {
65
- nd , err := req . InvocContext (). GetNode ()
75
+ Run : func (req * cmds.Request , res cmds.ResponseEmitter , env cmds. Environment ) error {
76
+ nd , err := cmdenv . GetNode (env )
66
77
if err != nil {
67
- res .SetError (err , cmdkit .ErrNormal )
68
- return
78
+ return err
69
79
}
70
80
71
- api , err := req . InvocContext (). GetApi ()
81
+ api , err := cmdenv . GetApi (env )
72
82
if err != nil {
73
- res .SetError (err , cmdkit .ErrNormal )
74
- return
75
- }
76
-
77
- // get options early -> exit early in case of error
78
- if _ , _ , err := req .Option (lsHeadersOptionNameTime ).Bool (); err != nil {
79
- res .SetError (err , cmdkit .ErrNormal )
80
- return
81
- }
82
-
83
- resolve , _ , err := req .Option (lsResolveTypeOptionName ).Bool ()
84
- if err != nil {
85
- res .SetError (err , cmdkit .ErrNormal )
86
- return
83
+ return err
87
84
}
88
85
86
+ resolve , _ := req .Options [lsResolveTypeOptionName ].(bool )
89
87
dserv := nd .DAG
90
88
if ! resolve {
91
89
offlineexch := offline .Exchange (nd .Blockstore )
92
90
bserv := blockservice .New (nd .Blockstore , offlineexch )
93
91
dserv = merkledag .NewDAGService (bserv )
94
92
}
95
93
96
- paths := req .Arguments ()
94
+ err = req .ParseBodyArgs ()
95
+ if err != nil {
96
+ return err
97
+ }
98
+
99
+ paths := req .Arguments
97
100
98
101
var dagnodes []ipld.Node
99
102
for _ , fpath := range paths {
100
103
p , err := iface .ParsePath (fpath )
101
104
if err != nil {
102
- res .SetError (err , cmdkit .ErrNormal )
103
- return
105
+ return err
104
106
}
105
107
106
- dagnode , err := api .ResolveNode (req .Context () , p )
108
+ dagnode , err := api .ResolveNode (req .Context , p )
107
109
if err != nil {
108
- res .SetError (err , cmdkit .ErrNormal )
109
- return
110
+ return err
110
111
}
111
112
dagnodes = append (dagnodes , dagnode )
112
113
}
113
-
114
- output := make ([]LsObject , len (req .Arguments ()))
115
- ng := merkledag .NewSession (req .Context (), nd .DAG )
114
+ ng := merkledag .NewSession (req .Context , nd .DAG )
116
115
ro := merkledag .NewReadOnlyDagService (ng )
117
116
117
+ stream , _ := req .Options [lsStreamOptionName ].(bool )
118
+ multipleFolders := len (req .Arguments ) > 1
119
+ if ! stream {
120
+ output := make ([]LsObject , len (req .Arguments ))
121
+
122
+ for i , dagnode := range dagnodes {
123
+ dir , err := uio .NewDirectoryFromNode (ro , dagnode )
124
+ if err != nil && err != uio .ErrNotADir {
125
+ return fmt .Errorf ("the data in %s (at %q) is not a UnixFS directory: %s" , dagnode .Cid (), paths [i ], err )
126
+ }
127
+
128
+ var links []* ipld.Link
129
+ if dir == nil {
130
+ links = dagnode .Links ()
131
+ } else {
132
+ links , err = dir .Links (req .Context )
133
+ if err != nil {
134
+ return err
135
+ }
136
+ }
137
+ outputLinks := make ([]LsLink , len (links ))
138
+ for j , link := range links {
139
+ lsLink , err := makeLsLink (req , dserv , resolve , link )
140
+ if err != nil {
141
+ return err
142
+ }
143
+ outputLinks [j ] = * lsLink
144
+ }
145
+ output [i ] = newFullDirectoryLsObject (paths [i ], outputLinks )
146
+ }
147
+
148
+ return cmds .EmitOnce (res , & LsOutput {multipleFolders , output })
149
+ }
150
+
118
151
for i , dagnode := range dagnodes {
119
152
dir , err := uio .NewDirectoryFromNode (ro , dagnode )
120
153
if err != nil && err != uio .ErrNotADir {
121
- res .SetError (fmt .Errorf ("the data in %s (at %q) is not a UnixFS directory: %s" , dagnode .Cid (), paths [i ], err ), cmdkit .ErrNormal )
122
- return
154
+ return fmt .Errorf ("the data in %s (at %q) is not a UnixFS directory: %s" , dagnode .Cid (), paths [i ], err )
123
155
}
124
156
125
- var links [] * ipld. Link
157
+ var linkResults <- chan unixfs. LinkResult
126
158
if dir == nil {
127
- links = dagnode . Links ( )
159
+ linkResults = makeDagNodeLinkResults ( req , dagnode )
128
160
} else {
129
- links , err = dir .Links (req .Context ())
130
- if err != nil {
131
- res .SetError (err , cmdkit .ErrNormal )
132
- return
133
- }
161
+ linkResults = dir .EnumLinksAsync (req .Context )
134
162
}
135
163
136
- output [i ] = LsObject {
137
- Hash : paths [i ],
138
- Links : make ([]LsLink , len (links )),
139
- }
140
-
141
- for j , link := range links {
142
- t := unixfspb .Data_DataType (- 1 )
143
-
144
- switch link .Cid .Type () {
145
- case cid .Raw :
146
- // No need to check with raw leaves
147
- t = unixfs .TFile
148
- case cid .DagProtobuf :
149
- linkNode , err := link .GetNode (req .Context (), dserv )
150
- if err == ipld .ErrNotFound && ! resolve {
151
- // not an error
152
- linkNode = nil
153
- } else if err != nil {
154
- res .SetError (err , cmdkit .ErrNormal )
155
- return
156
- }
164
+ output := make ([]LsObject , 1 )
165
+ outputLinks := make ([]LsLink , 1 )
157
166
158
- if pn , ok := linkNode .(* merkledag.ProtoNode ); ok {
159
- d , err := unixfs .FSNodeFromBytes (pn .Data ())
160
- if err != nil {
161
- res .SetError (err , cmdkit .ErrNormal )
162
- return
163
- }
164
- t = d .Type ()
165
- }
167
+ output [0 ] = newDirectoryHeaderLsObject (paths [i ])
168
+ if err = res .Emit (& LsOutput {multipleFolders , output }); err != nil {
169
+ return nil
170
+ }
171
+ for linkResult := range linkResults {
172
+ if linkResult .Err != nil {
173
+ return linkResult .Err
174
+ }
175
+ link := linkResult .Link
176
+ lsLink , err := makeLsLink (req , dserv , resolve , link )
177
+ if err != nil {
178
+ return err
166
179
}
167
- output [i ].Links [j ] = LsLink {
168
- Name : link .Name ,
169
- Hash : link .Cid .String (),
170
- Size : link .Size ,
171
- Type : t ,
180
+ outputLinks [0 ] = * lsLink
181
+ output [0 ] = newDirectoryLinksLsObject (outputLinks )
182
+ if err = res .Emit (& LsOutput {multipleFolders , output }); err != nil {
183
+ return err
172
184
}
173
185
}
186
+ output [0 ] = newDirectoryFooterLsObject ()
187
+ if err = res .Emit (& LsOutput {multipleFolders , output }); err != nil {
188
+ return err
189
+ }
174
190
}
175
-
176
- res .SetOutput (& LsOutput {output })
191
+ return nil
177
192
},
178
- Marshalers : cmds.MarshalerMap {
179
- cmds .Text : func (res cmds.Response ) (io.Reader , error ) {
180
-
181
- v , err := unwrapOutput (res .Output ())
182
- if err != nil {
183
- return nil , err
184
- }
185
-
186
- headers , _ , _ := res .Request ().Option (lsHeadersOptionNameTime ).Bool ()
193
+ Encoders : cmds.EncoderMap {
194
+ cmds .Text : cmds .MakeEncoder (func (req * cmds.Request , w io.Writer , v interface {}) error {
195
+ headers , _ := req .Options [lsHeadersOptionNameTime ].(bool )
187
196
output , ok := v .(* LsOutput )
188
197
if ! ok {
189
- return nil , e .TypeErr (output , v )
198
+ return e .TypeErr (output , v )
190
199
}
191
200
192
- buf := new (bytes.Buffer )
193
- w := tabwriter .NewWriter (buf , 1 , 2 , 1 , ' ' , 0 )
201
+ tw := tabwriter .NewWriter (w , 1 , 2 , 1 , ' ' , 0 )
194
202
for _ , object := range output .Objects {
195
- if len (output .Objects ) > 1 {
196
- fmt .Fprintf (w , "%s:\n " , object .Hash )
197
- }
198
- if headers {
199
- fmt .Fprintln (w , "Hash\t Size\t Name" )
203
+ if object .HasHeader {
204
+ if output .MultipleFolders {
205
+ fmt .Fprintf (tw , "%s:\n " , object .Hash )
206
+ }
207
+ if headers {
208
+ fmt .Fprintln (tw , "Hash\t Size\t Name" )
209
+ }
200
210
}
201
- for _ , link := range object .Links {
202
- if link .Type == unixfs .TDirectory {
203
- link .Name += "/"
211
+ if object .HasLinks {
212
+ for _ , link := range object .Links {
213
+ if link .Type == unixfs .TDirectory {
214
+ link .Name += "/"
215
+ }
216
+
217
+ fmt .Fprintf (tw , "%s\t %v\t %s\n " , link .Hash , link .Size , link .Name )
204
218
}
205
- fmt .Fprintf (w , "%s\t %v\t %s\n " , link .Hash , link .Size , link .Name )
206
219
}
207
- if len (output .Objects ) > 1 {
208
- fmt .Fprintln (w )
220
+ if object .HasFooter {
221
+ if output .MultipleFolders {
222
+ fmt .Fprintln (tw )
223
+ }
209
224
}
210
225
}
211
- w .Flush ()
212
-
213
- return buf , nil
214
- },
226
+ tw .Flush ()
227
+ return nil
228
+ }),
215
229
},
216
230
Type : LsOutput {},
217
231
}
232
+
233
+ func makeDagNodeLinkResults (req * cmds.Request , dagnode ipld.Node ) <- chan unixfs.LinkResult {
234
+ linkResults := make (chan unixfs.LinkResult )
235
+ go func () {
236
+ defer close (linkResults )
237
+ for _ , l := range dagnode .Links () {
238
+ select {
239
+ case linkResults <- unixfs.LinkResult {
240
+ Link : l ,
241
+ Err : nil ,
242
+ }:
243
+ case <- req .Context .Done ():
244
+ return
245
+ }
246
+ }
247
+ }()
248
+ return linkResults
249
+ }
250
+
251
+ func newFullDirectoryLsObject (hash string , links []LsLink ) LsObject {
252
+ return LsObject {hash , links , true , true , true }
253
+ }
254
+ func newDirectoryHeaderLsObject (hash string ) LsObject {
255
+ return LsObject {hash , nil , true , false , false }
256
+ }
257
+ func newDirectoryLinksLsObject (links []LsLink ) LsObject {
258
+ return LsObject {"" , links , false , true , false }
259
+ }
260
+ func newDirectoryFooterLsObject () LsObject {
261
+ return LsObject {"" , nil , false , false , true }
262
+ }
263
+
264
+ func makeLsLink (req * cmds.Request , dserv ipld.DAGService , resolve bool , link * ipld.Link ) (* LsLink , error ) {
265
+ t := unixfspb .Data_DataType (- 1 )
266
+
267
+ switch link .Cid .Type () {
268
+ case cid .Raw :
269
+ // No need to check with raw leaves
270
+ t = unixfs .TFile
271
+ case cid .DagProtobuf :
272
+ linkNode , err := link .GetNode (req .Context , dserv )
273
+ if err == ipld .ErrNotFound && ! resolve {
274
+ // not an error
275
+ linkNode = nil
276
+ } else if err != nil {
277
+ return nil , err
278
+ }
279
+
280
+ if pn , ok := linkNode .(* merkledag.ProtoNode ); ok {
281
+ d , err := unixfs .FSNodeFromBytes (pn .Data ())
282
+ if err != nil {
283
+ return nil , err
284
+ }
285
+ t = d .Type ()
286
+ }
287
+ }
288
+ return & LsLink {
289
+ Name : link .Name ,
290
+ Hash : link .Cid .String (),
291
+ Size : link .Size ,
292
+ Type : t ,
293
+ }, nil
294
+ }
0 commit comments