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