master
go 263 lines 5.08 KB
Raw
1 package rpc
2
3 import (
4 "context"
5 "encoding/json"
6 "errors"
7 "io"
8 "strings"
9
10 "github.com/ipfs/boxo/path"
11 "github.com/ipfs/go-cid"
12 iface "github.com/ipfs/kubo/core/coreiface"
13 caopts "github.com/ipfs/kubo/core/coreiface/options"
14 )
15
16 type PinAPI HttpApi
17
18 type pinRefKeyObject struct {
19 Type string
20 }
21
22 type pinRefKeyList struct {
23 Keys map[string]pinRefKeyObject
24 }
25
26 type pin struct {
27 path path.ImmutablePath
28 typ string
29 name string
30 err error
31 }
32
33 func (p pin) Err() error {
34 return p.err
35 }
36
37 func (p pin) Path() path.ImmutablePath {
38 return p.path
39 }
40
41 func (p pin) Name() string {
42 return p.name
43 }
44
45 func (p pin) Type() string {
46 return p.typ
47 }
48
49 func (api *PinAPI) Add(ctx context.Context, p path.Path, opts ...caopts.PinAddOption) error {
50 options, err := caopts.PinAddOptions(opts...)
51 if err != nil {
52 return err
53 }
54
55 req := api.core().Request("pin/add", p.String()).
56 Option("recursive", options.Recursive)
57 if options.Name != "" {
58 req = req.Option("name", options.Name)
59 }
60 return req.Exec(ctx, nil)
61 }
62
63 type pinLsObject struct {
64 Cid string
65 Name string
66 Type string
67 }
68
69 func (api *PinAPI) Ls(ctx context.Context, pins chan<- iface.Pin, opts ...caopts.PinLsOption) error {
70 defer close(pins)
71
72 options, err := caopts.PinLsOptions(opts...)
73 if err != nil {
74 return err
75 }
76
77 res, err := api.core().Request("pin/ls").
78 Option("type", options.Type).
79 Option("names", options.Detailed).
80 Option("stream", true).
81 Send(ctx)
82 if err != nil {
83 return err
84 }
85 defer res.Output.Close()
86
87 dec := json.NewDecoder(res.Output)
88 for {
89 var out pinLsObject
90 err := dec.Decode(&out)
91 if err != nil {
92 if err != io.EOF {
93 return err
94 }
95 return nil
96 }
97
98 c, err := cid.Parse(out.Cid)
99 if err != nil {
100 return err
101 }
102
103 select {
104 case pins <- pin{typ: out.Type, name: out.Name, path: path.FromCid(c)}:
105 case <-ctx.Done():
106 return ctx.Err()
107 }
108 }
109 }
110
111 // IsPinned returns whether or not the given cid is pinned
112 // and an explanation of why its pinned.
113 func (api *PinAPI) IsPinned(ctx context.Context, p path.Path, opts ...caopts.PinIsPinnedOption) (string, bool, error) {
114 options, err := caopts.PinIsPinnedOptions(opts...)
115 if err != nil {
116 return "", false, err
117 }
118 var out pinRefKeyList
119 err = api.core().Request("pin/ls").
120 Option("type", options.WithType).
121 Option("arg", p.String()).
122 Exec(ctx, &out)
123 if err != nil {
124 // TODO: This error-type discrimination based on sub-string matching is brittle.
125 // It is addressed by this open issue: https://github.com/ipfs/go-ipfs/issues/7563
126 if strings.Contains(err.Error(), "is not pinned") {
127 return "", false, nil
128 }
129 return "", false, err
130 }
131
132 for _, obj := range out.Keys {
133 return obj.Type, true, nil
134 }
135 return "", false, errors.New("http api returned no error and no results")
136 }
137
138 func (api *PinAPI) Rm(ctx context.Context, p path.Path, opts ...caopts.PinRmOption) error {
139 options, err := caopts.PinRmOptions(opts...)
140 if err != nil {
141 return err
142 }
143
144 return api.core().Request("pin/rm", p.String()).
145 Option("recursive", options.Recursive).
146 Exec(ctx, nil)
147 }
148
149 func (api *PinAPI) Update(ctx context.Context, from path.Path, to path.Path, opts ...caopts.PinUpdateOption) error {
150 options, err := caopts.PinUpdateOptions(opts...)
151 if err != nil {
152 return err
153 }
154
155 return api.core().Request("pin/update", from.String(), to.String()).
156 Option("unpin", options.Unpin).Exec(ctx, nil)
157 }
158
159 type pinVerifyRes struct {
160 ok bool
161 badNodes []iface.BadPinNode
162 err error
163 }
164
165 func (r pinVerifyRes) Ok() bool {
166 return r.ok
167 }
168
169 func (r pinVerifyRes) BadNodes() []iface.BadPinNode {
170 return r.badNodes
171 }
172
173 func (r pinVerifyRes) Err() error {
174 return r.err
175 }
176
177 type badNode struct {
178 err error
179 cid cid.Cid
180 }
181
182 func (n badNode) Path() path.ImmutablePath {
183 return path.FromCid(n.cid)
184 }
185
186 func (n badNode) Err() error {
187 return n.err
188 }
189
190 func (api *PinAPI) Verify(ctx context.Context) (<-chan iface.PinStatus, error) {
191 resp, err := api.core().Request("pin/verify").Option("verbose", true).Send(ctx)
192 if err != nil {
193 return nil, err
194 }
195 if resp.Error != nil {
196 return nil, resp.Error
197 }
198 res := make(chan iface.PinStatus)
199
200 go func() {
201 defer resp.Close()
202 defer close(res)
203 dec := json.NewDecoder(resp.Output)
204 for {
205 var out struct {
206 Cid string
207 Err string
208 Ok bool
209
210 BadNodes []struct {
211 Cid string
212 Err string
213 }
214 }
215 if err := dec.Decode(&out); err != nil {
216 if err == io.EOF {
217 return
218 }
219 select {
220 case res <- pinVerifyRes{err: err}:
221 return
222 case <-ctx.Done():
223 return
224 }
225 }
226
227 if out.Err != "" {
228 select {
229 case res <- pinVerifyRes{err: errors.New(out.Err)}:
230 return
231 case <-ctx.Done():
232 return
233 }
234 }
235
236 badNodes := make([]iface.BadPinNode, len(out.BadNodes))
237 for i, n := range out.BadNodes {
238 c, err := cid.Decode(n.Cid)
239 if err != nil {
240 badNodes[i] = badNode{cid: c, err: err}
241 continue
242 }
243
244 if n.Err != "" {
245 err = errors.New(n.Err)
246 }
247 badNodes[i] = badNode{cid: c, err: err}
248 }
249
250 select {
251 case res <- pinVerifyRes{ok: out.Ok, badNodes: badNodes}:
252 case <-ctx.Done():
253 return
254 }
255 }
256 }()
257
258 return res, nil
259 }
260
261 func (api *PinAPI) core() *HttpApi {
262 return (*HttpApi)(api)
263 }