+++ /dev/null
-// package shell implements a remote API interface for a running ipfs daemon
-package shell
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- gohttp "net/http"
- "os"
- "path"
- "strings"
- "time"
-
- files "github.com/ipfs/go-ipfs-files"
- homedir "github.com/mitchellh/go-homedir"
- ma "github.com/multiformats/go-multiaddr"
- manet "github.com/multiformats/go-multiaddr-net"
- tar "github.com/whyrusleeping/tar-utils"
-)
-
-const (
- DefaultPathName = ".ipfs"
- DefaultPathRoot = "~/" + DefaultPathName
- DefaultApiFile = "api"
- EnvDir = "IPFS_PATH"
-)
-
-type Shell struct {
- url string
- httpcli gohttp.Client
-}
-
-func NewLocalShell() *Shell {
- baseDir := os.Getenv(EnvDir)
- if baseDir == "" {
- baseDir = DefaultPathRoot
- }
-
- baseDir, err := homedir.Expand(baseDir)
- if err != nil {
- return nil
- }
-
- apiFile := path.Join(baseDir, DefaultApiFile)
-
- if _, err := os.Stat(apiFile); err != nil {
- return nil
- }
-
- api, err := ioutil.ReadFile(apiFile)
- if err != nil {
- return nil
- }
-
- return NewShell(strings.TrimSpace(string(api)))
-}
-
-func NewShell(url string) *Shell {
- c := &gohttp.Client{
- Transport: &gohttp.Transport{
- Proxy: gohttp.ProxyFromEnvironment,
- DisableKeepAlives: true,
- },
- }
-
- return NewShellWithClient(url, c)
-}
-
-func NewShellWithClient(url string, c *gohttp.Client) *Shell {
- if a, err := ma.NewMultiaddr(url); err == nil {
- _, host, err := manet.DialArgs(a)
- if err == nil {
- url = host
- }
- }
- var sh Shell
- sh.url = url
- sh.httpcli = *c
- // We don't support redirects.
- sh.httpcli.CheckRedirect = func(_ *gohttp.Request, _ []*gohttp.Request) error {
- return fmt.Errorf("unexpected redirect")
- }
- return &sh
-}
-
-func (s *Shell) SetTimeout(d time.Duration) {
- s.httpcli.Timeout = d
-}
-
-func (s *Shell) Request(command string, args ...string) *RequestBuilder {
- return &RequestBuilder{
- command: command,
- args: args,
- shell: s,
- }
-}
-
-type IdOutput struct {
- ID string
- PublicKey string
- Addresses []string
- AgentVersion string
- ProtocolVersion string
-}
-
-// ID gets information about a given peer. Arguments:
-//
-// peer: peer.ID of the node to look up. If no peer is specified,
-// return information about the local peer.
-func (s *Shell) ID(peer ...string) (*IdOutput, error) {
- if len(peer) > 1 {
- return nil, fmt.Errorf("Too many peer arguments")
- }
-
- var out IdOutput
- if err := s.Request("id", peer...).Exec(context.Background(), &out); err != nil {
- return nil, err
- }
- return &out, nil
-}
-
-// Cat the content at the given path. Callers need to drain and close the returned reader after usage.
-func (s *Shell) Cat(path string) (io.ReadCloser, error) {
- resp, err := s.Request("cat", path).Send(context.Background())
- if err != nil {
- return nil, err
- }
- if resp.Error != nil {
- return nil, resp.Error
- }
-
- return resp.Output, nil
-}
-
-const (
- TRaw = iota
- TDirectory
- TFile
- TMetadata
- TSymlink
-)
-
-// List entries at the given path
-func (s *Shell) List(path string) ([]*LsLink, error) {
- var out struct{ Objects []LsObject }
- err := s.Request("ls", path).Exec(context.Background(), &out)
- if err != nil {
- return nil, err
- }
- if len(out.Objects) != 1 {
- return nil, errors.New("bad response from server")
- }
- return out.Objects[0].Links, nil
-}
-
-type LsLink struct {
- Hash string
- Name string
- Size uint64
- Type int
-}
-
-type LsObject struct {
- Links []*LsLink
- LsLink
-}
-
-// Pin the given path
-func (s *Shell) Pin(path string) error {
- return s.Request("pin/add", path).
- Option("recursive", true).
- Exec(context.Background(), nil)
-}
-
-// Unpin the given path
-func (s *Shell) Unpin(path string) error {
- return s.Request("pin/rm", path).
- Option("recursive", true).
- Exec(context.Background(), nil)
-}
-
-const (
- DirectPin = "direct"
- RecursivePin = "recursive"
- IndirectPin = "indirect"
-)
-
-type PinInfo struct {
- Type string
-}
-
-// Pins returns a map of the pin hashes to their info (currently just the
-// pin type, one of DirectPin, RecursivePin, or IndirectPin. A map is returned
-// instead of a slice because it is easier to do existence lookup by map key
-// than unordered array searching. The map is likely to be more useful to a
-// client than a flat list.
-func (s *Shell) Pins() (map[string]PinInfo, error) {
- var raw struct{ Keys map[string]PinInfo }
- return raw.Keys, s.Request("pin/ls").Exec(context.Background(), &raw)
-}
-
-type PeerInfo struct {
- Addrs []string
- ID string
-}
-
-func (s *Shell) FindPeer(peer string) (*PeerInfo, error) {
- var peers struct{ Responses []PeerInfo }
- err := s.Request("dht/findpeer", peer).Exec(context.Background(), &peers)
- if err != nil {
- return nil, err
- }
- if len(peers.Responses) == 0 {
- return nil, errors.New("peer not found")
- }
- return &peers.Responses[0], nil
-}
-
-func (s *Shell) Refs(hash string, recursive bool) (<-chan string, error) {
- resp, err := s.Request("refs", hash).
- Option("recursive", recursive).
- Send(context.Background())
- if err != nil {
- return nil, err
- }
-
- if resp.Error != nil {
- resp.Close()
- return nil, resp.Error
- }
-
- out := make(chan string)
- go func() {
- defer resp.Close()
- var ref struct {
- Ref string
- }
- defer close(out)
- dec := json.NewDecoder(resp.Output)
- for {
- err := dec.Decode(&ref)
- if err != nil {
- return
- }
- if len(ref.Ref) > 0 {
- out <- ref.Ref
- }
- }
- }()
-
- return out, nil
-}
-
-func (s *Shell) Patch(root, action string, args ...string) (string, error) {
- var out object
- return out.Hash, s.Request("object/patch/"+action, root).
- Arguments(args...).
- Exec(context.Background(), &out)
-}
-
-func (s *Shell) PatchData(root string, set bool, data interface{}) (string, error) {
- var read io.Reader
- switch d := data.(type) {
- case io.Reader:
- read = d
- case []byte:
- read = bytes.NewReader(d)
- case string:
- read = strings.NewReader(d)
- default:
- return "", fmt.Errorf("unrecognized type: %#v", data)
- }
-
- cmd := "append-data"
- if set {
- cmd = "set-data"
- }
-
- fr := files.NewReaderFile(read)
- slf := files.NewSliceDirectory([]files.DirEntry{files.FileEntry("", fr)})
- fileReader := files.NewMultiFileReader(slf, true)
-
- var out object
- return out.Hash, s.Request("object/patch/"+cmd, root).
- Body(fileReader).
- Exec(context.Background(), &out)
-}
-
-func (s *Shell) PatchLink(root, path, childhash string, create bool) (string, error) {
- var out object
- return out.Hash, s.Request("object/patch/add-link", root, path, childhash).
- Option("create", create).
- Exec(context.Background(), &out)
-}
-
-func (s *Shell) Get(hash, outdir string) error {
- resp, err := s.Request("get", hash).Option("create", true).Send(context.Background())
- if err != nil {
- return err
- }
- defer resp.Close()
-
- if resp.Error != nil {
- return resp.Error
- }
-
- extractor := &tar.Extractor{Path: outdir}
- return extractor.Extract(resp.Output)
-}
-
-func (s *Shell) NewObject(template string) (string, error) {
- var out object
- req := s.Request("object/new")
- if template != "" {
- req.Arguments(template)
- }
- return out.Hash, req.Exec(context.Background(), &out)
-}
-
-func (s *Shell) ResolvePath(path string) (string, error) {
- var out struct {
- Path string
- }
- err := s.Request("resolve", path).Exec(context.Background(), &out)
- if err != nil {
- return "", err
- }
-
- return strings.TrimPrefix(out.Path, "/ipfs/"), nil
-}
-
-// returns ipfs version and commit sha
-func (s *Shell) Version() (string, string, error) {
- ver := struct {
- Version string
- Commit string
- }{}
-
- if err := s.Request("version").Exec(context.Background(), &ver); err != nil {
- return "", "", err
- }
- return ver.Version, ver.Commit, nil
-}
-
-func (s *Shell) IsUp() bool {
- _, _, err := s.Version()
- return err == nil
-}
-
-func (s *Shell) BlockStat(path string) (string, int, error) {
- var inf struct {
- Key string
- Size int
- }
-
- if err := s.Request("block/stat", path).Exec(context.Background(), &inf); err != nil {
- return "", 0, err
- }
- return inf.Key, inf.Size, nil
-}
-
-func (s *Shell) BlockGet(path string) ([]byte, error) {
- resp, err := s.Request("block/get", path).Send(context.Background())
- if err != nil {
- return nil, err
- }
- defer resp.Close()
-
- if resp.Error != nil {
- return nil, resp.Error
- }
-
- return ioutil.ReadAll(resp.Output)
-}
-
-func (s *Shell) BlockPut(block []byte, format, mhtype string, mhlen int) (string, error) {
- var out struct {
- Key string
- }
-
- fr := files.NewBytesFile(block)
- slf := files.NewSliceDirectory([]files.DirEntry{files.FileEntry("", fr)})
- fileReader := files.NewMultiFileReader(slf, true)
-
- return out.Key, s.Request("block/put").
- Option("mhtype", mhtype).
- Option("format", format).
- Option("mhlen", mhlen).
- Body(fileReader).
- Exec(context.Background(), &out)
-}
-
-type IpfsObject struct {
- Links []ObjectLink
- Data string
-}
-
-type ObjectLink struct {
- Name, Hash string
- Size uint64
-}
-
-func (s *Shell) ObjectGet(path string) (*IpfsObject, error) {
- var obj IpfsObject
- if err := s.Request("object/get", path).Exec(context.Background(), &obj); err != nil {
- return nil, err
- }
- return &obj, nil
-}
-
-func (s *Shell) ObjectPut(obj *IpfsObject) (string, error) {
- var data bytes.Buffer
- err := json.NewEncoder(&data).Encode(obj)
- if err != nil {
- return "", err
- }
-
- fr := files.NewReaderFile(&data)
- slf := files.NewSliceDirectory([]files.DirEntry{files.FileEntry("", fr)})
- fileReader := files.NewMultiFileReader(slf, true)
-
- var out object
- return out.Hash, s.Request("object/put").
- Body(fileReader).
- Exec(context.Background(), &out)
-}
-
-func (s *Shell) PubSubSubscribe(topic string) (*PubSubSubscription, error) {
- // connect
- resp, err := s.Request("pubsub/sub", topic).Send(context.Background())
- if err != nil {
- return nil, err
- }
- return newPubSubSubscription(resp), nil
-}
-
-func (s *Shell) PubSubPublish(topic, data string) (err error) {
- resp, err := s.Request("pubsub/pub", topic, data).Send(context.Background())
- if err != nil {
- return err
- }
- defer resp.Close()
- if resp.Error != nil {
- return resp.Error
- }
- return nil
-}
-
-type ObjectStats struct {
- Hash string
- BlockSize int
- CumulativeSize int
- DataSize int
- LinksSize int
- NumLinks int
-}
-
-// ObjectStat gets stats for the DAG object named by key. It returns
-// the stats of the requested Object or an error.
-func (s *Shell) ObjectStat(key string) (*ObjectStats, error) {
- var stat ObjectStats
- err := s.Request("object/stat", key).Exec(context.Background(), &stat)
- if err != nil {
- return nil, err
- }
- return &stat, nil
-}
-
-// ObjectStat gets stats for the DAG object named by key. It returns
-// the stats of the requested Object or an error.
-/*
-func (s *Shell) StatsBW(ctx context.Context) (*p2pmetrics.Stats, error) {
- v := &p2pmetrics.Stats{}
- err := s.Request("stats/bw").Exec(ctx, &v)
- return v, err
-}
-*/
-
-type SwarmStreamInfo struct {
- Protocol string
-}
-
-type SwarmConnInfo struct {
- Addr string
- Peer string
- Latency string
- Muxer string
- Streams []SwarmStreamInfo
-}
-
-type SwarmConnInfos struct {
- Peers []SwarmConnInfo
-}
-
-// SwarmPeers gets all the swarm peers
-func (s *Shell) SwarmPeers(ctx context.Context) (*SwarmConnInfos, error) {
- v := &SwarmConnInfos{}
- err := s.Request("swarm/peers").Exec(ctx, &v)
- return v, err
-}
-
-type swarmConnection struct {
- Strings []string
-}
-
-// SwarmConnect opens a swarm connection to a specific address.
-func (s *Shell) SwarmConnect(ctx context.Context, addr ...string) error {
- var conn *swarmConnection
- err := s.Request("swarm/connect").
- Arguments(addr...).
- Exec(ctx, &conn)
- return err
-}