mirror of
https://github.com/ipfs/kubo.git
synced 2025-06-05 23:53:19 +08:00
core/commands/unixfs/ls: Hash-map for Objects
Discussion with Juan on IRC ([1] through [2]) lead to this adjusted JSON output. Benefits over the old output include: * deduplication (we only check the children of a given Merkle node once, even if multiple arguments resolve to that hash) * alphabetized output (like POSIX's ls). As a side-effect of this change, I'm also matching GNU Coreutils' ls output (maybe in POSIX?) by printing an alphabetized list of non-directories (one per line) first, with alphabetized directory lists afterwards. [1]: https://botbot.me/freenode/ipfs/2015-06-12/?msg=41725570&page=5 [2]: https://botbot.me/freenode/ipfs/2015-06-12/?msg=41726547&page=5 License: MIT Signed-off-by: W. Trevor King <wking@tremily.us>
This commit is contained in:
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"sort"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -23,12 +24,12 @@ type LsLink struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LsObject struct {
|
type LsObject struct {
|
||||||
Argument string
|
Links []LsLink
|
||||||
Links []LsLink
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LsOutput struct {
|
type LsOutput struct {
|
||||||
Objects []*LsObject
|
Arguments map[string]string
|
||||||
|
Objects map[string]*LsObject
|
||||||
}
|
}
|
||||||
|
|
||||||
var LsCmd = &cmds.Command{
|
var LsCmd = &cmds.Command{
|
||||||
@ -57,8 +58,12 @@ directories, the child size is the IPFS link size.
|
|||||||
|
|
||||||
paths := req.Arguments()
|
paths := req.Arguments()
|
||||||
|
|
||||||
output := make([]*LsObject, len(paths))
|
output := LsOutput{
|
||||||
for i, fpath := range paths {
|
Arguments: map[string]string{},
|
||||||
|
Objects: map[string]*LsObject{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fpath := range paths {
|
||||||
ctx := req.Context().Context
|
ctx := req.Context().Context
|
||||||
merkleNode, err := core.Resolve(ctx, node, path.Path(fpath))
|
merkleNode, err := core.Resolve(ctx, node, path.Path(fpath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -66,13 +71,27 @@ directories, the child size is the IPFS link size.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
unixFSNode, err := unixfs.FromBytes(merkleNode.Data)
|
key, err := merkleNode.Key()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.SetError(err, cmds.ErrNormal)
|
res.SetError(err, cmds.ErrNormal)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
output[i] = &LsObject{Argument: fpath}
|
hash := key.B58String()
|
||||||
|
output.Arguments[fpath] = hash
|
||||||
|
|
||||||
|
if _, ok := output.Objects[hash]; ok {
|
||||||
|
// duplicate argument for an already-listed node
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
output.Objects[hash] = &LsObject{}
|
||||||
|
|
||||||
|
unixFSNode, err := unixfs.FromBytes(merkleNode.Data)
|
||||||
|
if err != nil {
|
||||||
|
res.SetError(err, cmds.ErrNormal)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
t := unixFSNode.GetType()
|
t := unixFSNode.GetType()
|
||||||
switch t {
|
switch t {
|
||||||
@ -85,15 +104,16 @@ directories, the child size is the IPFS link size.
|
|||||||
res.SetError(err, cmds.ErrNormal)
|
res.SetError(err, cmds.ErrNormal)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
output[i].Links = []LsLink{LsLink{
|
output.Objects[hash].Links = []LsLink{LsLink{
|
||||||
Name: fpath,
|
Name: fpath,
|
||||||
Hash: key.String(),
|
Hash: key.String(),
|
||||||
Type: t.String(),
|
Type: t.String(),
|
||||||
Size: unixFSNode.GetFilesize(),
|
Size: unixFSNode.GetFilesize(),
|
||||||
}}
|
}}
|
||||||
case unixfspb.Data_Directory:
|
case unixfspb.Data_Directory:
|
||||||
output[i].Links = make([]LsLink, len(merkleNode.Links))
|
links := make([]LsLink, len(merkleNode.Links))
|
||||||
for j, link := range merkleNode.Links {
|
output.Objects[hash].Links = links
|
||||||
|
for i, link := range merkleNode.Links {
|
||||||
getCtx, cancel := context.WithTimeout(ctx, time.Minute)
|
getCtx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
link.Node, err = link.GetNode(getCtx, node.DAG)
|
link.Node, err = link.GetNode(getCtx, node.DAG)
|
||||||
@ -117,12 +137,12 @@ directories, the child size is the IPFS link size.
|
|||||||
} else {
|
} else {
|
||||||
lsLink.Size = link.Size
|
lsLink.Size = link.Size
|
||||||
}
|
}
|
||||||
output[i].Links[j] = lsLink
|
links[i] = lsLink
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.SetOutput(&LsOutput{Objects: output})
|
res.SetOutput(&output)
|
||||||
},
|
},
|
||||||
Marshalers: cmds.MarshalerMap{
|
Marshalers: cmds.MarshalerMap{
|
||||||
cmds.Text: func(res cmds.Response) (io.Reader, error) {
|
cmds.Text: func(res cmds.Response) (io.Reader, error) {
|
||||||
@ -130,21 +150,44 @@ directories, the child size is the IPFS link size.
|
|||||||
output := res.Output().(*LsOutput)
|
output := res.Output().(*LsOutput)
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
|
w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
|
||||||
lastObjectDirHeader := false
|
|
||||||
for i, object := range output.Objects {
|
nonDirectories := []string{}
|
||||||
singleObject := (len(object.Links) == 1 &&
|
directories := []string{}
|
||||||
object.Links[0].Name == object.Argument)
|
for argument, hash := range output.Arguments {
|
||||||
if len(output.Objects) > 1 && !singleObject {
|
object, ok := output.Objects[hash]
|
||||||
if i > 0 {
|
if !ok {
|
||||||
fmt.Fprintln(w)
|
return nil, fmt.Errorf("unresolved hash: %s", hash)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "%s:\n", object.Argument)
|
|
||||||
lastObjectDirHeader = true
|
if len(object.Links) == 1 && object.Links[0].Hash == hash {
|
||||||
|
nonDirectories = append(nonDirectories, argument)
|
||||||
} else {
|
} else {
|
||||||
if lastObjectDirHeader {
|
directories = append(directories, argument)
|
||||||
fmt.Fprintln(w)
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
for _, arg := range directories[i:] {
|
||||||
|
if output.Arguments[arg] == hash {
|
||||||
|
fmt.Fprintf(w, "%s:\n", arg)
|
||||||
}
|
}
|
||||||
lastObjectDirHeader = false
|
|
||||||
}
|
}
|
||||||
for _, link := range object.Links {
|
for _, link := range object.Links {
|
||||||
fmt.Fprintf(w, "%s\n", link.Name)
|
fmt.Fprintf(w, "%s\n", link.Name)
|
||||||
|
@ -44,12 +44,6 @@ test_ls_cmd() {
|
|||||||
|
|
||||||
test_expect_success "'ipfs file ls <three dir hashes>' output looks good" '
|
test_expect_success "'ipfs file ls <three dir hashes>' output looks good" '
|
||||||
cat <<-\EOF >expected_ls &&
|
cat <<-\EOF >expected_ls &&
|
||||||
QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj:
|
|
||||||
d1
|
|
||||||
d2
|
|
||||||
f1
|
|
||||||
f2
|
|
||||||
|
|
||||||
QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy:
|
QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy:
|
||||||
1024
|
1024
|
||||||
a
|
a
|
||||||
@ -57,6 +51,12 @@ test_ls_cmd() {
|
|||||||
QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss:
|
QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss:
|
||||||
128
|
128
|
||||||
a
|
a
|
||||||
|
|
||||||
|
QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj:
|
||||||
|
d1
|
||||||
|
d2
|
||||||
|
f1
|
||||||
|
f2
|
||||||
EOF
|
EOF
|
||||||
test_cmp expected_ls actual_ls
|
test_cmp expected_ls actual_ls
|
||||||
'
|
'
|
||||||
@ -73,6 +73,23 @@ test_ls_cmd() {
|
|||||||
test_cmp expected_ls_file actual_ls_file
|
test_cmp expected_ls_file actual_ls_file
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success "'ipfs file ls <duplicates>' succeeds" '
|
||||||
|
ipfs file ls /ipfs/QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj/d1 /ipfs/QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 /ipfs/QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd >actual_ls_duplicates_file
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "'ipfs file ls <duplicates>' output looks good" '
|
||||||
|
cat <<-\EOF >expected_ls_duplicates_file &&
|
||||||
|
/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024
|
||||||
|
/ipfs/QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd
|
||||||
|
|
||||||
|
/ipfs/QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss:
|
||||||
|
/ipfs/QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj/d1:
|
||||||
|
128
|
||||||
|
a
|
||||||
|
EOF
|
||||||
|
test_cmp expected_ls_duplicates_file actual_ls_duplicates_file
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success "'ipfs --encoding=json file ls <file hashes>' succeeds" '
|
test_expect_success "'ipfs --encoding=json file ls <file hashes>' succeeds" '
|
||||||
ipfs --encoding=json file ls /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 >actual_json_ls_file
|
ipfs --encoding=json file ls /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 >actual_json_ls_file
|
||||||
'
|
'
|
||||||
@ -80,9 +97,11 @@ test_ls_cmd() {
|
|||||||
test_expect_success "'ipfs --encoding=json file ls <file hashes>' output looks good" '
|
test_expect_success "'ipfs --encoding=json file ls <file hashes>' output looks good" '
|
||||||
cat <<-\EOF >expected_json_ls_file_trailing_newline &&
|
cat <<-\EOF >expected_json_ls_file_trailing_newline &&
|
||||||
{
|
{
|
||||||
"Objects": [
|
"Arguments": {
|
||||||
{
|
"/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd"
|
||||||
"Argument": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024",
|
},
|
||||||
|
"Objects": {
|
||||||
|
"QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd": {
|
||||||
"Links": [
|
"Links": [
|
||||||
{
|
{
|
||||||
"Name": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024",
|
"Name": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024",
|
||||||
@ -92,12 +111,59 @@ test_ls_cmd() {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
printf %s "$(cat expected_json_ls_file_trailing_newline)" >expected_json_ls_file &&
|
printf %s "$(cat expected_json_ls_file_trailing_newline)" >expected_json_ls_file &&
|
||||||
test_cmp expected_json_ls_file actual_json_ls_file
|
test_cmp expected_json_ls_file actual_json_ls_file
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success "'ipfs --encoding=json file ls <duplicates>' succeeds" '
|
||||||
|
ipfs --encoding=json file ls /ipfs/QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj/d1 /ipfs/QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 /ipfs/QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd >actual_json_ls_duplicates_file
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "'ipfs --encoding=json file ls <duplicates>' output looks good" '
|
||||||
|
cat <<-\EOF >expected_json_ls_duplicates_file_trailing_newline &&
|
||||||
|
{
|
||||||
|
"Arguments": {
|
||||||
|
"/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd",
|
||||||
|
"/ipfs/QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss": "QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss",
|
||||||
|
"/ipfs/QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd",
|
||||||
|
"/ipfs/QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj/d1": "QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss"
|
||||||
|
},
|
||||||
|
"Objects": {
|
||||||
|
"QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss": {
|
||||||
|
"Links": [
|
||||||
|
{
|
||||||
|
"Name": "128",
|
||||||
|
"Hash": "QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe",
|
||||||
|
"Size": 128,
|
||||||
|
"Type": "File"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "a",
|
||||||
|
"Hash": "QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN",
|
||||||
|
"Size": 6,
|
||||||
|
"Type": "File"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd": {
|
||||||
|
"Links": [
|
||||||
|
{
|
||||||
|
"Name": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024",
|
||||||
|
"Hash": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd",
|
||||||
|
"Size": 1024,
|
||||||
|
"Type": "File"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
printf %s "$(cat expected_json_ls_duplicates_file_trailing_newline)" >expected_json_ls_duplicates_file &&
|
||||||
|
test_cmp expected_json_ls_duplicates_file actual_json_ls_duplicates_file
|
||||||
|
'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user