diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 534e51606..4cb9a36a9 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/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 a5fbbf6d3..5c4d16a83 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/main.go b/cmd/ipfs/main.go index 96f680bcb..a0a050023 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) @@ -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() @@ -347,6 +354,11 @@ func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command) (cmd res = root.Call(req) } + + if cmd.PostRun != nil { + cmd.PostRun(req, res) + } + return res, nil } 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/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/command.go b/commands/command.go index 332298431..c005b49cc 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) @@ -40,18 +40,14 @@ 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 { Options []Option Arguments []Argument + PreRun func(req Request) error Run Function + PostRun Function Marshalers map[EncodingType]Marshaler Helptext HelpText @@ -99,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 { @@ -144,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/commands/files/file.go b/commands/files/file.go index 9e9b043a1..f1196257d 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,22 @@ 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 { + SizeFile + + Peek(n int) File + Length() int +} + +type SizeFile interface { + File + + Size() (int64, error) +} 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..741042881 100644 --- a/commands/files/readerfile.go +++ b/commands/files/readerfile.go @@ -1,12 +1,21 @@ package files -import "io" +import ( + "errors" + "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 +27,24 @@ 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 +} + +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 21f3a9bb9..aeba01fa7 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,37 @@ func (f *serialFile) Close() error { return nil } + +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 e1035f2ce..f6e204812 100644 --- a/commands/files/slicefile.go +++ b/commands/files/slicefile.go @@ -1,13 +1,21 @@ 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`. // 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 +23,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 +42,30 @@ 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) +} + +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 +} diff --git a/commands/http/client.go b/commands/http/client.go index 528dcf045..95b12d24e 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/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 diff --git a/commands/request.go b/commands/request.go index ee3747442..aa5daae87 100644 --- a/commands/request.go +++ b/commands/request.go @@ -3,6 +3,8 @@ package commands import ( "errors" "fmt" + "io" + "os" "reflect" "strconv" @@ -77,6 +79,8 @@ type Request interface { Context() *Context SetContext(Context) Command() *Command + Values() map[string]interface{} + Stdin() io.Reader ConvertOptions() error } @@ -89,6 +93,8 @@ type request struct { cmd *Command ctx Context optionDefs map[string]Option + values map[string]interface{} + stdin io.Reader } // Path returns the command path of this request @@ -208,6 +214,14 @@ var converters = map[reflect.Kind]converter{ }, } +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] @@ -275,7 +289,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, os.Stdin} err := req.ConvertOptions() if err != nil { return nil, err diff --git a/commands/response.go b/commands/response.go index e727b574f..a720a8214 100644 --- a/commands/response.go +++ b/commands/response.go @@ -6,6 +6,7 @@ import ( "encoding/xml" "fmt" "io" + "os" "strings" ) @@ -95,19 +96,30 @@ 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) // 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 { - req Request - err *Error - value interface{} - out io.Reader + req Request + err *Error + value interface{} + out io.Reader + length uint64 + stdout io.Writer + stderr io.Writer } func (r *response) Request() Request { @@ -122,6 +134,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 } @@ -193,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 61c412a04..fe86e91cf 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -1,11 +1,11 @@ package commands import ( - "bytes" "errors" "fmt" "io" "path" + "strings" cmds "github.com/jbenet/go-ipfs/commands" files "github.com/jbenet/go-ipfs/commands/files" @@ -16,14 +16,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,14 +51,42 @@ 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"), }, - Run: func(req cmds.Request) (interface{}, error) { - n, err := req.Context().GetNode() - if err != nil { - return nil, err + 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) + 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() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + progress, _, _ := req.Option(progressOptionName).Bool() + outChan := make(chan interface{}) + res.SetOutput((<-chan interface{})(outChan)) go func() { defer close(outChan) @@ -61,47 +97,92 @@ remains to be implemented. return } - _, err = addFile(n, file, outChan) + _, err = addFile(n, file, outChan, progress) if err != nil { return } } }() - - return outChan, nil }, - Marshalers: cmds.MarshalerMap{ - cmds.Text: func(res cmds.Response) (io.Reader, error) { - outChan, ok := res.Output().(<-chan interface{}) - if !ok { - return nil, u.ErrCast() - } + PostRun: func(req cmds.Request, res cmds.Response) { + outChan, ok := res.Output().(<-chan interface{}) + if !ok { + res.SetError(u.ErrCast(), cmds.ErrNormal) + return + } + res.SetOutput(nil) - quiet, _, err := res.Request().Option("quiet").Bool() - if err != nil { - return nil, err - } + quiet, _, err := req.Option("quiet").Bool() + if err != nil { + res.SetError(u.ErrCast(), cmds.ErrNormal) + return + } - marshal := func(v interface{}) (io.Reader, error) { - obj, ok := v.(*AddedObject) - if !ok { - return nil, u.ErrCast() + size := int64(0) + s, found := req.Values()["size"] + if found { + size = s.(int64) + } + showProgressBar := !quiet && 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 = res.Stderr() + log.Infof("terminal width: %v\n", terminalWidth) + } + bar.Update() + } + + 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(res.Stderr(), "\r%s\r", strings.Repeat(" ", terminalWidth)) } - - var buf bytes.Buffer if quiet { - buf.WriteString(fmt.Sprintf("%s\n", obj.Hash)) + fmt.Fprintf(res.Stdout(), "%s\n", output.Hash) } else { - buf.WriteString(fmt.Sprintf("added %s %s\n", obj.Hash, obj.Name)) + fmt.Fprintf(res.Stdout(), "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{}, } @@ -144,12 +225,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 +249,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 +263,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 +303,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 +} 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 441a611a2..5344112d1 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..58c7a6be0 100644 --- a/core/commands/cat.go +++ b/core/commands/cat.go @@ -6,8 +6,12 @@ import ( 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", @@ -20,36 +24,60 @@ 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()) + readers, length, err := cat(node, req.Arguments()) if err != nil { - return nil, err + res.SetError(err, cmds.ErrNormal) + return } + res.SetLength(length) + reader := io.MultiReader(readers...) - return reader, nil + res.SetOutput(reader) + }, + PostRun: func(req cmds.Request, res cmds.Response) { + if res.Length() < progressBarMinSize { + return + } + + bar := pb.New(int(res.Length())).SetUnits(pb.U_BYTES) + bar.Output = res.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 } 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..dda77c5fc 100644 --- a/core/commands/config.go +++ b/core/commands/config.go @@ -57,24 +57,30 @@ 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 + var err error + var output *ConfigField if len(args) == 2 { - value = args[1] - return setConfig(r, key, value) - + value := args[1] + output, err = setConfig(r, key, value) } 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{ cmds.Text: func(res cmds.Response) (io.Reader, error) { @@ -117,13 +123,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 +148,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 +174,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) {