Skip to content

Commit

Permalink
Merge pull request #1348 from ipfs/tk/unixfs-ls
Browse files Browse the repository at this point in the history
Add 'ipfs file ls …'
  • Loading branch information
whyrusleeping committed Jun 25, 2015
2 parents aefdb4e + 4acab79 commit 0332f3d
Show file tree
Hide file tree
Showing 5 changed files with 414 additions and 2 deletions.
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

0 comments on commit 0332f3d

Please sign in to comment.