Skip to content

Commit 3b19e7e

Browse files
committed
feat(commands): --stream option for ls
Convert LS Command to use current cmds lib Update LS Command to support streaming Rebase fixes License: MIT Signed-off-by: hannahhoward <[email protected]>
1 parent b6e1d38 commit 3b19e7e

File tree

2 files changed

+184
-107
lines changed

2 files changed

+184
-107
lines changed

core/commands/ls.go

+182-105
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package commands
22

33
import (
4-
"bytes"
54
"fmt"
65
"io"
76
"text/tabwriter"
87

9-
cmds "github.com/ipfs/go-ipfs/commands"
8+
cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
109
e "github.com/ipfs/go-ipfs/core/commands/e"
1110
iface "github.com/ipfs/go-ipfs/core/coreapi/interface"
1211

1312
cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
1413
ipld "gx/ipfs/QmR7TcHkR9nxkUorfi8XMTAMLUK7GiP64TWWBzY3aacc1o/go-ipld-format"
14+
cmds "gx/ipfs/QmSXUokcP4TJpFfqozT69AVAYRtzXVMUjzQVkYX41R9Svs/go-ipfs-cmds"
1515
merkledag "gx/ipfs/QmSei8kFMfqdJq7Q68d2LMnHbTWKKg2daA29ezUYFAUNgc/go-merkledag"
1616
offline "gx/ipfs/QmT6dHGp3UYd3vUMpy7rzX2CXQv7HLcj42Vtq8qwwjgASb/go-ipfs-exchange-offline"
1717
blockservice "gx/ipfs/QmWfhv1D18DRSiSm73r4QGcByspzPtxxRTcmHW3axFXZo8/go-blockservice"
@@ -21,24 +21,34 @@ import (
2121
unixfspb "gx/ipfs/QmfB3oNXGGq9S4B2a9YeCajoATms3Zw2VvDm8fK7VeLSV8/go-unixfs/pb"
2222
)
2323

24+
// LsLink contains printable data for a single ipld link in ls output
2425
type LsLink struct {
2526
Name, Hash string
2627
Size uint64
2728
Type unixfspb.Data_DataType
2829
}
2930

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
3034
type LsObject struct {
31-
Hash string
32-
Links []LsLink
35+
Hash string
36+
Links []LsLink
37+
HasHeader bool
38+
HasLinks bool
39+
HasFooter bool
3340
}
3441

42+
// LsObject is a set of printable data for directories
3543
type LsOutput struct {
36-
Objects []LsObject
44+
MultipleFolders bool
45+
Objects []LsObject
3746
}
3847

3948
const (
4049
lsHeadersOptionNameTime = "headers"
4150
lsResolveTypeOptionName = "resolve-type"
51+
lsStreamOptionName = "stream"
4252
)
4353

4454
var LsCmd = &cmds.Command{
@@ -60,158 +70,225 @@ The JSON output contains type information.
6070
Options: []cmdkit.Option{
6171
cmdkit.BoolOption(lsHeadersOptionNameTime, "v", "Print table headers (Hash, Size, Name)."),
6272
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."),
6374
},
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)
6677
if err != nil {
67-
res.SetError(err, cmdkit.ErrNormal)
68-
return
78+
return err
6979
}
7080

71-
api, err := req.InvocContext().GetApi()
81+
api, err := cmdenv.GetApi(env)
7282
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
8784
}
8885

86+
resolve, _ := req.Options[lsResolveTypeOptionName].(bool)
8987
dserv := nd.DAG
9088
if !resolve {
9189
offlineexch := offline.Exchange(nd.Blockstore)
9290
bserv := blockservice.New(nd.Blockstore, offlineexch)
9391
dserv = merkledag.NewDAGService(bserv)
9492
}
9593

96-
paths := req.Arguments()
94+
err = req.ParseBodyArgs()
95+
if err != nil {
96+
return err
97+
}
98+
99+
paths := req.Arguments
97100

98101
var dagnodes []ipld.Node
99102
for _, fpath := range paths {
100103
p, err := iface.ParsePath(fpath)
101104
if err != nil {
102-
res.SetError(err, cmdkit.ErrNormal)
103-
return
105+
return err
104106
}
105107

106-
dagnode, err := api.ResolveNode(req.Context(), p)
108+
dagnode, err := api.ResolveNode(req.Context, p)
107109
if err != nil {
108-
res.SetError(err, cmdkit.ErrNormal)
109-
return
110+
return err
110111
}
111112
dagnodes = append(dagnodes, dagnode)
112113
}
113-
114-
output := make([]LsObject, len(req.Arguments()))
115-
ng := merkledag.NewSession(req.Context(), nd.DAG)
114+
ng := merkledag.NewSession(req.Context, nd.DAG)
116115
ro := merkledag.NewReadOnlyDagService(ng)
117116

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+
118151
for i, dagnode := range dagnodes {
119152
dir, err := uio.NewDirectoryFromNode(ro, dagnode)
120153
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)
123155
}
124156

125-
var links []*ipld.Link
157+
var linkResults <-chan unixfs.LinkResult
126158
if dir == nil {
127-
links = dagnode.Links()
159+
linkResults = makeDagNodeLinkResults(req, dagnode)
128160
} 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)
134162
}
135163

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

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
166179
}
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
172184
}
173185
}
186+
output[0] = newDirectoryFooterLsObject()
187+
if err = res.Emit(&LsOutput{multipleFolders, output}); err != nil {
188+
return err
189+
}
174190
}
175-
176-
res.SetOutput(&LsOutput{output})
191+
return nil
177192
},
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)
187196
output, ok := v.(*LsOutput)
188197
if !ok {
189-
return nil, e.TypeErr(output, v)
198+
return e.TypeErr(output, v)
190199
}
191200

192-
buf := new(bytes.Buffer)
193-
w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
201+
tw := tabwriter.NewWriter(w, 1, 2, 1, ' ', 0)
194202
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\tSize\tName")
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\tSize\tName")
209+
}
200210
}
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)
204218
}
205-
fmt.Fprintf(w, "%s\t%v\t%s\n", link.Hash, link.Size, link.Name)
206219
}
207-
if len(output.Objects) > 1 {
208-
fmt.Fprintln(w)
220+
if object.HasFooter {
221+
if output.MultipleFolders {
222+
fmt.Fprintln(tw)
223+
}
209224
}
210225
}
211-
w.Flush()
212-
213-
return buf, nil
214-
},
226+
tw.Flush()
227+
return nil
228+
}),
215229
},
216230
Type: LsOutput{},
217231
}
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

Comments
 (0)