diff --git a/commands/cli/helptext.go b/commands/cli/helptext.go index 9acc3c7d2..32cbb7076 100644 --- a/commands/cli/helptext.go +++ b/commands/cli/helptext.go @@ -239,7 +239,7 @@ func optionText(cmd ...*cmds.Command) []string { lines = append(lines, "") } - names := sortByLength(opt.Names) + names := sortByLength(opt.Names()) if len(names) >= j+1 { lines[i] += fmt.Sprintf(optionFlag, names[j]) } @@ -262,13 +262,13 @@ func optionText(cmd ...*cmds.Command) []string { // add option types to output for i, opt := range options { - lines[i] += " " + fmt.Sprintf("%v", opt.Type) + lines[i] += " " + fmt.Sprintf("%v", opt.Type()) } lines = align(lines) // add option descriptions to output for i, opt := range options { - lines[i] += " - " + opt.Description + lines[i] += " - " + opt.Description() } return lines diff --git a/commands/cli/parse.go b/commands/cli/parse.go index 1d05f5152..aceec578c 100644 --- a/commands/cli/parse.go +++ b/commands/cli/parse.go @@ -46,10 +46,17 @@ func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *c return nil, cmd, path, err } - recursive, _, err := req.Option(cmds.RecShort).Bool() - if err != nil { - return nil, nil, nil, u.ErrCast() + // if -r is provided, and it is associated with the package builtin + // recursive path option, allow recursive file paths + 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) if err != nil { return nil, cmd, path, err diff --git a/commands/cli/parse_test.go b/commands/cli/parse_test.go index cddd54669..8fe5b3510 100644 --- a/commands/cli/parse_test.go +++ b/commands/cli/parse_test.go @@ -11,7 +11,7 @@ func TestOptionParsing(t *testing.T) { subCmd := &commands.Command{} cmd := &commands.Command{ Options: []commands.Option{ - commands.Option{Names: []string{"b"}, Type: commands.String}, + commands.StringOption("b", "some option"), }, Subcommands: map[string]*commands.Command{ "test": subCmd, diff --git a/commands/command.go b/commands/command.go index 1f15fdab4..6baddc2b5 100644 --- a/commands/command.go +++ b/commands/command.go @@ -167,7 +167,7 @@ func (c *Command) Get(path []string) (*Command, error) { // GetOptions gets the options in the given path of commands 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) if err != nil { @@ -181,7 +181,7 @@ func (c *Command) GetOptions(path []string) (map[string]Option, error) { optionsMap := make(map[string]Option) for _, opt := range options { - for _, name := range opt.Names { + for _, name := range opt.Names() { if _, found := optionsMap[name]; found { return nil, fmt.Errorf("Option name '%s' used multiple times", name) } diff --git a/commands/command_test.go b/commands/command_test.go index ab090fe2c..4fcc48bd8 100644 --- a/commands/command_test.go +++ b/commands/command_test.go @@ -9,8 +9,8 @@ func noop(req Request) (interface{}, error) { func TestOptionValidation(t *testing.T) { cmd := Command{ Options: []Option{ - Option{[]string{"b", "beep"}, Int, "enables beeper"}, - Option{[]string{"B", "boop"}, String, "password for booper"}, + IntOption("b", "beep", "enables beeper"), + StringOption("B", "boop", "password for booper"), }, Run: noop, } @@ -93,14 +93,14 @@ func TestOptionValidation(t *testing.T) { func TestRegistration(t *testing.T) { cmdA := &Command{ Options: []Option{ - Option{[]string{"beep"}, Int, "number of beeps"}, + IntOption("beep", "number of beeps"), }, Run: noop, } cmdB := &Command{ Options: []Option{ - Option{[]string{"beep"}, Int, "number of beeps"}, + IntOption("beep", "number of beeps"), }, Run: noop, Subcommands: map[string]*Command{ @@ -110,7 +110,7 @@ func TestRegistration(t *testing.T) { cmdC := &Command{ Options: []Option{ - Option{[]string{"encoding"}, String, "data encoding type"}, + StringOption("encoding", "data encoding type"), }, Run: noop, } diff --git a/commands/option.go b/commands/option.go index d4bd75e42..78f01a7aa 100644 --- a/commands/option.go +++ b/commands/option.go @@ -17,14 +17,28 @@ const ( ) // Option is used to specify a field that will be provided by a consumer -type Option struct { - Names []string // a list of unique names to - Type reflect.Kind // value must be this type - Description string // a short string to describe this option +type Option interface { + Names() []string // a list of unique names matched with user-provided flags + Type() reflect.Kind // value must be this type + Description() string // a short string that describes this option +} - // MAYBE_TODO: add more features(?): - //Default interface{} // the default value (ignored if `Required` is true) - //Required bool // whether or not the option must be provided +type option struct { + names []string + 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 @@ -37,10 +51,10 @@ func NewOption(kind reflect.Kind, names ...string) Option { desc := names[len(names)-1] names = names[:len(names)-1] - return Option{ - Names: names, - Type: kind, - Description: desc, + return &option{ + names: names, + kind: kind, + description: desc, } } @@ -69,6 +83,7 @@ func StringOption(names ...string) Option { type OptionValue struct { value interface{} found bool + def Option } // 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 } +// 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 func (ov OptionValue) Bool() (value bool, found bool, err error) { if !ov.found { @@ -141,9 +161,12 @@ const ( ) // 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{ - StringOption(EncShort, EncLong, "The encoding type the output should be encoded with (json, xml, or text)"), - BoolOption(RecShort, RecLong, "Add directory paths recursively"), + OptionEncodingType, } // the above array of Options, wrapped in a Command diff --git a/commands/request.go b/commands/request.go index 112e3dde0..7d8b7cd8e 100644 --- a/commands/request.go +++ b/commands/request.go @@ -92,13 +92,6 @@ func (r *request) Path() []string { // Option returns the value of the option for given name. 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 option, found := r.optionDefs[name] if !found { @@ -106,15 +99,15 @@ func (r *request) Option(name string) *OptionValue { } // try all the possible names, break if we find a value - for _, n := range option.Names { - val, found = r.options[n] + for _, n := range option.Names() { + val, found := r.options[n] if found { - return &OptionValue{val, found} + return &OptionValue{val, found, option} } } // 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 @@ -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 - for _, n := range option.Names { + for _, n := range option.Names() { _, found := r.options[n] if found { r.options[n] = val @@ -222,9 +215,9 @@ func (r *request) ConvertOptions() error { } kind := reflect.TypeOf(v).Kind() - if kind != opt.Type { + if kind != opt.Type() { if kind == String { - convert := converters[opt.Type] + convert := converters[opt.Type()] str, ok := v.(string) if !ok { return u.ErrCast() @@ -236,19 +229,19 @@ func (r *request) ConvertOptions() error { value = "empty value" } 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 } else { 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 { r.options[k] = v } - for _, name := range opt.Names { + for _, name := range opt.Names() { if _, ok := r.options[name]; name != k && ok { return fmt.Errorf("Duplicate command options were provided ('%s' and '%s')", k, name) diff --git a/core/commands/add.go b/core/commands/add.go index 9306f81f2..f09787b9c 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -39,6 +39,9 @@ remains to be implemented. Arguments: []cmds.Argument{ 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) { added := &AddOutput{} n, err := req.Context().GetNode() diff --git a/core/commands/pin.go b/core/commands/pin.go index 33060de60..341d7dd4a 100644 --- a/core/commands/pin.go +++ b/core/commands/pin.go @@ -6,6 +6,7 @@ import ( cmds "github.com/jbenet/go-ipfs/commands" "github.com/jbenet/go-ipfs/core" "github.com/jbenet/go-ipfs/merkledag" + u "github.com/jbenet/go-ipfs/util" ) var pinCmd = &cmds.Command{ @@ -16,6 +17,7 @@ var pinCmd = &cmds.Command{ Subcommands: map[string]*cmds.Command{ "add": addPinCmd, "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= 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) { dagnodes := make([]*merkledag.Node, 0) diff --git a/core/commands/refs.go b/core/commands/refs.go index a40b3f57e..c977a3df8 100644 --- a/core/commands/refs.go +++ b/core/commands/refs.go @@ -10,8 +10,19 @@ import ( u "github.com/jbenet/go-ipfs/util" ) -type RefsOutput struct { - Refs []string +// KeyList is a general type for outputting lists of keys +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{ @@ -58,26 +69,19 @@ Note: list all refs recursively with -r. return getRefs(n, req.Arguments(), unique, recursive) }, - Type: &RefsOutput{}, + Type: &KeyList{}, Marshalers: cmds.MarshalerMap{ - cmds.Text: func(res cmds.Response) ([]byte, error) { - output := res.Output().(*RefsOutput) - s := "" - for _, ref := range output.Refs { - s += fmt.Sprintln(ref) - } - return []byte(s), nil - }, + cmds.Text: KeyListTextMarshaler, }, } -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 if unique { refsSeen = make(map[u.Key]bool) } - refs := make([]string, 0) + refs := make([]u.Key, 0) for _, path := range paths { 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 { var found bool 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 } -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 { - _, found := refsSeen[u.Key(h)] + _, found := refsSeen[key] if found { return true, refs } - refsSeen[u.Key(h)] = true + refsSeen[key] = true } - refs = append(refs, h.B58String()) + refs = append(refs, key) return false, refs } diff --git a/pin/indirect.go b/pin/indirect.go index b15b720ee..9e67bc2c9 100644 --- a/pin/indirect.go +++ b/pin/indirect.go @@ -65,3 +65,7 @@ func (i *indirectPin) Decrement(k util.Key) { func (i *indirectPin) HasKey(k util.Key) bool { return i.blockset.HasKey(k) } + +func (i *indirectPin) Set() set.BlockSet { + return i.blockset +} diff --git a/pin/pin.go b/pin/pin.go index 60828b597..371497da6 100644 --- a/pin/pin.go +++ b/pin/pin.go @@ -33,6 +33,9 @@ type Pinner interface { Unpin(util.Key, bool) error Flush() error GetManual() ManualPinner + DirectKeys() []util.Key + IndirectKeys() []util.Key + RecursiveKeys() []util.Key } // 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 } +// 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 func (p *pinner) Flush() error { p.lock.RLock()