package unixfs import ( "bytes" "fmt" "io" "sort" "text/tabwriter" 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" merkledag "github.com/ipfs/go-ipfs/merkledag" ) 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: ` Displays the contents of an IPFS or IPNS object(s) at the given path. The JSON output contains size information. 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.InvocContext().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() 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 { 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 { var linkNode *merkledag.Node linkNode, err = link.GetNode(ctx, node.DAG) if err != nil { res.SetError(err, cmds.ErrNormal) return } d, err := unixfs.FromBytes(linkNode.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 } case unixfspb.Data_Symlink: res.SetError(fmt.Errorf("cannot list symlinks yet"), cmds.ErrNormal) return default: res.SetError(fmt.Errorf("unrecognized type: %s", t), cmds.ErrImplementation) return } } 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{}, }