1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-07-01 02:30:39 +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, "")
}
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

View File

@ -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

View File

@ -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,

View File

@ -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)
}

View File

@ -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,
}

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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=<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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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()