237
vendor/github.com/status-im/status-go/ipfs/ipfs.go
generated
vendored
Normal file
237
vendor/github.com/status-im/status-go/ipfs/ipfs.go
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
package ipfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/wealdtech/go-multicodec"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/params"
|
||||
)
|
||||
|
||||
const maxRequestsPerSecond = 3
|
||||
|
||||
type taskResponse struct {
|
||||
err error
|
||||
response []byte
|
||||
}
|
||||
|
||||
type taskRequest struct {
|
||||
cid string
|
||||
download bool
|
||||
doneChan chan taskResponse
|
||||
}
|
||||
|
||||
type Downloader struct {
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
ipfsDir string
|
||||
wg sync.WaitGroup
|
||||
rateLimiterChan chan taskRequest
|
||||
inputTaskChan chan taskRequest
|
||||
client *http.Client
|
||||
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
func NewDownloader(rootDir string) *Downloader {
|
||||
ipfsDir := filepath.Clean(filepath.Join(rootDir, "./ipfs"))
|
||||
if err := os.MkdirAll(ipfsDir, 0700); err != nil {
|
||||
panic("could not create IPFSDir")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
|
||||
d := &Downloader{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
ipfsDir: ipfsDir,
|
||||
rateLimiterChan: make(chan taskRequest, maxRequestsPerSecond),
|
||||
inputTaskChan: make(chan taskRequest, 1000),
|
||||
wg: sync.WaitGroup{},
|
||||
client: &http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
},
|
||||
|
||||
quit: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
go d.taskDispatcher()
|
||||
go d.worker()
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Downloader) Stop() {
|
||||
close(d.quit)
|
||||
|
||||
d.cancel()
|
||||
|
||||
d.wg.Wait()
|
||||
|
||||
close(d.inputTaskChan)
|
||||
close(d.rateLimiterChan)
|
||||
}
|
||||
|
||||
func (d *Downloader) worker() {
|
||||
for request := range d.rateLimiterChan {
|
||||
resp, err := d.download(request.cid, request.download)
|
||||
request.doneChan <- taskResponse{
|
||||
err: err,
|
||||
response: resp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Downloader) taskDispatcher() {
|
||||
ticker := time.NewTicker(time.Second / maxRequestsPerSecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
<-ticker.C
|
||||
request, ok := <-d.inputTaskChan
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
d.rateLimiterChan <- request
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func hashToCid(hash []byte) (string, error) {
|
||||
// contract response includes a contenthash, which needs to be decoded to reveal
|
||||
// an IPFS identifier. Once decoded, download the content from IPFS. This content
|
||||
// is in EDN format, ie https://ipfs.infura.io/ipfs/QmWVVLwVKCwkVNjYJrRzQWREVvEk917PhbHYAUhA1gECTM
|
||||
// and it also needs to be decoded in to a nim type
|
||||
|
||||
data, codec, err := multicodec.RemoveCodec(hash)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
codecName, err := multicodec.Name(codec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if codecName != "ipfs-ns" {
|
||||
return "", errors.New("codecName is not ipfs-ns")
|
||||
}
|
||||
|
||||
thisCID, err := cid.Parse(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return thisCID.Hash().B58String(), nil
|
||||
}
|
||||
|
||||
func decodeStringHash(input string) (string, error) {
|
||||
hash, err := hexutil.Decode("0x" + input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cid, err := hashToCid(hash)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cid, nil
|
||||
}
|
||||
|
||||
// Get checks if an IPFS image exists and returns it from cache
|
||||
// otherwise downloads it from INFURA's ipfs gateway
|
||||
func (d *Downloader) Get(hash string, download bool) ([]byte, error) {
|
||||
cid, err := decodeStringHash(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exists, content, err := d.exists(cid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
doneChan := make(chan taskResponse, 1)
|
||||
|
||||
d.wg.Add(1)
|
||||
|
||||
d.inputTaskChan <- taskRequest{
|
||||
cid: cid,
|
||||
download: download,
|
||||
doneChan: doneChan,
|
||||
}
|
||||
|
||||
done := <-doneChan
|
||||
close(doneChan)
|
||||
|
||||
d.wg.Done()
|
||||
|
||||
return done.response, done.err
|
||||
}
|
||||
|
||||
func (d *Downloader) exists(cid string) (bool, []byte, error) {
|
||||
path := filepath.Join(d.ipfsDir, cid)
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
fileContent, err := os.ReadFile(path)
|
||||
return true, fileContent, err
|
||||
}
|
||||
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
func (d *Downloader) download(cid string, download bool) ([]byte, error) {
|
||||
path := filepath.Join(d.ipfsDir, cid)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, params.IpfsGatewayURL+cid, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req = req.WithContext(d.ctx)
|
||||
|
||||
resp, err := d.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Error("failed to close the stickerpack request body", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
log.Error("could not load data for", "cid", cid, "code", resp.StatusCode)
|
||||
return nil, errors.New("could not load ipfs data")
|
||||
}
|
||||
|
||||
fileContent, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if download {
|
||||
// #nosec G306
|
||||
err = os.WriteFile(path, fileContent, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return fileContent, nil
|
||||
}
|
||||
Reference in New Issue
Block a user