Skip to content

Commit 7dadaa5

Browse files
committed
Update LS Command to support streaming
License: MIT Signed-off-by: hannahhoward <[email protected]>
1 parent e2d2c55 commit 7dadaa5

File tree

1 file changed

+161
-62
lines changed

1 file changed

+161
-62
lines changed

core/commands/ls.go

+161-62
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,34 @@ import (
2121
"gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
2222
)
2323

24-
// printable data for a single ipld link in ls output
24+
// LsLink contains printable data for a single ipld link in ls output
2525
type LsLink struct {
2626
Name, Hash string
2727
Size uint64
2828
Type unixfspb.Data_DataType
2929
}
3030

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
3234
type LsObject struct {
33-
Hash string
34-
Links []LsLink
35+
Hash string
36+
Links []LsLink
37+
HasHeader bool
38+
HasLinks bool
39+
HasFooter bool
3540
}
3641

37-
// printable data for multiple unixfs directories
42+
// LsObject is a set of printable data for directories
3843
type LsOutput struct {
39-
Objects []LsObject
44+
MultipleFolders bool
45+
Objects []LsObject
4046
}
4147

4248
const (
4349
lsHeadersOptionNameTime = "headers"
4450
lsResolveTypeOptionName = "resolve-type"
51+
lsStreamOptionName = "stream"
4552
)
4653

4754
var LsCmd = &cmds.Command{
@@ -63,6 +70,7 @@ The JSON output contains type information.
6370
Options: []cmdkit.Option{
6471
cmdkit.BoolOption(lsHeadersOptionNameTime, "v", "Print table headers (Hash, Size, Name)."),
6572
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."),
6674
},
6775
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
6876
nd, err := cmdenv.GetNode(env)
@@ -75,7 +83,7 @@ The JSON output contains type information.
7583
return err
7684
}
7785

78-
resolve := req.Options[lsResolveTypeOptionName].(bool)
86+
resolve, _ := req.Options[lsResolveTypeOptionName].(bool)
7987
dserv := nd.DAG
8088
if !resolve {
8189
offlineexch := offline.Exchange(nd.Blockstore)
@@ -103,96 +111,187 @@ The JSON output contains type information.
103111
}
104112
dagnodes = append(dagnodes, dagnode)
105113
}
106-
107-
output := make([]LsObject, len(req.Arguments))
108114
ng := merkledag.NewSession(req.Context, nd.DAG)
109115
ro := merkledag.NewReadOnlyDagService(ng)
110116

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+
111151
for i, dagnode := range dagnodes {
112152
dir, err := uio.NewDirectoryFromNode(ro, dagnode)
113153
if err != nil && err != uio.ErrNotADir {
114154
return fmt.Errorf("the data in %s (at %q) is not a UnixFS directory: %s", dagnode.Cid(), paths[i], err)
115155
}
116156

117-
var links []*ipld.Link
157+
var linkResults <-chan unixfs.LinkResult
118158
if dir == nil {
119-
links = dagnode.Links()
159+
linkResults, err = makeDagNodeLinkResults(req, dagnode)
120160
} else {
121-
links, err = dir.Links(req.Context)
122-
if err != nil {
123-
return err
124-
}
161+
linkResults, err = dir.EnumLinksAsync(req.Context)
125162
}
126-
127-
output[i] = LsObject{
128-
Hash: paths[i],
129-
Links: make([]LsLink, len(links)),
163+
if err != nil {
164+
return err
130165
}
131166

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)
147169

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
155182
}
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
161187
}
162188
}
189+
output[0] = newDirectoryFooterLsObject()
190+
if err = res.Emit(&LsOutput{multipleFolders, output}); err != nil {
191+
return err
192+
}
163193
}
164-
165-
return cmds.EmitOnce(res, &LsOutput{output})
194+
return nil
166195
},
167196
Encoders: cmds.EncoderMap{
168197
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)
170199
output, ok := v.(*LsOutput)
171200
if !ok {
172201
return e.TypeErr(output, v)
173202
}
174203

175-
w = tabwriter.NewWriter(w, 1, 2, 1, ' ', 0)
204+
tw := tabwriter.NewWriter(w, 1, 2, 1, ' ', 0)
176205
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\tSize\tName")
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\tSize\tName")
212+
}
182213
}
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)
186221
}
187-
fmt.Fprintf(w, "%s\t%v\t%s\n", link.Hash, link.Size, link.Name)
188222
}
189-
if len(output.Objects) > 1 {
190-
fmt.Fprintln(w)
223+
if object.HasFooter {
224+
if output.MultipleFolders {
225+
fmt.Fprintln(tw)
226+
}
191227
}
192228
}
193-
229+
tw.Flush()
194230
return nil
195231
}),
196232
},
197233
Type: LsOutput{},
198234
}
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

Comments
 (0)