mirror of
https://github.com/ipfs/kubo.git
synced 2025-06-29 01:12:24 +08:00
add a --with-local option to ipfs files stat
License: MIT Signed-off-by: Michael Muré <batolettre@gmail.com>
This commit is contained in:
@ -10,10 +10,14 @@ import (
|
|||||||
gopath "path"
|
gopath "path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize"
|
||||||
|
|
||||||
|
bservice "github.com/ipfs/go-ipfs/blockservice"
|
||||||
oldcmds "github.com/ipfs/go-ipfs/commands"
|
oldcmds "github.com/ipfs/go-ipfs/commands"
|
||||||
lgc "github.com/ipfs/go-ipfs/commands/legacy"
|
lgc "github.com/ipfs/go-ipfs/commands/legacy"
|
||||||
core "github.com/ipfs/go-ipfs/core"
|
core "github.com/ipfs/go-ipfs/core"
|
||||||
e "github.com/ipfs/go-ipfs/core/commands/e"
|
e "github.com/ipfs/go-ipfs/core/commands/e"
|
||||||
|
"github.com/ipfs/go-ipfs/exchange/offline"
|
||||||
dag "github.com/ipfs/go-ipfs/merkledag"
|
dag "github.com/ipfs/go-ipfs/merkledag"
|
||||||
mfs "github.com/ipfs/go-ipfs/mfs"
|
mfs "github.com/ipfs/go-ipfs/mfs"
|
||||||
path "github.com/ipfs/go-ipfs/path"
|
path "github.com/ipfs/go-ipfs/path"
|
||||||
@ -69,6 +73,17 @@ var hashOption = cmdkit.StringOption("hash", "Hash function to use. Will set Cid
|
|||||||
|
|
||||||
var formatError = errors.New("Format was set by multiple options. Only one format option is allowed")
|
var formatError = errors.New("Format was set by multiple options. Only one format option is allowed")
|
||||||
|
|
||||||
|
type StatOutput struct {
|
||||||
|
Hash string
|
||||||
|
Size uint64
|
||||||
|
CumulativeSize uint64
|
||||||
|
Blocks int
|
||||||
|
Type string
|
||||||
|
WithLocality bool
|
||||||
|
Local bool `json:",omitempty"`
|
||||||
|
SizeLocal uint64 `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
const defaultStatFormat = `<hash>
|
const defaultStatFormat = `<hash>
|
||||||
Size: <size>
|
Size: <size>
|
||||||
CumulativeSize: <cumulsize>
|
CumulativeSize: <cumulsize>
|
||||||
@ -88,6 +103,7 @@ var filesStatCmd = &cmds.Command{
|
|||||||
"<hash> <size> <cumulsize> <type> <childs>. Conflicts with other format options.").WithDefault(defaultStatFormat),
|
"<hash> <size> <cumulsize> <type> <childs>. Conflicts with other format options.").WithDefault(defaultStatFormat),
|
||||||
cmdkit.BoolOption("hash", "Print only hash. Implies '--format=<hash>'. Conflicts with other format options."),
|
cmdkit.BoolOption("hash", "Print only hash. Implies '--format=<hash>'. Conflicts with other format options."),
|
||||||
cmdkit.BoolOption("size", "Print only size. Implies '--format=<cumulsize>'. Conflicts with other format options."),
|
cmdkit.BoolOption("size", "Print only size. Implies '--format=<cumulsize>'. Conflicts with other format options."),
|
||||||
|
cmdkit.BoolOption("with-local", "Compute the amount of the dag that is local, and if possible the total size"),
|
||||||
},
|
},
|
||||||
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) {
|
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) {
|
||||||
|
|
||||||
@ -108,23 +124,41 @@ var filesStatCmd = &cmds.Command{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fsn, err := mfs.Lookup(node.FilesRoot, path)
|
nd, err := getNodeFromPath(req.Context, node, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.SetError(err, cmdkit.ErrNormal)
|
res.SetError(err, cmdkit.ErrNormal)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
o, err := statNode(fsn)
|
o, err := statNode(nd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.SetError(err, cmdkit.ErrNormal)
|
res.SetError(err, cmdkit.ErrNormal)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withLocal, _ := req.Options["with-local"].(bool)
|
||||||
|
if !withLocal {
|
||||||
|
cmds.EmitOnce(res, o)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// an offline DAGService will not fetch from the network
|
||||||
|
dagserv := dag.NewDAGService(bservice.New(
|
||||||
|
node.Blockstore,
|
||||||
|
offline.Exchange(node.Blockstore),
|
||||||
|
))
|
||||||
|
|
||||||
|
local, sizeLocal, err := walkBlock(req.Context, dagserv, nd)
|
||||||
|
|
||||||
|
o.WithLocality = true
|
||||||
|
o.Local = local
|
||||||
|
o.SizeLocal = sizeLocal
|
||||||
|
|
||||||
cmds.EmitOnce(res, o)
|
cmds.EmitOnce(res, o)
|
||||||
},
|
},
|
||||||
Encoders: cmds.EncoderMap{
|
Encoders: cmds.EncoderMap{
|
||||||
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error {
|
cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error {
|
||||||
out, ok := v.(*Object)
|
out, ok := v.(*StatOutput)
|
||||||
if !ok {
|
if !ok {
|
||||||
return e.TypeErr(out, v)
|
return e.TypeErr(out, v)
|
||||||
}
|
}
|
||||||
@ -136,11 +170,20 @@ var filesStatCmd = &cmds.Command{
|
|||||||
s = strings.Replace(s, "<childs>", fmt.Sprintf("%d", out.Blocks), -1)
|
s = strings.Replace(s, "<childs>", fmt.Sprintf("%d", out.Blocks), -1)
|
||||||
s = strings.Replace(s, "<type>", out.Type, -1)
|
s = strings.Replace(s, "<type>", out.Type, -1)
|
||||||
|
|
||||||
_, err := fmt.Fprintln(w, s)
|
fmt.Fprintln(w, s)
|
||||||
return err
|
|
||||||
|
if out.WithLocality {
|
||||||
|
fmt.Fprintf(w, "Local: %s of %s (%.2f%%)\n",
|
||||||
|
humanize.Bytes(out.SizeLocal),
|
||||||
|
humanize.Bytes(out.CumulativeSize),
|
||||||
|
100.0*float64(out.SizeLocal)/float64(out.CumulativeSize),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Type: Object{},
|
Type: StatOutput{},
|
||||||
}
|
}
|
||||||
|
|
||||||
func moreThanOne(a, b, c bool) bool {
|
func moreThanOne(a, b, c bool) bool {
|
||||||
@ -166,12 +209,7 @@ func statGetFormatOptions(req *cmds.Request) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func statNode(fsn mfs.FSNode) (*Object, error) {
|
func statNode(nd ipld.Node) (*StatOutput, error) {
|
||||||
nd, err := fsn.GetNode()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c := nd.Cid()
|
c := nd.Cid()
|
||||||
|
|
||||||
cumulsize, err := nd.Size()
|
cumulsize, err := nd.Size()
|
||||||
@ -187,16 +225,16 @@ func statNode(fsn mfs.FSNode) (*Object, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ndtype string
|
var ndtype string
|
||||||
switch fsn.Type() {
|
switch d.GetType() {
|
||||||
case mfs.TDir:
|
case ft.TDirectory, ft.THAMTShard:
|
||||||
ndtype = "directory"
|
ndtype = "directory"
|
||||||
case mfs.TFile:
|
case ft.TFile, ft.TMetadata, ft.TRaw:
|
||||||
ndtype = "file"
|
ndtype = "file"
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unrecognized node type: %s", fsn.Type())
|
return nil, fmt.Errorf("unrecognized node type: %s", d.GetType())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Object{
|
return &StatOutput{
|
||||||
Hash: c.String(),
|
Hash: c.String(),
|
||||||
Blocks: len(nd.Links()),
|
Blocks: len(nd.Links()),
|
||||||
Size: d.GetFilesize(),
|
Size: d.GetFilesize(),
|
||||||
@ -204,7 +242,7 @@ func statNode(fsn mfs.FSNode) (*Object, error) {
|
|||||||
Type: ndtype,
|
Type: ndtype,
|
||||||
}, nil
|
}, nil
|
||||||
case *dag.RawNode:
|
case *dag.RawNode:
|
||||||
return &Object{
|
return &StatOutput{
|
||||||
Hash: c.String(),
|
Hash: c.String(),
|
||||||
Blocks: 0,
|
Blocks: 0,
|
||||||
Size: cumulsize,
|
Size: cumulsize,
|
||||||
@ -216,6 +254,38 @@ func statNode(fsn mfs.FSNode) (*Object, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func walkBlock(ctx context.Context, dagserv ipld.DAGService, nd ipld.Node) (bool, uint64, error) {
|
||||||
|
// Start with the block data size
|
||||||
|
sizeLocal := uint64(len(nd.RawData()))
|
||||||
|
|
||||||
|
local := true
|
||||||
|
|
||||||
|
for _, link := range nd.Links() {
|
||||||
|
child, err := dagserv.Get(ctx, link.Cid)
|
||||||
|
|
||||||
|
if err == ipld.ErrNotFound {
|
||||||
|
local = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return local, sizeLocal, err
|
||||||
|
}
|
||||||
|
|
||||||
|
childLocal, childLocalSize, err := walkBlock(ctx, dagserv, child)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return local, sizeLocal, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively add the child size
|
||||||
|
local = local && childLocal
|
||||||
|
sizeLocal += childLocalSize
|
||||||
|
}
|
||||||
|
|
||||||
|
return local, sizeLocal, nil
|
||||||
|
}
|
||||||
|
|
||||||
var filesCpCmd = &oldcmds.Command{
|
var filesCpCmd = &oldcmds.Command{
|
||||||
Helptext: cmdkit.HelpText{
|
Helptext: cmdkit.HelpText{
|
||||||
Tagline: "Copy files into mfs.",
|
Tagline: "Copy files into mfs.",
|
||||||
@ -298,14 +368,6 @@ func getNodeFromPath(ctx context.Context, node *core.IpfsNode, p string) (ipld.N
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Object struct {
|
|
||||||
Hash string
|
|
||||||
Size uint64
|
|
||||||
CumulativeSize uint64
|
|
||||||
Blocks int
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
|
|
||||||
type filesLsOutput struct {
|
type filesLsOutput struct {
|
||||||
Entries []mfs.NodeListing
|
Entries []mfs.NodeListing
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user