Skip to content
This repository was archived by the owner on Apr 18, 2024. It is now read-only.

Commit 8e2c190

Browse files
author
Alan Shaw
authored
feat: upload list (#9)
resolves #5
1 parent a335720 commit 8e2c190

File tree

10 files changed

+203
-61
lines changed

10 files changed

+203
-61
lines changed

client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ import (
1515
w3http "github.com/web3-storage/go-w3s-client/http"
1616
)
1717

18+
const clientName = "web3.storage/go"
19+
1820
// Client is a HTTP API client to the web3.storage service.
1921
type Client interface {
2022
Get(context.Context, cid.Cid) (*w3http.Web3Response, error)
2123
Put(context.Context, fs.File, ...PutOption) (cid.Cid, error)
2224
PutCar(context.Context, io.Reader) (cid.Cid, error)
2325
Status(context.Context, cid.Cid) (*Status, error)
26+
List(context.Context, ...ListOption) (*UploadIterator, error)
2427
}
2528

2629
type clientConfig struct {
@@ -57,3 +60,5 @@ func NewClient(options ...Option) (Client, error) {
5760
}
5861
return &c, nil
5962
}
63+
64+
var _ Client = (*client)(nil)

example/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,8 @@ github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpP
12241224
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
12251225
github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e/go.mod h1:XDKHRm5ThF8YJjx001LtgelzsoaEcvnA7lVWz9EeX3g=
12261226
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
1227+
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
1228+
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
12271229
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
12281230
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
12291231
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=

example/main.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"context"
55
"fmt"
6+
"io"
67
"io/fs"
78
"os"
89

@@ -24,8 +25,9 @@ func main() {
2425

2526
// cid := putSingleFile(c)
2627
// getStatusForCid(c, cid)
27-
28+
// getStatusForKnownCid(c)
2829
getFiles(c)
30+
// listUploads(c)
2931
}
3032

3133
func putSingleFile(c w3s.Client) cid.Cid {
@@ -90,7 +92,7 @@ func getStatusForCid(c w3s.Client, cid cid.Cid) {
9092
}
9193

9294
func getStatusForKnownCid(c w3s.Client) {
93-
cid, _ := cid.Parse("bafybeig7qnlzyregxe2m63b4kkpx3ujqm5bwmn5wtvtftp7j27tmdtznji")
95+
cid, _ := cid.Parse("bafybeiauyddeo2axgargy56kwxirquxaxso3nobtjtjvoqu552oqciudrm")
9496
getStatusForCid(c, cid)
9597
}
9698

@@ -125,3 +127,23 @@ func getFiles(c w3s.Client) {
125127
fmt.Printf("%s (%d bytes)\n", cid.String(), info.Size())
126128
}
127129
}
130+
131+
func listUploads(c w3s.Client) {
132+
uploads, err := c.List(context.Background())
133+
if err != nil {
134+
panic(err)
135+
}
136+
137+
for {
138+
u, err := uploads.Next()
139+
if err != nil {
140+
// finished successfully
141+
if err == io.EOF {
142+
break
143+
}
144+
panic(err)
145+
}
146+
147+
fmt.Printf("%s %s Size: %d Deals: %d Pins: %d\n", u.Created.Format("2006-01-02 15:04:05"), u.Cid, u.DagSize, len(u.Deals), len(u.Pins))
148+
}
149+
}

get.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import (
1010
)
1111

1212
func (c *client) Get(ctx context.Context, cid cid.Cid) (*w3http.Web3Response, error) {
13-
req, err := http.NewRequest("GET", fmt.Sprintf("%s/car/%s", c.cfg.endpoint, cid), nil)
13+
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/car/%s", c.cfg.endpoint, cid), nil)
1414
if err != nil {
1515
return nil, err
1616
}
1717
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.cfg.token))
18-
req.Header.Add("X-Client", "web3.storage/go")
18+
req.Header.Add("X-Client", clientName)
1919
res, err := c.hc.Do(req)
2020
return w3http.NewWeb3Response(res, c.bsvc), err
2121
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ require (
2424
github.com/ipld/go-codec-dagpb v1.3.0
2525
github.com/ipld/go-ipld-prime v0.12.3
2626
github.com/libp2p/go-libp2p-core v0.9.0
27+
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
2728
)

go.sum

Lines changed: 4 additions & 53 deletions
Large diffs are not rendered by default.

