From 8aec84311ebfcf08bcf551c099d6f6205ac969c5 Mon Sep 17 00:00:00 2001 From: jbenet Date: Sat, 7 Jul 2018 01:18:09 -0700 Subject: [PATCH 1/6] added Request and RequestDecode related to #92 --- shell.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/shell.go b/shell.go index 2a46737db..2a3a6631b 100644 --- a/shell.go +++ b/shell.go @@ -92,6 +92,25 @@ func (s *Shell) newRequest(ctx context.Context, command string, args ...string) return NewRequest(ctx, s.url, command, args...) } +func (s *Shell) Request(ctx context.Context, command string, args ...string) (*Request, *Response, error) { + req := s.newRequest(ctx, command, args...) + res, err := req.Send(s.httpcli) + return req, res, err +} + +func (s *Shell) RequestDecode(ctx context.Context, dec interface{}, command string, args ...string) error { + _, res, err := s.Request(ctx, command, args...) + if err != nil { + return err + } + defer res.Close() + if res.Error != nil { + return res.Error + } + + return json.NewDecoder(res.Output).Decode(dec) +} + type IdOutput struct { ID string PublicKey string From 0a76d867e89c148ab9be0837846a6440c8e578e5 Mon Sep 17 00:00:00 2001 From: jbenet Date: Sat, 7 Jul 2018 01:19:09 -0700 Subject: [PATCH 2/6] added stats bw and swarm peers --- shell.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/shell.go b/shell.go index 2a3a6631b..09bbd234d 100644 --- a/shell.go +++ b/shell.go @@ -20,6 +20,8 @@ import ( ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr-net" tar "github.com/whyrusleeping/tar-utils" + + p2pmetrics "gx/ipfs/QmdeBtQGXjSt7cb97nx9JyLHHv5va2LyEAue7Q5tDFzpLy/go-libp2p-metrics" ) const ( @@ -850,3 +852,34 @@ func (s *Shell) ObjectStat(key string) (*ObjectStats, error) { return stat, nil } + +// ObjectStat gets stats for the DAG object named by key. It returns +// the stats of the requested Object or an error. +func (s *Shell) StatsBW() (*p2pmetrics.Stats, error) { + v := &p2pmetrics.Stats{} + err := s.RequestDecode(context.TODO(), &v, "stats/bw") + return v, err +} + +type SwarmStreamInfo struct { + Protocol string +} + +type SwarmConnInfo struct { + Addr string + Peer string + Latency string + Muxer string + Streams []SwarmStreamInfo +} + +type SwarmConnInfos struct { + Peers []SwarmConnInfo +} + +// SwarmPeers gets all the swarm peers +func (s *Shell) SwarmPeers() (*SwarmConnInfos, error) { + v := &SwarmConnInfos{} + err := s.RequestDecode(context.TODO(), &v, "swarm/peers") + return v, err +} From 7173b3cc953726ad00eb7e55a559ab313922758d Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 11 Jul 2018 16:04:35 +0200 Subject: [PATCH 3/6] gx import go libp2p metrics (and unrewrite) --- package.json | 6 ++++++ shell.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bd0e456df..15d1d0df8 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,12 @@ "hash": "QmQine7gvHncNevKtG9QXxf3nXcwSj6aDDmMm52mHofEEp", "name": "tar-utils", "version": "0.0.3" + }, + { + "author": "whyrusleeping", + "hash": "QmcoBbyTiL9PFjo1GFixJwqQ8mZLJ36CribuqyKmS1okPu", + "name": "go-libp2p-metrics", + "version": "2.1.3" } ], "gxVersion": "0.7.0", diff --git a/shell.go b/shell.go index 09bbd234d..5bdb573a0 100644 --- a/shell.go +++ b/shell.go @@ -21,7 +21,7 @@ import ( manet "github.com/multiformats/go-multiaddr-net" tar "github.com/whyrusleeping/tar-utils" - p2pmetrics "gx/ipfs/QmdeBtQGXjSt7cb97nx9JyLHHv5va2LyEAue7Q5tDFzpLy/go-libp2p-metrics" + p2pmetrics "github.com/libp2p/go-libp2p-metrics" ) const ( From 2960bf935289bb0e1eb5c0914489d962f1095ce3 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 11 Jul 2018 16:08:41 +0200 Subject: [PATCH 4/6] add basic tests for stats and swarm peers --- shell_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/shell_test.go b/shell_test.go index 26b43eaa1..376ea7b8f 100644 --- a/shell_test.go +++ b/shell_test.go @@ -242,3 +242,17 @@ func TestDagPut(t *testing.T) { is.Nil(err) is.Equal(c, "zdpuAt47YjE9XTgSxUBkiYCbmnktKajQNheQBGASHj3FfYf8M") } + +func TestStatsBW(t *testing.T) { + is := is.New(t) + s := NewShell(shellUrl) + _, err := s.StatsBW() + is.Nil(err) +} + +func TestSwarmPeers(t *testing.T) { + is := is.New(t) + s := NewShell(shellUrl) + _, err := s.SwarmPeers() + is.Nil(err) +} From 046ddfb31afccb1d4f5e1da35de85c53ec726c5b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 11 Jul 2018 17:26:36 +0200 Subject: [PATCH 5/6] use a request builder This allows specifying custom options. --- request.go | 9 ++++ requestbuilder.go | 97 ++++++++++++++++++++++++++++++++++++++++++ requestbuilder_test.go | 35 +++++++++++++++ shell.go | 29 ++++--------- shell_test.go | 5 ++- 5 files changed, 153 insertions(+), 22 deletions(-) create mode 100644 requestbuilder.go create mode 100644 requestbuilder_test.go diff --git a/request.go b/request.go index 5ca4675f9..9b9c24180 100644 --- a/request.go +++ b/request.go @@ -55,6 +55,15 @@ func (r *Response) Close() error { return nil } +func (r *Response) Decode(dec interface{}) error { + defer r.Close() + if r.Error != nil { + return r.Error + } + + return json.NewDecoder(r.Output).Decode(dec) +} + type Error struct { Command string Message string diff --git a/requestbuilder.go b/requestbuilder.go new file mode 100644 index 000000000..0939fbd54 --- /dev/null +++ b/requestbuilder.go @@ -0,0 +1,97 @@ +package shell + +import ( + "bytes" + "context" + "fmt" + "io" + "strconv" + "strings" +) + +// RequestBuilder is an IPFS commands request builder. +type RequestBuilder struct { + command string + args []string + opts map[string]string + headers map[string]string + body io.Reader + + shell *Shell +} + +// Arguments adds the arguments to the args. +func (r *RequestBuilder) Arguments(args ...string) *RequestBuilder { + r.args = append(r.args, args...) + return r +} + +// BodyString sets the request body to the given string. +func (r *RequestBuilder) BodyString(body string) *RequestBuilder { + return r.Body(strings.NewReader(body)) +} + +// BodyBytes sets the request body to the given buffer. +func (r *RequestBuilder) BodyBytes(body []byte) *RequestBuilder { + return r.Body(bytes.NewReader(body)) +} + +// Body sets the request body to the given reader. +func (r *RequestBuilder) Body(body io.Reader) *RequestBuilder { + r.body = body + return r +} + +// Option sets the given option. +func (r *RequestBuilder) Option(key string, value interface{}) *RequestBuilder { + var s string + switch v := value.(type) { + case bool: + s = strconv.FormatBool(v) + case string: + s = v + case []byte: + s = string(v) + default: + // slow case. + s = fmt.Sprint(value) + } + if r.opts == nil { + r.opts = make(map[string]string, 1) + } + r.opts[key] = s + return r +} + +// Header sets the given header. +func (r *RequestBuilder) Header(name, value string) *RequestBuilder { + if r.headers == nil { + r.headers = make(map[string]string, 1) + } + r.headers[name] = value + return r +} + +// Send sends the request and return the response. +func (r *RequestBuilder) Send(ctx context.Context) (*Response, error) { + req := r.shell.newRequest(ctx, r.command, r.args...) + req.Opts = r.opts + req.Headers = r.headers + req.Body = r.body + return req.Send(r.shell.httpcli) +} + +// Exec sends the request a request and decodes the response. +func (r *RequestBuilder) Exec(ctx context.Context, res interface{}) error { + httpRes, err := r.Send(ctx) + if err != nil { + return err + } + + if res == nil { + httpRes.Close() + return httpRes.Error + } + + return httpRes.Decode(res) +} diff --git a/requestbuilder_test.go b/requestbuilder_test.go new file mode 100644 index 000000000..7950d463b --- /dev/null +++ b/requestbuilder_test.go @@ -0,0 +1,35 @@ +package shell + +import ( + "testing" + "time" + + "github.com/cheekybits/is" +) + +func TestRequestBuilder(t *testing.T) { + is := is.New(t) + + now := time.Now() + r := RequestBuilder{} + r.Arguments("1", "2", "3") + r.Arguments("4") + r.Option("stringkey", "stringvalue") + r.Option("bytekey", []byte("bytevalue")) + r.Option("boolkey", true) + r.Option("otherkey", now) + r.Header("some-header", "header-value") + r.Header("some-header-2", "header-value-2") + + is.Equal(r.args, []string{"1", "2", "3", "4"}) + is.Equal(r.opts, map[string]string{ + "stringkey": "stringvalue", + "bytekey": "bytevalue", + "boolkey": "true", + "otherkey": now.String(), + }) + is.Equal(r.headers, map[string]string{ + "some-header": "header-value", + "some-header-2": "header-value-2", + }) +} diff --git a/shell.go b/shell.go index 5bdb573a0..ac3f95484 100644 --- a/shell.go +++ b/shell.go @@ -94,23 +94,12 @@ func (s *Shell) newRequest(ctx context.Context, command string, args ...string) return NewRequest(ctx, s.url, command, args...) } -func (s *Shell) Request(ctx context.Context, command string, args ...string) (*Request, *Response, error) { - req := s.newRequest(ctx, command, args...) - res, err := req.Send(s.httpcli) - return req, res, err -} - -func (s *Shell) RequestDecode(ctx context.Context, dec interface{}, command string, args ...string) error { - _, res, err := s.Request(ctx, command, args...) - if err != nil { - return err - } - defer res.Close() - if res.Error != nil { - return res.Error +func (s *Shell) Request(command string, args ...string) *RequestBuilder { + return &RequestBuilder{ + command: command, + args: args, + shell: s, } - - return json.NewDecoder(res.Output).Decode(dec) } type IdOutput struct { @@ -855,9 +844,9 @@ func (s *Shell) ObjectStat(key string) (*ObjectStats, error) { // ObjectStat gets stats for the DAG object named by key. It returns // the stats of the requested Object or an error. -func (s *Shell) StatsBW() (*p2pmetrics.Stats, error) { +func (s *Shell) StatsBW(ctx context.Context) (*p2pmetrics.Stats, error) { v := &p2pmetrics.Stats{} - err := s.RequestDecode(context.TODO(), &v, "stats/bw") + err := s.Request("stats/bw").Exec(ctx, &v) return v, err } @@ -878,8 +867,8 @@ type SwarmConnInfos struct { } // SwarmPeers gets all the swarm peers -func (s *Shell) SwarmPeers() (*SwarmConnInfos, error) { +func (s *Shell) SwarmPeers(ctx context.Context) (*SwarmConnInfos, error) { v := &SwarmConnInfos{} - err := s.RequestDecode(context.TODO(), &v, "swarm/peers") + err := s.Request("swarm/peers").Exec(ctx, &v) return v, err } diff --git a/shell_test.go b/shell_test.go index 376ea7b8f..535f1fd52 100644 --- a/shell_test.go +++ b/shell_test.go @@ -2,6 +2,7 @@ package shell import ( "bytes" + "context" "crypto/md5" "fmt" "io" @@ -246,13 +247,13 @@ func TestDagPut(t *testing.T) { func TestStatsBW(t *testing.T) { is := is.New(t) s := NewShell(shellUrl) - _, err := s.StatsBW() + _, err := s.StatsBW(context.Background()) is.Nil(err) } func TestSwarmPeers(t *testing.T) { is := is.New(t) s := NewShell(shellUrl) - _, err := s.SwarmPeers() + _, err := s.SwarmPeers(context.Background()) is.Nil(err) } From 46768a3cbe152e13e1189dfbadb7c8e1f1c7ac6f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 16 Jul 2018 08:19:53 -0700 Subject: [PATCH 6/6] switch to using Request internally --- bootstrap.go | 70 ++------ dag.go | 45 +---- ipns.go | 72 ++------ requestbuilder.go | 2 +- shell.go | 442 +++++++++------------------------------------- unixfs.go | 14 +- 6 files changed, 122 insertions(+), 523 deletions(-) diff --git a/bootstrap.go b/bootstrap.go index 29c50afcf..e28eef096 100644 --- a/bootstrap.go +++ b/bootstrap.go @@ -2,70 +2,26 @@ package shell import ( "context" - "encoding/json" ) -func (s *Shell) BootstrapAdd(peers []string) ([]string, error) { - resp, err := s.newRequest(context.Background(), "bootstrap/add", peers...).Send(s.httpcli) - if err != nil { - return nil, err - } - defer resp.Close() - - if resp.Error != nil { - return nil, resp.Error - } - - addOutput := &struct { - Peers []string - }{} - - err = json.NewDecoder(resp.Output).Decode(addOutput) - if err != nil { - return nil, err - } +type PeersList struct { + Peers []string +} - return addOutput.Peers, nil +func (s *Shell) BootstrapAdd(peers []string) ([]string, error) { + var addOutput PeersList + err := s.Request("bootstrap/add", peers...).Exec(context.Background(), &addOutput) + return addOutput.Peers, err } func (s *Shell) BootstrapAddDefault() ([]string, error) { - resp, err := s.newRequest(context.Background(), "bootstrap/add/default").Send(s.httpcli) - if err != nil { - return nil, err - } - defer resp.Close() - - if resp.Error != nil { - return nil, resp.Error - } - - addOutput := &struct { - Peers []string - }{} - - err = json.NewDecoder(resp.Output).Decode(addOutput) - if err != nil { - return nil, err - } - - return addOutput.Peers, nil + var addOutput PeersList + err := s.Request("bootstrap/add/default").Exec(context.Background(), &addOutput) + return addOutput.Peers, err } func (s *Shell) BootstrapRmAll() ([]string, error) { - resp, err := s.newRequest(context.Background(), "bootstrap/rm/all").Send(s.httpcli) - if err != nil { - return nil, err - } - defer resp.Close() - - rmAllOutput := &struct { - Peers []string - }{} - - err = json.NewDecoder(resp.Output).Decode(rmAllOutput) - if err != nil { - return nil, err - } - - return rmAllOutput.Peers, nil + var rmAllOutput PeersList + err := s.Request("bootstrap/rm/all").Exec(context.Background(), &rmAllOutput) + return rmAllOutput.Peers, err } diff --git a/dag.go b/dag.go index 27899af58..52fd4d6e7 100644 --- a/dag.go +++ b/dag.go @@ -3,7 +3,6 @@ package shell import ( "bytes" "context" - "encoding/json" "fmt" "io" "io/ioutil" @@ -13,29 +12,10 @@ import ( ) func (s *Shell) DagGet(ref string, out interface{}) error { - req := s.newRequest(context.Background(), "dag/get") - req.Args = []string{ref} - - resp, err := req.Send(s.httpcli) - if err != nil { - return err - } - defer resp.Close() - - if resp.Error != nil { - return resp.Error - } - - return json.NewDecoder(resp.Output).Decode(out) + return s.Request("dag/get", ref).Exec(context.Background(), out) } func (s *Shell) DagPut(data interface{}, ienc, kind string) (string, error) { - req := s.newRequest(context.Background(), "dag/put") - req.Opts = map[string]string{ - "input-enc": ienc, - "format": kind, - } - var r io.Reader switch data := data.(type) { case string: @@ -47,31 +27,22 @@ func (s *Shell) DagPut(data interface{}, ienc, kind string) (string, error) { default: return "", fmt.Errorf("cannot current handle putting values of type %T", data) } + rc := ioutil.NopCloser(r) fr := files.NewReaderFile("", "", rc, nil) slf := files.NewSliceFile("", "", []files.File{fr}) fileReader := files.NewMultiFileReader(slf, true) - req.Body = fileReader - - resp, err := req.Send(s.httpcli) - if err != nil { - return "", err - } - defer resp.Close() - - if resp.Error != nil { - return "", resp.Error - } var out struct { Cid struct { Target string `json:"/"` } } - err = json.NewDecoder(resp.Output).Decode(&out) - if err != nil { - return "", err - } - return out.Cid.Target, nil + return out.Cid.Target, s. + Request("dag/put"). + Option("input-enc", ienc). + Option("format", kind). + Body(fileReader). + Exec(context.Background(), &out) } diff --git a/ipns.go b/ipns.go index aad687343..790309603 100644 --- a/ipns.go +++ b/ipns.go @@ -1,10 +1,7 @@ package shell import ( - "bytes" "context" - "encoding/json" - "strconv" "time" ) @@ -15,79 +12,44 @@ type PublishResponse struct { // Publish updates a mutable name to point to a given value func (s *Shell) Publish(node string, value string) error { - args := []string{value} + var pubResp PublishResponse + req := s.Request("name/publish") if node != "" { - args = []string{node, value} + req.Arguments(node) } + req.Arguments(value) - resp, err := s.newRequest(context.Background(), "name/publish", args...).Send(s.httpcli) - if err != nil { - return err - } - defer resp.Close() - - if resp.Error != nil { - return resp.Error - } - - return nil + return req.Exec(context.Background(), &pubResp) } // PublishWithDetails is used for fine grained control over record publishing func (s *Shell) PublishWithDetails(contentHash, key string, lifetime, ttl time.Duration, resolve bool) (*PublishResponse, error) { - - args := []string{contentHash} - req := s.newRequest(context.Background(), "name/publish", args...) - if key == "" { - key = "self" + var pubResp PublishResponse + req := s.Request("name/publish", contentHash).Option("resolve", resolve) + if key != "" { + req.Option("key", key) } - req.Opts["key"] = key - if lifetime.Seconds() > 0 { - req.Opts["lifetime"] = lifetime.String() + if lifetime != 0 { + req.Option("lifetime", lifetime) } if ttl.Seconds() > 0 { - req.Opts["ttl"] = ttl.String() + req.Option("ttl", ttl) } - req.Opts["resolve"] = strconv.FormatBool(resolve) - resp, err := req.Send(s.httpcli) + err := req.Exec(context.Background(), &pubResp) if err != nil { return nil, err } - defer resp.Close() - if resp.Error != nil { - return nil, resp.Error - } - buf := new(bytes.Buffer) - buf.ReadFrom(resp.Output) - var pubResp PublishResponse - json.Unmarshal(buf.Bytes(), &pubResp) return &pubResp, nil } // Resolve gets resolves the string provided to an /ipns/[name]. If asked to // resolve an empty string, resolve instead resolves the node's own /ipns value. func (s *Shell) Resolve(id string) (string, error) { - var resp *Response - var err error + req := s.Request("name/resolve") if id != "" { - resp, err = s.newRequest(context.Background(), "name/resolve", id).Send(s.httpcli) - } else { - resp, err = s.newRequest(context.Background(), "name/resolve").Send(s.httpcli) - } - if err != nil { - return "", err - } - defer resp.Close() - - if resp.Error != nil { - return "", resp.Error + req.Arguments(id) } - var out struct{ Path string } - err = json.NewDecoder(resp.Output).Decode(&out) - if err != nil { - return "", err - } - - return out.Path, nil + err := req.Exec(context.Background(), &out) + return out.Path, err } diff --git a/requestbuilder.go b/requestbuilder.go index 0939fbd54..3830ce34d 100644 --- a/requestbuilder.go +++ b/requestbuilder.go @@ -74,7 +74,7 @@ func (r *RequestBuilder) Header(name, value string) *RequestBuilder { // Send sends the request and return the response. func (r *RequestBuilder) Send(ctx context.Context) (*Response, error) { - req := r.shell.newRequest(ctx, r.command, r.args...) + req := NewRequest(ctx, r.shell.url, r.command, r.args...) req.Opts = r.opts req.Headers = r.headers req.Body = r.body diff --git a/shell.go b/shell.go index ac3f95484..53dbb8d8f 100644 --- a/shell.go +++ b/shell.go @@ -90,10 +90,6 @@ func (s *Shell) SetTimeout(d time.Duration) { s.httpcli.Timeout = d } -func (s *Shell) newRequest(ctx context.Context, command string, args ...string) *Request { - return NewRequest(ctx, s.url, command, args...) -} - func (s *Shell) Request(command string, args ...string) *RequestBuilder { return &RequestBuilder{ command: command, @@ -119,29 +115,16 @@ func (s *Shell) ID(peer ...string) (*IdOutput, error) { return nil, fmt.Errorf("Too many peer arguments") } - resp, err := NewRequest(context.Background(), s.url, "id", peer...).Send(s.httpcli) - if err != nil { + var out IdOutput + if err := s.Request("id", peer...).Exec(context.Background(), &out); err != nil { return nil, err } - - defer resp.Close() - if resp.Error != nil { - return nil, resp.Error - } - - decoder := json.NewDecoder(resp.Output) - out := new(IdOutput) - err = decoder.Decode(out) - if err != nil { - return nil, err - } - - return out, nil + return &out, nil } // Cat the content at the given path. Callers need to drain and close the returned reader after usage. func (s *Shell) Cat(path string) (io.ReadCloser, error) { - resp, err := NewRequest(context.Background(), s.url, "cat", path).Send(s.httpcli) + resp, err := s.Request("cat", path).Send(context.Background()) if err != nil { return nil, err } @@ -179,33 +162,13 @@ func (s *Shell) AddWithOpts(r io.Reader, pin bool, rawLeaves bool) (string, erro slf := files.NewSliceFile("", "", []files.File{fr}) fileReader := files.NewMultiFileReader(slf, true) - req := NewRequest(context.Background(), s.url, "add") - req.Body = fileReader - req.Opts["progress"] = "false" - if !pin { - req.Opts["pin"] = "false" - } - - if rawLeaves { - req.Opts["raw-leaves"] = "true" - } - - resp, err := req.Send(s.httpcli) - if err != nil { - return "", err - } - defer resp.Close() - if resp.Error != nil { - return "", resp.Error - } - var out object - err = json.NewDecoder(resp.Output).Decode(&out) - if err != nil { - return "", err - } - - return out.Hash, nil + return out.Hash, s.Request("add"). + Option("progress", false). + Option("pin", pin). + Option("raw-leaves", rawLeaves). + Body(fileReader). + Exec(context.Background(), &out) } func (s *Shell) AddLink(target string) (string, error) { @@ -213,25 +176,8 @@ func (s *Shell) AddLink(target string) (string, error) { slf := files.NewSliceFile("", "", []files.File{link}) reader := files.NewMultiFileReader(slf, true) - req := s.newRequest(context.Background(), "add") - req.Body = reader - - resp, err := req.Send(s.httpcli) - if err != nil { - return "", err - } - defer resp.Close() - if resp.Error != nil { - return "", resp.Error - } - var out object - err = json.NewDecoder(resp.Output).Decode(&out) - if err != nil { - return "", err - } - - return out.Hash, nil + return out.Hash, s.Request("add").Body(reader).Exec(context.Background(), &out) } // AddDir adds a directory recursively with all of the files under it @@ -248,15 +194,17 @@ func (s *Shell) AddDir(dir string) (string, error) { slf := files.NewSliceFile("", dir, []files.File{sf}) reader := files.NewMultiFileReader(slf, true) - req := NewRequest(context.Background(), s.url, "add") - req.Opts["r"] = "true" - req.Body = reader + resp, err := s.Request("add"). + Option("recursive", true). + Body(reader). + Send(context.Background()) - resp, err := req.Send(s.httpcli) if err != nil { - return "", err + return "", nil } + defer resp.Close() + if resp.Error != nil { return "", resp.Error } @@ -292,22 +240,14 @@ const ( // List entries at the given path func (s *Shell) List(path string) ([]*LsLink, error) { - resp, err := NewRequest(context.Background(), s.url, "ls", path).Send(s.httpcli) - if err != nil { - return nil, err - } - defer resp.Close() - - if resp.Error != nil { - return nil, resp.Error - } - var out struct{ Objects []LsObject } - err = json.NewDecoder(resp.Output).Decode(&out) + err := s.Request("ls", path).Exec(context.Background(), &out) if err != nil { return nil, err } - + if len(out.Objects) != 1 { + return nil, errors.New("bad response from server") + } return out.Objects[0].Links, nil } @@ -325,36 +265,16 @@ type LsObject struct { // Pin the given path func (s *Shell) Pin(path string) error { - req := NewRequest(context.Background(), s.url, "pin/add", path) - req.Opts["r"] = "true" - - resp, err := req.Send(s.httpcli) - if err != nil { - return err - } - defer resp.Close() - if resp.Error != nil { - return resp.Error - } - - return nil + return s.Request("pin/add", path). + Option("recursive", true). + Exec(context.Background(), nil) } // Unpin the given path func (s *Shell) Unpin(path string) error { - req := NewRequest(context.Background(), s.url, "pin/rm", path) - req.Opts["r"] = "true" - - resp, err := req.Send(s.httpcli) - if err != nil { - return err - } - defer resp.Close() - if resp.Error != nil { - return resp.Error - } - - return nil + return s.Request("pin/rm", path). + Option("recursive", true). + Exec(context.Background(), nil) } const ( @@ -373,23 +293,8 @@ type PinInfo struct { // than unordered array searching. The map is likely to be more useful to a // client than a flat list. func (s *Shell) Pins() (map[string]PinInfo, error) { - resp, err := s.newRequest(context.Background(), "pin/ls").Send(s.httpcli) - if err != nil { - return nil, err - } - defer resp.Close() - - if resp.Error != nil { - return nil, resp.Error - } - - raw := struct{ Keys map[string]PinInfo }{} - err = json.NewDecoder(resp.Output).Decode(&raw) - if err != nil { - return nil, err - } - - return raw.Keys, nil + var raw struct{ Keys map[string]PinInfo } + return raw.Keys, s.Request("pin/ls").Exec(context.Background(), &raw) } type PeerInfo struct { @@ -398,40 +303,26 @@ type PeerInfo struct { } func (s *Shell) FindPeer(peer string) (*PeerInfo, error) { - resp, err := s.newRequest(context.Background(), "dht/findpeer", peer).Send(s.httpcli) - if err != nil { - return nil, err - } - defer resp.Close() - - if resp.Error != nil { - return nil, resp.Error - } - - str := struct{ Responses []PeerInfo }{} - err = json.NewDecoder(resp.Output).Decode(&str) + var peers struct{ Responses []PeerInfo } + err := s.Request("dht/findpeer", peer).Exec(context.Background(), &peers) if err != nil { return nil, err } - - if len(str.Responses) == 0 { + if len(peers.Responses) == 0 { return nil, errors.New("peer not found") } - - return &str.Responses[0], nil + return &peers.Responses[0], nil } func (s *Shell) Refs(hash string, recursive bool) (<-chan string, error) { - req := s.newRequest(context.Background(), "refs", hash) - if recursive { - req.Opts["r"] = "true" - } - - resp, err := req.Send(s.httpcli) + resp, err := s.Request("refs", hash). + Option("recursive", recursive). + Send(context.Background()) if err != nil { return nil, err } + defer resp.Close() if resp.Error != nil { return nil, resp.Error } @@ -441,7 +332,6 @@ func (s *Shell) Refs(hash string, recursive bool) (<-chan string, error) { var ref struct { Ref string } - defer resp.Close() defer close(out) dec := json.NewDecoder(resp.Output) for { @@ -459,25 +349,10 @@ func (s *Shell) Refs(hash string, recursive bool) (<-chan string, error) { } func (s *Shell) Patch(root, action string, args ...string) (string, error) { - cmdargs := append([]string{root}, args...) - resp, err := s.newRequest(context.Background(), "object/patch/"+action, cmdargs...).Send(s.httpcli) - if err != nil { - return "", err - } - defer resp.Close() - - if resp.Error != nil { - return "", resp.Error - } - - dec := json.NewDecoder(resp.Output) var out object - err = dec.Decode(&out) - if err != nil { - return "", err - } - - return out.Hash, nil + return out.Hash, s.Request("object/patch/"+action, root). + Arguments(args...). + Exec(context.Background(), &out) } func (s *Shell) PatchData(root string, set bool, data interface{}) (string, error) { @@ -502,58 +377,21 @@ func (s *Shell) PatchData(root string, set bool, data interface{}) (string, erro slf := files.NewSliceFile("", "", []files.File{fr}) fileReader := files.NewMultiFileReader(slf, true) - req := s.newRequest(context.Background(), "object/patch/"+cmd, root) - req.Body = fileReader - - resp, err := req.Send(s.httpcli) - if err != nil { - return "", err - } - defer resp.Close() - - if resp.Error != nil { - return "", resp.Error - } - - dec := json.NewDecoder(resp.Output) var out object - err = dec.Decode(&out) - if err != nil { - return "", err - } - - return out.Hash, nil + return out.Hash, s.Request("object/patch/"+cmd, root). + Body(fileReader). + Exec(context.Background(), &out) } func (s *Shell) PatchLink(root, path, childhash string, create bool) (string, error) { - cmdargs := []string{root, path, childhash} - - req := s.newRequest(context.Background(), "object/patch/add-link", cmdargs...) - if create { - req.Opts["create"] = "true" - } - - resp, err := req.Send(s.httpcli) - if err != nil { - return "", err - } - defer resp.Close() - - if resp.Error != nil { - return "", resp.Error - } - var out object - err = json.NewDecoder(resp.Output).Decode(&out) - if err != nil { - return "", err - } - - return out.Hash, nil + return out.Hash, s.Request("object/patch/add-link", root, path, childhash). + Option("create", true). + Exec(context.Background(), &out) } func (s *Shell) Get(hash, outdir string) error { - resp, err := s.newRequest(context.Background(), "get", hash).Send(s.httpcli) + resp, err := s.Request("get", hash).Option("create", true).Send(context.Background()) if err != nil { return err } @@ -568,76 +406,36 @@ func (s *Shell) Get(hash, outdir string) error { } func (s *Shell) NewObject(template string) (string, error) { - args := []string{} - if template != "" { - args = []string{template} - } - - resp, err := s.newRequest(context.Background(), "object/new", args...).Send(s.httpcli) - if err != nil { - return "", err - } - defer resp.Close() - - if resp.Error != nil { - return "", resp.Error - } - var out object - err = json.NewDecoder(resp.Output).Decode(&out) - if err != nil { - return "", err + req := s.Request("object/new") + if template != "" { + req.Arguments(template) } - - return out.Hash, nil + return out.Hash, req.Exec(context.Background(), &out) } func (s *Shell) ResolvePath(path string) (string, error) { - resp, err := s.newRequest(context.Background(), "resolve", path).Send(s.httpcli) - if err != nil { - return "", err - } - defer resp.Close() - - if resp.Error != nil { - return "", resp.Error - } - var out struct { Path string } - err = json.NewDecoder(resp.Output).Decode(&out) + err := s.Request("resolve", path).Exec(context.Background(), &out) if err != nil { return "", err } - p := strings.TrimPrefix(out.Path, "/ipfs/") - - return p, nil + return strings.TrimPrefix(out.Path, "/ipfs/"), nil } // returns ipfs version and commit sha func (s *Shell) Version() (string, string, error) { - resp, err := s.newRequest(context.Background(), "version").Send(s.httpcli) - if err != nil { - return "", "", err - } - - defer resp.Close() - if resp.Error != nil { - return "", "", resp.Error - } - ver := struct { Version string Commit string }{} - err = json.NewDecoder(resp.Output).Decode(&ver) - if err != nil { + if err := s.Request("version").Exec(context.Background(), &ver); err != nil { return "", "", err } - return ver.Version, ver.Commit, nil } @@ -647,31 +445,19 @@ func (s *Shell) IsUp() bool { } func (s *Shell) BlockStat(path string) (string, int, error) { - resp, err := s.newRequest(context.Background(), "block/stat", path).Send(s.httpcli) - if err != nil { - return "", 0, err - } - defer resp.Close() - - if resp.Error != nil { - return "", 0, resp.Error - } - var inf struct { Key string Size int } - err = json.NewDecoder(resp.Output).Decode(&inf) - if err != nil { + if err := s.Request("block/stat", path).Exec(context.Background(), &inf); err != nil { return "", 0, err } - return inf.Key, inf.Size, nil } func (s *Shell) BlockGet(path string) ([]byte, error) { - resp, err := s.newRequest(context.Background(), "block/get", path).Send(s.httpcli) + resp, err := s.Request("block/get", path).Send(context.Background()) if err != nil { return nil, err } @@ -685,38 +471,22 @@ func (s *Shell) BlockGet(path string) ([]byte, error) { } func (s *Shell) BlockPut(block []byte, format, mhtype string, mhlen int) (string, error) { + var out struct { + Key string + } + data := bytes.NewReader(block) rc := ioutil.NopCloser(data) fr := files.NewReaderFile("", "", rc, nil) slf := files.NewSliceFile("", "", []files.File{fr}) fileReader := files.NewMultiFileReader(slf, true) - req := s.newRequest(context.Background(), "block/put") - req.Body = fileReader - req.Opts = map[string]string{ - "mhtype": mhtype, - "mhlen": fmt.Sprintf("%d", mhlen), - "format": format, - } - resp, err := req.Send(s.httpcli) - if err != nil { - return "", err - } - defer resp.Close() - - if resp.Error != nil { - return "", resp.Error - } - - var out struct { - Key string - } - err = json.NewDecoder(resp.Output).Decode(&out) - if err != nil { - return "", err - } - - return out.Key, nil + return out.Key, s.Request("block/put"). + Option("mhtype", mhtype). + Option("format", format). + Option("mhlen", mhlen). + Body(fileReader). + Exec(context.Background(), &out) } type IpfsObject struct { @@ -730,84 +500,48 @@ type ObjectLink struct { } func (s *Shell) ObjectGet(path string) (*IpfsObject, error) { - resp, err := s.newRequest(context.Background(), "object/get", path).Send(s.httpcli) - if err != nil { - return nil, err - } - defer resp.Close() - - if resp.Error != nil { - return nil, resp.Error - } - var obj IpfsObject - err = json.NewDecoder(resp.Output).Decode(&obj) - if err != nil { + if err := s.Request("object/get", path).Exec(context.Background(), &obj); err != nil { return nil, err } - return &obj, nil } func (s *Shell) ObjectPut(obj *IpfsObject) (string, error) { - data := new(bytes.Buffer) - err := json.NewEncoder(data).Encode(obj) + var data bytes.Buffer + err := json.NewEncoder(&data).Encode(obj) if err != nil { return "", err } - rc := ioutil.NopCloser(data) + rc := ioutil.NopCloser(&data) fr := files.NewReaderFile("", "", rc, nil) slf := files.NewSliceFile("", "", []files.File{fr}) fileReader := files.NewMultiFileReader(slf, true) - req := s.newRequest(context.Background(), "object/put") - req.Body = fileReader - resp, err := req.Send(s.httpcli) - if err != nil { - return "", err - } - defer resp.Close() - - if resp.Error != nil { - return "", resp.Error - } - var out object - err = json.NewDecoder(resp.Output).Decode(&out) - if err != nil { - return "", err - } - - return out.Hash, nil + return out.Hash, s.Request("object/put"). + Body(fileReader). + Exec(context.Background(), &out) } func (s *Shell) PubSubSubscribe(topic string) (*PubSubSubscription, error) { // connect - req := s.newRequest(context.Background(), "pubsub/sub", topic) - - resp, err := req.Send(s.httpcli) + resp, err := s.Request("pubsub/sub", topic).Send(context.Background()) if err != nil { return nil, err } - return newPubSubSubscription(resp), nil } func (s *Shell) PubSubPublish(topic, data string) (err error) { - resp, err := s.newRequest(context.Background(), "pubsub/pub", topic, data).Send(s.httpcli) + resp, err := s.Request("pubsub/pub", topic, data).Send(context.Background()) if err != nil { - return + return err } - defer func() { - err1 := resp.Close() - if err == nil { - err = err1 - } - }() - - return nil + defer resp.Close() + return resp.Error } type ObjectStats struct { @@ -822,24 +556,12 @@ type ObjectStats struct { // ObjectStat gets stats for the DAG object named by key. It returns // the stats of the requested Object or an error. func (s *Shell) ObjectStat(key string) (*ObjectStats, error) { - resp, err := s.newRequest(context.Background(), "object/stat", key).Send(s.httpcli) - if err != nil { - return nil, err - } - defer resp.Close() - - if resp.Error != nil { - return nil, resp.Error - } - - stat := &ObjectStats{} - - err = json.NewDecoder(resp.Output).Decode(stat) + var stat ObjectStats + err := s.Request("object/stat", key).Exec(context.Background(), &stat) if err != nil { return nil, err } - - return stat, nil + return &stat, nil } // ObjectStat gets stats for the DAG object named by key. It returns diff --git a/unixfs.go b/unixfs.go index 11df763b0..bc862a373 100644 --- a/unixfs.go +++ b/unixfs.go @@ -2,7 +2,6 @@ package shell import ( "context" - "encoding/json" "fmt" ) @@ -26,19 +25,8 @@ type lsOutput struct { // FileList entries at the given path using the UnixFS commands func (s *Shell) FileList(path string) (*UnixLsObject, error) { - resp, err := s.newRequest(context.Background(), "file/ls", path).Send(s.httpcli) - if err != nil { - return nil, err - } - defer resp.Close() - - if resp.Error != nil { - return nil, resp.Error - } - var out lsOutput - err = json.NewDecoder(resp.Output).Decode(&out) - if err != nil { + if err := s.Request("file/ls", path).Exec(context.Background(), &out); err != nil { return nil, err }