Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'ipfs file ls …' #1348

Merged
merged 9 commits into from
Jun 25, 2015
5 changes: 3 additions & 2 deletions commands/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ type ErrorType uint

// ErrorTypes convey what category of error ocurred
const (
ErrNormal ErrorType = iota // general errors
ErrClient // error was caused by the client, (e.g. invalid CLI usage)
ErrNormal ErrorType = iota // general errors
ErrClient // error was caused by the client, (e.g. invalid CLI usage)
ErrImplementation // programmer error in the server
// TODO: add more types of errors for better error-specific handling
)

Expand Down
3 changes: 3 additions & 0 deletions core/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"

cmds "github.com/ipfs/go-ipfs/commands"
unixfs "github.com/ipfs/go-ipfs/core/commands/unixfs"
evlog "github.com/ipfs/go-ipfs/thirdparty/eventlog"
)

Expand Down Expand Up @@ -35,6 +36,7 @@ DATA STRUCTURE COMMANDS

block Interact with raw blocks in the datastore
object Interact with raw dag nodes
file Interact with Unix filesystem objects

ADVANCED COMMANDS

Expand Down Expand Up @@ -102,6 +104,7 @@ var rootSubcommands = map[string]*cmds.Command{
"stats": StatsCmd,
"swarm": SwarmCmd,
"tour": tourCmd,
"file": unixfs.UnixFSCmd,
"update": UpdateCmd,
"version": VersionCmd,
"bitswap": BitswapCmd,
Expand Down
202 changes: 202 additions & 0 deletions core/commands/unixfs/ls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package unixfs

import (
"bytes"
"fmt"
"io"
"sort"
"text/tabwriter"
"time"

context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"

cmds "github.com/ipfs/go-ipfs/commands"
core "github.com/ipfs/go-ipfs/core"
path "github.com/ipfs/go-ipfs/path"
unixfs "github.com/ipfs/go-ipfs/unixfs"
unixfspb "github.com/ipfs/go-ipfs/unixfs/pb"
)

type LsLink struct {
Name, Hash string
Size uint64
Type string
}

type LsObject struct {
Hash string
Size uint64
Type string
Links []LsLink
}

type LsOutput struct {
Arguments map[string]string
Objects map[string]*LsObject
}

var LsCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "List directory contents for Unix-filesystem objects",
ShortDescription: `
Retrieves the object named by <ipfs-or-ipns-path> and displays the
contents with the following format:

<hash> <type> <size> <name>

For files, the child size is the total size of the file contents. For
directories, the child size is the IPFS link size.
`,
},

Arguments: []cmds.Argument{
cmds.StringArg("ipfs-path", true, true, "The path to the IPFS object(s) to list links from").EnableStdin(),
},
Run: func(req cmds.Request, res cmds.Response) {
node, err := req.Context().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

paths := req.Arguments()

output := LsOutput{
Arguments: map[string]string{},
Objects: map[string]*LsObject{},
}

for _, fpath := range paths {
ctx := req.Context().Context
merkleNode, err := core.Resolve(ctx, node, path.Path(fpath))
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

key, err := merkleNode.Key()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

hash := key.B58String()
output.Arguments[fpath] = hash

if _, ok := output.Objects[hash]; ok {
// duplicate argument for an already-listed node
continue
}

unixFSNode, err := unixfs.FromBytes(merkleNode.Data)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}

t := unixFSNode.GetType()

output.Objects[hash] = &LsObject{
Hash: key.String(),
Type: t.String(),
Size: unixFSNode.GetFilesize(),
}

switch t {
default:
res.SetError(fmt.Errorf("unrecognized type: %s", t), cmds.ErrImplementation)
return
case unixfspb.Data_File:
break
case unixfspb.Data_Directory:
links := make([]LsLink, len(merkleNode.Links))
output.Objects[hash].Links = links
for i, link := range merkleNode.Links {
getCtx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
link.Node, err = link.GetNode(getCtx, node.DAG)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
d, err := unixfs.FromBytes(link.Node.Data)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
t := d.GetType()
lsLink := LsLink{
Name: link.Name,
Hash: link.Hash.B58String(),
Type: t.String(),
}
if t == unixfspb.Data_File {
lsLink.Size = d.GetFilesize()
} else {
lsLink.Size = link.Size
}
links[i] = lsLink
}
}
}

res.SetOutput(&output)
},
Marshalers: cmds.MarshalerMap{
cmds.Text: func(res cmds.Response) (io.Reader, error) {

output := res.Output().(*LsOutput)
buf := new(bytes.Buffer)
w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)

nonDirectories := []string{}
directories := []string{}
for argument, hash := range output.Arguments {
object, ok := output.Objects[hash]
if !ok {
return nil, fmt.Errorf("unresolved hash: %s", hash)
}

if object.Type == "Directory" {
directories = append(directories, argument)
} else {
nonDirectories = append(nonDirectories, argument)
}
}
sort.Strings(nonDirectories)
sort.Strings(directories)

for _, argument := range nonDirectories {
fmt.Fprintf(w, "%s\n", argument)
}

seen := map[string]bool{}
for i, argument := range directories {
hash := output.Arguments[argument]
if _, ok := seen[hash]; ok {
continue
}
seen[hash] = true

object := output.Objects[hash]
if i > 0 || len(nonDirectories) > 0 {
fmt.Fprintln(w)
}
if len(output.Arguments) > 1 {
for _, arg := range directories[i:] {
if output.Arguments[arg] == hash {
fmt.Fprintf(w, "%s:\n", arg)
}
}
}
for _, link := range object.Links {
fmt.Fprintf(w, "%s\n", link.Name)
}
}
w.Flush()

return buf, nil
},
},
Type: LsOutput{},
}
21 changes: 21 additions & 0 deletions core/commands/unixfs/unixfs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package unixfs

import cmds "github.com/ipfs/go-ipfs/commands"

var UnixFSCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Interact with ipfs objects representing Unix filesystems",
ShortDescription: `
'ipfs file' provides a familar interface to filesystems represtented
by IPFS objects that hides IPFS-implementation details like layout
objects (e.g. fanout and chunking).
`,
Synopsis: `
ipfs file ls <path>... - List directory contents for <path>...
`,
},

Subcommands: map[string]*cmds.Command{
"ls": LsCmd,
},
}
Loading