Merge pull request #613 from jbenet/progress-bars

Progress Bars
This commit is contained in:
Juan Batiz-Benet 2015-01-23 22:20:34 -08:00
commit 4334f6fda3
53 changed files with 1591 additions and 323 deletions

4
Godeps/Godeps.json generated
View File

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

12
Godeps/_workspace/src/github.com/cheggaaa/pb/LICENSE generated vendored Normal file
View File

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

98
Godeps/_workspace/src/github.com/cheggaaa/pb/README.md generated vendored Normal file
View File

@ -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("<.- >")
```

View File

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

View File

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

42
Godeps/_workspace/src/github.com/cheggaaa/pb/format.go generated vendored Normal file
View File

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

View File

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

341
Godeps/_workspace/src/github.com/cheggaaa/pb/pb.go generated vendored Normal file
View File

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

View File

@ -0,0 +1,7 @@
// +build linux darwin freebsd openbsd
package pb
import "syscall"
const sys_ioctl = syscall.SYS_IOCTL

View File

@ -0,0 +1,5 @@
// +build solaris
package pb
const sys_ioctl = 54

View File

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

16
Godeps/_workspace/src/github.com/cheggaaa/pb/pb_win.go generated vendored Normal file
View File

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

46
Godeps/_workspace/src/github.com/cheggaaa/pb/pb_x.go generated vendored Normal file
View File

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

17
Godeps/_workspace/src/github.com/cheggaaa/pb/reader.go generated vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 '<multiaddr>/<peerID>'.",
},
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{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <encoding>.
cmds.FileArg("data", true, false, "Data to be stored as a DAG object"),
cmds.StringArg("encoding", true, false, "Encoding type of <data>, 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 <encoding>.
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) {

View File

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

View File

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

View File

@ -46,21 +46,23 @@ Publish a <ref> 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 <name>").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 <ref> 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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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