list.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package w3s
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"net/url"
10+
"time"
11+
12+
"github.com/tomnomnom/linkheader"
13+
)
14+
15+
const maxPageSize = 100
16+
17+
type UploadIterator struct {
18+
paginator *pageIterator
19+
max int
20+
count int
21+
page []*Status
22+
}
23+
24+
// Next retrieves status information for the next upload in the list.
25+
func (li *UploadIterator) Next() (*Status, error) {
26+
li.count++
27+
if li.max > 0 && li.count > li.max {
28+
return nil, io.EOF
29+
}
30+
if len(li.page) > 0 {
31+
item := li.page[0]
32+
li.page = li.page[1:]
33+
return item, nil
34+
}
35+
res, err := li.paginator.Next()
36+
if err != nil {
37+
return nil, err
38+
}
39+
var page []*Status
40+
d := json.NewDecoder(res.Body)
41+
err = d.Decode(&page)
42+
if err != nil {
43+
return nil, err
44+
}
45+
li.page = page
46+
if len(li.page) > 0 {
47+
item := li.page[0]
48+
li.page = li.page[1:]
49+
return item, nil
50+
}
51+
return nil, io.EOF
52+
}
53+
54+
type listConfig struct {
55+
before time.Time
56+
maxResults int
57+
}
58+
59+
// List retrieves the list of uploads to Web3.Storage.
60+
func (c *client) List(ctx context.Context, options ...ListOption) (*UploadIterator, error) {
61+
var cfg listConfig
62+
for _, opt := range options {
63+
if err := opt(&cfg); err != nil {
64+
return nil, err
65+
}
66+
}
67+
68+
fetchNextPage := func(url string) (*http.Response, error) {
69+
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s%s", c.cfg.endpoint, url), nil)
70+
if err != nil {
71+
return nil, err
72+
}
73+
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.cfg.token))
74+
req.Header.Add("Access-Control-Request-Headers", "Link")
75+
req.Header.Add("X-Client", clientName)
76+
res, err := c.hc.Do(req)
77+
if err != nil {
78+
return nil, err
79+
}
80+
if res.StatusCode != 200 {
81+
return nil, fmt.Errorf("unexpected response status: %d", res.StatusCode)
82+
}
83+
return res, nil
84+
}
85+
86+
var before string
87+
if cfg.before.IsZero() {
88+
before = time.Now().Format(iso8601)
89+
} else {
90+
before = cfg.before.Format(iso8601)
91+
}
92+
93+
size := cfg.maxResults
94+
if size > maxPageSize {
95+
size = maxPageSize
96+
}
97+
98+
var urlPath string
99+
if size <= 0 {
100+
urlPath = fmt.Sprintf("/user/uploads?before=%s", url.QueryEscape(before))
101+
} else {
102+
urlPath = fmt.Sprintf("/user/uploads?before=%s&size=%d", url.QueryEscape(before), size)
103+
}
104+
105+
return &UploadIterator{
106+
paginator: newPageIterator(urlPath, fetchNextPage),
107+
max: cfg.maxResults,
108+
}, nil
109+
}
110+
111+
type pageIterator struct {
112+
nextURL string
113+
fetchNextPage func(string) (*http.Response, error)
114+
}
115+
116+
func newPageIterator(url string, fetchNextPage func(string) (*http.Response, error)) *pageIterator {
117+
return &pageIterator{
118+
nextURL: url,
119+
fetchNextPage: fetchNextPage,
120+
}
121+
}
122+
123+
func (pi *pageIterator) Next() (*http.Response, error) {
124+
res, err := pi.fetchNextPage(pi.nextURL)
125+
if err != nil {
126+
return nil, err
127+
}
128+
linkHdrs := res.Header["Link"]
129+
if len(linkHdrs) == 0 {
130+
return nil, io.EOF
131+
}
132+
links := linkheader.Parse(linkHdrs[0])
133+
for _, l := range links {
134+
if l.Rel == "next" {
135+
pi.nextURL = links[0].URL
136+
return res, nil
137+
}
138+
}
139+
return nil, io.EOF
140+
}

opts.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package w3s
22

33
import (
44
"io/fs"
5+
"time"
56

67
ds "github.com/ipfs/go-datastore"
78
)
@@ -64,3 +65,23 @@ func WithDirname(dirname string) PutOption {
6465
return nil
6566
}
6667
}
68+
69+
// ListOption is an option configuring a call to List.
70+
type ListOption func(cfg *listConfig) error
71+
72+
// WithBefore sets the time that items in the list were uploaded before.
73+
func WithBefore(before time.Time) ListOption {
74+
return func(cfg *listConfig) error {
75+
cfg.before = before
76+
return nil
77+
}
78+
}
79+
80+
// WithMaxResults sets the maximum number of results that will be available from
81+
// the iterator.
82+
func WithMaxResults(maxResults int) ListOption {
83+
return func(cfg *listConfig) error {
84+
cfg.maxResults = maxResults
85+
return nil
86+
}
87+
}

put.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (c *client) sendCar(ctx context.Context, r io.Reader) (cid.Cid, error) {
120120
}
121121
req.Header.Add("Content-Type", "application/car")
122122
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.cfg.token))
123-
req.Header.Add("X-Client", "web3.storage/go")
123+
req.Header.Add("X-Client", clientName)
124124
res, err := c.hc.Do(req)
125125
if err != nil {
126126
return cid.Undef, err

status.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
peer "github.com/libp2p/go-libp2p-core/peer"
1414
)
1515

16-
const iso8601 = "2006-01-02T15:04:05Z0700"
16+
const iso8601 = "2006-01-02T15:04:05.999Z07:00"
1717

1818
type PinStatus int
1919

@@ -204,12 +204,12 @@ func (s *Status) UnmarshalJSON(b []byte) error {
204204
}
205205

206206
func (c *client) Status(ctx context.Context, cid cid.Cid) (*Status, error) {
207-
req, err := http.NewRequest("GET", fmt.Sprintf("%s/status/%s", c.cfg.endpoint, cid), nil)
207+
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/status/%s", c.cfg.endpoint, cid), nil)
208208
if err != nil {
209209
return nil, err
210210
}
211211
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.cfg.token))
212-
req.Header.Add("X-Client", "web3.storage/go")
212+
req.Header.Add("X-Client", clientName)
213213
res, err := c.hc.Do(req)
214214
if err != nil {
215215
return nil, err

0 commit comments

Comments
 (0)