Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 103 additions & 35 deletions core/commands/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,11 +437,68 @@ func getNodeFromPath(ctx context.Context, node *core.IpfsNode, api iface.CoreAPI

type filesLsOutput struct {
Entries []mfs.NodeListing
// Optionally set the path for the recursive listing option.
Path string
}

func listNode(req *cmds.Request, fsNode mfs.FSNode, path string) ([]mfs.NodeListing, error) {
long, _ := req.Options[longOptionName].(bool)

enc, err := cmdenv.GetCidEncoder(req)
if err != nil {
return nil, err
}

switch fsn := fsNode.(type) {
case *mfs.Directory:
if !long {
var output []mfs.NodeListing
names, err := fsn.ListNames(req.Context)
if err != nil {
return nil, err
}

for _, name := range names {
output = append(output, mfs.NodeListing{
Name: name,
Type: int(fsn.Type()),
})
}
return output, nil
}
listing, err := fsn.List(req.Context)
if err != nil {
return nil, err
}
return listing, nil
case *mfs.File:
// FIXME: `mfs.FSNode` should expose a `Name()` API (it stores the name
// in its inode) and we shouldn't require the path argument here.
_, name := gopath.Split(path)
out := []mfs.NodeListing{{Name: name, Type: int(fsn.Type())}}
if long {
size, err := fsn.Size()
if err != nil {
return nil, err
}
out[0].Size = size

nd, err := fsn.GetNode()
if err != nil {
return nil, err
}
out[0].Hash = enc.Encode(nd.Cid())
}
return out, nil
default:
return nil, errors.New("unrecognized type")
}
}

const (
longOptionName = "long"
dontSortOptionName = "U"
longOptionName = "long"
dontSortOptionName = "U"
filesLsRecursiveOptionName = "recursive"
)

var filesLsCmd = &cmds.Command{
Expand Down Expand Up @@ -471,6 +528,7 @@ Examples:
Options: []cmds.Option{
cmds.BoolOption(longOptionName, "l", "Use long listing format."),
cmds.BoolOption(dontSortOptionName, "Do not sort; list entries in directory order."),
cmds.BoolOption(filesLsRecursiveOptionName, "R", "List subdirectories recursively.").WithDefault(false),
},
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
var arg string
Expand All @@ -496,56 +554,59 @@ Examples:
return err
}

long, _ := req.Options[longOptionName].(bool)

enc, err := cmdenv.GetCidEncoder(req)
entries, err := listNode(req, fsn, path)
if err != nil {
return err
}

switch fsn := fsn.(type) {
case *mfs.Directory:
if !long {
var output []mfs.NodeListing
names, err := fsn.ListNames(req.Context)
if err != nil {
return err
}
recursive := req.Options[filesLsRecursiveOptionName].(bool)
if !recursive || mfs.IsFile(fsn) {
return cmds.EmitOnce(res, filesLsOutput{Entries: entries})
}

for _, name := range names {
output = append(output, mfs.NodeListing{
Name: name,
})
}
return cmds.EmitOnce(res, &filesLsOutput{output})
}
listing, err := fsn.List(req.Context)
// Recursive listing directories; mimic Unix `ls -R` behavior (BFS).
listsToProcess := make([]filesLsOutput, 1)
listsToProcess[0] = filesLsOutput{Entries: entries, Path: path}
for len(listsToProcess) > 0 {
listOut := listsToProcess[0]
listsToProcess = listsToProcess[1:]

err = res.Emit(listOut)
if err != nil {
return err
}
return cmds.EmitOnce(res, &filesLsOutput{listing})
case *mfs.File:
_, name := gopath.Split(path)
out := &filesLsOutput{[]mfs.NodeListing{{Name: name}}}
if long {
out.Entries[0].Type = int(fsn.Type())

size, err := fsn.Size()

for _, entry := range listOut.Entries {
if entry.Type != int(mfs.TDir) {
continue
}
dirPath, err := checkPath(listOut.Path)
if err != nil {
return err
}
if dirPath[len(dirPath)-1] != '/' {
dirPath += "/"
}
dirPath += entry.Name

fsn, err := mfs.Lookup(nd.FilesRoot, dirPath)
if err != nil {
return err
}
out.Entries[0].Size = size

nd, err := fsn.GetNode()
childList, err := listNode(req, fsn, dirPath)
if err != nil {
return err
}
out.Entries[0].Hash = enc.Encode(nd.Cid())
listsToProcess = append(listsToProcess,
filesLsOutput{Entries: childList, Path: dirPath})
}
return cmds.EmitOnce(res, out)
default:
return errors.New("unrecognized type")
}
// FIXME(BLOCKING): Evaluate joining this loop with the basic non-recursive case,
// but it might not be worth it adding complexity to the most common basic case.
// (We almost never will list recursively.)

return nil
},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *filesLsOutput) error {
Expand All @@ -557,6 +618,10 @@ Examples:
}

long, _ := req.Options[longOptionName].(bool)
if out.Path != "" {
// We are listing recursively and the listed path is a directory.
fmt.Fprintf(w, "%s:\n", out.Path)
}
for _, o := range out.Entries {
if long {
if o.Type == int(mfs.TDir) {
Expand All @@ -567,6 +632,9 @@ Examples:
fmt.Fprintf(w, "%s\n", o.Name)
}
}
if out.Path != "" {
fmt.Fprint(w, "\n")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we should skip this for the last entry we list. What would be the cleanest way to signal that? Closing the response?

}

return nil
}),
Expand Down