mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 13:46:43 +00:00
integrate RPC v2 via develop
* integrate #2035 via latest develop. fixes #2041 * refactor apis * fix tests for windows, add simple download test * separate dns api and chequebook api * http proxy api in separate subpackage * remove legacy rpc code partially, adding only legacy to make v2 work * TODO: add dns tests see #2048 * TODO: further refactor due to #2040
This commit is contained in:
parent
3ab152ebaf
commit
9d2605ee32
21 changed files with 942 additions and 1300 deletions
|
|
@ -1,83 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/swarm"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
)
|
||||
|
||||
var port = 8500
|
||||
|
||||
func bzzREPL(t *testing.T, configf func(*api.Config)) (string, string, *testjethre, *node.Node) {
|
||||
prvKey, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatal("unable to generate key")
|
||||
}
|
||||
bzztmp, err := ioutil.TempDir("", "bzz-js-test")
|
||||
config, err := api.NewConfig(bzztmp, common.Address{}, prvKey)
|
||||
if err != nil {
|
||||
t.Fatal("unable to configure swarm")
|
||||
}
|
||||
if configf != nil {
|
||||
configf(config)
|
||||
}
|
||||
tmp, repl, stack := testREPL(t, func(n *node.Node) {
|
||||
if err := n.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
return swarm.NewSwarm(ctx, config, false)
|
||||
}); err != nil {
|
||||
t.Fatalf("Failed to register the Swarm service: %v", err)
|
||||
}
|
||||
})
|
||||
return bzztmp, tmp, repl, stack
|
||||
}
|
||||
|
||||
func withREPL(t *testing.T, cf func(*api.Config), f func(repl *testjethre)) {
|
||||
bzztmp, tmp, repl, stack := bzzREPL(t, cf)
|
||||
defer stack.Stop()
|
||||
defer os.RemoveAll(tmp)
|
||||
defer os.RemoveAll(bzztmp)
|
||||
f(repl)
|
||||
}
|
||||
|
||||
func TestBzzPutGet(t *testing.T) {
|
||||
withREPL(t,
|
||||
func(c *api.Config) {
|
||||
c.Port = ""
|
||||
}, func(repl *testjethre) {
|
||||
if checkEvalJSON(t, repl, `hash = bzz.put("console.log(\"hello from console\")", "application/javascript")`, `"97f1b7c7ea12468fd37c262383b9aa862d0cfbc4fc7218652374679fc5cf40cd"`) != nil {
|
||||
return
|
||||
}
|
||||
want := `{"content":"console.log(\"hello from console\")","contentType":"application/javascript","size":"33","status":"0"}`
|
||||
if checkEvalJSON(t, repl, `bzz.get(hash)`, want) != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// the server can be initialized only once per test session !
|
||||
// until we implement a stoppable http server
|
||||
// further http tests will need to make sure the correct server is running
|
||||
func TestHTTP(t *testing.T) {
|
||||
withREPL(t, nil, func(repl *testjethre) {
|
||||
if checkEvalJSON(t, repl, `hash = bzz.put("f42 = function() { return 42 }", "application/javascript")`, `"e6847876f00102441f850b2d438a06d10e3bf24e6a0a76d47b073a86c3c2f9ac"`) != nil {
|
||||
return
|
||||
}
|
||||
if checkEvalJSON(t, repl, `admin.httpGet("bzz://"+hash)`, `"f42 = function() { return 42 }"`) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if checkEvalJSON(t, repl, `http.loadScript("bzz://"+hash)`, `true`) != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// if checkEvalJSON(t, repl, `f42()`, `42`) != nil {
|
||||
// return
|
||||
// }
|
||||
})
|
||||
}
|
||||
29
common/chequebook/api.go
Normal file
29
common/chequebook/api.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package chequebook
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
const Version = "1.0"
|
||||
|
||||
type Api struct {
|
||||
ch *Chequebook
|
||||
}
|
||||
|
||||
func NewApi(ch *Chequebook) *Api {
|
||||
return &Api{ch}
|
||||
}
|
||||
|
||||
func (self *Api) Issue(beneficiary common.Address, amount *big.Int) (cheque *Cheque, err error) {
|
||||
return self.ch.Issue(beneficiary, amount)
|
||||
}
|
||||
|
||||
func (self *Api) Cash(cheque *Cheque) (txhash string, err error) {
|
||||
return self.ch.Cash(cheque)
|
||||
}
|
||||
|
||||
func (self *Api) Deposit(amount *big.Int) (txhash string, err error) {
|
||||
return self.ch.Deposit(amount)
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ const (
|
|||
TxPoolApiName = "txpool"
|
||||
PersonalApiName = "personal"
|
||||
Web3ApiName = "web3"
|
||||
ChequebookApiName = "chequebook"
|
||||
|
||||
JsonRpcVersion = "2.0"
|
||||
)
|
||||
|
|
@ -42,7 +43,7 @@ const (
|
|||
var (
|
||||
// All API's
|
||||
AllApis = strings.Join([]string{
|
||||
AdminApiName, BzzApiName, DbApiName, EthApiName, DebugApiName, MinerApiName, NetApiName,
|
||||
AdminApiName, BzzApiName, ChequebookApiName, DbApiName, EthApiName, DebugApiName, MinerApiName, NetApiName,
|
||||
ShhApiName, TxPoolApiName, PersonalApiName, Web3ApiName,
|
||||
}, ",")
|
||||
)
|
||||
|
|
|
|||
278
rpc/api/bzz.go
278
rpc/api/bzz.go
|
|
@ -1,278 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/rpc/codec"
|
||||
"github.com/ethereum/go-ethereum/rpc/shared"
|
||||
"github.com/ethereum/go-ethereum/swarm"
|
||||
)
|
||||
|
||||
const (
|
||||
BzzApiVersion = "1.0"
|
||||
)
|
||||
|
||||
// eth api provider
|
||||
// See https://github.com/ethereum/wiki/wiki/JSON-RPC
|
||||
type bzzApi struct {
|
||||
swarm *swarm.Swarm
|
||||
methods map[string]bzzhandler
|
||||
codec codec.ApiCoder
|
||||
}
|
||||
|
||||
// eth callback handler
|
||||
type bzzhandler func(*bzzApi, *shared.Request) (interface{}, error)
|
||||
|
||||
var (
|
||||
bzzMapping = map[string]bzzhandler{
|
||||
"bzz_info": (*bzzApi).Info,
|
||||
"bzz_issue": (*bzzApi).Issue,
|
||||
"bzz_cash": (*bzzApi).Cash,
|
||||
"bzz_deposit": (*bzzApi).Deposit,
|
||||
"bzz_register": (*bzzApi).Register,
|
||||
"bzz_resolve": (*bzzApi).Resolve,
|
||||
"bzz_download": (*bzzApi).Download,
|
||||
"bzz_upload": (*bzzApi).Upload,
|
||||
"bzz_get": (*bzzApi).Get,
|
||||
"bzz_put": (*bzzApi).Put,
|
||||
"bzz_modify": (*bzzApi).Modify,
|
||||
}
|
||||
)
|
||||
|
||||
func newSwarmOfflineError(method string) error {
|
||||
return shared.NewNotAvailableError(method, "swarm offline")
|
||||
}
|
||||
|
||||
// create new bzzApi instance
|
||||
func NewBzzApi(stack *node.Node, codec codec.Codec) *bzzApi {
|
||||
var swarm *swarm.Swarm
|
||||
stack.Service(&swarm)
|
||||
return &bzzApi{swarm, bzzMapping, codec.New(nil)}
|
||||
}
|
||||
|
||||
// collection with supported methods
|
||||
func (self *bzzApi) Methods() []string {
|
||||
methods := make([]string, len(self.methods))
|
||||
i := 0
|
||||
for k := range self.methods {
|
||||
methods[i] = k
|
||||
i++
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
// Execute given request
|
||||
func (self *bzzApi) Execute(req *shared.Request) (interface{}, error) {
|
||||
if callback, ok := self.methods[req.Method]; ok {
|
||||
return callback(self, req)
|
||||
}
|
||||
|
||||
return nil, shared.NewNotImplementedError(req.Method)
|
||||
}
|
||||
|
||||
func (self *bzzApi) Name() string {
|
||||
return shared.BzzApiName
|
||||
}
|
||||
|
||||
func (self *bzzApi) ApiVersion() string {
|
||||
return BzzApiVersion
|
||||
}
|
||||
|
||||
func (self *bzzApi) Info(req *shared.Request) (interface{}, error) {
|
||||
s := self.swarm
|
||||
if s == nil {
|
||||
return nil, newSwarmOfflineError(req.Method)
|
||||
}
|
||||
return s.Api().Info(), nil
|
||||
}
|
||||
|
||||
func (self *bzzApi) Issue(req *shared.Request) (interface{}, error) {
|
||||
s := self.swarm
|
||||
if s == nil {
|
||||
return nil, newSwarmOfflineError(req.Method)
|
||||
}
|
||||
|
||||
args := new(BzzIssueArgs)
|
||||
if err := self.codec.Decode(req.Params, &args); err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
cheque, err := s.Api().Issue(common.HexToAddress(args.Beneficiary), args.Amount)
|
||||
if err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(cheque, " ", "")
|
||||
if err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
func (self *bzzApi) Cash(req *shared.Request) (interface{}, error) {
|
||||
s := self.swarm
|
||||
if s == nil {
|
||||
return nil, newSwarmOfflineError(req.Method)
|
||||
}
|
||||
|
||||
args := new(BzzCashArgs)
|
||||
if err := self.codec.Decode(req.Params, &args); err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
return s.Api().Cash(args.Cheque)
|
||||
|
||||
}
|
||||
|
||||
func (self *bzzApi) Deposit(req *shared.Request) (interface{}, error) {
|
||||
s := self.swarm
|
||||
if s == nil {
|
||||
return nil, newSwarmOfflineError(req.Method)
|
||||
}
|
||||
|
||||
args := new(BzzDepositArgs)
|
||||
if err := self.codec.Decode(req.Params, &args); err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
return s.Api().Deposit(args.Amount)
|
||||
}
|
||||
|
||||
func (self *bzzApi) Register(req *shared.Request) (interface{}, error) {
|
||||
s := self.swarm
|
||||
if s == nil {
|
||||
return nil, newSwarmOfflineError(req.Method)
|
||||
}
|
||||
|
||||
args := new(BzzRegisterArgs)
|
||||
if err := self.codec.Decode(req.Params, &args); err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
err := s.Api().Register(common.HexToAddress(args.Address), args.Domain, common.HexToHash(args.ContentHash))
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func (self *bzzApi) Resolve(req *shared.Request) (interface{}, error) {
|
||||
s := self.swarm
|
||||
if s == nil {
|
||||
return nil, newSwarmOfflineError(req.Method)
|
||||
}
|
||||
|
||||
args := new(BzzResolveArgs)
|
||||
if err := self.codec.Decode(req.Params, &args); err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
key, err := s.Api().Resolve(args.Domain)
|
||||
return key.Hex(), err
|
||||
}
|
||||
|
||||
func (self *bzzApi) Download(req *shared.Request) (interface{}, error) {
|
||||
|
||||
s := self.swarm
|
||||
if s == nil {
|
||||
return nil, newSwarmOfflineError(req.Method)
|
||||
}
|
||||
|
||||
args := new(BzzDownloadArgs)
|
||||
if err := self.codec.Decode(req.Params, &args); err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
err := s.Api().Download(args.BzzPath, args.LocalPath)
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func (self *bzzApi) Upload(req *shared.Request) (interface{}, error) {
|
||||
|
||||
s := self.swarm
|
||||
if s == nil {
|
||||
return nil, newSwarmOfflineError(req.Method)
|
||||
}
|
||||
|
||||
args := new(BzzUploadArgs)
|
||||
if err := self.codec.Decode(req.Params, &args); err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
return s.Api().Upload(args.LocalPath, args.Index)
|
||||
}
|
||||
|
||||
func (self *bzzApi) Get(req *shared.Request) (interface{}, error) {
|
||||
|
||||
s := self.swarm
|
||||
if s == nil {
|
||||
return nil, newSwarmOfflineError(req.Method)
|
||||
}
|
||||
|
||||
args := new(BzzGetArgs)
|
||||
if err := self.codec.Decode(req.Params, &args); err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
var content []byte
|
||||
var mimeType string
|
||||
var status, size int
|
||||
var err error
|
||||
content, mimeType, status, size, err = s.Api().Get(args.Path)
|
||||
|
||||
obj := map[string]string{
|
||||
"content": string(content),
|
||||
"contentType": mimeType,
|
||||
"status": fmt.Sprintf("%v", status),
|
||||
"size": fmt.Sprintf("%v", size),
|
||||
}
|
||||
|
||||
return obj, err
|
||||
}
|
||||
|
||||
func (self *bzzApi) Put(req *shared.Request) (interface{}, error) {
|
||||
|
||||
s := self.swarm
|
||||
if s == nil {
|
||||
return nil, newSwarmOfflineError(req.Method)
|
||||
}
|
||||
|
||||
args := new(BzzPutArgs)
|
||||
if err := self.codec.Decode(req.Params, &args); err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
return s.Api().Put(args.Content, args.ContenType)
|
||||
}
|
||||
|
||||
func (self *bzzApi) Modify(req *shared.Request) (interface{}, error) {
|
||||
|
||||
s := self.swarm
|
||||
if s == nil {
|
||||
return nil, newSwarmOfflineError(req.Method)
|
||||
}
|
||||
|
||||
args := new(BzzModifyArgs)
|
||||
if err := self.codec.Decode(req.Params, &args); err != nil {
|
||||
return nil, shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
return s.Api().Modify(args.RootHash, args.Path, args.ContentHash, args.ContentType)
|
||||
}
|
||||
|
|
@ -1,322 +0,0 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/chequebook"
|
||||
"github.com/ethereum/go-ethereum/rpc/shared"
|
||||
)
|
||||
|
||||
type BzzDepositArgs struct {
|
||||
Amount *big.Int
|
||||
}
|
||||
|
||||
func (args *BzzDepositArgs) UnmarshalJSON(b []byte) (err error) {
|
||||
var obj []interface{}
|
||||
if err := json.Unmarshal(b, &obj); err != nil {
|
||||
return shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
if len(obj) < 1 {
|
||||
return shared.NewInsufficientParamsError(len(obj), 1)
|
||||
}
|
||||
|
||||
amount, ok := obj[0].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Amount", "not a string")
|
||||
}
|
||||
args.Amount, ok = new(big.Int).SetString(amount, 10)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Amount", "not a number")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BzzCashArgs struct {
|
||||
Cheque *chequebook.Cheque
|
||||
}
|
||||
|
||||
func (args *BzzCashArgs) UnmarshalJSON(b []byte) (err error) {
|
||||
var obj []interface{}
|
||||
if err := json.Unmarshal(b, &obj); err != nil {
|
||||
return shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
if len(obj) < 1 {
|
||||
return shared.NewInsufficientParamsError(len(obj), 1)
|
||||
}
|
||||
|
||||
chequestr, ok := obj[0].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Cheque", "not a string")
|
||||
}
|
||||
var cheque chequebook.Cheque
|
||||
err = json.Unmarshal([]byte(chequestr), &cheque)
|
||||
if err != nil {
|
||||
return shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
args.Cheque = &cheque
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BzzIssueArgs struct {
|
||||
Beneficiary string
|
||||
Amount *big.Int
|
||||
}
|
||||
|
||||
func (args *BzzIssueArgs) UnmarshalJSON(b []byte) (err error) {
|
||||
var obj []interface{}
|
||||
if err := json.Unmarshal(b, &obj); err != nil {
|
||||
return shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
if len(obj) < 2 {
|
||||
return shared.NewInsufficientParamsError(len(obj), 2)
|
||||
}
|
||||
|
||||
beneficiary, ok := obj[0].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Amount", "not a string")
|
||||
}
|
||||
args.Beneficiary = beneficiary
|
||||
|
||||
amount, ok := obj[1].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Amount", "not a string")
|
||||
}
|
||||
args.Amount, ok = new(big.Int).SetString(amount, 10)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Amount", "not a number")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BzzRegisterArgs struct {
|
||||
Address, ContentHash, Domain string
|
||||
}
|
||||
|
||||
func (args *BzzRegisterArgs) UnmarshalJSON(b []byte) (err error) {
|
||||
var obj []interface{}
|
||||
if err := json.Unmarshal(b, &obj); err != nil {
|
||||
return shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
if len(obj) < 3 {
|
||||
return shared.NewInsufficientParamsError(len(obj), 1)
|
||||
}
|
||||
|
||||
addstr, ok := obj[0].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Address", "not a string")
|
||||
}
|
||||
args.Address = addstr
|
||||
|
||||
addstr, ok = obj[1].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Domain", "not a string")
|
||||
}
|
||||
args.Domain = addstr
|
||||
|
||||
addstr, ok = obj[2].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("ContentHash", "not a string")
|
||||
}
|
||||
args.ContentHash = addstr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BzzResolveArgs struct {
|
||||
Domain string
|
||||
}
|
||||
|
||||
func (args *BzzResolveArgs) UnmarshalJSON(b []byte) (err error) {
|
||||
var obj []interface{}
|
||||
if err := json.Unmarshal(b, &obj); err != nil {
|
||||
return shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
if len(obj) < 1 {
|
||||
return shared.NewInsufficientParamsError(len(obj), 1)
|
||||
}
|
||||
|
||||
addstr, ok := obj[0].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Domain", "not a string")
|
||||
}
|
||||
args.Domain = addstr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BzzDownloadArgs struct {
|
||||
BzzPath, LocalPath string
|
||||
}
|
||||
|
||||
func (args *BzzDownloadArgs) UnmarshalJSON(b []byte) (err error) {
|
||||
var obj []interface{}
|
||||
if err := json.Unmarshal(b, &obj); err != nil {
|
||||
return shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
if len(obj) < 2 {
|
||||
return shared.NewInsufficientParamsError(len(obj), 1)
|
||||
}
|
||||
|
||||
addstr, ok := obj[0].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("BzzPath", "not a string")
|
||||
}
|
||||
args.BzzPath = addstr
|
||||
|
||||
addstr, ok = obj[1].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("LocalPath", "not a string")
|
||||
}
|
||||
args.LocalPath = addstr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BzzUploadArgs struct {
|
||||
LocalPath, Index string
|
||||
}
|
||||
|
||||
func (args *BzzUploadArgs) UnmarshalJSON(b []byte) (err error) {
|
||||
var obj []interface{}
|
||||
if err := json.Unmarshal(b, &obj); err != nil {
|
||||
return shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
if len(obj) < 1 {
|
||||
return shared.NewInsufficientParamsError(len(obj), 1)
|
||||
}
|
||||
|
||||
addstr, ok := obj[0].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("LocalPath", "not a string")
|
||||
}
|
||||
args.LocalPath = addstr
|
||||
|
||||
if len(obj) > 1 {
|
||||
addstr, ok := obj[1].(string)
|
||||
if ok {
|
||||
args.Index = addstr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BzzGetArgs struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (args *BzzGetArgs) UnmarshalJSON(b []byte) (err error) {
|
||||
var obj []interface{}
|
||||
if err := json.Unmarshal(b, &obj); err != nil {
|
||||
return shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
if len(obj) < 1 {
|
||||
return shared.NewInsufficientParamsError(len(obj), 1)
|
||||
}
|
||||
|
||||
addstr, ok := obj[0].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Path", "not a string")
|
||||
}
|
||||
args.Path = addstr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BzzPutArgs struct {
|
||||
Content, ContenType string
|
||||
}
|
||||
|
||||
func (args *BzzPutArgs) UnmarshalJSON(b []byte) (err error) {
|
||||
var obj []interface{}
|
||||
if err := json.Unmarshal(b, &obj); err != nil {
|
||||
return shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
if len(obj) < 1 {
|
||||
return shared.NewInsufficientParamsError(len(obj), 1)
|
||||
}
|
||||
|
||||
addstr, ok := obj[0].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Content", "not a string")
|
||||
}
|
||||
args.Content = addstr
|
||||
|
||||
addstr, ok = obj[1].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("ContenType", "not a string")
|
||||
}
|
||||
args.ContenType = addstr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BzzModifyArgs struct {
|
||||
RootHash, Path, ContentHash, ContentType string
|
||||
}
|
||||
|
||||
func (args *BzzModifyArgs) UnmarshalJSON(b []byte) (err error) {
|
||||
var obj []interface{}
|
||||
if err := json.Unmarshal(b, &obj); err != nil {
|
||||
return shared.NewDecodeParamError(err.Error())
|
||||
}
|
||||
|
||||
if len(obj) < 2 {
|
||||
return shared.NewInsufficientParamsError(len(obj), 1)
|
||||
}
|
||||
|
||||
addstr, ok := obj[0].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("RootHash", "not a string")
|
||||
}
|
||||
args.RootHash = addstr
|
||||
|
||||
addstr, ok = obj[1].(string)
|
||||
if !ok {
|
||||
return shared.NewInvalidTypeError("Path", "not a string")
|
||||
}
|
||||
args.Path = addstr
|
||||
|
||||
if len(obj) >= 4 {
|
||||
addstr, ok = obj[2].(string)
|
||||
if ok {
|
||||
args.ContentHash = addstr
|
||||
}
|
||||
|
||||
addstr, ok = obj[3].(string)
|
||||
if ok {
|
||||
args.ContentType = addstr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
50
rpc/api/chequebook_js.go
Normal file
50
rpc/api/chequebook_js.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package api
|
||||
|
||||
const Chequebook_JS = `
|
||||
web3._extend({
|
||||
property: 'chequebook',
|
||||
methods:
|
||||
[
|
||||
new web3._extend.Method({
|
||||
name: 'deposit',
|
||||
call: 'chequebook_deposit',
|
||||
params: 1,
|
||||
inputFormatter: [null]
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'info',
|
||||
call: 'chequebook_info',
|
||||
params: 1,
|
||||
inputFormatter: [null]
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'cash',
|
||||
call: 'chequebook_cash',
|
||||
params: 1,
|
||||
inputFormatter: [null]
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'issue',
|
||||
call: 'chequebook_issue',
|
||||
params: 2,
|
||||
inputFormatter: [null, null]
|
||||
}),
|
||||
]
|
||||
});
|
||||
`
|
||||
|
|
@ -64,10 +64,8 @@ var (
|
|||
"verbosity",
|
||||
},
|
||||
"bzz": []string{
|
||||
"version",
|
||||
"info",
|
||||
"issue",
|
||||
"cash",
|
||||
"deposit",
|
||||
"register",
|
||||
"resolve",
|
||||
"download",
|
||||
|
|
@ -76,6 +74,13 @@ var (
|
|||
"put",
|
||||
"modify",
|
||||
},
|
||||
"chequebook": []string{
|
||||
"version",
|
||||
"info",
|
||||
"issue",
|
||||
"cash",
|
||||
"deposit",
|
||||
},
|
||||
"db": []string{
|
||||
"getString",
|
||||
"putString",
|
||||
|
|
@ -129,7 +134,7 @@ var (
|
|||
"sign",
|
||||
"syncing",
|
||||
},
|
||||
"miner": []string{
|
||||
"miner ": []string{
|
||||
"hashrate",
|
||||
"makeDAG",
|
||||
"setEtherbase",
|
||||
|
|
@ -195,7 +200,7 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, stack *no
|
|||
case shared.AdminApiName:
|
||||
apis[i] = NewAdminApi(xeth, stack, codec)
|
||||
case shared.BzzApiName:
|
||||
apis[i] = NewBzzApi(stack, codec)
|
||||
case shared.ChequebookApiName:
|
||||
case shared.DebugApiName:
|
||||
apis[i] = NewDebugApi(xeth, eth, codec)
|
||||
case shared.DbApiName:
|
||||
|
|
@ -229,6 +234,8 @@ func Javascript(name string) string {
|
|||
return Admin_JS
|
||||
case shared.BzzApiName:
|
||||
return Bzz_JS
|
||||
case shared.ChequebookApiName:
|
||||
return Chequebook_JS
|
||||
case shared.DebugApiName:
|
||||
return Debug_JS
|
||||
case shared.DbApiName:
|
||||
|
|
|
|||
478
swarm/api/api.go
478
swarm/api/api.go
|
|
@ -1,24 +1,16 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/chequebook"
|
||||
"github.com/ethereum/go-ethereum/common/registrar"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -27,68 +19,86 @@ var (
|
|||
domainAndVersion = regexp.MustCompile("[@:;,]+")
|
||||
)
|
||||
|
||||
type Resolver interface {
|
||||
Resolve(string) (storage.Key, error)
|
||||
}
|
||||
|
||||
/*
|
||||
Api implements webserver/file system related content storage and retrieval
|
||||
on top of the dpa
|
||||
it is the public interface of the dpa which is included in the ethereum stack
|
||||
*/
|
||||
type Api struct {
|
||||
dpa *storage.DPA
|
||||
registrar registrar.VersionedRegistrar
|
||||
conf *Config
|
||||
dpa *storage.DPA
|
||||
dns Resolver
|
||||
}
|
||||
|
||||
//the api constructor initialises
|
||||
func NewApi(dpa *storage.DPA, registrar registrar.VersionedRegistrar, conf *Config) (self *Api) {
|
||||
return &Api{dpa, registrar, conf}
|
||||
}
|
||||
|
||||
// this should move over to chequebook ipc api
|
||||
func (self *Api) Issue(beneficiary common.Address, amount *big.Int) (cheque *chequebook.Cheque, err error) {
|
||||
return self.conf.Swap.Chequebook().Issue(beneficiary, amount)
|
||||
}
|
||||
|
||||
func (self *Api) Cash(cheque *chequebook.Cheque) (txhash string, err error) {
|
||||
return self.conf.Swap.Chequebook().Cash(cheque)
|
||||
}
|
||||
|
||||
func (self *Api) Deposit(amount *big.Int) (txhash string, err error) {
|
||||
return self.conf.Swap.Chequebook().Deposit(amount)
|
||||
}
|
||||
|
||||
// serialisable info about swarm
|
||||
type Info struct {
|
||||
*Config
|
||||
*chequebook.Params
|
||||
}
|
||||
|
||||
func (self *Api) Info() *Info {
|
||||
|
||||
return &Info{
|
||||
Config: self.conf,
|
||||
Params: chequebook.ContractParams,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Get uses iterative manifest retrieval and prefix matching
|
||||
// to resolve path to content using dpa retrieve
|
||||
func (self *Api) Get(bzzpath string) (content []byte, mimeType string, status int, size int, err error) {
|
||||
var reader storage.SectionReader
|
||||
reader, mimeType, status, err = self.getPath("/" + bzzpath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
content = make([]byte, reader.Size())
|
||||
size, err = reader.Read(content)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
func NewApi(dpa *storage.DPA, dns Resolver) (self *Api) {
|
||||
self = &Api{
|
||||
dpa: dpa,
|
||||
dns: dns,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Put provides singleton manifest creation and optional name registration
|
||||
// on top of dpa store
|
||||
// DPA reader API
|
||||
func (self *Api) Retrieve(key storage.Key) storage.SectionReader {
|
||||
return self.dpa.Retrieve(key)
|
||||
}
|
||||
|
||||
func (self *Api) Store(data storage.SectionReader, wg *sync.WaitGroup) (key storage.Key, err error) {
|
||||
return self.dpa.Store(data, wg)
|
||||
}
|
||||
|
||||
// DNS Resolver
|
||||
func (self *Api) Resolve(hostPort string) (contentHash storage.Key, err error) {
|
||||
if hashMatcher.MatchString(hostPort) || self.dns == nil {
|
||||
glog.V(logger.Debug).Infof("[BZZ] host is a contentHash: '%v'", contentHash)
|
||||
return storage.Key(common.Hex2Bytes(hostPort)), nil
|
||||
}
|
||||
contentHash, err = self.dns.Resolve(hostPort)
|
||||
if err != nil {
|
||||
err = ErrResolve(err)
|
||||
glog.V(logger.Debug).Infof("[BZZ] DNS error : %v", err)
|
||||
}
|
||||
glog.V(logger.Debug).Infof("[BZZ] host lookup: %v -> %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
func parse(uri string) (hostPort, path string) {
|
||||
parts := slashes.Split(uri, 3)
|
||||
var i int
|
||||
if len(parts) == 0 {
|
||||
return
|
||||
}
|
||||
// beginning with slash is now optional
|
||||
if len(parts[0]) == 0 {
|
||||
i++
|
||||
}
|
||||
hostPort = parts[i]
|
||||
if len(parts) > i+1 {
|
||||
path = parts[i+1]
|
||||
if len(parts) == 3 {
|
||||
path += "/" + parts[2]
|
||||
}
|
||||
path += "/"
|
||||
}
|
||||
if len(path) > 0 {
|
||||
path = "/" + path
|
||||
}
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Api) parseAndResolve(uri string) (contentHash storage.Key, hostPort, path string, err error) {
|
||||
hostPort, path = parse(uri)
|
||||
//resolving host and port
|
||||
contentHash, err = self.Resolve(hostPort)
|
||||
return
|
||||
}
|
||||
|
||||
// Put provides singleton manifest creation on top of dpa store
|
||||
func (self *Api) Put(content, contentType string) (string, error) {
|
||||
sr := io.NewSectionReader(strings.NewReader(content), 0, int64(len(content)))
|
||||
wg := &sync.WaitGroup{}
|
||||
|
|
@ -106,6 +116,34 @@ func (self *Api) Put(content, contentType string) (string, error) {
|
|||
return key.String(), nil
|
||||
}
|
||||
|
||||
// Get uses iterative manifest retrieval and prefix matching
|
||||
// to resolve path to content using dpa retrieve
|
||||
// it returns a section reader, mimeType, status and an error
|
||||
func (self *Api) Get(uri string) (reader storage.SectionReader, mimeType string, status int, err error) {
|
||||
|
||||
key, _, path, err := self.parseAndResolve(uri)
|
||||
|
||||
trie, err := loadManifest(self.dpa, key)
|
||||
if err != nil {
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: getEntry(%s)", path)
|
||||
entry, _ := trie.getEntry(path)
|
||||
if entry != nil {
|
||||
key = common.Hex2Bytes(entry.Hash)
|
||||
status = entry.Status
|
||||
mimeType = entry.ContentType
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: content lookup key: '%v' (%v)", key, mimeType)
|
||||
reader = self.dpa.Retrieve(key)
|
||||
} else {
|
||||
err = fmt.Errorf("manifest entry for '%s' not found", path)
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Api) Modify(rootHash, path, contentHash, contentType string) (newRootHash string, err error) {
|
||||
root := common.Hex2Bytes(rootHash)
|
||||
trie, err := loadManifest(self.dpa, root)
|
||||
|
|
@ -130,327 +168,3 @@ func (self *Api) Modify(rootHash, path, contentHash, contentType string) (newRoo
|
|||
}
|
||||
return trie.hash.String(), nil
|
||||
}
|
||||
|
||||
const maxParallelFiles = 5
|
||||
|
||||
// Download replicates the manifest path structure on the local filesystem
|
||||
// under localpath
|
||||
func (self *Api) Download(bzzpath, localpath string) (err error) {
|
||||
lpath, err := filepath.Abs(filepath.Clean(localpath))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = os.MkdirAll(lpath, os.ModePerm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
parts := slashes.Split(bzzpath, 3)
|
||||
if len(parts) < 2 {
|
||||
return fmt.Errorf("Invalid bzz path")
|
||||
}
|
||||
hostPort := parts[1]
|
||||
var path string
|
||||
if len(parts) > 2 {
|
||||
path = regularSlashes(parts[2]) + "/"
|
||||
}
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path)
|
||||
|
||||
//resolving host and port
|
||||
var key storage.Key
|
||||
key, err = self.Resolve(hostPort)
|
||||
if err != nil {
|
||||
err = errResolve(err)
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: error : %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
trie, err := loadManifest(self.dpa, key)
|
||||
if err != nil {
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
type downloadListEntry struct {
|
||||
key storage.Key
|
||||
path string
|
||||
}
|
||||
|
||||
var list []*downloadListEntry
|
||||
var mde, mderr error
|
||||
|
||||
prevPath := lpath
|
||||
err = trie.listWithPrefix(path, func(entry *manifestTrieEntry, suffix string) { // TODO: paralellize
|
||||
key := common.Hex2Bytes(entry.Hash)
|
||||
path := lpath + "/" + suffix
|
||||
dir := filepath.Dir(path)
|
||||
if dir != prevPath {
|
||||
mde = os.MkdirAll(dir, os.ModePerm)
|
||||
if mde != nil {
|
||||
mderr = mde
|
||||
}
|
||||
prevPath = dir
|
||||
}
|
||||
if (mde == nil) && (path != dir+"/") {
|
||||
list = append(list, &downloadListEntry{key: key, path: path})
|
||||
}
|
||||
})
|
||||
if err == nil {
|
||||
err = mderr
|
||||
}
|
||||
|
||||
cnt := len(list)
|
||||
errors := make([]error, cnt)
|
||||
done := make(chan bool, maxParallelFiles)
|
||||
dcnt := 0
|
||||
|
||||
for i, entry := range list {
|
||||
if i >= dcnt+maxParallelFiles {
|
||||
<-done
|
||||
dcnt++
|
||||
}
|
||||
go func(i int, entry *downloadListEntry, done chan bool) {
|
||||
f, err := os.Create(entry.path) // TODO: path separators
|
||||
if err == nil {
|
||||
reader := self.dpa.Retrieve(entry.key)
|
||||
writer := bufio.NewWriter(f)
|
||||
_, err = io.CopyN(writer, reader, reader.Size()) // TODO: handle errors
|
||||
err2 := writer.Flush()
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
err2 = f.Close()
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
|
||||
errors[i] = err
|
||||
done <- true
|
||||
}(i, entry, done)
|
||||
}
|
||||
for dcnt < cnt {
|
||||
<-done
|
||||
dcnt++
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i, _ := range list {
|
||||
if errors[i] != nil {
|
||||
return errors[i]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Upload replicates a local directory as a manifest file and uploads it
|
||||
// using dpa store
|
||||
// TODO: localpath should point to a manifest
|
||||
func (self *Api) Upload(lpath, index string) (string, error) {
|
||||
var list []*manifestTrieEntry
|
||||
localpath, err := filepath.Abs(filepath.Clean(lpath))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
f, err := os.Open(localpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var start int
|
||||
if stat.IsDir() {
|
||||
start = len(localpath)
|
||||
glog.V(logger.Debug).Infof("[BZZ] uploading '%s'", localpath)
|
||||
err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error {
|
||||
if (err == nil) && !info.IsDir() {
|
||||
//fmt.Printf("lp %s path %s\n", localpath, path)
|
||||
if len(path) <= start {
|
||||
return fmt.Errorf("Path is too short")
|
||||
}
|
||||
if path[:start] != localpath {
|
||||
return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath)
|
||||
}
|
||||
entry := &manifestTrieEntry{
|
||||
Path: path,
|
||||
}
|
||||
list = append(list, entry)
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
dir := filepath.Dir(localpath)
|
||||
start = len(dir)
|
||||
if len(localpath) <= start {
|
||||
return "", fmt.Errorf("Path is too short")
|
||||
}
|
||||
if localpath[:start] != dir {
|
||||
return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir)
|
||||
}
|
||||
entry := &manifestTrieEntry{
|
||||
Path: localpath,
|
||||
}
|
||||
list = append(list, entry)
|
||||
}
|
||||
|
||||
cnt := len(list)
|
||||
errors := make([]error, cnt)
|
||||
done := make(chan bool, maxParallelFiles)
|
||||
dcnt := 0
|
||||
|
||||
for i, entry := range list {
|
||||
if i >= dcnt+maxParallelFiles {
|
||||
<-done
|
||||
dcnt++
|
||||
}
|
||||
go func(i int, entry *manifestTrieEntry, done chan bool) {
|
||||
f, err := os.Open(entry.Path)
|
||||
if err == nil {
|
||||
stat, _ := f.Stat()
|
||||
sr := io.NewSectionReader(f, 0, stat.Size())
|
||||
wg := &sync.WaitGroup{}
|
||||
var hash storage.Key
|
||||
hash, err = self.dpa.Store(sr, wg)
|
||||
if hash != nil {
|
||||
list[i].Hash = hash.String()
|
||||
}
|
||||
wg.Wait()
|
||||
if err == nil {
|
||||
first512 := make([]byte, 512)
|
||||
fread, _ := sr.ReadAt(first512, 0)
|
||||
if fread > 0 {
|
||||
mimeType := http.DetectContentType(first512[:fread])
|
||||
if filepath.Ext(entry.Path) == ".css" {
|
||||
mimeType = "text/css"
|
||||
}
|
||||
list[i].ContentType = mimeType
|
||||
//fmt.Printf("%v %v %v\n", entry.Path, mimeType, filepath.Ext(entry.Path))
|
||||
}
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
errors[i] = err
|
||||
done <- true
|
||||
}(i, entry, done)
|
||||
}
|
||||
for dcnt < cnt {
|
||||
<-done
|
||||
dcnt++
|
||||
}
|
||||
|
||||
trie := &manifestTrie{
|
||||
dpa: self.dpa,
|
||||
}
|
||||
for i, entry := range list {
|
||||
if errors[i] != nil {
|
||||
return "", errors[i]
|
||||
}
|
||||
entry.Path = regularSlashes(entry.Path[start:])
|
||||
if entry.Path == index {
|
||||
ientry := &manifestTrieEntry{
|
||||
Path: "",
|
||||
Hash: entry.Hash,
|
||||
ContentType: entry.ContentType,
|
||||
}
|
||||
trie.addEntry(ientry)
|
||||
}
|
||||
trie.addEntry(entry)
|
||||
}
|
||||
|
||||
err2 := trie.recalcAndStore()
|
||||
var hs string
|
||||
if err2 == nil {
|
||||
hs = trie.hash.String()
|
||||
}
|
||||
return hs, err2
|
||||
}
|
||||
|
||||
func (self *Api) Register(sender common.Address, domain string, hash common.Hash) (err error) {
|
||||
domainhash := common.BytesToHash(crypto.Sha3([]byte(domain)))
|
||||
|
||||
if self.registrar != nil {
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: host '%s' (hash: '%v') to be registered as '%v'", domain, domainhash.Hex(), hash.Hex())
|
||||
_, err = self.registrar.Registry().SetHashToHash(sender, domainhash, hash)
|
||||
} else {
|
||||
err = fmt.Errorf("no registry: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type errResolve error
|
||||
|
||||
func (self *Api) Resolve(hostPort string) (contentHash storage.Key, err error) {
|
||||
host := hostPort
|
||||
if hashMatcher.MatchString(host) {
|
||||
contentHash = storage.Key(common.Hex2Bytes(host))
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: host is a contentHash: '%v'", contentHash)
|
||||
} else {
|
||||
if self.registrar != nil {
|
||||
var hash common.Hash
|
||||
var version *big.Int
|
||||
parts := domainAndVersion.Split(host, 3)
|
||||
if len(parts) > 1 && parts[1] != "" {
|
||||
host = parts[0]
|
||||
version = common.Big(parts[1])
|
||||
}
|
||||
hostHash := crypto.Sha3Hash([]byte(host))
|
||||
hash, err = self.registrar.Resolver(version).HashToHash(hostHash)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to resolve '%s': %v", hostPort, err)
|
||||
}
|
||||
contentHash = storage.Key(hash.Bytes())
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: resolve host '%s' to contentHash: '%v'", hostPort, contentHash)
|
||||
} else {
|
||||
err = fmt.Errorf("no resolver '%s': %v", hostPort, err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *Api) getPath(uri string) (reader storage.SectionReader, mimeType string, status int, err error) {
|
||||
parts := slashes.Split(uri, 3)
|
||||
hostPort := parts[1]
|
||||
var path string
|
||||
if len(parts) > 2 {
|
||||
path = parts[2]
|
||||
}
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path)
|
||||
|
||||
//resolving host and port
|
||||
var key storage.Key
|
||||
key, err = self.Resolve(hostPort)
|
||||
if err != nil {
|
||||
err = errResolve(err)
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: error : %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
trie, err := loadManifest(self.dpa, key)
|
||||
if err != nil {
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: getEntry(%s)", path)
|
||||
entry, _ := trie.getEntry(path)
|
||||
if entry != nil {
|
||||
key = common.Hex2Bytes(entry.Hash)
|
||||
status = entry.Status
|
||||
mimeType = entry.ContentType
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: content lookup key: '%v' (%v)", key, mimeType)
|
||||
reader = self.dpa.Retrieve(key)
|
||||
} else {
|
||||
err = fmt.Errorf("manifest entry for '%s' not found", path)
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,205 +1,86 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
// "bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
)
|
||||
|
||||
//TODO: add tests for resolver/registrar
|
||||
// will most likely be its own package under service?
|
||||
|
||||
var (
|
||||
testDir string
|
||||
)
|
||||
|
||||
func init() {
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
testDir = path.Join(path.Dir(filename), "../test")
|
||||
}
|
||||
|
||||
func testApi() (api *Api, err error) {
|
||||
func testApi(t *testing.T, f func(*Api)) {
|
||||
datadir, err := ioutil.TempDir("", "bzz-test")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
os.RemoveAll(datadir)
|
||||
defer os.RemoveAll(datadir)
|
||||
dpa, err := storage.NewLocalDPA(datadir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
prvkey, _ := crypto.GenerateKey()
|
||||
api := NewApi(dpa, nil)
|
||||
dpa.Start()
|
||||
f(api)
|
||||
dpa.Stop()
|
||||
}
|
||||
|
||||
config, err := NewConfig(datadir, common.Address{}, prvkey)
|
||||
if err != nil {
|
||||
return
|
||||
type testResponse struct {
|
||||
reader storage.SectionReader
|
||||
*Response
|
||||
}
|
||||
|
||||
func checkResponse(t *testing.T, resp *testResponse, exp *Response) {
|
||||
|
||||
if resp.MimeType != exp.MimeType {
|
||||
t.Errorf("incorrect mimeType. expected '%s', got '%s'", exp.MimeType, resp.MimeType)
|
||||
}
|
||||
api = NewApi(dpa, nil, config)
|
||||
api.dpa.Start()
|
||||
if resp.Status != exp.Status {
|
||||
t.Errorf("incorrect status. expected '%d', got '%d'", exp.Status, resp.Status)
|
||||
}
|
||||
if resp.Size != exp.Size {
|
||||
t.Errorf("incorrect size. expected '%d', got '%d'", exp.Size, resp.Size)
|
||||
}
|
||||
if resp.reader != nil {
|
||||
content := make([]byte, resp.Size)
|
||||
read, _ := resp.reader.Read(content)
|
||||
if int64(read) != exp.Size {
|
||||
t.Errorf("incorrect content length. expected '%s...', got '%s...'", read, exp.Size)
|
||||
}
|
||||
resp.Content = string(content)
|
||||
}
|
||||
if resp.Content != exp.Content {
|
||||
// if !bytes.Equal(resp.Content, exp.Content) {
|
||||
t.Errorf("incorrect content. expected '%s...', got '%s...'", string(exp.Content), string(resp.Content))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
// func expResponse(content []byte, mimeType string, status int) *Response {
|
||||
func expResponse(content string, mimeType string, status int) *Response {
|
||||
return &Response{mimeType, status, int64(len(content)), content}
|
||||
}
|
||||
|
||||
// func testGet(t *testing.T, api *Api, bzzhash string) *testResponse {
|
||||
func testGet(t *testing.T, api *Api, bzzhash string) *testResponse {
|
||||
reader, mimeType, status, err := api.Get(bzzhash)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
return &testResponse{reader, &Response{mimeType, status, reader.Size(), ""}}
|
||||
// return &testResponse{reader, &Response{mimeType, status, reader.Size(), nil}}
|
||||
}
|
||||
|
||||
func TestApiPut(t *testing.T) {
|
||||
api, err := testApi()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
defer api.dpa.Stop()
|
||||
expContent := "hello"
|
||||
expMimeType := "text/plain"
|
||||
expStatus := 0
|
||||
expSize := len(expContent)
|
||||
bzzhash, err := api.Put(expContent, expMimeType)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
testGet(t, api, bzzhash, []byte(expContent), expMimeType, expStatus, expSize)
|
||||
}
|
||||
|
||||
func testGet(t *testing.T, api *Api, bzzhash string, expContent []byte, expMimeType string, expStatus int, expSize int) {
|
||||
content, mimeType, status, size, err := api.Get(bzzhash)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(content, expContent) {
|
||||
t.Errorf("incorrect content. expected '%s...', got '%s...'", string(expContent), string(content))
|
||||
}
|
||||
if mimeType != expMimeType {
|
||||
t.Errorf("incorrect mimeType. expected '%s', got '%s'", expMimeType, mimeType)
|
||||
}
|
||||
if status != expStatus {
|
||||
t.Errorf("incorrect status. expected '%d', got '%d'", expStatus, status)
|
||||
}
|
||||
if size != expSize {
|
||||
t.Errorf("incorrect size. expected '%d', got '%d'", expSize, size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiDirUpload(t *testing.T) {
|
||||
t.Skip("FIXME")
|
||||
api, err := testApi()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
bzzhash, err := api.Upload(path.Join(testDir, "test0"), "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html"))
|
||||
testGet(t, api, path.Join(bzzhash, "index.html"), content, "text/html; charset=utf-8", 0, 202)
|
||||
|
||||
content, err = ioutil.ReadFile(path.Join(testDir, "test0", "index.css"))
|
||||
testGet(t, api, path.Join(bzzhash, "index.css"), content, "text/css", 0, 132)
|
||||
|
||||
content, err = ioutil.ReadFile(path.Join(testDir, "test0", "img", "logo.png"))
|
||||
testGet(t, api, path.Join(bzzhash, "img", "logo.png"), content, "image/png", 0, 18136)
|
||||
|
||||
_, _, _, _, err = api.Get(bzzhash)
|
||||
if err == nil {
|
||||
t.Errorf("expected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiDirUploadModify(t *testing.T) {
|
||||
t.Skip("FIXME")
|
||||
api, err := testApi()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
bzzhash, err := api.Upload(path.Join(testDir, "test0"), "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
bzzhash, err = api.Modify(bzzhash, "index.html", "", "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
bzzhash, err = api.Modify(bzzhash, "index2.html", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
bzzhash, err = api.Modify(bzzhash, "img/logo.png", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html"))
|
||||
testGet(t, api, path.Join(bzzhash, "index2.html"), content, "text/html; charset=utf-8", 0, 202)
|
||||
testGet(t, api, path.Join(bzzhash, "img", "logo.png"), content, "text/html; charset=utf-8", 0, 202)
|
||||
|
||||
content, err = ioutil.ReadFile(path.Join(testDir, "test0", "index.css"))
|
||||
testGet(t, api, path.Join(bzzhash, "index.css"), content, "text/css", 0, 132)
|
||||
|
||||
_, _, _, _, err = api.Get(bzzhash)
|
||||
if err == nil {
|
||||
t.Errorf("expected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiDirUploadWithRootFile(t *testing.T) {
|
||||
api, err := testApi()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
bzzhash, err := api.Upload(path.Join(testDir, "test0"), "index.html")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html"))
|
||||
testGet(t, api, bzzhash, content, "text/html; charset=utf-8", 0, 202)
|
||||
}
|
||||
|
||||
func TestApiFileUpload(t *testing.T) {
|
||||
api, err := testApi()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
bzzhash, err := api.Upload(path.Join(testDir, "test0", "index.html"), "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html"))
|
||||
testGet(t, api, path.Join(bzzhash, "index.html"), content, "text/html; charset=utf-8", 0, 202)
|
||||
}
|
||||
|
||||
func TestApiFileUploadWithRootFile(t *testing.T) {
|
||||
api, err := testApi()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
bzzhash, err := api.Upload(path.Join(testDir, "test0", "index.html"), "index.html")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html"))
|
||||
testGet(t, api, bzzhash, content, "text/html; charset=utf-8", 0, 202)
|
||||
testApi(t, func(api *Api) {
|
||||
content := "hello"
|
||||
exp := expResponse(content, "text/plain", 0)
|
||||
// exp := expResponse([]byte(content), "text/plain", 0)
|
||||
bzzhash, err := api.Put(content, exp.MimeType)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
resp := testGet(t, api, bzzhash)
|
||||
checkResponse(t, resp, exp)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ func TestConfigWriteRead(t *testing.T) {
|
|||
t.Fatalf("default config file cannot be read: %v", err)
|
||||
}
|
||||
exp := strings.Replace(defaultConfig, "TMPDIR", tmp, -1)
|
||||
exp = strings.Replace(exp, "\\", "\\\\", -1)
|
||||
|
||||
if string(data) != exp {
|
||||
t.Fatalf("default config mismatch:\nexpected:\n'%v'\ngot:\n'%v'", exp, string(data))
|
||||
|
|
|
|||
58
swarm/api/dns.go
Normal file
58
swarm/api/dns.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/registrar"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
)
|
||||
|
||||
// swarm domain name registry and resolver
|
||||
// the DNS instance can be directly wrapped in rpc.Api
|
||||
type DNS struct {
|
||||
registrar registrar.VersionedRegistrar
|
||||
}
|
||||
|
||||
func NewDNS(registrar registrar.VersionedRegistrar) *DNS {
|
||||
return &DNS{registrar}
|
||||
}
|
||||
|
||||
// Register involves sending a transaction, sender is an account with funds
|
||||
// the same account is used to register the authors of commits
|
||||
func (self *DNS) Register(sender common.Address, domain string, hash common.Hash) (err error) {
|
||||
domainhash := common.BytesToHash(crypto.Sha3([]byte(domain)))
|
||||
|
||||
if self.registrar != nil {
|
||||
glog.V(logger.Debug).Infof("[DNR]: host '%s' (hash: '%v') to be registered as '%v'", domain, domainhash.Hex(), hash.Hex())
|
||||
_, err = self.registrar.Registry().SetHashToHash(sender, domainhash, hash)
|
||||
} else {
|
||||
err = fmt.Errorf("no registry: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ErrResolve error
|
||||
|
||||
func (self *DNS) Resolve(hostPort string) (contentHash storage.Key, err error) {
|
||||
host := hostPort
|
||||
var hash common.Hash
|
||||
var version *big.Int
|
||||
parts := domainAndVersion.Split(host, 3)
|
||||
if len(parts) > 1 && parts[1] != "" {
|
||||
host = parts[0]
|
||||
version = common.Big(parts[1])
|
||||
}
|
||||
hostHash := crypto.Sha3Hash([]byte(host))
|
||||
hash, err = self.registrar.Resolver(version).HashToHash(hostHash)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to resolve '%s': %v", hostPort, err)
|
||||
}
|
||||
contentHash = storage.Key(hash.Bytes())
|
||||
glog.V(logger.Debug).Infof("[DNR] resolve host '%s' to contentHash: '%v'", hostPort, contentHash)
|
||||
return
|
||||
}
|
||||
258
swarm/api/filesystem.go
Normal file
258
swarm/api/filesystem.go
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
)
|
||||
|
||||
const maxParallelFiles = 5
|
||||
|
||||
type FileSystem struct {
|
||||
api *Api
|
||||
}
|
||||
|
||||
func NewFileSystem(api *Api) *FileSystem {
|
||||
return &FileSystem{api}
|
||||
}
|
||||
|
||||
// Upload replicates a local directory as a manifest file and uploads it
|
||||
// using dpa store
|
||||
// TODO: localpath should point to a manifest
|
||||
func (self *FileSystem) Upload(lpath, index string) (string, error) {
|
||||
var list []*manifestTrieEntry
|
||||
localpath, err := filepath.Abs(filepath.Clean(lpath))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
f, err := os.Open(localpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var start int
|
||||
if stat.IsDir() {
|
||||
start = len(localpath)
|
||||
glog.V(logger.Debug).Infof("[BZZ] uploading '%s'", localpath)
|
||||
err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error {
|
||||
if (err == nil) && !info.IsDir() {
|
||||
//fmt.Printf("lp %s path %s\n", localpath, path)
|
||||
if len(path) <= start {
|
||||
return fmt.Errorf("Path is too short")
|
||||
}
|
||||
if path[:start] != localpath {
|
||||
return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath)
|
||||
}
|
||||
entry := &manifestTrieEntry{
|
||||
Path: path,
|
||||
}
|
||||
list = append(list, entry)
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
dir := filepath.Dir(localpath)
|
||||
start = len(dir)
|
||||
if len(localpath) <= start {
|
||||
return "", fmt.Errorf("Path is too short")
|
||||
}
|
||||
if localpath[:start] != dir {
|
||||
return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir)
|
||||
}
|
||||
entry := &manifestTrieEntry{
|
||||
Path: localpath,
|
||||
}
|
||||
list = append(list, entry)
|
||||
}
|
||||
|
||||
cnt := len(list)
|
||||
errors := make([]error, cnt)
|
||||
done := make(chan bool, maxParallelFiles)
|
||||
dcnt := 0
|
||||
|
||||
for i, entry := range list {
|
||||
if i >= dcnt+maxParallelFiles {
|
||||
<-done
|
||||
dcnt++
|
||||
}
|
||||
go func(i int, entry *manifestTrieEntry, done chan bool) {
|
||||
f, err := os.Open(entry.Path)
|
||||
if err == nil {
|
||||
stat, _ := f.Stat()
|
||||
sr := io.NewSectionReader(f, 0, stat.Size())
|
||||
wg := &sync.WaitGroup{}
|
||||
var hash storage.Key
|
||||
hash, err = self.api.dpa.Store(sr, wg)
|
||||
if hash != nil {
|
||||
list[i].Hash = hash.String()
|
||||
}
|
||||
wg.Wait()
|
||||
if err == nil {
|
||||
first512 := make([]byte, 512)
|
||||
fread, _ := sr.ReadAt(first512, 0)
|
||||
if fread > 0 {
|
||||
mimeType := http.DetectContentType(first512[:fread])
|
||||
if filepath.Ext(entry.Path) == ".css" {
|
||||
mimeType = "text/css"
|
||||
}
|
||||
list[i].ContentType = mimeType
|
||||
//fmt.Printf("%v %v %v\n", entry.Path, mimeType, filepath.Ext(entry.Path))
|
||||
}
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
errors[i] = err
|
||||
done <- true
|
||||
}(i, entry, done)
|
||||
}
|
||||
for dcnt < cnt {
|
||||
<-done
|
||||
dcnt++
|
||||
}
|
||||
|
||||
trie := &manifestTrie{
|
||||
dpa: self.api.dpa,
|
||||
}
|
||||
for i, entry := range list {
|
||||
if errors[i] != nil {
|
||||
return "", errors[i]
|
||||
}
|
||||
entry.Path = RegularSlashes(entry.Path[start:])
|
||||
if entry.Path == index {
|
||||
ientry := &manifestTrieEntry{
|
||||
Path: "",
|
||||
Hash: entry.Hash,
|
||||
ContentType: entry.ContentType,
|
||||
}
|
||||
trie.addEntry(ientry)
|
||||
}
|
||||
trie.addEntry(entry)
|
||||
}
|
||||
|
||||
err2 := trie.recalcAndStore()
|
||||
var hs string
|
||||
if err2 == nil {
|
||||
hs = trie.hash.String()
|
||||
}
|
||||
return hs, err2
|
||||
}
|
||||
|
||||
// Download replicates the manifest path structure on the local filesystem
|
||||
// under localpath
|
||||
func (self *FileSystem) Download(bzzpath, localpath string) error {
|
||||
lpath, err := filepath.Abs(filepath.Clean(localpath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(lpath, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//resolving host and port
|
||||
key, _, path, err := self.api.parseAndResolve(bzzpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// if len(path) > 0 {
|
||||
// path += "/"
|
||||
// }
|
||||
|
||||
trie, err := loadManifest(self.api.dpa, key)
|
||||
if err != nil {
|
||||
glog.V(logger.Debug).Infof("[BZZ] fs.Download: loadManifestTrie error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
type downloadListEntry struct {
|
||||
key storage.Key
|
||||
path string
|
||||
}
|
||||
|
||||
var list []*downloadListEntry
|
||||
var mde, mderr error
|
||||
|
||||
prevPath := lpath
|
||||
err = trie.listWithPrefix(path, func(entry *manifestTrieEntry, suffix string) { // TODO: paralellize
|
||||
glog.V(logger.Detail).Infof("[BZZ] fs.Download: %#v", entry)
|
||||
|
||||
key := common.Hex2Bytes(entry.Hash)
|
||||
path := lpath + "/" + suffix
|
||||
dir := filepath.Dir(path)
|
||||
if dir != prevPath {
|
||||
mde = os.MkdirAll(dir, os.ModePerm)
|
||||
if mde != nil {
|
||||
mderr = mde
|
||||
}
|
||||
prevPath = dir
|
||||
}
|
||||
if (mde == nil) && (path != dir+"/") {
|
||||
list = append(list, &downloadListEntry{key: key, path: path})
|
||||
}
|
||||
})
|
||||
if err == nil {
|
||||
err = mderr
|
||||
}
|
||||
|
||||
cnt := len(list)
|
||||
errors := make([]error, cnt)
|
||||
done := make(chan bool, maxParallelFiles)
|
||||
dcnt := 0
|
||||
|
||||
for i, entry := range list {
|
||||
if i >= dcnt+maxParallelFiles {
|
||||
<-done
|
||||
dcnt++
|
||||
}
|
||||
go func(i int, entry *downloadListEntry, done chan bool) {
|
||||
f, err := os.Create(entry.path) // TODO: path separators
|
||||
if err == nil {
|
||||
reader := self.api.dpa.Retrieve(entry.key)
|
||||
writer := bufio.NewWriter(f)
|
||||
_, err = io.CopyN(writer, reader, reader.Size()) // TODO: handle errors
|
||||
err2 := writer.Flush()
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
err2 = f.Close()
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
|
||||
errors[i] = err
|
||||
done <- true
|
||||
}(i, entry, done)
|
||||
}
|
||||
for dcnt < cnt {
|
||||
<-done
|
||||
dcnt++
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, _ := range list {
|
||||
if errors[i] != nil {
|
||||
return errors[i]
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
176
swarm/api/filesystem_test.go
Normal file
176
swarm/api/filesystem_test.go
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
testDir string
|
||||
testDownloadDir string
|
||||
)
|
||||
|
||||
func init() {
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
testDir = path.Join(path.Dir(filename), "../test")
|
||||
testDownloadDir, _ = ioutil.TempDir(os.TempDir(), "bzz-test")
|
||||
}
|
||||
|
||||
func testFileSystem(t *testing.T, f func(*FileSystem)) {
|
||||
testApi(t, func(api *Api) {
|
||||
f(NewFileSystem(api))
|
||||
})
|
||||
}
|
||||
|
||||
func readPath(t *testing.T, parts ...string) string {
|
||||
// func readPath(t *testing.T, parts ...string) []byte {
|
||||
file := path.Join(parts...)
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error reading '%v': %v", file, err)
|
||||
}
|
||||
return string(content)
|
||||
}
|
||||
|
||||
func TestApiDirUpload0(t *testing.T) {
|
||||
// t.Skip("FIXME")
|
||||
testFileSystem(t, func(fs *FileSystem) {
|
||||
api := fs.api
|
||||
bzzhash, err := fs.Upload(path.Join(testDir, "test0"), "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
content := readPath(t, testDir, "test0", "index.html")
|
||||
resp := testGet(t, api, bzzhash+"/index.html")
|
||||
exp := expResponse(content, "text/html; charset=utf-8", 0)
|
||||
checkResponse(t, resp, exp)
|
||||
|
||||
content = readPath(t, testDir, "test0", "index.css")
|
||||
resp = testGet(t, api, bzzhash+"/index.css")
|
||||
exp = expResponse(content, "text/css", 0)
|
||||
checkResponse(t, resp, exp)
|
||||
|
||||
content = readPath(t, testDir, "test0", "img", "logo.png")
|
||||
resp = testGet(t, api, bzzhash+"/img/logo.png")
|
||||
exp = expResponse(content, "image/png", 0)
|
||||
|
||||
_, _, _, err = api.Get(bzzhash)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error: %v", err)
|
||||
}
|
||||
|
||||
downloadDir := path.Join(testDownloadDir, "test0")
|
||||
os.RemoveAll(downloadDir)
|
||||
defer os.RemoveAll(downloadDir)
|
||||
err = fs.Download(bzzhash, downloadDir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
newbzzhash, err := fs.Upload(downloadDir, "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if bzzhash != newbzzhash {
|
||||
t.Fatalf("download %v reuploaded has incorrect hash, expected %v, got %v", downloadDir, bzzhash, newbzzhash)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestApiDirUploadModify(t *testing.T) {
|
||||
// t.Skip("FIXME")
|
||||
testFileSystem(t, func(fs *FileSystem) {
|
||||
api := fs.api
|
||||
bzzhash, err := fs.Upload(path.Join(testDir, "test0"), "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
bzzhash, err = api.Modify(bzzhash, "index.html", "", "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
bzzhash, err = api.Modify(bzzhash, "index2.html", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
bzzhash, err = api.Modify(bzzhash, "img/logo.png", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
content := readPath(t, testDir, "test0", "index.html")
|
||||
resp := testGet(t, api, bzzhash+"/index2.html")
|
||||
exp := expResponse(content, "text/html; charset=utf-8", 0)
|
||||
checkResponse(t, resp, exp)
|
||||
|
||||
resp = testGet(t, api, bzzhash+"/img/logo.png")
|
||||
exp = expResponse(content, "text/html; charset=utf-8", 0)
|
||||
checkResponse(t, resp, exp)
|
||||
|
||||
content = readPath(t, testDir, "test0", "index.css")
|
||||
resp = testGet(t, api, bzzhash+"/index.css")
|
||||
exp = expResponse(content, "text/css", 0)
|
||||
|
||||
_, _, _, err = api.Get(bzzhash)
|
||||
if err == nil {
|
||||
t.Errorf("expected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestApiDirUploadWithRootFile(t *testing.T) {
|
||||
testFileSystem(t, func(fs *FileSystem) {
|
||||
api := fs.api
|
||||
bzzhash, err := fs.Upload(path.Join(testDir, "test0"), "index.html")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
content := readPath(t, testDir, "test0", "index.html")
|
||||
resp := testGet(t, api, bzzhash)
|
||||
exp := expResponse(content, "text/html; charset=utf-8", 0)
|
||||
checkResponse(t, resp, exp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestApiFileUpload(t *testing.T) {
|
||||
testFileSystem(t, func(fs *FileSystem) {
|
||||
api := fs.api
|
||||
bzzhash, err := fs.Upload(path.Join(testDir, "test0", "index.html"), "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
content := readPath(t, testDir, "test0", "index.html")
|
||||
resp := testGet(t, api, bzzhash+"/index.html")
|
||||
exp := expResponse(content, "text/html; charset=utf-8", 0)
|
||||
checkResponse(t, resp, exp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestApiFileUploadWithRootFile(t *testing.T) {
|
||||
testFileSystem(t, func(fs *FileSystem) {
|
||||
api := fs.api
|
||||
bzzhash, err := fs.Upload(path.Join(testDir, "test0", "index.html"), "index.html")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
content := readPath(t, testDir, "test0", "index.html")
|
||||
resp := testGet(t, api, bzzhash)
|
||||
exp := expResponse(content, "text/html; charset=utf-8", 0)
|
||||
checkResponse(t, resp, exp)
|
||||
})
|
||||
}
|
||||
49
swarm/api/http/roundtripper.go
Normal file
49
swarm/api/http/roundtripper.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
)
|
||||
|
||||
/*
|
||||
http roundtripper to register for bzz url scheme
|
||||
see https://github.com/ethereum/go-ethereum/issues/2040
|
||||
Usage:
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common/httpclient"
|
||||
"github.com/ethereum/go-ethereum/swarm/api/http"
|
||||
)
|
||||
client := httpclient.New()
|
||||
// for (private) swarm proxy running locally
|
||||
client.RegisterScheme("bzz", &http.RoundTripper{Port: port})
|
||||
// for public swarm gateway
|
||||
client.RegisterScheme(scheme, &http.RoundTripper{Host: host, Port: port})
|
||||
|
||||
The port you give the Roundtripper is the port the swarm proxy is listening on.
|
||||
If Host is left empty, localhost is assumed.
|
||||
|
||||
Using a public gateway, the above few lines gives you the leanest
|
||||
bzz-scheme aware read-only http client. You really only ever need this
|
||||
if you need go-native swarm access to bzz addresses, e.g.,
|
||||
github.com/ethereum/go-ethereum/common/natspec
|
||||
|
||||
*/
|
||||
|
||||
type RoundTripper struct {
|
||||
Host string
|
||||
Port string
|
||||
}
|
||||
|
||||
func (self *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
host := self.Host
|
||||
if len(host) == 0 {
|
||||
host = "localhost"
|
||||
}
|
||||
url := fmt.Sprintf("http://%s:%s/%s/%s", host, self.Port, req.URL.Host, req.URL.Path)
|
||||
glog.V(logger.Info).Infof("[BZZ] roundtripper: proxying request '%s' to '%s'", req.RequestURI, url)
|
||||
return http.Get(url)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package api
|
||||
package http
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
|
@ -22,7 +22,7 @@ func TestRoundTripper(t *testing.T) {
|
|||
})
|
||||
go http.ListenAndServe(":8600", serveMux)
|
||||
|
||||
rt := &RoundTripper{"8600"}
|
||||
rt := &RoundTripper{Port: "8600"}
|
||||
client := httpclient.New("/")
|
||||
client.RegisterProtocol("bzz", rt)
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
A simple http server interface to Swarm
|
||||
*/
|
||||
package api
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -41,7 +42,7 @@ type sequentialReader struct {
|
|||
// https://github.com/atom/electron/blob/master/docs/api/protocol.md
|
||||
|
||||
// starts up http server
|
||||
func StartHttpServer(api *Api, port string) {
|
||||
func StartHttpServer(api *api.Api, port string) {
|
||||
serveMux := http.NewServeMux()
|
||||
serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
handler(w, r, api)
|
||||
|
|
@ -50,7 +51,7 @@ func StartHttpServer(api *Api, port string) {
|
|||
glog.V(logger.Info).Infof("[BZZ] Swarm HTTP proxy started on localhost:%s", port)
|
||||
}
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request, api *Api) {
|
||||
func handler(w http.ResponseWriter, r *http.Request, a *api.Api) {
|
||||
requestURL := r.URL
|
||||
// This is wrong
|
||||
// if requestURL.Host == "" {
|
||||
|
|
@ -78,7 +79,7 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
|
|||
|
||||
switch {
|
||||
case r.Method == "POST" || r.Method == "PUT":
|
||||
key, err := api.dpa.Store(io.NewSectionReader(&sequentialReader{
|
||||
key, err := a.Store(io.NewSectionReader(&sequentialReader{
|
||||
reader: r.Body,
|
||||
ahead: make(map[int64]chan bool),
|
||||
}, 0, r.ContentLength), nil)
|
||||
|
|
@ -102,11 +103,11 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
|
|||
http.Error(w, "No PUT to /raw allowed.", http.StatusBadRequest)
|
||||
return
|
||||
} else {
|
||||
path = regularSlashes(path)
|
||||
path = api.RegularSlashes(path)
|
||||
mime := r.Header.Get("Content-Type")
|
||||
// TODO proper root hash separation
|
||||
glog.V(logger.Debug).Infof("[BZZ] Modify '%s' to store %v as '%s'.", path, key.Log(), mime)
|
||||
newKey, err := api.Modify(path[:64], path[65:], common.Bytes2Hex(key), mime)
|
||||
newKey, err := a.Modify(path[:64], path[65:], common.Bytes2Hex(key), mime)
|
||||
if err == nil {
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
|
|
@ -122,9 +123,9 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
|
|||
http.Error(w, "No DELETE to /raw allowed.", http.StatusBadRequest)
|
||||
return
|
||||
} else {
|
||||
path = regularSlashes(path)
|
||||
path = api.RegularSlashes(path)
|
||||
glog.V(logger.Debug).Infof("[BZZ] Delete '%s'.", path)
|
||||
newKey, err := api.Modify(path[:64], path[65:], "", "")
|
||||
newKey, err := a.Modify(path[:64], path[65:], "", "")
|
||||
if err == nil {
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
|
|
@ -138,7 +139,7 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
|
|||
path = trailingSlashes.ReplaceAllString(path, "")
|
||||
if raw {
|
||||
// resolving host
|
||||
key, err := api.Resolve(path)
|
||||
key, err := a.Resolve(path)
|
||||
if err != nil {
|
||||
glog.V(logger.Error).Infof("[BZZ] Swarm: %v", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
|
|
@ -146,7 +147,7 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
|
|||
}
|
||||
|
||||
// retrieving content
|
||||
reader := api.dpa.Retrieve(key)
|
||||
reader := a.Retrieve(key)
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: Reading %d bytes.", reader.Size())
|
||||
|
||||
// setting mime type
|
||||
|
|
@ -165,10 +166,9 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
|
|||
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: Structured GET request '%s' received.", uri)
|
||||
|
||||
// call to api.getPath on uri
|
||||
reader, mimeType, status, err := api.getPath(path)
|
||||
reader, mimeType, status, err := a.Get(path)
|
||||
if err != nil {
|
||||
if _, ok := err.(errResolve); ok {
|
||||
if _, ok := err.(api.ErrResolve); ok {
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err)
|
||||
status = http.StatusBadRequest
|
||||
} else {
|
||||
|
|
@ -290,7 +290,7 @@ func (self *manifestTrie) findPrefixOf(path string) (entry *manifestTrieEntry, p
|
|||
|
||||
// file system manifest always contains regularized paths
|
||||
// no leading or trailing slashes, only single slashes inside
|
||||
func regularSlashes(path string) (res string) {
|
||||
func RegularSlashes(path string) (res string) {
|
||||
for i := 0; i < len(path); i++ {
|
||||
if (path[i] != '/') || ((i > 0) && (path[i-1] != '/')) {
|
||||
res = res + path[i:i+1]
|
||||
|
|
@ -303,7 +303,7 @@ func regularSlashes(path string) (res string) {
|
|||
}
|
||||
|
||||
func (self *manifestTrie) getEntry(spath string) (entry *manifestTrieEntry, fullpath string) {
|
||||
path := regularSlashes(spath)
|
||||
path := RegularSlashes(spath)
|
||||
var pos int
|
||||
entry, pos = self.findPrefixOf(path)
|
||||
return entry, path[:pos]
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
|
||||
// "github.com/ethereum/go-ethereum/common/httpclient"
|
||||
// "github.com/ethereum/go-ethereum/jsre"
|
||||
)
|
||||
|
||||
type RoundTripper struct {
|
||||
Port string
|
||||
}
|
||||
|
||||
func (self *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
url := fmt.Sprintf("http://localhost:%s/%s/%s", self.Port, req.URL.Host, req.URL.Path)
|
||||
glog.V(logger.Info).Infof("[BZZ] roundtripper: proxying request '%s' to '%s'", req.RequestURI, url)
|
||||
return http.Get(url)
|
||||
}
|
||||
56
swarm/api/storage.go
Normal file
56
swarm/api/storage.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
// "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
// "github.com/ethereum/go-ethereum/swarm/storage"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
MimeType string
|
||||
Status int
|
||||
Size int64
|
||||
// Content []byte
|
||||
Content string
|
||||
}
|
||||
|
||||
// implements a service
|
||||
type Storage struct {
|
||||
api *Api
|
||||
}
|
||||
|
||||
func NewStorage(api *Api) *Storage {
|
||||
return &Storage{api}
|
||||
}
|
||||
|
||||
// Put uploads the content to the swarm with a simple manifest speficying
|
||||
// its content type
|
||||
func (self *Storage) Put(content, contentType string) (string, error) {
|
||||
return self.api.Put(content, contentType)
|
||||
}
|
||||
|
||||
// Get retrieves the content from bzzpath and reads the response in full
|
||||
// It returns the Response object, which serialises containing the
|
||||
// response body as the value of the Content field
|
||||
// NOTE: if error is non-nil, sResponse may still have partial content
|
||||
// the actual size of which is given in len(resp.Content), while the expected
|
||||
// size is resp.Size
|
||||
func (self *Storage) Get(bzzpath string) (*Response, error) {
|
||||
reader, mimeType, status, err := self.api.Get(bzzpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expsize := reader.Size()
|
||||
body := make([]byte, expsize)
|
||||
size, err := reader.Read(body)
|
||||
if int64(size) == expsize {
|
||||
err = nil
|
||||
}
|
||||
glog.V(logger.Detail).Infof("body: %s", body[:size])
|
||||
return &Response{mimeType, status, expsize, string(body[:size])}, err
|
||||
}
|
||||
|
||||
func (self *Storage) Modify(rootHash, path, contentHash, contentType string) (newRootHash string, err error) {
|
||||
return self.api.Modify(rootHash, path, contentHash, contentType)
|
||||
}
|
||||
33
swarm/api/storage_test.go
Normal file
33
swarm/api/storage_test.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testStorage(t *testing.T, f func(*Storage)) {
|
||||
testApi(t, func(api *Api) {
|
||||
f(NewStorage(api))
|
||||
})
|
||||
}
|
||||
|
||||
func TestStoragePutGet(t *testing.T) {
|
||||
testStorage(t, func(api *Storage) {
|
||||
content := "hello"
|
||||
exp := expResponse(content, "text/plain", 0)
|
||||
// exp := expResponse([]byte(content), "text/plain", 0)
|
||||
bzzhash, err := api.Put(content, exp.MimeType)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// to check put against the Api#Get
|
||||
resp0 := testGet(t, api.api, bzzhash)
|
||||
checkResponse(t, resp0, exp)
|
||||
|
||||
// check storage#Get
|
||||
resp, err := api.Get(bzzhash)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
checkResponse(t, &testResponse{nil, resp}, exp)
|
||||
})
|
||||
}
|
||||
|
|
@ -15,15 +15,23 @@ import (
|
|||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
rpc "github.com/ethereum/go-ethereum/rpc/v2"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
httpapi "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||
"github.com/ethereum/go-ethereum/swarm/network"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
)
|
||||
|
||||
const (
|
||||
Namespace = "bzz"
|
||||
Version = "0.1" // versioning reflect POC and release versions
|
||||
)
|
||||
|
||||
// the swarm stack
|
||||
type Swarm struct {
|
||||
config *api.Config // swarm configuration
|
||||
api *api.Api // high level api layer (fs/manifest)
|
||||
dns api.Resolver // DNS registrar
|
||||
dbAccess *network.DbAccess // access to local chunk db iterator and storage counter
|
||||
storage storage.ChunkStore // internal access to storage, common interface to cloud storage backends
|
||||
dpa *storage.DPA // distributed preimage archive, the local API to the storage with document level storage/retrieval support
|
||||
|
|
@ -78,9 +86,9 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool)
|
|||
// setup cloud storage internal access layer
|
||||
|
||||
self.storage = storage.NewNetStore(hash, lstore, cloud, config.StoreParams)
|
||||
glog.V(logger.Debug).Infof("[BZZ] -> Level 0: swarm net store shared access layer to Swarm Chunk Store")
|
||||
glog.V(logger.Debug).Infof("[BZZ] -> swarm net store shared access layer to Swarm Chunk Store")
|
||||
|
||||
// set up Depo (storage handler = remote cloud storage access layer)
|
||||
// set up Depo (storage handler = cloud storage access layer for incoming remote requests)
|
||||
self.depo = network.NewDepo(hash, lstore, self.storage)
|
||||
glog.V(logger.Debug).Infof("[BZZ] -> REmote Access to CHunks")
|
||||
|
||||
|
|
@ -89,14 +97,17 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool)
|
|||
glog.V(logger.Debug).Infof("[BZZ] -> Local Access to Swarm")
|
||||
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
|
||||
self.dpa = storage.NewDPA(dpaChunkStore, self.config.ChunkerParams)
|
||||
glog.V(logger.Debug).Infof("[BZZ] -> Level 1: Document/File API")
|
||||
glog.V(logger.Debug).Infof("[BZZ] -> Content Store API")
|
||||
|
||||
// set up high level api
|
||||
backend := api.NewEthApi(ethereum)
|
||||
backend.UpdateState()
|
||||
self.api = api.NewApi(self.dpa, ethreg.New(backend), self.config)
|
||||
self.dns = api.NewDNS(ethreg.New(backend))
|
||||
glog.V(logger.Debug).Infof("[BZZ] -> Swarm Domain Registrar")
|
||||
|
||||
self.api = api.NewApi(self.dpa, self.dns)
|
||||
// Manifests for Smart Hosting
|
||||
glog.V(logger.Debug).Infof("[BZZ] -> Level 2: Collection/Directory API")
|
||||
glog.V(logger.Debug).Infof("[BZZ] -> Web3 virtual server API")
|
||||
|
||||
// set chequebook
|
||||
if swapEnabled {
|
||||
|
|
@ -143,7 +154,7 @@ func (self *Swarm) Start(net *p2p.Server) error {
|
|||
|
||||
// start swarm http proxy server
|
||||
if self.config.Port != "" {
|
||||
go api.StartHttpServer(self.api, self.config.Port)
|
||||
go httpapi.StartHttpServer(self.api, self.config.Port)
|
||||
}
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm http proxy started on port: %v", self.config.Port)
|
||||
|
||||
|
|
@ -154,7 +165,7 @@ func (self *Swarm) Start(net *p2p.Server) error {
|
|||
"bzz": self.config.Port,
|
||||
}
|
||||
for scheme, port := range schemes {
|
||||
self.client.RegisterScheme(scheme, &api.RoundTripper{Port: port})
|
||||
self.client.RegisterScheme(scheme, &httpapi.RoundTripper{Port: port})
|
||||
}
|
||||
glog.V(logger.Debug).Infof("[BZZ] Swarm protocol handlers registered for url schemes: %v", schemes)
|
||||
|
||||
|
|
@ -182,8 +193,21 @@ func (self *Swarm) Protocols() []p2p.Protocol {
|
|||
return []p2p.Protocol{proto}
|
||||
}
|
||||
|
||||
func (self *Swarm) Api() *api.Api {
|
||||
return self.api
|
||||
// implements node.Service
|
||||
// Apis returns the RPC Api descriptors the Swarm implementation offers
|
||||
func (self *Swarm) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
// public APIs.
|
||||
rpc.API{Namespace, Version, api.NewStorage(self.api), true},
|
||||
rpc.API{Namespace, Version, self.dns, true},
|
||||
rpc.API{Namespace, Version, &Info{self.config, chequebook.ContractParams}, true},
|
||||
// admin APIs
|
||||
rpc.API{Namespace, Version, api.NewFileSystem(self.api), false},
|
||||
// rpc.API{Namespace, Version, test.New(self), false},
|
||||
// rpc.API{Namespace, Version, api.NewAdmin(self), false},
|
||||
// TODO: external apis exposed
|
||||
rpc.API{"chequebook", chequebook.Version, chequebook.NewApi(self.config.Swap.Chequebook()), true},
|
||||
}
|
||||
}
|
||||
|
||||
// Backend interface implemented by eth or JSON-IPC client
|
||||
|
|
@ -223,9 +247,19 @@ func NewLocalSwarm(datadir, port string) (self *Swarm, err error) {
|
|||
}
|
||||
|
||||
self = &Swarm{
|
||||
api: api.NewApi(dpa, nil, config),
|
||||
api: api.NewApi(dpa, nil),
|
||||
config: config,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// serialisable info about swarm
|
||||
type Info struct {
|
||||
*api.Config
|
||||
*chequebook.Params
|
||||
}
|
||||
|
||||
func (self *Info) Info() *Info {
|
||||
return self
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue