This repository was archived by the owner on Sep 14, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.go
189 lines (168 loc) · 5.3 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package muse
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
"go.sia.tech/siad/types"
"go.uber.org/multierr"
"lukechampine.com/shard"
"lukechampine.com/us/hostdb"
"lukechampine.com/us/renter"
)
// Error is an error wrapper that provides Is function.
type Error struct {
error
}
// NewError returns an error that formats as the given text.
func NewError(str string) Error {
return Error{error: errors.New(str)}
}
// Is reports whether this error matches target.
func (e Error) Is(err error) bool {
return strings.Contains(e.Error(), err.Error())
}
// A Client communicates with a muse server.
type Client struct {
addr string
ctx context.Context
}
func (c *Client) req(method string, route string, data, resp interface{}) (err error) {
var body io.Reader
if data != nil {
js, _ := json.Marshal(data)
body = bytes.NewReader(js)
}
req, err := http.NewRequestWithContext(c.ctx, method, fmt.Sprintf("%v%v", c.addr, route), body)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
r, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer multierr.AppendInvoke(&err, multierr.Close(r.Body))
if r.StatusCode != 200 {
err, _ := ioutil.ReadAll(r.Body)
return NewError(strings.TrimSpace(string(err)))
}
if resp == nil {
return nil
}
return json.NewDecoder(r.Body).Decode(resp)
}
func (c *Client) get(route string, r interface{}) error { return c.req("GET", route, nil, r) }
func (c *Client) post(route string, d, r interface{}) error { return c.req("POST", route, d, r) }
func (c *Client) put(route string, d, r interface{}) error { return c.req("PUT", route, d, r) }
// WithContext returns a new Client whose requests are subject to the supplied
// context.
func (c *Client) WithContext(ctx context.Context) *Client {
return &Client{
addr: c.addr,
ctx: ctx,
}
}
// AllContracts returns all contracts formed by the server.
func (c *Client) AllContracts() (cs []Contract, err error) {
err = c.get("/contracts", &cs)
return
}
// Contracts returns the contracts in the specified set.
func (c *Client) Contracts(set string) (cs []Contract, err error) {
if set == "" {
return nil, errors.New("no host set provided; to retrieve all contracts, use AllContracts")
}
err = c.get("/contracts?hostset="+set, &cs)
return
}
// Scan queries the specified host for its current settings.
//
// Note that the host may also be scanned via the hostdb.Scan function.
func (c *Client) Scan(host hostdb.HostPublicKey) (settings hostdb.HostSettings, err error) {
err = c.post("/scan", RequestScan{
HostKey: host,
}, &settings)
return
}
// Form forms a contract with a host. The settings should be obtained from a
// recent call to Scan. If the settings have changed in the interim, the host
// may reject the contract.
func (c *Client) Form(host *hostdb.ScannedHost, funds types.Currency, start, end types.BlockHeight) (contract Contract, err error) {
err = c.post("/form", RequestForm{
HostKey: host.PublicKey,
Funds: funds,
StartHeight: start,
EndHeight: end,
Settings: host.HostSettings,
}, &contract)
return
}
// Renew renews the contract with the specified ID, which must refer to a
// contract previously formed by the server. The settings should be obtained
// from a recent call to Scan. If the settings have changed in the interim, the
// host may reject the contract.
func (c *Client) Renew(host *hostdb.ScannedHost, old *renter.Contract, funds types.Currency, start, end types.BlockHeight) (contract Contract, err error) {
err = c.post("/renew", RequestRenew{
ID: old.ID,
Funds: funds,
StartHeight: start,
EndHeight: end,
Settings: host.HostSettings,
HostKey: host.PublicKey,
RenterKey: old.RenterKey,
}, &contract)
return
}
// Delete removes the record of a contract from the server. The contract itself
// is not revised or otherwise affected in any way. In general, this method
// should only be used on contracts that have expired and are no longer needed.
func (c *Client) Delete(id types.FileContractID) (err error) {
err = c.post("/delete/"+id.String(), nil, nil)
return
}
// HostSets returns the current list of host sets.
func (c *Client) HostSets() (hs []string, err error) {
err = c.get("/hostsets/", &hs)
return
}
// HostSet returns the contents of the named host set.
func (c *Client) HostSet(name string) (hosts []hostdb.HostPublicKey, err error) {
err = c.get("/hostsets/"+name, &hosts)
return
}
// SetHostSet sets the contents of a host set, creating it if it does not exist.
// If an empty slice is passed, the host set is deleted.
func (c *Client) SetHostSet(name string, hosts []hostdb.HostPublicKey) (err error) {
err = c.put("/hostsets/"+name, hosts, nil)
return
}
// SHARD returns a client for the muse server's shard endpoints.
func (c *Client) SHARD() *shard.Client {
u, err := url.Parse(c.addr)
if err != nil {
panic(err)
}
u.Path = path.Join(u.Path, "shard")
return shard.NewClient(u.String())
}
// NewClient returns a client that communicates with a muse server listening
// on the specified address.
func NewClient(addr string) *Client {
return &Client{addr, context.Background()}
}
func modifyURL(str string, fn func(*url.URL)) string {
u, err := url.Parse(str)
if err != nil {
panic(err)
}
fn(u)
return u.String()
}