From 7294f6334ab2f08c506ebc2ecab3d36c39e28988 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Tue, 20 Jan 2015 16:07:32 -0800 Subject: [PATCH 01/16] commands: Removed old TODOs --- commands/command.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/commands/command.go b/commands/command.go index 332298431..577e2649e 100644 --- a/commands/command.go +++ b/commands/command.go @@ -40,12 +40,6 @@ type HelpText struct { Subcommands string // overrides SUBCOMMANDS section } -// TODO: check Argument definitions when creating a Command -// (might need to use a Command constructor) -// * make sure any variadic args are at the end -// * make sure there aren't duplicate names -// * make sure optional arguments aren't followed by required arguments - // Command is a runnable command, with input arguments and options (flags). // It can also have Subcommands, to group units of work into sets. type Command struct { From 1281b251058fdfce67e105efe0b02f63a2a854ce Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Tue, 20 Jan 2015 19:03:37 -0800 Subject: [PATCH 02/16] commands: Added PostRun function, called on the client --- cmd/ipfs/main.go | 9 +++++++-- commands/command.go | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index 96f680bcb..17d773ab2 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -158,7 +158,7 @@ func (i *cmdInvocation) Run(ctx context.Context) (output io.Reader, err error) { defer stopProfilingFunc() // to be executed as late as possible } - res, err := callCommand(ctx, i.req, Root) + res, err := callCommand(ctx, i.req, Root, i.cmd) if err != nil { return nil, err } @@ -296,7 +296,7 @@ func callPreCommandHooks(ctx context.Context, details cmdDetails, req cmds.Reque return nil } -func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command) (cmds.Response, error) { +func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command, cmd *cmds.Command) (cmds.Response, error) { var res cmds.Response details, err := commandDetails(req.Path(), root) @@ -347,6 +347,11 @@ func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command) (cmd res = root.Call(req) } + + if cmd.PostRun != nil { + cmd.PostRun(res) + } + return res, nil } diff --git a/commands/command.go b/commands/command.go index 577e2649e..74beeec6e 100644 --- a/commands/command.go +++ b/commands/command.go @@ -46,6 +46,7 @@ type Command struct { Options []Option Arguments []Argument Run Function + PostRun func(res Response) Marshalers map[EncodingType]Marshaler Helptext HelpText From 856d2896a7dce5d509f8438b81677a218a8125bb Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Wed, 21 Jan 2015 15:18:45 -0800 Subject: [PATCH 03/16] commands: Added PreRun function to command --- cmd/ipfs/main.go | 7 +++++++ commands/command.go | 1 + 2 files changed, 8 insertions(+) diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index 17d773ab2..e50a5ca16 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -315,6 +315,13 @@ func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command, cmd return nil, err } + if cmd.PreRun != nil { + err = cmd.PreRun(req) + if err != nil { + return nil, err + } + } + if useDaemon { cfg, err := req.Context().GetConfig() diff --git a/commands/command.go b/commands/command.go index 74beeec6e..f79bf31c0 100644 --- a/commands/command.go +++ b/commands/command.go @@ -45,6 +45,7 @@ type HelpText struct { type Command struct { Options []Option Arguments []Argument + PreRun func(req Request) error Run Function PostRun func(res Response) Marshalers map[EncodingType]Marshaler From 7b4de230eb3d33c503f7b42211c9ffd2b337a38f Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Tue, 20 Jan 2015 17:58:50 -0800 Subject: [PATCH 04/16] commands: Refactored Command#Run function signature to (req Request, res Response) --- cmd/ipfs/daemon.go | 38 ++++++++++++++------- cmd/ipfs/init.go | 15 ++++++--- cmd/ipfs/tour.go | 38 +++++++++++---------- commands/command.go | 18 +++------- commands/command_test.go | 4 +-- core/commands/add.go | 8 ++--- core/commands/block.go | 41 ++++++++++++++--------- core/commands/bootstrap.go | 51 +++++++++++++++++++---------- core/commands/cat.go | 10 +++--- core/commands/commands.go | 4 +-- core/commands/config.go | 57 +++++++++++++++++++++++--------- core/commands/diag.go | 36 ++++++++++++++------ core/commands/id.go | 33 ++++++++++++++----- core/commands/log.go | 11 ++++--- core/commands/ls.go | 10 +++--- core/commands/mount_unix.go | 22 ++++++++----- core/commands/mount_windows.go | 4 +-- core/commands/object.go | 60 ++++++++++++++++++++++------------ core/commands/pin.go | 39 +++++++++++++--------- core/commands/ping.go | 16 +++++---- core/commands/publish.go | 19 +++++++---- core/commands/refs.go | 32 +++++++++++------- core/commands/repo.go | 12 ++++--- core/commands/resolve.go | 16 +++++---- core/commands/swarm.go | 23 ++++++++----- core/commands/update.go | 39 +++++++++++++++++----- core/commands/version.go | 6 ++-- 27 files changed, 426 insertions(+), 236 deletions(-) diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index 0e83e9a8a..471e14778 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -47,13 +47,14 @@ the daemon. Run: daemonFunc, } -func daemonFunc(req cmds.Request) (interface{}, error) { +func daemonFunc(req cmds.Request, res cmds.Response) { // first, whether user has provided the initialization flag. we may be // running in an uninitialized state. initialize, _, err := req.Option(initOptionKwd).Bool() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if initialize { @@ -64,7 +65,8 @@ func daemonFunc(req cmds.Request) (interface{}, error) { if !util.FileExists(req.Context().ConfigRoot) { err := initWithDefaults(req.Context().ConfigRoot) if err != nil { - return nil, debugerror.Wrap(err) + res.SetError(debugerror.Wrap(err), cmds.ErrNormal) + return } } } @@ -77,14 +79,16 @@ func daemonFunc(req cmds.Request) (interface{}, error) { ctx := req.Context() cfg, err := ctx.GetConfig() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } // acquire the repo lock _before_ constructing a node. we need to make // sure we are permitted to access the resources (datastore, etc.) repo := fsrepo.At(req.Context().ConfigRoot) if err := repo.Open(); err != nil { - return nil, debugerror.Errorf("Couldn't obtain lock. Is another daemon already running?") + res.SetError(debugerror.Errorf("Couldn't obtain lock. Is another daemon already running?"), cmds.ErrNormal) + return } defer repo.Close() @@ -93,13 +97,15 @@ func daemonFunc(req cmds.Request) (interface{}, error) { ctx.Online = true node, err := ctx.GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } // verify api address is valid multiaddr apiMaddr, err := ma.NewMultiaddr(cfg.Addresses.API) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } var gatewayMaddr ma.Multiaddr @@ -115,12 +121,14 @@ func daemonFunc(req cmds.Request) (interface{}, error) { // mount if the user provided the --mount flag mount, _, err := req.Option(mountKwd).Bool() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if mount { fsdir, found, err := req.Option(ipfsMountKwd).String() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if !found { fsdir = cfg.Mounts.IPFS @@ -128,7 +136,8 @@ func daemonFunc(req cmds.Request) (interface{}, error) { nsdir, found, err := req.Option(ipnsMountKwd).String() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if !found { nsdir = cfg.Mounts.IPNS @@ -136,7 +145,8 @@ func daemonFunc(req cmds.Request) (interface{}, error) { err = commands.Mount(node, fsdir, nsdir) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } fmt.Printf("IPFS mounted at: %s\n", fsdir) fmt.Printf("IPNS mounted at: %s\n", nsdir) @@ -156,5 +166,9 @@ func daemonFunc(req cmds.Request) (interface{}, error) { corehttp.WebUIOption, corehttp.GatewayOption, } - return nil, corehttp.ListenAndServe(node, apiMaddr, opts...) + err = corehttp.ListenAndServe(node, apiMaddr, opts...) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } } diff --git a/cmd/ipfs/init.go b/cmd/ipfs/init.go index ec8025bc0..cdb61f2c4 100644 --- a/cmd/ipfs/init.go +++ b/cmd/ipfs/init.go @@ -39,22 +39,29 @@ var initCmd = &cmds.Command{ // name of the file? // TODO cmds.StringOption("event-logs", "l", "Location for machine-readable event logs"), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { force, _, err := req.Option("f").Bool() // if !found, it's okay force == false if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } nBitsForKeypair, bitsOptFound, err := req.Option("b").Int() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if !bitsOptFound { nBitsForKeypair = nBitsForKeypairDefault } - return doInit(req.Context().ConfigRoot, force, nBitsForKeypair) + output, err := doInit(req.Context().ConfigRoot, force, nBitsForKeypair) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) }, } diff --git a/cmd/ipfs/tour.go b/cmd/ipfs/tour.go index 168bacc9c..9a5277301 100644 --- a/cmd/ipfs/tour.go +++ b/cmd/ipfs/tour.go @@ -36,11 +36,12 @@ IPFS very quickly. To start, run: Run: tourRunFunc, } -func tourRunFunc(req cmds.Request) (interface{}, error) { +func tourRunFunc(req cmds.Request, res cmds.Response) { cfg, err := req.Context().GetConfig() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } id := tour.TopicID(cfg.Tour.Last) @@ -64,11 +65,10 @@ func tourRunFunc(req cmds.Request) (interface{}, error) { fmt.Fprintln(&w, "") fprintTourList(&w, tour.TopicID(cfg.Tour.Last)) - return nil, nil + return } fprintTourShow(&w, t) - return nil, nil } var cmdIpfsTourNext = &cmds.Command{ @@ -76,21 +76,24 @@ var cmdIpfsTourNext = &cmds.Command{ Tagline: "Show the next IPFS Tour topic", }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { var w bytes.Buffer path := req.Context().ConfigRoot cfg, err := req.Context().GetConfig() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } id := tour.NextTopic(tour.TopicID(cfg.Tour.Last)) topic, err := tourGet(id) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if err := fprintTourShow(&w, topic); err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } // topic changed, not last. write it out. @@ -98,12 +101,12 @@ var cmdIpfsTourNext = &cmds.Command{ cfg.Tour.Last = string(id) err := writeConfig(path, cfg) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } } w.WriteTo(os.Stdout) - return nil, nil }, } @@ -112,19 +115,20 @@ var cmdIpfsTourRestart = &cmds.Command{ Tagline: "Restart the IPFS Tour", }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { path := req.Context().ConfigRoot cfg, err := req.Context().GetConfig() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } cfg.Tour.Last = "" err = writeConfig(path, cfg) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return nil, nil }, } @@ -133,16 +137,16 @@ var cmdIpfsTourList = &cmds.Command{ Tagline: "Show a list of IPFS Tour topics", }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { cfg, err := req.Context().GetConfig() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } var w bytes.Buffer fprintTourList(&w, tour.TopicID(cfg.Tour.Last)) w.WriteTo(os.Stdout) - return nil, nil }, } diff --git a/commands/command.go b/commands/command.go index f79bf31c0..f52ce7f06 100644 --- a/commands/command.go +++ b/commands/command.go @@ -14,7 +14,7 @@ var log = u.Logger("command") // Function is the type of function that Commands use. // It reads from the Request, and writes results to the Response. -type Function func(Request) (interface{}, error) +type Function func(Request, Response) // Marshaler is a function that takes in a Response, and returns an io.Reader // (or an error on failure) @@ -95,21 +95,12 @@ func (c *Command) Call(req Request) Response { return res } - output, err := cmd.Run(req) - if err != nil { - // if returned error is a commands.Error, use its error code - // otherwise, just default the code to ErrNormal - switch e := err.(type) { - case *Error: - res.SetError(e, e.Code) - case Error: - res.SetError(e, e.Code) - default: - res.SetError(err, ErrNormal) - } + cmd.Run(req, res) + if res.Error() != nil { return res } + output := res.Output() isChan := false actualType := reflect.TypeOf(output) if actualType != nil { @@ -140,7 +131,6 @@ func (c *Command) Call(req Request) Response { } } - res.SetOutput(output) return res } diff --git a/commands/command_test.go b/commands/command_test.go index 4fcc48bd8..a5cad4d02 100644 --- a/commands/command_test.go +++ b/commands/command_test.go @@ -2,8 +2,8 @@ package commands import "testing" -func noop(req Request) (interface{}, error) { - return nil, nil +func noop(req Request, res Response) { + return } func TestOptionValidation(t *testing.T) { diff --git a/core/commands/add.go b/core/commands/add.go index 61c412a04..0e82e473f 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -44,13 +44,15 @@ remains to be implemented. cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive) cmds.BoolOption("quiet", "q", "Write minimal output"), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } outChan := make(chan interface{}) + res.SetOutput((<-chan interface{})(outChan)) go func() { defer close(outChan) @@ -67,8 +69,6 @@ remains to be implemented. } } }() - - return outChan, nil }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { diff --git a/core/commands/block.go b/core/commands/block.go index 2df004407..3f1563307 100644 --- a/core/commands/block.go +++ b/core/commands/block.go @@ -2,6 +2,7 @@ package commands import ( "bytes" + "errors" "fmt" "io" "io/ioutil" @@ -57,16 +58,17 @@ on raw ipfs blocks. It outputs the following to stdout: Arguments: []cmds.Argument{ cmds.StringArg("key", true, false, "The base58 multihash of an existing block to get").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { b, err := getBlockForKey(req, req.Arguments()[0]) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return &BlockStat{ + res.SetOutput(&BlockStat{ Key: b.Key().Pretty(), Size: len(b.Data), - }, nil + }) }, Type: BlockStat{}, Marshalers: cmds.MarshalerMap{ @@ -89,13 +91,14 @@ It outputs to stdout, and is a base58 encoded multihash. Arguments: []cmds.Argument{ cmds.StringArg("key", true, false, "The base58 multihash of an existing block to get").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { b, err := getBlockForKey(req, req.Arguments()[0]) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return bytes.NewReader(b.Data), nil + res.SetOutput(bytes.NewReader(b.Data)) }, } @@ -111,25 +114,29 @@ It reads from stdin, and is a base58 encoded multihash. Arguments: []cmds.Argument{ cmds.FileArg("data", true, false, "The data to be stored as an IPFS block").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } file, err := req.Files().NextFile() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } data, err := ioutil.ReadAll(file) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } err = file.Close() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } b := blocks.NewBlock(data) @@ -137,13 +144,14 @@ It reads from stdin, and is a base58 encoded multihash. k, err := n.Blocks.AddBlock(b) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return &BlockStat{ + res.SetOutput(&BlockStat{ Key: k.String(), Size: len(data), - }, nil + }) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { @@ -160,7 +168,7 @@ func getBlockForKey(req cmds.Request, key string) (*blocks.Block, error) { } if !u.IsValidHash(key) { - return nil, cmds.Error{"Not a valid hash", cmds.ErrClient} + return nil, errors.New("Not a valid hash") } h, err := mh.FromB58String(key) @@ -173,6 +181,7 @@ func getBlockForKey(req cmds.Request, key string) (*blocks.Block, error) { if err != nil { return nil, err } + log.Debugf("ipfs block: got block with key: %q", b.Key()) return b, nil } diff --git a/core/commands/bootstrap.go b/core/commands/bootstrap.go index a84682a8c..78397ef9a 100644 --- a/core/commands/bootstrap.go +++ b/core/commands/bootstrap.go @@ -76,29 +76,33 @@ in the bootstrap list). cmds.BoolOption("default", "add default bootstrap nodes"), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { inputPeers, err := config.ParseBootstrapPeers(req.Arguments()) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } r := fsrepo.At(req.Context().ConfigRoot) if err := r.Open(); err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } defer r.Close() cfg := r.Config() deflt, _, err := req.Option("default").Bool() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if deflt { // parse separately for meaningful, correct error. defltPeers, err := DefaultBootstrapPeers() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } inputPeers = append(inputPeers, defltPeers...) @@ -106,14 +110,16 @@ in the bootstrap list). added, err := bootstrapAdd(r, cfg, inputPeers) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if len(inputPeers) == 0 { - return nil, cmds.ClientError("no bootstrap peers to add") + res.SetError(errors.New("no bootstrap peers to add"), cmds.ErrClient) + return } - return &BootstrapOutput{added}, nil + res.SetOutput(&BootstrapOutput{added}) }, Type: BootstrapOutput{}, Marshalers: cmds.MarshalerMap{ @@ -125,7 +131,11 @@ in the bootstrap list). var buf bytes.Buffer err := bootstrapWritePeers(&buf, "added ", v.Peers) - return &buf, err + if err != nil { + return nil, err + } + + return &buf, nil }, }, } @@ -143,22 +153,25 @@ var bootstrapRemoveCmd = &cmds.Command{ Options: []cmds.Option{ cmds.BoolOption("all", "Remove all bootstrap peers."), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { input, err := config.ParseBootstrapPeers(req.Arguments()) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } r := fsrepo.At(req.Context().ConfigRoot) if err := r.Open(); err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } defer r.Close() cfg := r.Config() all, _, err := req.Option("all").Bool() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } var removed []config.BootstrapPeer @@ -168,10 +181,11 @@ var bootstrapRemoveCmd = &cmds.Command{ removed, err = bootstrapRemove(r, cfg, input) } if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return &BootstrapOutput{removed}, nil + res.SetOutput(&BootstrapOutput{removed}) }, Type: BootstrapOutput{}, Marshalers: cmds.MarshalerMap{ @@ -194,14 +208,15 @@ var bootstrapListCmd = &cmds.Command{ ShortDescription: "Peers are output in the format '/'.", }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { cfg, err := req.Context().GetConfig() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } peers := cfg.Bootstrap - return &BootstrapOutput{peers}, nil + res.SetOutput(&BootstrapOutput{peers}) }, Type: BootstrapOutput{}, Marshalers: cmds.MarshalerMap{ diff --git a/core/commands/cat.go b/core/commands/cat.go index fe67cdb91..208908937 100644 --- a/core/commands/cat.go +++ b/core/commands/cat.go @@ -20,21 +20,23 @@ it contains. Arguments: []cmds.Argument{ cmds.StringArg("ipfs-path", true, true, "The path to the IPFS object(s) to be outputted").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { node, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } readers := make([]io.Reader, 0, len(req.Arguments())) readers, err = cat(node, req.Arguments()) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } reader := io.MultiReader(readers...) - return reader, nil + res.SetOutput(reader) }, } diff --git a/core/commands/commands.go b/core/commands/commands.go index df447d290..b0e38c902 100644 --- a/core/commands/commands.go +++ b/core/commands/commands.go @@ -22,9 +22,9 @@ func CommandsCmd(root *cmds.Command) *cmds.Command { ShortDescription: `Lists all available commands (and subcommands) and exits.`, }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { root := cmd2outputCmd("ipfs", root) - return &root, nil + res.SetOutput(&root) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { diff --git a/core/commands/config.go b/core/commands/config.go index 267b33177..33d56cf0f 100644 --- a/core/commands/config.go +++ b/core/commands/config.go @@ -57,23 +57,34 @@ Set the value of the 'datastore.path' key: cmds.StringArg("key", true, false, "The key of the config entry (e.g. \"Addresses.API\")"), cmds.StringArg("value", false, false, "The value to set the config entry to"), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { args := req.Arguments() key := args[0] r := fsrepo.At(req.Context().ConfigRoot) if err := r.Open(); err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } defer r.Close() var value string if len(args) == 2 { value = args[1] - return setConfig(r, key, value) + output, err := setConfig(r, key, value) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) } else { - return getConfig(r, key) + output, err := getConfig(r, key) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) } }, Marshalers: cmds.MarshalerMap{ @@ -117,13 +128,19 @@ included in the output of this command. `, }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { filename, err := config.Filename(req.Context().ConfigRoot) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return showConfig(filename) + output, err := showConfig(filename) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) }, } @@ -136,19 +153,23 @@ variable set to your preferred text editor. `, }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { filename, err := config.Filename(req.Context().ConfigRoot) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return nil, editConfig(filename) + err = editConfig(filename) + if err != nil { + res.SetError(err, cmds.ErrNormal) + } }, } var configReplaceCmd = &cmds.Command{ Helptext: cmds.HelpText{ - Tagline: "Replaces the config with ", + Tagline: "Replaces the config with `file>", ShortDescription: ` Make sure to back up the config file first if neccessary, this operation can't be undone. @@ -158,20 +179,26 @@ can't be undone. Arguments: []cmds.Argument{ cmds.FileArg("file", true, false, "The file to use as the new config"), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { r := fsrepo.At(req.Context().ConfigRoot) if err := r.Open(); err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } defer r.Close() file, err := req.Files().NextFile() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } defer file.Close() - return nil, replaceConfig(r, file) + err = replaceConfig(r, file) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } }, } diff --git a/core/commands/diag.go b/core/commands/diag.go index 98790f674..28b3673a3 100644 --- a/core/commands/diag.go +++ b/core/commands/diag.go @@ -2,6 +2,7 @@ package commands import ( "bytes" + "errors" "io" "strings" "text/template" @@ -63,50 +64,65 @@ connected peers and latencies between them. cmds.StringOption("vis", "output vis. one of: "+strings.Join(visFmts, ", ")), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if !n.OnlineMode() { - return nil, errNotOnline + res.SetError(errNotOnline, cmds.ErrClient) + return } vis, _, err := req.Option("vis").String() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } timeoutS, _, err := req.Option("timeout").String() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } timeout := DefaultDiagnosticTimeout if timeoutS != "" { t, err := time.ParseDuration(timeoutS) if err != nil { - return nil, cmds.ClientError("error parsing timeout") + res.SetError(errors.New("error parsing timeout"), cmds.ErrNormal) + return } timeout = t } info, err := n.Diagnostics.GetDiagnostic(timeout) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } switch vis { case visD3: - return bytes.NewReader(diag.GetGraphJson(info)), nil + res.SetOutput(bytes.NewReader(diag.GetGraphJson(info))) case visDot: var buf bytes.Buffer w := diag.DotWriter{W: &buf} err := w.WriteGraph(info) - return io.Reader(&buf), err + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(io.Reader(&buf)) } - return stdDiagOutputMarshal(standardDiagOutput(info)) + output, err := stdDiagOutputMarshal(standardDiagOutput(info)) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) }, } diff --git a/core/commands/id.go b/core/commands/id.go index 0452a2fdf..7d47465d9 100644 --- a/core/commands/id.go +++ b/core/commands/id.go @@ -45,37 +45,54 @@ if no peer is specified, prints out local peers info. Arguments: []cmds.Argument{ cmds.StringArg("peerid", false, false, "peer.ID of node to look up").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { node, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if len(req.Arguments()) == 0 { - return printPeer(node.Peerstore, node.Identity) + output, err := printPeer(node.Peerstore, node.Identity) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) + return } pid := req.Arguments()[0] id := peer.ID(b58.Decode(pid)) if len(id) == 0 { - return nil, cmds.ClientError("Invalid peer id") + res.SetError(cmds.ClientError("Invalid peer id"), cmds.ErrClient) + return } ctx, _ := context.WithTimeout(context.TODO(), time.Second*5) // TODO handle offline mode with polymorphism instead of conditionals if !node.OnlineMode() { - return nil, errors.New(offlineIdErrorMessage) + res.SetError(errors.New(offlineIdErrorMessage), cmds.ErrClient) + return } p, err := node.Routing.FindPeer(ctx, id) if err == kb.ErrLookupFailure { - return nil, errors.New(offlineIdErrorMessage) + res.SetError(errors.New(offlineIdErrorMessage), cmds.ErrClient) + return } if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return printPeer(node.Peerstore, p.ID) + + output, err := printPeer(node.Peerstore, p.ID) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { diff --git a/core/commands/log.go b/core/commands/log.go index f202d1d3e..34e33730e 100644 --- a/core/commands/log.go +++ b/core/commands/log.go @@ -47,7 +47,7 @@ output of a running daemon. cmds.StringArg("subsystem", true, false, fmt.Sprintf("the subsystem logging identifier. Use '%s' for all subsystems.", logAllKeyword)), cmds.StringArg("level", true, false, "one of: debug, info, notice, warning, error, critical"), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { args := req.Arguments() subsystem, level := args[0], args[1] @@ -57,12 +57,13 @@ output of a running daemon. } if err := u.SetLogLevel(subsystem, level); err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } s := fmt.Sprintf("Changed log level of '%s' to '%s'", subsystem, level) log.Info(s) - return &MessageOutput{s}, nil + res.SetOutput(&MessageOutput{s}) }, Marshalers: cmds.MarshalerMap{ cmds.Text: MessageTextMarshaler, @@ -78,7 +79,7 @@ var logTailCmd = &cmds.Command{ `, }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { path := fmt.Sprintf("%s/logs/events.log", req.Context().ConfigRoot) outChan := make(chan interface{}) @@ -108,7 +109,7 @@ var logTailCmd = &cmds.Command{ } }() - return (<-chan interface{})(outChan), nil + res.SetOutput((<-chan interface{})(outChan)) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { diff --git a/core/commands/ls.go b/core/commands/ls.go index 43b38339a..3901dfd83 100644 --- a/core/commands/ls.go +++ b/core/commands/ls.go @@ -37,10 +37,11 @@ it contains, with the following format: Arguments: []cmds.Argument{ cmds.StringArg("ipfs-path", true, true, "The path to the IPFS object(s) to list links from").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { node, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } paths := req.Arguments() @@ -49,7 +50,8 @@ it contains, with the following format: for _, path := range paths { dagnode, err := node.Resolver.ResolvePath(path) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } dagnodes = append(dagnodes, dagnode) } @@ -69,7 +71,7 @@ it contains, with the following format: } } - return &LsOutput{output}, nil + res.SetOutput(&LsOutput{output}) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { diff --git a/core/commands/mount_unix.go b/core/commands/mount_unix.go index d07a513b9..966463176 100644 --- a/core/commands/mount_unix.go +++ b/core/commands/mount_unix.go @@ -90,25 +90,29 @@ baz // TODO longform cmds.StringOption("n", "The path where IPNS should be mounted"), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { cfg, err := req.Context().GetConfig() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } node, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } // error if we aren't running node in online mode if !node.OnlineMode() { - return nil, errNotOnline + res.SetError(errNotOnline, cmds.ErrClient) + return } fsdir, found, err := req.Option("f").String() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if !found { fsdir = cfg.Mounts.IPFS // use default value @@ -117,7 +121,8 @@ baz // get default mount points nsdir, found, err := req.Option("n").String() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if !found { nsdir = cfg.Mounts.IPNS // NB: be sure to not redeclare! @@ -125,13 +130,14 @@ baz err = Mount(node, fsdir, nsdir) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } var output config.Mounts output.IPFS = fsdir output.IPNS = nsdir - return &output, nil + res.SetOutput(&output) }, Type: config.Mounts{}, Marshalers: cmds.MarshalerMap{ diff --git a/core/commands/mount_windows.go b/core/commands/mount_windows.go index 01bcc698c..1b796c9ef 100644 --- a/core/commands/mount_windows.go +++ b/core/commands/mount_windows.go @@ -13,8 +13,8 @@ var MountCmd = &cmds.Command{ ShortDescription: "Not yet implemented on Windows. :(", }, - Run: func(req cmds.Request) (interface{}, error) { - return errors.New("Mount isn't compatible with Windows yet"), nil + Run: func(req cmds.Request, res cmds.Response) { + res.SetError(errors.New("Mount isn't compatible with Windows yet"), cmds.ErrNormal) }, } diff --git a/core/commands/object.go b/core/commands/object.go index e977698b5..dd09306ed 100644 --- a/core/commands/object.go +++ b/core/commands/object.go @@ -71,14 +71,20 @@ output is the raw data of the object. Arguments: []cmds.Argument{ cmds.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } key := req.Arguments()[0] - return objectData(n, key) + output, err := objectData(n, key) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) }, } @@ -95,14 +101,20 @@ multihash. Arguments: []cmds.Argument{ cmds.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } key := req.Arguments()[0] - return objectLinks(n, key) + output, err := objectLinks(n, key) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { @@ -137,17 +149,19 @@ This command outputs data in the following encodings: Arguments: []cmds.Argument{ cmds.StringArg("key", true, false, "Key of the object to retrieve (in base58-encoded multihash format)").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } key := req.Arguments()[0] object, err := objectGet(n, key) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } node := &Node{ @@ -163,7 +177,7 @@ This command outputs data in the following encodings: } } - return node, nil + res.SetOutput(node) }, Type: Node{}, Marshalers: cmds.MarshalerMap{ @@ -201,25 +215,28 @@ var objectStatCmd = &cmds.Command{ Arguments: []cmds.Argument{ cmds.StringArg("key", true, false, "Key of the object to retrieve (in base58-encoded multihash format)").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } key := req.Arguments()[0] object, err := objectGet(n, key) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } ns, err := object.Stat() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return ns, nil + res.SetOutput(ns) }, Type: dag.NodeStat{}, Marshalers: cmds.MarshalerMap{ @@ -263,15 +280,17 @@ Data should be in the format specified by . cmds.FileArg("data", true, false, "Data to be stored as a DAG object"), cmds.StringArg("encoding", true, false, "Encoding type of , either \"protobuf\" or \"json\""), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } input, err := req.Files().NextFile() if err != nil && err != io.EOF { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } encoding := req.Arguments()[0] @@ -282,10 +301,11 @@ Data should be in the format specified by . if err == ErrUnknownObjectEnc { errType = cmds.ErrClient } - return nil, cmds.Error{err.Error(), errType} + res.SetError(err, errType) + return } - return output, nil + res.SetOutput(output) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { diff --git a/core/commands/pin.go b/core/commands/pin.go index bd6c5589b..9b27f6e66 100644 --- a/core/commands/pin.go +++ b/core/commands/pin.go @@ -42,16 +42,18 @@ on disk. cmds.BoolOption("recursive", "r", "Recursively pin the object linked to by the specified object(s)"), }, Type: PinOutput{}, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } // set recursive flag recursive, found, err := req.Option("recursive").Bool() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if !found { recursive = false @@ -59,10 +61,11 @@ on disk. added, err := corerepo.Pin(n, req.Arguments(), recursive) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return &PinOutput{added}, nil + res.SetOutput(&PinOutput{added}) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { @@ -104,16 +107,18 @@ collected if needed. cmds.BoolOption("recursive", "r", "Recursively unpin the object linked to by the specified object(s)"), }, Type: PinOutput{}, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } // set recursive flag recursive, found, err := req.Option("recursive").Bool() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if !found { recursive = false // default @@ -121,10 +126,11 @@ collected if needed. removed, err := corerepo.Unpin(n, req.Arguments(), recursive) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return &PinOutput{removed}, nil + res.SetOutput(&PinOutput{removed}) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { @@ -165,15 +171,17 @@ Use --type= to specify the type of pinned keys to list. Valid values are: 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) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } typeStr, found, err := req.Option("type").String() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if !found { typeStr = "direct" @@ -182,7 +190,8 @@ Use --type= to specify the type of pinned keys to list. Valid values are: switch typeStr { case "all", "direct", "indirect", "recursive": default: - return nil, cmds.ClientError("Invalid type '" + typeStr + "', must be one of {direct, indirect, recursive, all}") + err = fmt.Errorf("Invalid type '%s', must be one of {direct, indirect, recursive, all}", typeStr) + res.SetError(err, cmds.ErrClient) } keys := make([]u.Key, 0) @@ -196,7 +205,7 @@ Use --type= to specify the type of pinned keys to list. Valid values are: keys = append(keys, n.Pinning.RecursiveKeys()...) } - return &KeyList{Keys: keys}, nil + res.SetOutput(&KeyList{Keys: keys}) }, Type: KeyList{}, Marshalers: cmds.MarshalerMap{ diff --git a/core/commands/ping.go b/core/commands/ping.go index d14beb8f0..73636db92 100644 --- a/core/commands/ping.go +++ b/core/commands/ping.go @@ -74,21 +74,24 @@ trip latency information. }, nil }, }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { ctx := req.Context().Context n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } // Must be online! if !n.OnlineMode() { - return nil, errNotOnline + res.SetError(errNotOnline, cmds.ErrClient) + return } addr, peerID, err := ParsePeerParam(req.Arguments()[0]) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if addr != nil { @@ -99,14 +102,15 @@ trip latency information. numPings := 10 val, found, err := req.Option("count").Int() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if found { numPings = val } outChan := pingPeer(ctx, n, peerID, numPings) - return outChan, nil + res.SetOutput(outChan) }, Type: PingResult{}, } diff --git a/core/commands/publish.go b/core/commands/publish.go index 4304ef1e8..9b4d4014f 100644 --- a/core/commands/publish.go +++ b/core/commands/publish.go @@ -46,21 +46,23 @@ Publish a to another public key: cmds.StringArg("name", false, false, "The IPNS name to publish to. Defaults to your node's peerID"), cmds.StringArg("ipfs-path", true, false, "IPFS path of the obejct to be published at ").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { log.Debug("Begin Publish") n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } args := req.Arguments() if n.PeerHost == nil { - return nil, errNotOnline + res.SetError(errNotOnline, cmds.ErrClient) } if n.Identity == "" { - return nil, errors.New("Identity not loaded!") + res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal) + return } // name := "" @@ -70,14 +72,19 @@ Publish a to another public key: case 2: // name = args[0] ref = args[1] - return nil, errors.New("keychains not yet implemented") + res.SetError(errors.New("keychains not yet implemented"), cmds.ErrNormal) case 1: // name = n.Identity.ID.String() ref = args[0] } // TODO n.Keychain.Get(name).PrivKey - return publish(n, n.PrivateKey, ref) + output, err := publish(n, n.PrivateKey, ref) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { diff --git a/core/commands/refs.go b/core/commands/refs.go index 3aa38d2dd..36f5db026 100644 --- a/core/commands/refs.go +++ b/core/commands/refs.go @@ -53,36 +53,42 @@ Note: list all refs recursively with -r. cmds.BoolOption("unique", "u", "Omit duplicate refs from output"), cmds.BoolOption("recursive", "r", "Recursively list links of child nodes"), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { ctx := req.Context().Context n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } unique, _, err := req.Option("unique").Bool() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } recursive, _, err := req.Option("recursive").Bool() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } edges, _, err := req.Option("edges").Bool() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } format, _, err := req.Option("format").String() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } objs, err := objectsForPaths(n, req.Arguments()) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } piper, pipew := io.Pipe() @@ -110,7 +116,7 @@ Note: list all refs recursively with -r. } }() - return eptr, nil + res.SetOutput(eptr) }, } @@ -122,17 +128,19 @@ Displays the hashes of all local objects. `, }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { ctx := req.Context().Context n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } // todo: make async allKeys, err := n.Blockstore.AllKeysChan(ctx, 0, 0) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } piper, pipew := io.Pipe() @@ -151,7 +159,7 @@ Displays the hashes of all local objects. } }() - return eptr, nil + res.SetOutput(eptr) }, } diff --git a/core/commands/repo.go b/core/commands/repo.go index 1d9a43b77..eae7a77ec 100644 --- a/core/commands/repo.go +++ b/core/commands/repo.go @@ -36,26 +36,28 @@ order to reclaim hard disk space. Options: []cmds.Option{ cmds.BoolOption("quiet", "q", "Write minimal output"), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } gcOutChan, err := corerepo.GarbageCollectBlockstore(n, req.Context().Context) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } outChan := make(chan interface{}) + res.SetOutput((<-chan interface{})(outChan)) + go func() { defer close(outChan) for k := range gcOutChan { outChan <- k } }() - - return outChan, nil }, Type: corerepo.KeyRemoved{}, Marshalers: cmds.MarshalerMap{ diff --git a/core/commands/resolve.go b/core/commands/resolve.go index d76cf8f8f..cb4703e11 100644 --- a/core/commands/resolve.go +++ b/core/commands/resolve.go @@ -40,22 +40,25 @@ Resolve te value of another name: Arguments: []cmds.Argument{ cmds.StringArg("name", false, false, "The IPNS name to resolve. Defaults to your node's peerID.").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } var name string if n.PeerHost == nil { - return nil, errNotOnline + res.SetError(errNotOnline, cmds.ErrClient) + return } if len(req.Arguments()) == 0 { if n.Identity == "" { - return nil, errors.New("Identity not loaded!") + res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal) + return } name = n.Identity.Pretty() @@ -65,12 +68,13 @@ Resolve te value of another name: output, err := n.Namesys.Resolve(name) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } // TODO: better errors (in the case of not finding the name, we get "failed to find any peer in table") - return output, nil + res.SetOutput(output) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { diff --git a/core/commands/swarm.go b/core/commands/swarm.go index 4fc53022d..1e97a30f9 100644 --- a/core/commands/swarm.go +++ b/core/commands/swarm.go @@ -45,16 +45,18 @@ var swarmPeersCmd = &cmds.Command{ ipfs swarm peers lists the set of peers this node is connected to. `, }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { log.Debug("ipfs swarm peers") n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } if n.PeerHost == nil { - return nil, errNotOnline + res.SetError(errNotOnline, cmds.ErrClient) + return } conns := n.PeerHost.Network().Conns() @@ -66,7 +68,7 @@ ipfs swarm peers lists the set of peers this node is connected to. } sort.Sort(sort.StringSlice(addrs)) - return &stringList{addrs}, nil + res.SetOutput(&stringList{addrs}) }, Marshalers: cmds.MarshalerMap{ cmds.Text: stringListMarshaler, @@ -87,24 +89,27 @@ ipfs swarm connect /ip4/104.131.131.82/tcp/4001/QmaCpDMGvV2BGHeYERUEnRQAwe3N8Szb Arguments: []cmds.Argument{ cmds.StringArg("address", true, true, "address of peer to connect to").EnableStdin(), }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { ctx := context.TODO() log.Debug("ipfs swarm connect") n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } addrs := req.Arguments() if n.PeerHost == nil { - return nil, errNotOnline + res.SetError(errNotOnline, cmds.ErrClient) + return } peers, err := peersWithAddresses(n.Peerstore, addrs) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } output := make([]string, len(peers)) @@ -119,7 +124,7 @@ ipfs swarm connect /ip4/104.131.131.82/tcp/4001/QmaCpDMGvV2BGHeYERUEnRQAwe3N8Szb } } - return &stringList{output}, nil + res.SetOutput(&stringList{output}) }, Marshalers: cmds.MarshalerMap{ cmds.Text: stringListMarshaler, diff --git a/core/commands/update.go b/core/commands/update.go index bc4fd40c7..715ac6354 100644 --- a/core/commands/update.go +++ b/core/commands/update.go @@ -22,12 +22,19 @@ var UpdateCmd = &cmds.Command{ ShortDescription: "ipfs update is a utility command used to check for updates and apply them.", }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return updateApply(n) + + output, err := updateApply(n) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) }, Type: UpdateOutput{}, Subcommands: map[string]*cmds.Command{ @@ -55,12 +62,19 @@ var UpdateCheckCmd = &cmds.Command{ ShortDescription: "'ipfs update check' checks if any updates are available for IPFS.\nNothing will be downloaded or installed.", }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return updateCheck(n) + + output, err := updateCheck(n) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) }, Type: UpdateOutput{}, Marshalers: cmds.MarshalerMap{ @@ -84,12 +98,19 @@ var UpdateLogCmd = &cmds.Command{ ShortDescription: "This command is not yet implemented.", }, - Run: func(req cmds.Request) (interface{}, error) { + Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } - return updateLog(n) + + output, err := updateLog(n) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) }, } diff --git a/core/commands/version.go b/core/commands/version.go index 9ed89d315..d142c4ea2 100644 --- a/core/commands/version.go +++ b/core/commands/version.go @@ -22,10 +22,10 @@ var VersionCmd = &cmds.Command{ Options: []cmds.Option{ cmds.BoolOption("number", "n", "Only show the version number"), }, - Run: func(req cmds.Request) (interface{}, error) { - return &VersionOutput{ + Run: func(req cmds.Request, res cmds.Response) { + res.SetOutput(&VersionOutput{ Version: config.CurrentVersionNumber, - }, nil + }) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { From 6adebfad11d452226def4ffdd9cfbd4bbdfb7d8a Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Tue, 20 Jan 2015 19:03:03 -0800 Subject: [PATCH 05/16] commands: Added Length field to Response squash! commands: Added Length field to Response commands/http: client: Fixed error on unset length --- commands/http/client.go | 12 +++++++++++- commands/http/handler.go | 6 ++++++ commands/response.go | 21 +++++++++++++++++---- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/commands/http/client.go b/commands/http/client.go index 6f4015a30..ba7c5b764 100644 --- a/commands/http/client.go +++ b/commands/http/client.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "reflect" + "strconv" "strings" cmds "github.com/jbenet/go-ipfs/commands" @@ -137,9 +138,18 @@ func getResponse(httpRes *http.Response, req cmds.Request) (cmds.Response, error var err error res := cmds.NewResponse(req) - contentType := httpRes.Header["Content-Type"][0] + contentType := httpRes.Header.Get(contentTypeHeader) contentType = strings.Split(contentType, ";")[0] + lengthHeader := httpRes.Header.Get(contentLengthHeader) + if len(lengthHeader) > 0 { + length, err := strconv.ParseUint(lengthHeader, 10, 64) + if err != nil { + return nil, err + } + res.SetLength(length) + } + if len(httpRes.Header.Get(streamHeader)) > 0 { // if output is a stream, we can just use the body reader res.SetOutput(httpRes.Body) diff --git a/commands/http/handler.go b/commands/http/handler.go index cfca7500d..c16541504 100644 --- a/commands/http/handler.go +++ b/commands/http/handler.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "strconv" context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" @@ -92,6 +93,11 @@ func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set(contentTypeHeader, mime) } + // set the Content-Length from the response length + if res.Length() > 0 { + w.Header().Set(contentLengthHeader, strconv.FormatUint(res.Length(), 10)) + } + // if response contains an error, write an HTTP error status code if e := res.Error(); e != nil { if e.Code == cmds.ErrClient { diff --git a/commands/response.go b/commands/response.go index e727b574f..71d49b69b 100644 --- a/commands/response.go +++ b/commands/response.go @@ -95,6 +95,10 @@ type Response interface { SetOutput(interface{}) Output() interface{} + // Sets/Returns the length of the output + SetLength(uint64) + Length() uint64 + // Marshal marshals out the response into a buffer. It uses the EncodingType // on the Request to chose a Marshaler (Codec). Marshal() (io.Reader, error) @@ -104,10 +108,11 @@ type Response interface { } type response struct { - req Request - err *Error - value interface{} - out io.Reader + req Request + err *Error + value interface{} + out io.Reader + length uint64 } func (r *response) Request() Request { @@ -122,6 +127,14 @@ func (r *response) SetOutput(v interface{}) { r.value = v } +func (r *response) Length() uint64 { + return r.length +} + +func (r *response) SetLength(l uint64) { + r.length = l +} + func (r *response) Error() *Error { return r.err } From 487ef33e677f7fa0ccb775a84660076d8c0ba3f3 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Tue, 20 Jan 2015 21:31:05 -0800 Subject: [PATCH 06/16] core/commands: Added progress bar to 'cat' squash! core/commands: Added progress bar to 'cat' Vendored progress bar lib --- Godeps/Godeps.json | 4 + .../src/github.com/cheggaaa/pb/LICENSE | 12 + .../src/github.com/cheggaaa/pb/README.md | 98 +++++ .../cheggaaa/pb/example/copy/copy.go | 81 +++++ .../src/github.com/cheggaaa/pb/example/pb.go | 30 ++ .../src/github.com/cheggaaa/pb/format.go | 42 +++ .../src/github.com/cheggaaa/pb/format_test.go | 37 ++ .../src/github.com/cheggaaa/pb/pb.go | 341 ++++++++++++++++++ .../src/github.com/cheggaaa/pb/pb_nix.go | 7 + .../src/github.com/cheggaaa/pb/pb_solaris.go | 5 + .../src/github.com/cheggaaa/pb/pb_test.go | 30 ++ .../src/github.com/cheggaaa/pb/pb_win.go | 16 + .../src/github.com/cheggaaa/pb/pb_x.go | 46 +++ .../src/github.com/cheggaaa/pb/reader.go | 17 + core/commands/cat.go | 37 +- 15 files changed, 798 insertions(+), 5 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/LICENSE create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/README.md create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/example/copy/copy.go create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/example/pb.go create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/format.go create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/format_test.go create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/pb.go create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/pb_nix.go create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/pb_solaris.go create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/pb_test.go create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/pb_win.go create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/pb_x.go create mode 100644 Godeps/_workspace/src/github.com/cheggaaa/pb/reader.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 3c767116d..d90d2af06 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -68,6 +68,10 @@ "ImportPath": "github.com/cenkalti/backoff", "Rev": "9831e1e25c874e0a0601b6dc43641071414eec7a" }, + { + "ImportPath": "github.com/cheggaaa/pb", + "Rev": "e8c7cc515bfde3e267957a3b110080ceed51354e" + }, { "ImportPath": "github.com/coreos/go-semver/semver", "Rev": "6fe83ccda8fb9b7549c9ab4ba47f47858bc950aa" diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/LICENSE b/Godeps/_workspace/src/github.com/cheggaaa/pb/LICENSE new file mode 100644 index 000000000..13ef3fe53 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2012, Sergey Cherepanov +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/README.md b/Godeps/_workspace/src/github.com/cheggaaa/pb/README.md new file mode 100644 index 000000000..9a6f6b41f --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/README.md @@ -0,0 +1,98 @@ +## Terminal progress bar for Go + +Simple progress bar for console programms. + + +### Installation +``` +go get github.com/cheggaaa/pb +``` + +### Usage +```Go +package main + +import ( + "github.com/cheggaaa/pb" + "time" +) + +func main() { + count := 100000 + bar := pb.StartNew(count) + for i := 0; i < count; i++ { + bar.Increment() + time.Sleep(time.Millisecond) + } + bar.FinishPrint("The End!") +} +``` +Result will be like this: +``` +> go run test.go +37158 / 100000 [================>_______________________________] 37.16% 1m11s +``` + + +More functions? +```Go +// create bar +bar := pb.New(count) + +// refresh info every second (default 200ms) +bar.SetRefreshRate(time.Second) + +// show percents (by default already true) +bar.ShowPercent = true + +// show bar (by default already true) +bar.ShowBar = true + +// no need counters +bar.ShowCounters = false + +// show "time left" +bar.ShowTimeLeft = true + +// show average speed +bar.ShowSpeed = true + +// sets the width of the progress bar +bar.SetWith(80) + +// sets the width of the progress bar, but if terminal size smaller will be ignored +bar.SetMaxWith(80) + +// convert output to readable format (like KB, MB) +bar.SetUnits(pb.U_BYTES) + +// and start +bar.Start() +``` + +Want handle progress of io operations? +```Go +// create and start bar +bar := pb.New(myDataLen).SetUnits(pb.U_BYTES) +bar.Start() + +// my io.Reader +r := myReader + +// my io.Writer +w := myWriter + +// create multi writer +writer := io.MultiWriter(w, bar) + +// and copy +io.Copy(writer, r) + +// show example/copy/copy.go for advanced example + +``` + +Not like the looks? +```Go +bar.Format("<.- >") +``` diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/example/copy/copy.go b/Godeps/_workspace/src/github.com/cheggaaa/pb/example/copy/copy.go new file mode 100644 index 000000000..0c83f44cb --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/example/copy/copy.go @@ -0,0 +1,81 @@ +package main + +import ( + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/cheggaaa/pb" + "os" + "fmt" + "io" + "time" + "strings" + "net/http" + "strconv" +) + +func main() { + // check args + if len(os.Args) < 3 { + printUsage() + return + } + sourceName, destName := os.Args[1], os.Args[2] + + // check source + var source io.Reader + var sourceSize int64 + if strings.HasPrefix(sourceName, "http://") { + // open as url + resp, err := http.Get(sourceName) + if err != nil { + fmt.Printf("Can't get %s: %v\n", sourceName, err) + return + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + fmt.Printf("Server return non-200 status: %v\n", resp.Status) + return + } + i, _ := strconv.Atoi(resp.Header.Get("Content-Length")) + sourceSize = int64(i) + source = resp.Body + } else { + // open as file + s, err := os.Open(sourceName) + if err != nil { + fmt.Printf("Can't open %s: %v\n", sourceName, err) + return + } + defer s.Close() + // get source size + sourceStat, err := s.Stat() + if err != nil { + fmt.Printf("Can't stat %s: %v\n", sourceName, err) + return + } + sourceSize = sourceStat.Size() + source = s + } + + // create dest + dest, err := os.Create(destName) + if err != nil { + fmt.Printf("Can't create %s: %v\n", destName, err) + return + } + defer dest.Close() + + // create bar + bar := pb.New(int(sourceSize)).SetUnits(pb.U_BYTES).SetRefreshRate(time.Millisecond * 10) + bar.ShowSpeed = true + bar.Start() + + // create multi writer + writer := io.MultiWriter(dest, bar) + + // and copy + io.Copy(writer, source) + bar.Finish() +} + +func printUsage() { + fmt.Println("copy [source file or url] [dest file]") +} diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/example/pb.go b/Godeps/_workspace/src/github.com/cheggaaa/pb/example/pb.go new file mode 100644 index 000000000..8652efeef --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/example/pb.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/cheggaaa/pb" + "time" +) + +func main() { + count := 5000 + bar := pb.New(count) + + // show percents (by default already true) + bar.ShowPercent = true + + // show bar (by default already true) + bar.ShowPercent = true + + // no need counters + bar.ShowCounters = true + + bar.ShowTimeLeft = true + + // and start + bar.Start() + for i := 0; i < count; i++ { + bar.Increment() + time.Sleep(time.Millisecond) + } + bar.FinishPrint("The End!") +} diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/format.go b/Godeps/_workspace/src/github.com/cheggaaa/pb/format.go new file mode 100644 index 000000000..e024e36d4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/format.go @@ -0,0 +1,42 @@ +package pb + +import ( + "fmt" + "strconv" + "strings" +) + +const ( + // By default, without type handle + U_NO = 0 + // Handle as b, Kb, Mb, etc + U_BYTES = 1 +) + +// Format integer +func Format(i int64, units int) string { + switch units { + case U_BYTES: + return FormatBytes(i) + } + // by default just convert to string + return strconv.Itoa(int(i)) +} + +// Convert bytes to human readable string. Like a 2 MB, 64.2 KB, 52 B +func FormatBytes(i int64) (result string) { + switch { + case i > (1024 * 1024 * 1024 * 1024): + result = fmt.Sprintf("%#.02f TB", float64(i)/1024/1024/1024/1024) + case i > (1024 * 1024 * 1024): + result = fmt.Sprintf("%#.02f GB", float64(i)/1024/1024/1024) + case i > (1024 * 1024): + result = fmt.Sprintf("%#.02f MB", float64(i)/1024/1024) + case i > 1024: + result = fmt.Sprintf("%#.02f KB", float64(i)/1024) + default: + result = fmt.Sprintf("%d B", i) + } + result = strings.Trim(result, " ") + return +} diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/format_test.go b/Godeps/_workspace/src/github.com/cheggaaa/pb/format_test.go new file mode 100644 index 000000000..b76275e29 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/format_test.go @@ -0,0 +1,37 @@ +package pb + +import ( + "fmt" + "strconv" + "testing" +) + +func Test_DefaultsToInteger(t *testing.T) { + value := int64(1000) + expected := strconv.Itoa(int(value)) + actual := Format(value, -1) + + if actual != expected { + t.Error(fmt.Sprintf("Expected {%s} was {%s}", expected, actual)) + } +} + +func Test_CanFormatAsInteger(t *testing.T) { + value := int64(1000) + expected := strconv.Itoa(int(value)) + actual := Format(value, U_NO) + + if actual != expected { + t.Error(fmt.Sprintf("Expected {%s} was {%s}", expected, actual)) + } +} + +func Test_CanFormatAsBytes(t *testing.T) { + value := int64(1000) + expected := "1000 B" + actual := Format(value, U_BYTES) + + if actual != expected { + t.Error(fmt.Sprintf("Expected {%s} was {%s}", expected, actual)) + } +} diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/pb.go b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb.go new file mode 100644 index 000000000..01c715720 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb.go @@ -0,0 +1,341 @@ +package pb + +import ( + "fmt" + "io" + "math" + "strings" + "sync/atomic" + "time" +) + +const ( + // Default refresh rate - 200ms + DEFAULT_REFRESH_RATE = time.Millisecond * 200 + FORMAT = "[=>-]" +) + +// DEPRECATED +// variables for backward compatibility, from now do not work +// use pb.Format and pb.SetRefreshRate +var ( + DefaultRefreshRate = DEFAULT_REFRESH_RATE + BarStart, BarEnd, Empty, Current, CurrentN string +) + +// Create new progress bar object +func New(total int) (pb *ProgressBar) { + return New64(int64(total)) +} + +// Create new progress bar object uding int64 as total +func New64(total int64) (pb *ProgressBar) { + pb = &ProgressBar{ + Total: total, + RefreshRate: DEFAULT_REFRESH_RATE, + ShowPercent: true, + ShowCounters: true, + ShowBar: true, + ShowTimeLeft: true, + ShowFinalTime: true, + ManualUpdate: false, + currentValue: -1, + } + pb.Format(FORMAT) + return +} + +// Create new object and start +func StartNew(total int) (pb *ProgressBar) { + pb = New(total) + pb.Start() + return +} + +// Callback for custom output +// For example: +// bar.Callback = func(s string) { +// mySuperPrint(s) +// } +// +type Callback func(out string) + +type ProgressBar struct { + current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278) + + Total int64 + RefreshRate time.Duration + ShowPercent, ShowCounters bool + ShowSpeed, ShowTimeLeft, ShowBar bool + ShowFinalTime bool + Output io.Writer + Callback Callback + NotPrint bool + Units int + Width int + ForceWidth bool + ManualUpdate bool + + isFinish bool + startTime time.Time + currentValue int64 + + prefix, postfix string + + BarStart string + BarEnd string + Empty string + Current string + CurrentN string +} + +// Start print +func (pb *ProgressBar) Start() { + pb.startTime = time.Now() + if pb.Total == 0 { + pb.ShowBar = false + pb.ShowTimeLeft = false + pb.ShowPercent = false + } + if !pb.ManualUpdate { + go pb.writer() + } +} + +// Increment current value +func (pb *ProgressBar) Increment() int { + return pb.Add(1) +} + +// Set current value +func (pb *ProgressBar) Set(current int) { + atomic.StoreInt64(&pb.current, int64(current)) +} + +// Add to current value +func (pb *ProgressBar) Add(add int) int { + return int(pb.Add64(int64(add))) +} + +func (pb *ProgressBar) Add64(add int64) int64 { + return atomic.AddInt64(&pb.current, add) +} + +// Set prefix string +func (pb *ProgressBar) Prefix(prefix string) (bar *ProgressBar) { + pb.prefix = prefix + return pb +} + +// Set postfix string +func (pb *ProgressBar) Postfix(postfix string) (bar *ProgressBar) { + pb.postfix = postfix + return pb +} + +// Set custom format for bar +// Example: bar.Format("[=>_]") +func (pb *ProgressBar) Format(format string) (bar *ProgressBar) { + bar = pb + formatEntries := strings.Split(format, "") + if len(formatEntries) != 5 { + return + } + pb.BarStart = formatEntries[0] + pb.BarEnd = formatEntries[4] + pb.Empty = formatEntries[3] + pb.Current = formatEntries[1] + pb.CurrentN = formatEntries[2] + return +} + +// Set bar refresh rate +func (pb *ProgressBar) SetRefreshRate(rate time.Duration) (bar *ProgressBar) { + bar = pb + pb.RefreshRate = rate + return +} + +// Set units +// bar.SetUnits(U_NO) - by default +// bar.SetUnits(U_BYTES) - for Mb, Kb, etc +func (pb *ProgressBar) SetUnits(units int) (bar *ProgressBar) { + bar = pb + switch units { + case U_NO, U_BYTES: + pb.Units = units + } + return +} + +// Set max width, if width is bigger than terminal width, will be ignored +func (pb *ProgressBar) SetMaxWidth(width int) (bar *ProgressBar) { + bar = pb + pb.Width = width + pb.ForceWidth = false + return +} + +// Set bar width +func (pb *ProgressBar) SetWidth(width int) (bar *ProgressBar) { + bar = pb + pb.Width = width + pb.ForceWidth = true + return +} + +// End print +func (pb *ProgressBar) Finish() { + pb.isFinish = true + pb.write(atomic.LoadInt64(&pb.current)) + if !pb.NotPrint { + fmt.Println() + } +} + +// End print and write string 'str' +func (pb *ProgressBar) FinishPrint(str string) { + pb.Finish() + fmt.Println(str) +} + +// implement io.Writer +func (pb *ProgressBar) Write(p []byte) (n int, err error) { + n = len(p) + pb.Add(n) + return +} + +// implement io.Reader +func (pb *ProgressBar) Read(p []byte) (n int, err error) { + n = len(p) + pb.Add(n) + return +} + +// Create new proxy reader over bar +func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { + return &Reader{r, pb} +} + +func (pb *ProgressBar) write(current int64) { + width := pb.getWidth() + + var percentBox, countersBox, timeLeftBox, speedBox, barBox, end, out string + + // percents + if pb.ShowPercent { + percent := float64(current) / (float64(pb.Total) / float64(100)) + percentBox = fmt.Sprintf(" %#.02f %% ", percent) + } + + // counters + if pb.ShowCounters { + if pb.Total > 0 { + countersBox = fmt.Sprintf("%s / %s ", Format(current, pb.Units), Format(pb.Total, pb.Units)) + } else { + countersBox = Format(current, pb.Units) + " " + } + } + + // time left + fromStart := time.Now().Sub(pb.startTime) + if pb.isFinish { + if pb.ShowFinalTime { + left := (fromStart / time.Second) * time.Second + timeLeftBox = left.String() + } + } else if pb.ShowTimeLeft && current > 0 { + perEntry := fromStart / time.Duration(current) + left := time.Duration(pb.Total-current) * perEntry + left = (left / time.Second) * time.Second + timeLeftBox = left.String() + } + + // speed + if pb.ShowSpeed && current > 0 { + fromStart := time.Now().Sub(pb.startTime) + speed := float64(current) / (float64(fromStart) / float64(time.Second)) + speedBox = Format(int64(speed), pb.Units) + "/s " + } + + // bar + if pb.ShowBar { + size := width - len(countersBox+pb.BarStart+pb.BarEnd+percentBox+timeLeftBox+speedBox+pb.prefix+pb.postfix) + if size > 0 { + curCount := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size))) + emptCount := size - curCount + barBox = pb.BarStart + if emptCount < 0 { + emptCount = 0 + } + if curCount > size { + curCount = size + } + if emptCount <= 0 { + barBox += strings.Repeat(pb.Current, curCount) + } else if curCount > 0 { + barBox += strings.Repeat(pb.Current, curCount-1) + pb.CurrentN + } + + barBox += strings.Repeat(pb.Empty, emptCount) + pb.BarEnd + } + } + + // check len + out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix + if len(out) < width { + end = strings.Repeat(" ", width-len(out)) + } + + // and print! + switch { + case pb.Output != nil: + fmt.Fprint(pb.Output, "\r"+out+end) + case pb.Callback != nil: + pb.Callback(out + end) + case !pb.NotPrint: + fmt.Print("\r" + out + end) + } +} + +func (pb *ProgressBar) getWidth() int { + if pb.ForceWidth { + return pb.Width + } + + width := pb.Width + termWidth, _ := terminalWidth() + if width == 0 || termWidth <= width { + width = termWidth + } + + return width +} + +// Write the current state of the progressbar +func (pb *ProgressBar) Update() { + c := atomic.LoadInt64(&pb.current) + if c != pb.currentValue { + pb.write(c) + pb.currentValue = c + } +} + +// Internal loop for writing progressbar +func (pb *ProgressBar) writer() { + for { + if pb.isFinish { + break + } + pb.Update() + time.Sleep(pb.RefreshRate) + } +} + +type window struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 +} diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_nix.go b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_nix.go new file mode 100644 index 000000000..75f9ba314 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_nix.go @@ -0,0 +1,7 @@ +// +build linux darwin freebsd openbsd + +package pb + +import "syscall" + +const sys_ioctl = syscall.SYS_IOCTL diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_solaris.go b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_solaris.go new file mode 100644 index 000000000..ac31110da --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_solaris.go @@ -0,0 +1,5 @@ +// +build solaris + +package pb + +const sys_ioctl = 54 \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_test.go b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_test.go new file mode 100644 index 000000000..75783d630 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_test.go @@ -0,0 +1,30 @@ +package pb + +import ( + "testing" +) + +func Test_IncrementAddsOne(t *testing.T) { + count := 5000 + bar := New(count) + expected := 1 + actual := bar.Increment() + + if actual != expected { + t.Errorf("Expected {%d} was {%d}", expected, actual) + } +} + +func Test_Width(t *testing.T) { + count := 5000 + bar := New(count) + width := 100 + bar.SetWidth(100).Callback = func(out string) { + if len(out) != width { + t.Errorf("Bar width expected {%d} was {%d}", len(out), width) + } + } + bar.Start() + bar.Increment() + bar.Finish() +} diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_win.go b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_win.go new file mode 100644 index 000000000..eb36190cc --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_win.go @@ -0,0 +1,16 @@ +// +build windows + +package pb + +import ( + "github.com/olekukonko/ts" +) + +func bold(str string) string { + return str +} + +func terminalWidth() (int, error) { + size, err := ts.GetSize() + return size.Col(), err +} diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_x.go b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_x.go new file mode 100644 index 000000000..b630e592f --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/pb_x.go @@ -0,0 +1,46 @@ +// +build linux darwin freebsd openbsd solaris + +package pb + +import ( + "os" + "runtime" + "syscall" + "unsafe" +) + +const ( + TIOCGWINSZ = 0x5413 + TIOCGWINSZ_OSX = 1074295912 +) + +var tty *os.File + +func init() { + var err error + tty, err = os.Open("/dev/tty") + if err != nil { + tty = os.Stdin + } +} + +func bold(str string) string { + return "\033[1m" + str + "\033[0m" +} + +func terminalWidth() (int, error) { + w := new(window) + tio := syscall.TIOCGWINSZ + if runtime.GOOS == "darwin" { + tio = TIOCGWINSZ_OSX + } + res, _, err := syscall.Syscall(sys_ioctl, + tty.Fd(), + uintptr(tio), + uintptr(unsafe.Pointer(w)), + ) + if int(res) == -1 { + return 0, err + } + return int(w.Col), nil +} diff --git a/Godeps/_workspace/src/github.com/cheggaaa/pb/reader.go b/Godeps/_workspace/src/github.com/cheggaaa/pb/reader.go new file mode 100644 index 000000000..beb3d1994 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cheggaaa/pb/reader.go @@ -0,0 +1,17 @@ +package pb + +import ( + "io" +) + +// It's proxy reader, implement io.Reader +type Reader struct { + io.Reader + bar *ProgressBar +} + +func (r *Reader) Read(p []byte) (n int, err error) { + n, err = r.Reader.Read(p) + r.bar.Add(n) + return +} \ No newline at end of file diff --git a/core/commands/cat.go b/core/commands/cat.go index 208908937..b1f3df28c 100644 --- a/core/commands/cat.go +++ b/core/commands/cat.go @@ -2,12 +2,17 @@ package commands import ( "io" + "os" cmds "github.com/jbenet/go-ipfs/commands" core "github.com/jbenet/go-ipfs/core" uio "github.com/jbenet/go-ipfs/unixfs/io" + + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/cheggaaa/pb" ) +const progressBarMinSize = 1024 * 1024 * 8 // show progress bar for outputs > 8MiB + var CatCmd = &cmds.Command{ Helptext: cmds.HelpText{ Tagline: "Show IPFS object data", @@ -29,29 +34,51 @@ it contains. readers := make([]io.Reader, 0, len(req.Arguments())) - readers, err = cat(node, req.Arguments()) + readers, length, err := cat(node, req.Arguments()) if err != nil { res.SetError(err, cmds.ErrNormal) return } + res.SetLength(length) + reader := io.MultiReader(readers...) res.SetOutput(reader) }, + PostRun: func(res cmds.Response) { + if res.Length() < progressBarMinSize { + return + } + + bar := pb.New(int(res.Length())).SetUnits(pb.U_BYTES) + bar.Output = os.Stderr + bar.Start() + + reader := bar.NewProxyReader(res.Output().(io.Reader)) + res.SetOutput(reader) + }, } -func cat(node *core.IpfsNode, paths []string) ([]io.Reader, error) { +func cat(node *core.IpfsNode, paths []string) ([]io.Reader, uint64, error) { readers := make([]io.Reader, 0, len(paths)) + length := uint64(0) for _, path := range paths { dagnode, err := node.Resolver.ResolvePath(path) if err != nil { - return nil, err + return nil, 0, err } + + nodeLength, err := dagnode.Size() + if err != nil { + return nil, 0, err + } + length += nodeLength + read, err := uio.NewDagReader(dagnode, node.DAG) if err != nil { - return nil, err + return nil, 0, err } readers = append(readers, read) } - return readers, nil + return readers, length, nil } From 8ca8d6ef71909d80aeb9771ca3790512cfe21482 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Wed, 21 Jan 2015 17:36:37 -0800 Subject: [PATCH 07/16] commands/files: Added PeekFile and StatFile interfaces squash! commands/files: Added PeekFile and StatFile interfaces commands/http: Updated test --- commands/cli/parse.go | 4 ++-- commands/files/file.go | 14 ++++++++++++++ commands/files/file_test.go | 10 +++++----- commands/files/readerfile.go | 24 ++++++++++++++++++------ commands/files/serialfile.go | 9 +++++++-- commands/files/slicefile.go | 25 +++++++++++++++++++------ commands/http/multifilereader_test.go | 14 +++++++------- 7 files changed, 72 insertions(+), 28 deletions(-) diff --git a/commands/cli/parse.go b/commands/cli/parse.go index ece7014fb..49817475e 100644 --- a/commands/cli/parse.go +++ b/commands/cli/parse.go @@ -64,7 +64,7 @@ func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *c } req.SetArguments(stringArgs) - file := &files.SliceFile{"", fileArgs} + file := files.NewSliceFile("", fileArgs) req.SetFiles(file) err = cmd.CheckArguments(req) @@ -298,7 +298,7 @@ func appendFile(args []files.File, inputs []string, argDef *cmds.Argument, recur } func appendStdinAsFile(args []files.File, stdin *os.File) ([]files.File, *os.File) { - arg := &files.ReaderFile{"", stdin} + arg := files.NewReaderFile("", stdin, nil) return append(args, arg), nil } diff --git a/commands/files/file.go b/commands/files/file.go index 9e9b043a1..8f76ea71d 100644 --- a/commands/files/file.go +++ b/commands/files/file.go @@ -3,6 +3,7 @@ package files import ( "errors" "io" + "os" ) var ( @@ -29,3 +30,16 @@ type File interface { // If the file is a regular file (not a directory), NextFile will return a non-nil error. NextFile() (File, error) } + +type StatFile interface { + File + + Stat() os.FileInfo +} + +type PeekFile interface { + File + + Peek(n int) File + Length() int +} diff --git a/commands/files/file_test.go b/commands/files/file_test.go index a9499fb78..01b7a9d02 100644 --- a/commands/files/file_test.go +++ b/commands/files/file_test.go @@ -11,13 +11,13 @@ import ( func TestSliceFiles(t *testing.T) { name := "testname" files := []File{ - &ReaderFile{"file.txt", ioutil.NopCloser(strings.NewReader("Some text!\n"))}, - &ReaderFile{"beep.txt", ioutil.NopCloser(strings.NewReader("beep"))}, - &ReaderFile{"boop.txt", ioutil.NopCloser(strings.NewReader("boop"))}, + NewReaderFile("file.txt", ioutil.NopCloser(strings.NewReader("Some text!\n")), nil), + NewReaderFile("beep.txt", ioutil.NopCloser(strings.NewReader("beep")), nil), + NewReaderFile("boop.txt", ioutil.NopCloser(strings.NewReader("boop")), nil), } buf := make([]byte, 20) - sf := &SliceFile{name, files} + sf := NewSliceFile(name, files) if !sf.IsDirectory() { t.Error("SliceFile should always be a directory") @@ -55,7 +55,7 @@ func TestSliceFiles(t *testing.T) { func TestReaderFiles(t *testing.T) { message := "beep boop" - rf := &ReaderFile{"file.txt", ioutil.NopCloser(strings.NewReader(message))} + rf := NewReaderFile("file.txt", ioutil.NopCloser(strings.NewReader(message)), nil) buf := make([]byte, len(message)) if rf.IsDirectory() { diff --git a/commands/files/readerfile.go b/commands/files/readerfile.go index af88562fd..38c28efe3 100644 --- a/commands/files/readerfile.go +++ b/commands/files/readerfile.go @@ -1,12 +1,20 @@ package files -import "io" +import ( + "io" + "os" +) // ReaderFile is a implementation of File created from an `io.Reader`. // ReaderFiles are never directories, and can be read from and closed. type ReaderFile struct { - Filename string - Reader io.ReadCloser + filename string + reader io.ReadCloser + stat os.FileInfo +} + +func NewReaderFile(filename string, reader io.ReadCloser, stat os.FileInfo) *ReaderFile { + return &ReaderFile{filename, reader, stat} } func (f *ReaderFile) IsDirectory() bool { @@ -18,13 +26,17 @@ func (f *ReaderFile) NextFile() (File, error) { } func (f *ReaderFile) FileName() string { - return f.Filename + return f.filename } func (f *ReaderFile) Read(p []byte) (int, error) { - return f.Reader.Read(p) + return f.reader.Read(p) } func (f *ReaderFile) Close() error { - return f.Reader.Close() + return f.reader.Close() +} + +func (f *ReaderFile) Stat() os.FileInfo { + return f.stat } diff --git a/commands/files/serialfile.go b/commands/files/serialfile.go index 21f3a9bb9..9a81f8bd1 100644 --- a/commands/files/serialfile.go +++ b/commands/files/serialfile.go @@ -20,6 +20,7 @@ func (es sortFIByName) Less(i, j int) bool { return es[i].Name() < es[j].Name() type serialFile struct { path string files []os.FileInfo + stat os.FileInfo current *os.File } @@ -35,7 +36,7 @@ func NewSerialFile(path string, file *os.File) (File, error) { func newSerialFile(path string, file *os.File, stat os.FileInfo) (File, error) { // for non-directories, return a ReaderFile if !stat.IsDir() { - return &ReaderFile{path, file}, nil + return &ReaderFile{path, file, stat}, nil } // for directories, stat all of the contents first, so we know what files to @@ -55,7 +56,7 @@ func newSerialFile(path string, file *os.File, stat os.FileInfo) (File, error) { // make sure contents are sorted so -- repeatably -- we get the same inputs. sort.Sort(sortFIByName(contents)) - return &serialFile{path, contents, nil}, nil + return &serialFile{path, contents, stat, nil}, nil } func (f *serialFile) IsDirectory() bool { @@ -113,3 +114,7 @@ func (f *serialFile) Close() error { return nil } + +func (f *serialFile) Stat() os.FileInfo { + return f.stat +} diff --git a/commands/files/slicefile.go b/commands/files/slicefile.go index e1035f2ce..fe0332d59 100644 --- a/commands/files/slicefile.go +++ b/commands/files/slicefile.go @@ -6,8 +6,13 @@ import "io" // It contains children files, and is created from a `[]File`. // SliceFiles are always directories, and can't be read from or closed. type SliceFile struct { - Filename string - Files []File + filename string + files []File + n int +} + +func NewSliceFile(filename string, files []File) *SliceFile { + return &SliceFile{filename, files, 0} } func (f *SliceFile) IsDirectory() bool { @@ -15,16 +20,16 @@ func (f *SliceFile) IsDirectory() bool { } func (f *SliceFile) NextFile() (File, error) { - if len(f.Files) == 0 { + if f.n >= len(f.files) { return nil, io.EOF } - file := f.Files[0] - f.Files = f.Files[1:] + file := f.files[f.n] + f.n++ return file, nil } func (f *SliceFile) FileName() string { - return f.Filename + return f.filename } func (f *SliceFile) Read(p []byte) (int, error) { @@ -34,3 +39,11 @@ func (f *SliceFile) Read(p []byte) (int, error) { func (f *SliceFile) Close() error { return ErrNotReader } + +func (f *SliceFile) Peek(n int) File { + return f.files[n] +} + +func (f *SliceFile) Length() int { + return len(f.files) +} diff --git a/commands/http/multifilereader_test.go b/commands/http/multifilereader_test.go index 8d832c9cc..8242e2777 100644 --- a/commands/http/multifilereader_test.go +++ b/commands/http/multifilereader_test.go @@ -13,14 +13,14 @@ import ( func TestOutput(t *testing.T) { text := "Some text! :)" fileset := []files.File{ - &files.ReaderFile{"file.txt", ioutil.NopCloser(strings.NewReader(text))}, - &files.SliceFile{"boop", []files.File{ - &files.ReaderFile{"boop/a.txt", ioutil.NopCloser(strings.NewReader("bleep"))}, - &files.ReaderFile{"boop/b.txt", ioutil.NopCloser(strings.NewReader("bloop"))}, - }}, - &files.ReaderFile{"beep.txt", ioutil.NopCloser(strings.NewReader("beep"))}, + files.NewReaderFile("file.txt", ioutil.NopCloser(strings.NewReader(text)), nil), + files.NewSliceFile("boop", []files.File{ + files.NewReaderFile("boop/a.txt", ioutil.NopCloser(strings.NewReader("bleep")), nil), + files.NewReaderFile("boop/b.txt", ioutil.NopCloser(strings.NewReader("bloop")), nil), + }), + files.NewReaderFile("beep.txt", ioutil.NopCloser(strings.NewReader("beep")), nil), } - sf := &files.SliceFile{"", fileset} + sf := files.NewSliceFile("", fileset) buf := make([]byte, 20) // testing output by reading it with the go stdlib "mime/multipart" Reader From 4a6aec645e028c601f1ddb01a99e86409d670533 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Wed, 21 Jan 2015 17:37:05 -0800 Subject: [PATCH 08/16] commands: request: Added Values map --- commands/request.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/commands/request.go b/commands/request.go index ee3747442..8cceebc46 100644 --- a/commands/request.go +++ b/commands/request.go @@ -77,6 +77,7 @@ type Request interface { Context() *Context SetContext(Context) Command() *Command + Values() map[string]interface{} ConvertOptions() error } @@ -89,6 +90,7 @@ type request struct { cmd *Command ctx Context optionDefs map[string]Option + values map[string]interface{} } // Path returns the command path of this request @@ -208,6 +210,10 @@ var converters = map[reflect.Kind]converter{ }, } +func (r *request) Values() map[string]interface{} { + return r.values +} + func (r *request) ConvertOptions() error { for k, v := range r.options { opt, ok := r.optionDefs[k] @@ -275,7 +281,8 @@ func NewRequest(path []string, opts optMap, args []string, file files.File, cmd } ctx := Context{Context: context.TODO()} - req := &request{path, opts, args, file, cmd, ctx, optDefs} + values := make(map[string]interface{}) + req := &request{path, opts, args, file, cmd, ctx, optDefs, values} err := req.ConvertOptions() if err != nil { return nil, err From c73c4ae55d90a007ceb3b73d5d9ab747dbe631a5 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Wed, 21 Jan 2015 18:38:09 -0800 Subject: [PATCH 09/16] commands/files: Added SizeFile interface --- commands/files/file.go | 8 +++++++- commands/files/readerfile.go | 8 ++++++++ commands/files/serialfile.go | 30 ++++++++++++++++++++++++++++++ commands/files/slicefile.go | 24 +++++++++++++++++++++++- 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/commands/files/file.go b/commands/files/file.go index 8f76ea71d..f1196257d 100644 --- a/commands/files/file.go +++ b/commands/files/file.go @@ -38,8 +38,14 @@ type StatFile interface { } type PeekFile interface { - File + SizeFile Peek(n int) File Length() int } + +type SizeFile interface { + File + + Size() (int64, error) +} diff --git a/commands/files/readerfile.go b/commands/files/readerfile.go index 38c28efe3..741042881 100644 --- a/commands/files/readerfile.go +++ b/commands/files/readerfile.go @@ -1,6 +1,7 @@ package files import ( + "errors" "io" "os" ) @@ -40,3 +41,10 @@ func (f *ReaderFile) Close() error { func (f *ReaderFile) Stat() os.FileInfo { return f.stat } + +func (f *ReaderFile) Size() (int64, error) { + if f.stat == nil { + return 0, errors.New("File size unknown") + } + return f.stat.Size(), nil +} diff --git a/commands/files/serialfile.go b/commands/files/serialfile.go index 9a81f8bd1..aeba01fa7 100644 --- a/commands/files/serialfile.go +++ b/commands/files/serialfile.go @@ -118,3 +118,33 @@ func (f *serialFile) Close() error { func (f *serialFile) Stat() os.FileInfo { return f.stat } + +func (f *serialFile) Size() (int64, error) { + return size(f.stat, f.FileName()) +} + +func size(stat os.FileInfo, filename string) (int64, error) { + if !stat.IsDir() { + return stat.Size(), nil + } + + file, err := os.Open(filename) + if err != nil { + return 0, err + } + files, err := file.Readdir(0) + if err != nil { + return 0, err + } + file.Close() + + var output int64 + for _, child := range files { + s, err := size(child, fp.Join(filename, child.Name())) + if err != nil { + return 0, err + } + output += s + } + return output, nil +} diff --git a/commands/files/slicefile.go b/commands/files/slicefile.go index fe0332d59..f6e204812 100644 --- a/commands/files/slicefile.go +++ b/commands/files/slicefile.go @@ -1,6 +1,9 @@ package files -import "io" +import ( + "errors" + "io" +) // SliceFile implements File, and provides simple directory handling. // It contains children files, and is created from a `[]File`. @@ -47,3 +50,22 @@ func (f *SliceFile) Peek(n int) File { func (f *SliceFile) Length() int { return len(f.files) } + +func (f *SliceFile) Size() (int64, error) { + var size int64 + + for _, file := range f.files { + sizeFile, ok := file.(SizeFile) + if !ok { + return 0, errors.New("Could not get size of child file") + } + + s, err := sizeFile.Size() + if err != nil { + return 0, err + } + size += s + } + + return size, nil +} From c3ea164f64b24ee871e1c30f44938120afac2861 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Wed, 21 Jan 2015 18:38:51 -0800 Subject: [PATCH 10/16] core/commands: Added progress bars for 'add' squash! core/commands: Added progress bars for 'add' Use vendored progress bar lib in 'add' --- core/commands/add.go | 151 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 143 insertions(+), 8 deletions(-) diff --git a/core/commands/add.go b/core/commands/add.go index 0e82e473f..7162d6a26 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -5,7 +5,9 @@ import ( "errors" "fmt" "io" + "os" "path" + "strings" cmds "github.com/jbenet/go-ipfs/commands" files "github.com/jbenet/go-ipfs/commands/files" @@ -16,14 +18,22 @@ import ( pinning "github.com/jbenet/go-ipfs/pin" ft "github.com/jbenet/go-ipfs/unixfs" u "github.com/jbenet/go-ipfs/util" + + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/cheggaaa/pb" ) // Error indicating the max depth has been exceded. var ErrDepthLimitExceeded = fmt.Errorf("depth limit exceeded") +// how many bytes of progress to wait before sending a progress update message +const progressReaderIncrement = 1024 * 256 + +const progressOptionName = "progress" + type AddedObject struct { - Name string - Hash string + Name string + Hash string `json:",omitempty"` + Bytes int64 `json:",omitempty"` } var AddCmd = &cmds.Command{ @@ -43,6 +53,26 @@ remains to be implemented. Options: []cmds.Option{ cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive) cmds.BoolOption("quiet", "q", "Write minimal output"), + cmds.BoolOption(progressOptionName, "p", "Stream progress data"), + }, + PreRun: func(req cmds.Request) error { + req.SetOption(progressOptionName, true) + + sizeFile, ok := req.Files().(files.SizeFile) + if !ok { + // we don't need to error, the progress bar just won't know how big the files are + return nil + } + + size, err := sizeFile.Size() + if err != nil { + // see comment above + return nil + } + log.Debugf("Total size of file being added: %v\n", size) + req.Values()["size"] = size + + return nil }, Run: func(req cmds.Request, res cmds.Response) { n, err := req.Context().GetNode() @@ -51,6 +81,8 @@ remains to be implemented. return } + progress, _, _ := req.Option(progressOptionName).Bool() + outChan := make(chan interface{}) res.SetOutput((<-chan interface{})(outChan)) @@ -63,13 +95,87 @@ remains to be implemented. return } - _, err = addFile(n, file, outChan) + _, err = addFile(n, file, outChan, progress) if err != nil { return } } }() }, + PostRun: func(res cmds.Response) { + outChan, ok := res.Output().(<-chan interface{}) + if !ok { + res.SetError(u.ErrCast(), cmds.ErrNormal) + return + } + + wrapperChan := make(chan interface{}) + res.SetOutput((<-chan interface{})(wrapperChan)) + + size := int64(0) + s, found := res.Request().Values()["size"] + if found { + size = s.(int64) + } + showProgressBar := size >= progressBarMinSize + + var bar *pb.ProgressBar + var terminalWidth int + if showProgressBar { + bar = pb.New64(size).SetUnits(pb.U_BYTES) + bar.ManualUpdate = true + bar.Start() + + // the progress bar lib doesn't give us a way to get the width of the output, + // so as a hack we just use a callback to measure the output, then git rid of it + terminalWidth = 0 + bar.Callback = func(line string) { + terminalWidth = len(line) + bar.Callback = nil + bar.Output = os.Stderr + log.Infof("terminal width: %v\n", terminalWidth) + } + bar.Update() + } + + go func() { + lastFile := "" + var totalProgress, prevFiles, lastBytes int64 + + for out := range outChan { + output := out.(*AddedObject) + if len(output.Hash) > 0 { + if showProgressBar { + // clear progress bar line before we print "added x" output + fmt.Fprintf(os.Stderr, "\r%s\r", strings.Repeat(" ", terminalWidth)) + } + wrapperChan <- output + + } else { + log.Debugf("add progress: %v %v\n", output.Name, output.Bytes) + + if !showProgressBar { + continue + } + + if len(lastFile) == 0 { + lastFile = output.Name + } + if output.Name != lastFile || output.Bytes < lastBytes { + prevFiles += lastBytes + lastFile = output.Name + } + lastBytes = output.Bytes + delta := prevFiles + lastBytes - totalProgress + totalProgress = bar.Add64(delta) + + bar.Update() + } + } + + close(wrapperChan) + }() + }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { outChan, ok := res.Output().(<-chan interface{}) @@ -144,12 +250,19 @@ func addNode(n *core.IpfsNode, node *dag.Node) error { return nil } -func addFile(n *core.IpfsNode, file files.File, out chan interface{}) (*dag.Node, error) { +func addFile(n *core.IpfsNode, file files.File, out chan interface{}, progress bool) (*dag.Node, error) { if file.IsDirectory() { - return addDir(n, file, out) + return addDir(n, file, out, progress) } - dns, err := add(n, []io.Reader{file}) + // if the progress flag was specified, wrap the file so that we can send + // progress updates to the client (over the output channel) + var reader io.Reader = file + if progress { + reader = &progressReader{file: file, out: out} + } + + dns, err := add(n, []io.Reader{reader}) if err != nil { return nil, err } @@ -161,7 +274,7 @@ func addFile(n *core.IpfsNode, file files.File, out chan interface{}) (*dag.Node return dns[len(dns)-1], nil // last dag node is the file. } -func addDir(n *core.IpfsNode, dir files.File, out chan interface{}) (*dag.Node, error) { +func addDir(n *core.IpfsNode, dir files.File, out chan interface{}, progress bool) (*dag.Node, error) { log.Infof("adding directory: %s", dir.FileName()) tree := &dag.Node{Data: ft.FolderPBData()} @@ -175,7 +288,7 @@ func addDir(n *core.IpfsNode, dir files.File, out chan interface{}) (*dag.Node, break } - node, err := addFile(n, file, out) + node, err := addFile(n, file, out, progress) if err != nil { return nil, err } @@ -215,3 +328,25 @@ func outputDagnode(out chan interface{}, name string, dn *dag.Node) error { return nil } + +type progressReader struct { + file files.File + out chan interface{} + bytes int64 + lastProgress int64 +} + +func (i *progressReader) Read(p []byte) (int, error) { + n, err := i.file.Read(p) + + i.bytes += int64(n) + if i.bytes-i.lastProgress >= progressReaderIncrement || err == io.EOF { + i.lastProgress = i.bytes + i.out <- &AddedObject{ + Name: i.file.FileName(), + Bytes: i.bytes, + } + } + + return n, err +} From 2363cb63e606a26d347565a2de36c109d1db3bc8 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Wed, 21 Jan 2015 19:49:50 -0800 Subject: [PATCH 11/16] core/commands: Fixed 'add' progress bar sometimes not clearing progress bar output line --- core/commands/add.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/commands/add.go b/core/commands/add.go index 7162d6a26..56a8af4fc 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -168,7 +168,9 @@ remains to be implemented. lastBytes = output.Bytes delta := prevFiles + lastBytes - totalProgress totalProgress = bar.Add64(delta) + } + if showProgressBar { bar.Update() } } From 121dfb10b4b8d3a2fd4268943e65b30e5a8301a9 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Thu, 22 Jan 2015 12:58:12 -0800 Subject: [PATCH 12/16] core/commands: Fixed progress bar line clear race condition in 'add' --- core/commands/add.go | 104 +++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 67 deletions(-) diff --git a/core/commands/add.go b/core/commands/add.go index 56a8af4fc..659f546c0 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -1,7 +1,6 @@ package commands import ( - "bytes" "errors" "fmt" "io" @@ -108,9 +107,13 @@ remains to be implemented. res.SetError(u.ErrCast(), cmds.ErrNormal) return } + res.SetOutput(nil) - wrapperChan := make(chan interface{}) - res.SetOutput((<-chan interface{})(wrapperChan)) + quiet, _, err := res.Request().Option("quiet").Bool() + if err != nil { + res.SetError(u.ErrCast(), cmds.ErrNormal) + return + } size := int64(0) s, found := res.Request().Values()["size"] @@ -138,78 +141,45 @@ remains to be implemented. bar.Update() } - go func() { - lastFile := "" - var totalProgress, prevFiles, lastBytes int64 - - for out := range outChan { - output := out.(*AddedObject) - if len(output.Hash) > 0 { - if showProgressBar { - // clear progress bar line before we print "added x" output - fmt.Fprintf(os.Stderr, "\r%s\r", strings.Repeat(" ", terminalWidth)) - } - wrapperChan <- output - - } else { - log.Debugf("add progress: %v %v\n", output.Name, output.Bytes) - - if !showProgressBar { - continue - } - - if len(lastFile) == 0 { - lastFile = output.Name - } - if output.Name != lastFile || output.Bytes < lastBytes { - prevFiles += lastBytes - lastFile = output.Name - } - lastBytes = output.Bytes - delta := prevFiles + lastBytes - totalProgress - totalProgress = bar.Add64(delta) - } + lastFile := "" + var totalProgress, prevFiles, lastBytes int64 + for out := range outChan { + output := out.(*AddedObject) + if len(output.Hash) > 0 { if showProgressBar { - bar.Update() + // clear progress bar line before we print "added x" output + fmt.Fprintf(os.Stderr, "\r%s\r", strings.Repeat(" ", terminalWidth)) } - } - - close(wrapperChan) - }() - }, - Marshalers: cmds.MarshalerMap{ - cmds.Text: func(res cmds.Response) (io.Reader, error) { - outChan, ok := res.Output().(<-chan interface{}) - if !ok { - return nil, u.ErrCast() - } - - quiet, _, err := res.Request().Option("quiet").Bool() - if err != nil { - return nil, err - } - - marshal := func(v interface{}) (io.Reader, error) { - obj, ok := v.(*AddedObject) - if !ok { - return nil, u.ErrCast() - } - - var buf bytes.Buffer if quiet { - buf.WriteString(fmt.Sprintf("%s\n", obj.Hash)) + fmt.Printf("%s\n", output.Hash) } else { - buf.WriteString(fmt.Sprintf("added %s %s\n", obj.Hash, obj.Name)) + fmt.Printf("added %s %s\n", output.Hash, output.Name) } - return &buf, nil + + } else { + log.Debugf("add progress: %v %v\n", output.Name, output.Bytes) + + if !showProgressBar { + continue + } + + if len(lastFile) == 0 { + lastFile = output.Name + } + if output.Name != lastFile || output.Bytes < lastBytes { + prevFiles += lastBytes + lastFile = output.Name + } + lastBytes = output.Bytes + delta := prevFiles + lastBytes - totalProgress + totalProgress = bar.Add64(delta) } - return &cmds.ChannelMarshaler{ - Channel: outChan, - Marshaler: marshal, - }, nil - }, + if showProgressBar { + bar.Update() + } + } }, Type: AddedObject{}, } From b77d1c21b6dc9083923f3d2fb2f14b0ea79e32c8 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Thu, 22 Jan 2015 22:48:07 -0800 Subject: [PATCH 13/16] core/commands: add: Don't show progress bar when using --quiet option --- core/commands/add.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/commands/add.go b/core/commands/add.go index 659f546c0..c6d456187 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -55,6 +55,10 @@ remains to be implemented. cmds.BoolOption(progressOptionName, "p", "Stream progress data"), }, PreRun: func(req cmds.Request) error { + if quiet, _, _ := req.Option("quiet").Bool(); quiet { + return nil + } + req.SetOption(progressOptionName, true) sizeFile, ok := req.Files().(files.SizeFile) @@ -120,7 +124,7 @@ remains to be implemented. if found { size = s.(int64) } - showProgressBar := size >= progressBarMinSize + showProgressBar := !quiet && size >= progressBarMinSize var bar *pb.ProgressBar var terminalWidth int From 3fc9bedb0bd7138f1ce1a5f9e9a45ebcc2f4c5ce Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Thu, 22 Jan 2015 23:12:22 -0800 Subject: [PATCH 14/16] commands: Made Std{in|out|err} accessible in Request/Response --- commands/request.go | 10 +++++++++- commands/response.go | 21 ++++++++++++++++++++- core/commands/add.go | 9 ++++----- core/commands/cat.go | 3 +-- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/commands/request.go b/commands/request.go index 8cceebc46..aa5daae87 100644 --- a/commands/request.go +++ b/commands/request.go @@ -3,6 +3,8 @@ package commands import ( "errors" "fmt" + "io" + "os" "reflect" "strconv" @@ -78,6 +80,7 @@ type Request interface { SetContext(Context) Command() *Command Values() map[string]interface{} + Stdin() io.Reader ConvertOptions() error } @@ -91,6 +94,7 @@ type request struct { ctx Context optionDefs map[string]Option values map[string]interface{} + stdin io.Reader } // Path returns the command path of this request @@ -214,6 +218,10 @@ func (r *request) Values() map[string]interface{} { return r.values } +func (r *request) Stdin() io.Reader { + return r.stdin +} + func (r *request) ConvertOptions() error { for k, v := range r.options { opt, ok := r.optionDefs[k] @@ -282,7 +290,7 @@ func NewRequest(path []string, opts optMap, args []string, file files.File, cmd ctx := Context{Context: context.TODO()} values := make(map[string]interface{}) - req := &request{path, opts, args, file, cmd, ctx, optDefs, values} + req := &request{path, opts, args, file, cmd, ctx, optDefs, values, os.Stdin} err := req.ConvertOptions() if err != nil { return nil, err diff --git a/commands/response.go b/commands/response.go index 71d49b69b..a720a8214 100644 --- a/commands/response.go +++ b/commands/response.go @@ -6,6 +6,7 @@ import ( "encoding/xml" "fmt" "io" + "os" "strings" ) @@ -105,6 +106,10 @@ type Response interface { // Gets a io.Reader that reads the marshalled output Reader() (io.Reader, error) + + // Gets Stdout and Stderr, for writing to console without using SetOutput + Stdout() io.Writer + Stderr() io.Writer } type response struct { @@ -113,6 +118,8 @@ type response struct { value interface{} out io.Reader length uint64 + stdout io.Writer + stderr io.Writer } func (r *response) Request() Request { @@ -206,7 +213,19 @@ func (r *response) Reader() (io.Reader, error) { return r.out, nil } +func (r *response) Stdout() io.Writer { + return r.stdout +} + +func (r *response) Stderr() io.Writer { + return r.stderr +} + // NewResponse returns a response to match given Request func NewResponse(req Request) Response { - return &response{req: req} + return &response{ + req: req, + stdout: os.Stdout, + stderr: os.Stderr, + } } diff --git a/core/commands/add.go b/core/commands/add.go index c6d456187..bc157fcc3 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "io" - "os" "path" "strings" @@ -139,7 +138,7 @@ remains to be implemented. bar.Callback = func(line string) { terminalWidth = len(line) bar.Callback = nil - bar.Output = os.Stderr + bar.Output = res.Stderr() log.Infof("terminal width: %v\n", terminalWidth) } bar.Update() @@ -153,12 +152,12 @@ remains to be implemented. if len(output.Hash) > 0 { if showProgressBar { // clear progress bar line before we print "added x" output - fmt.Fprintf(os.Stderr, "\r%s\r", strings.Repeat(" ", terminalWidth)) + fmt.Fprintf(res.Stderr(), "\r%s\r", strings.Repeat(" ", terminalWidth)) } if quiet { - fmt.Printf("%s\n", output.Hash) + fmt.Fprintf(res.Stdout(), "%s\n", output.Hash) } else { - fmt.Printf("added %s %s\n", output.Hash, output.Name) + fmt.Fprintf(res.Stdout(), "added %s %s\n", output.Hash, output.Name) } } else { diff --git a/core/commands/cat.go b/core/commands/cat.go index b1f3df28c..d917b450f 100644 --- a/core/commands/cat.go +++ b/core/commands/cat.go @@ -2,7 +2,6 @@ package commands import ( "io" - "os" cmds "github.com/jbenet/go-ipfs/commands" core "github.com/jbenet/go-ipfs/core" @@ -51,7 +50,7 @@ it contains. } bar := pb.New(int(res.Length())).SetUnits(pb.U_BYTES) - bar.Output = os.Stderr + bar.Output = res.Stderr() bar.Start() reader := bar.NewProxyReader(res.Output().(io.Reader)) From 79741438d9b351460b2baad3665fd00024565b41 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Thu, 22 Jan 2015 23:12:48 -0800 Subject: [PATCH 15/16] core/commands: config: Simplified some syntax --- core/commands/config.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/core/commands/config.go b/core/commands/config.go index 33d56cf0f..dda77c5fc 100644 --- a/core/commands/config.go +++ b/core/commands/config.go @@ -68,24 +68,19 @@ Set the value of the 'datastore.path' key: } defer r.Close() - var value string + var err error + var output *ConfigField if len(args) == 2 { - value = args[1] - output, err := setConfig(r, key, value) - if err != nil { - res.SetError(err, cmds.ErrNormal) - return - } - res.SetOutput(output) - + value := args[1] + output, err = setConfig(r, key, value) } else { - output, err := getConfig(r, key) - if err != nil { - res.SetError(err, cmds.ErrNormal) - return - } - res.SetOutput(output) + output, err = getConfig(r, key) } + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + res.SetOutput(output) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { From 5706471897583b9a3ce2f2afa70fb81a354519b5 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Thu, 22 Jan 2015 23:21:19 -0800 Subject: [PATCH 16/16] commands: Made PostRun signature match Run --- cmd/ipfs/main.go | 2 +- commands/command.go | 2 +- core/commands/add.go | 6 +++--- core/commands/cat.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index e50a5ca16..a0a050023 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -356,7 +356,7 @@ func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command, cmd } if cmd.PostRun != nil { - cmd.PostRun(res) + cmd.PostRun(req, res) } return res, nil diff --git a/commands/command.go b/commands/command.go index f52ce7f06..c005b49cc 100644 --- a/commands/command.go +++ b/commands/command.go @@ -47,7 +47,7 @@ type Command struct { Arguments []Argument PreRun func(req Request) error Run Function - PostRun func(res Response) + PostRun Function Marshalers map[EncodingType]Marshaler Helptext HelpText diff --git a/core/commands/add.go b/core/commands/add.go index bc157fcc3..fe86e91cf 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -104,7 +104,7 @@ remains to be implemented. } }() }, - PostRun: func(res cmds.Response) { + PostRun: func(req cmds.Request, res cmds.Response) { outChan, ok := res.Output().(<-chan interface{}) if !ok { res.SetError(u.ErrCast(), cmds.ErrNormal) @@ -112,14 +112,14 @@ remains to be implemented. } res.SetOutput(nil) - quiet, _, err := res.Request().Option("quiet").Bool() + quiet, _, err := req.Option("quiet").Bool() if err != nil { res.SetError(u.ErrCast(), cmds.ErrNormal) return } size := int64(0) - s, found := res.Request().Values()["size"] + s, found := req.Values()["size"] if found { size = s.(int64) } diff --git a/core/commands/cat.go b/core/commands/cat.go index d917b450f..58c7a6be0 100644 --- a/core/commands/cat.go +++ b/core/commands/cat.go @@ -44,7 +44,7 @@ it contains. reader := io.MultiReader(readers...) res.SetOutput(reader) }, - PostRun: func(res cmds.Response) { + PostRun: func(req cmds.Request, res cmds.Response) { if res.Length() < progressBarMinSize { return }