1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-07-03 04:37:30 +08:00

Merge pull request #364 from jbenet/pin-ls

Added 'pin ls' Command
This commit is contained in:
Matt Bell
2014-11-19 03:28:27 -08:00
12 changed files with 180 additions and 63 deletions

View File

@ -239,7 +239,7 @@ func optionText(cmd ...*cmds.Command) []string {
lines = append(lines, "") lines = append(lines, "")
} }
names := sortByLength(opt.Names) names := sortByLength(opt.Names())
if len(names) >= j+1 { if len(names) >= j+1 {
lines[i] += fmt.Sprintf(optionFlag, names[j]) lines[i] += fmt.Sprintf(optionFlag, names[j])
} }
@ -262,13 +262,13 @@ func optionText(cmd ...*cmds.Command) []string {
// add option types to output // add option types to output
for i, opt := range options { for i, opt := range options {
lines[i] += " " + fmt.Sprintf("%v", opt.Type) lines[i] += " " + fmt.Sprintf("%v", opt.Type())
} }
lines = align(lines) lines = align(lines)
// add option descriptions to output // add option descriptions to output
for i, opt := range options { for i, opt := range options {
lines[i] += " - " + opt.Description lines[i] += " - " + opt.Description()
} }
return lines return lines

View File

@ -46,10 +46,17 @@ func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *c
return nil, cmd, path, err return nil, cmd, path, err
} }
recursive, _, err := req.Option(cmds.RecShort).Bool() // if -r is provided, and it is associated with the package builtin
if err != nil { // recursive path option, allow recursive file paths
return nil, nil, nil, u.ErrCast() recursiveOpt := req.Option(cmds.RecShort)
recursive := false
if recursiveOpt != nil && recursiveOpt.Definition() == cmds.OptionRecursivePath {
recursive, _, err = recursiveOpt.Bool()
if err != nil {
return nil, nil, nil, u.ErrCast()
}
} }
stringArgs, fileArgs, err := parseArgs(stringVals, stdin, cmd.Arguments, recursive) stringArgs, fileArgs, err := parseArgs(stringVals, stdin, cmd.Arguments, recursive)
if err != nil { if err != nil {
return nil, cmd, path, err return nil, cmd, path, err

View File

@ -11,7 +11,7 @@ func TestOptionParsing(t *testing.T) {
subCmd := &commands.Command{} subCmd := &commands.Command{}
cmd := &commands.Command{ cmd := &commands.Command{
Options: []commands.Option{ Options: []commands.Option{
commands.Option{Names: []string{"b"}, Type: commands.String}, commands.StringOption("b", "some option"),
}, },
Subcommands: map[string]*commands.Command{ Subcommands: map[string]*commands.Command{
"test": subCmd, "test": subCmd,

View File

@ -167,7 +167,7 @@ func (c *Command) Get(path []string) (*Command, error) {
// GetOptions gets the options in the given path of commands // GetOptions gets the options in the given path of commands
func (c *Command) GetOptions(path []string) (map[string]Option, error) { func (c *Command) GetOptions(path []string) (map[string]Option, error) {
options := make([]Option, len(c.Options)) options := make([]Option, 0, len(c.Options))
cmds, err := c.Resolve(path) cmds, err := c.Resolve(path)
if err != nil { if err != nil {
@ -181,7 +181,7 @@ func (c *Command) GetOptions(path []string) (map[string]Option, error) {
optionsMap := make(map[string]Option) optionsMap := make(map[string]Option)
for _, opt := range options { for _, opt := range options {
for _, name := range opt.Names { for _, name := range opt.Names() {
if _, found := optionsMap[name]; found { if _, found := optionsMap[name]; found {
return nil, fmt.Errorf("Option name '%s' used multiple times", name) return nil, fmt.Errorf("Option name '%s' used multiple times", name)
} }

View File

@ -9,8 +9,8 @@ func noop(req Request) (interface{}, error) {
func TestOptionValidation(t *testing.T) { func TestOptionValidation(t *testing.T) {
cmd := Command{ cmd := Command{
Options: []Option{ Options: []Option{
Option{[]string{"b", "beep"}, Int, "enables beeper"}, IntOption("b", "beep", "enables beeper"),
Option{[]string{"B", "boop"}, String, "password for booper"}, StringOption("B", "boop", "password for booper"),
}, },
Run: noop, Run: noop,
} }
@ -93,14 +93,14 @@ func TestOptionValidation(t *testing.T) {
func TestRegistration(t *testing.T) { func TestRegistration(t *testing.T) {
cmdA := &Command{ cmdA := &Command{
Options: []Option{ Options: []Option{
Option{[]string{"beep"}, Int, "number of beeps"}, IntOption("beep", "number of beeps"),
}, },
Run: noop, Run: noop,
} }
cmdB := &Command{ cmdB := &Command{
Options: []Option{ Options: []Option{
Option{[]string{"beep"}, Int, "number of beeps"}, IntOption("beep", "number of beeps"),
}, },
Run: noop, Run: noop,
Subcommands: map[string]*Command{ Subcommands: map[string]*Command{
@ -110,7 +110,7 @@ func TestRegistration(t *testing.T) {
cmdC := &Command{ cmdC := &Command{
Options: []Option{ Options: []Option{
Option{[]string{"encoding"}, String, "data encoding type"}, StringOption("encoding", "data encoding type"),
}, },
Run: noop, Run: noop,
} }

View File

@ -17,14 +17,28 @@ const (
) )
// Option is used to specify a field that will be provided by a consumer // Option is used to specify a field that will be provided by a consumer
type Option struct { type Option interface {
Names []string // a list of unique names to Names() []string // a list of unique names matched with user-provided flags
Type reflect.Kind // value must be this type Type() reflect.Kind // value must be this type
Description string // a short string to describe this option Description() string // a short string that describes this option
}
// MAYBE_TODO: add more features(?): type option struct {
//Default interface{} // the default value (ignored if `Required` is true) names []string
//Required bool // whether or not the option must be provided kind reflect.Kind
description string
}
func (o *option) Names() []string {
return o.names
}
func (o *option) Type() reflect.Kind {
return o.kind
}
func (o *option) Description() string {
return o.description
} }
// constructor helper functions // constructor helper functions
@ -37,10 +51,10 @@ func NewOption(kind reflect.Kind, names ...string) Option {
desc := names[len(names)-1] desc := names[len(names)-1]
names = names[:len(names)-1] names = names[:len(names)-1]
return Option{ return &option{
Names: names, names: names,
Type: kind, kind: kind,
Description: desc, description: desc,
} }
} }
@ -69,6 +83,7 @@ func StringOption(names ...string) Option {
type OptionValue struct { type OptionValue struct {
value interface{} value interface{}
found bool found bool
def Option
} }
// Found returns true if the option value was provided by the user (not a default value) // Found returns true if the option value was provided by the user (not a default value)
@ -76,6 +91,11 @@ func (ov OptionValue) Found() bool {
return ov.found return ov.found
} }
// Definition returns the option definition for the provided value
func (ov OptionValue) Definition() Option {
return ov.def
}
// value accessor methods, gets the value as a certain type // value accessor methods, gets the value as a certain type
func (ov OptionValue) Bool() (value bool, found bool, err error) { func (ov OptionValue) Bool() (value bool, found bool, err error) {
if !ov.found { if !ov.found {
@ -141,9 +161,12 @@ const (
) )
// options that are used by this package // options that are used by this package
var OptionEncodingType = StringOption(EncShort, EncLong, "The encoding type the output should be encoded with (json, xml, or text)")
var OptionRecursivePath = BoolOption(RecShort, RecLong, "Add directory paths recursively")
// global options, added to every command
var globalOptions = []Option{ var globalOptions = []Option{
StringOption(EncShort, EncLong, "The encoding type the output should be encoded with (json, xml, or text)"), OptionEncodingType,
BoolOption(RecShort, RecLong, "Add directory paths recursively"),
} }
// the above array of Options, wrapped in a Command // the above array of Options, wrapped in a Command

View File

@ -92,13 +92,6 @@ func (r *request) Path() []string {
// Option returns the value of the option for given name. // Option returns the value of the option for given name.
func (r *request) Option(name string) *OptionValue { func (r *request) Option(name string) *OptionValue {
val, found := r.options[name]
if found {
return &OptionValue{val, found}
}
// if a value isn't defined for that name, we will try to look it up by its aliases
// find the option with the specified name // find the option with the specified name
option, found := r.optionDefs[name] option, found := r.optionDefs[name]
if !found { if !found {
@ -106,15 +99,15 @@ func (r *request) Option(name string) *OptionValue {
} }
// try all the possible names, break if we find a value // try all the possible names, break if we find a value
for _, n := range option.Names { for _, n := range option.Names() {
val, found = r.options[n] val, found := r.options[n]
if found { if found {
return &OptionValue{val, found} return &OptionValue{val, found, option}
} }
} }
// MAYBE_TODO: use default value instead of nil // MAYBE_TODO: use default value instead of nil
return &OptionValue{nil, false} return &OptionValue{nil, false, option}
} }
// Options returns a copy of the option map // Options returns a copy of the option map
@ -135,7 +128,7 @@ func (r *request) SetOption(name string, val interface{}) {
} }
// try all the possible names, if we already have a value then set over it // try all the possible names, if we already have a value then set over it
for _, n := range option.Names { for _, n := range option.Names() {
_, found := r.options[n] _, found := r.options[n]
if found { if found {
r.options[n] = val r.options[n] = val
@ -222,9 +215,9 @@ func (r *request) ConvertOptions() error {
} }
kind := reflect.TypeOf(v).Kind() kind := reflect.TypeOf(v).Kind()
if kind != opt.Type { if kind != opt.Type() {
if kind == String { if kind == String {
convert := converters[opt.Type] convert := converters[opt.Type()]
str, ok := v.(string) str, ok := v.(string)
if !ok { if !ok {
return u.ErrCast() return u.ErrCast()
@ -236,19 +229,19 @@ func (r *request) ConvertOptions() error {
value = "empty value" value = "empty value"
} }
return fmt.Errorf("Could not convert %s to type '%s' (for option '-%s')", return fmt.Errorf("Could not convert %s to type '%s' (for option '-%s')",
value, opt.Type.String(), k) value, opt.Type().String(), k)
} }
r.options[k] = val r.options[k] = val
} else { } else {
return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'", return fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
k, opt.Type.String(), kind.String()) k, opt.Type().String(), kind.String())
} }
} else { } else {
r.options[k] = v r.options[k] = v
} }
for _, name := range opt.Names { for _, name := range opt.Names() {
if _, ok := r.options[name]; name != k && ok { if _, ok := r.options[name]; name != k && ok {
return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')", return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
k, name) k, name)

View File

@ -39,6 +39,9 @@ remains to be implemented.
Arguments: []cmds.Argument{ Arguments: []cmds.Argument{
cmds.FileArg("path", true, true, "The path to a file to be added to IPFS").EnableRecursive(), cmds.FileArg("path", true, true, "The path to a file to be added to IPFS").EnableRecursive(),
}, },
Options: []cmds.Option{
cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive)
},
Run: func(req cmds.Request) (interface{}, error) { Run: func(req cmds.Request) (interface{}, error) {
added := &AddOutput{} added := &AddOutput{}
n, err := req.Context().GetNode() n, err := req.Context().GetNode()

View File

@ -6,6 +6,7 @@ import (
cmds "github.com/jbenet/go-ipfs/commands" cmds "github.com/jbenet/go-ipfs/commands"
"github.com/jbenet/go-ipfs/core" "github.com/jbenet/go-ipfs/core"
"github.com/jbenet/go-ipfs/merkledag" "github.com/jbenet/go-ipfs/merkledag"
u "github.com/jbenet/go-ipfs/util"
) )
var pinCmd = &cmds.Command{ var pinCmd = &cmds.Command{
@ -16,6 +17,7 @@ var pinCmd = &cmds.Command{
Subcommands: map[string]*cmds.Command{ Subcommands: map[string]*cmds.Command{
"add": addPinCmd, "add": addPinCmd,
"rm": rmPinCmd, "rm": rmPinCmd,
"ls": listPinCmd,
}, },
} }
@ -99,6 +101,68 @@ collected if needed.
}, },
} }
var listPinCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "List objects pinned to local storage",
ShortDescription: `
Returns a list of hashes of objects being pinned. Objects that are indirectly
or recursively pinned are not included in the list.
`,
LongDescription: `
Returns a list of hashes of objects being pinned. Objects that are indirectly
or recursively pinned are not included in the list.
Use --type=<type> to specify the type of pinned keys to list. Valid values are:
* "direct"
* "indirect"
* "recursive"
* "all"
(Defaults to "direct")
`,
},
Options: []cmds.Option{
cmds.StringOption("type", "t", "The type of pinned keys to list. Can be \"direct\", \"indirect\", \"recursive\", or \"all\". Defaults to \"direct\""),
},
Run: func(req cmds.Request) (interface{}, error) {
n, err := req.Context().GetNode()
if err != nil {
return nil, err
}
typeStr, found, err := req.Option("type").String()
if err != nil {
return nil, err
}
if !found {
typeStr = "direct"
}
switch typeStr {
case "all", "direct", "indirect", "recursive":
default:
return nil, cmds.ClientError("Invalid type '" + typeStr + "', must be one of {direct, indirect, recursive, all}")
}
keys := make([]u.Key, 0)
if typeStr == "direct" || typeStr == "all" {
keys = append(keys, n.Pinning.DirectKeys()...)
}
if typeStr == "indirect" || typeStr == "all" {
keys = append(keys, n.Pinning.IndirectKeys()...)
}
if typeStr == "recursive" || typeStr == "all" {
keys = append(keys, n.Pinning.RecursiveKeys()...)
}
return &KeyList{Keys: keys}, nil
},
Type: &KeyList{},
Marshalers: cmds.MarshalerMap{
cmds.Text: KeyListTextMarshaler,
},
}
func pin(n *core.IpfsNode, paths []string, recursive bool) ([]*merkledag.Node, error) { func pin(n *core.IpfsNode, paths []string, recursive bool) ([]*merkledag.Node, error) {
dagnodes := make([]*merkledag.Node, 0) dagnodes := make([]*merkledag.Node, 0)

View File

@ -10,8 +10,19 @@ import (
u "github.com/jbenet/go-ipfs/util" u "github.com/jbenet/go-ipfs/util"
) )
type RefsOutput struct { // KeyList is a general type for outputting lists of keys
Refs []string type KeyList struct {
Keys []u.Key
}
// KeyListTextMarshaler outputs a KeyList as plaintext, one key per line
func KeyListTextMarshaler(res cmds.Response) ([]byte, error) {
output := res.Output().(*KeyList)
s := ""
for _, key := range output.Keys {
s += key.B58String() + "\n"
}
return []byte(s), nil
} }
var refsCmd = &cmds.Command{ var refsCmd = &cmds.Command{
@ -58,26 +69,19 @@ Note: list all refs recursively with -r.
return getRefs(n, req.Arguments(), unique, recursive) return getRefs(n, req.Arguments(), unique, recursive)
}, },
Type: &RefsOutput{}, Type: &KeyList{},
Marshalers: cmds.MarshalerMap{ Marshalers: cmds.MarshalerMap{
cmds.Text: func(res cmds.Response) ([]byte, error) { cmds.Text: KeyListTextMarshaler,
output := res.Output().(*RefsOutput)
s := ""
for _, ref := range output.Refs {
s += fmt.Sprintln(ref)
}
return []byte(s), nil
},
}, },
} }
func getRefs(n *core.IpfsNode, paths []string, unique, recursive bool) (*RefsOutput, error) { func getRefs(n *core.IpfsNode, paths []string, unique, recursive bool) (*KeyList, error) {
var refsSeen map[u.Key]bool var refsSeen map[u.Key]bool
if unique { if unique {
refsSeen = make(map[u.Key]bool) refsSeen = make(map[u.Key]bool)
} }
refs := make([]string, 0) refs := make([]u.Key, 0)
for _, path := range paths { for _, path := range paths {
object, err := n.Resolver.ResolvePath(path) object, err := n.Resolver.ResolvePath(path)
@ -91,10 +95,10 @@ func getRefs(n *core.IpfsNode, paths []string, unique, recursive bool) (*RefsOut
} }
} }
return &RefsOutput{refs}, nil return &KeyList{refs}, nil
} }
func addRefs(n *core.IpfsNode, object *dag.Node, refs []string, refsSeen map[u.Key]bool, recursive bool) ([]string, error) { func addRefs(n *core.IpfsNode, object *dag.Node, refs []u.Key, refsSeen map[u.Key]bool, recursive bool) ([]u.Key, error) {
for _, link := range object.Links { for _, link := range object.Links {
var found bool var found bool
found, refs = addRef(link.Hash, refs, refsSeen) found, refs = addRef(link.Hash, refs, refsSeen)
@ -115,15 +119,16 @@ func addRefs(n *core.IpfsNode, object *dag.Node, refs []string, refsSeen map[u.K
return refs, nil return refs, nil
} }
func addRef(h mh.Multihash, refs []string, refsSeen map[u.Key]bool) (bool, []string) { func addRef(h mh.Multihash, refs []u.Key, refsSeen map[u.Key]bool) (bool, []u.Key) {
key := u.Key(h)
if refsSeen != nil { if refsSeen != nil {
_, found := refsSeen[u.Key(h)] _, found := refsSeen[key]
if found { if found {
return true, refs return true, refs
} }
refsSeen[u.Key(h)] = true refsSeen[key] = true
} }
refs = append(refs, h.B58String()) refs = append(refs, key)
return false, refs return false, refs
} }

View File

@ -65,3 +65,7 @@ func (i *indirectPin) Decrement(k util.Key) {
func (i *indirectPin) HasKey(k util.Key) bool { func (i *indirectPin) HasKey(k util.Key) bool {
return i.blockset.HasKey(k) return i.blockset.HasKey(k)
} }
func (i *indirectPin) Set() set.BlockSet {
return i.blockset
}

View File

@ -33,6 +33,9 @@ type Pinner interface {
Unpin(util.Key, bool) error Unpin(util.Key, bool) error
Flush() error Flush() error
GetManual() ManualPinner GetManual() ManualPinner
DirectKeys() []util.Key
IndirectKeys() []util.Key
RecursiveKeys() []util.Key
} }
// ManualPinner is for manually editing the pin structure // ManualPinner is for manually editing the pin structure
@ -207,6 +210,21 @@ func LoadPinner(d ds.Datastore, dserv mdag.DAGService) (Pinner, error) {
return p, nil return p, nil
} }
// DirectKeys returns a slice containing the directly pinned keys
func (p *pinner) DirectKeys() []util.Key {
return p.directPin.GetKeys()
}
// IndirectKeys returns a slice containing the indirectly pinned keys
func (p *pinner) IndirectKeys() []util.Key {
return p.indirPin.Set().GetKeys()
}
// RecursiveKeys returns a slice containing the recursively pinned keys
func (p *pinner) RecursiveKeys() []util.Key {
return p.recursePin.GetKeys()
}
// Flush encodes and writes pinner keysets to the datastore // Flush encodes and writes pinner keysets to the datastore
func (p *pinner) Flush() error { func (p *pinner) Flush() error {
p.lock.RLock() p.lock.RLock()