1 // package shell implements a remote API interface for a running ipfs daemon
18 files "github.com/ipfs/go-ipfs-files"
19 homedir "github.com/mitchellh/go-homedir"
20 ma "github.com/multiformats/go-multiaddr"
21 manet "github.com/multiformats/go-multiaddr-net"
22 tar "github.com/whyrusleeping/tar-utils"
24 p2pmetrics "github.com/libp2p/go-libp2p-metrics"
28 DefaultPathName = ".ipfs"
29 DefaultPathRoot = "~/" + DefaultPathName
30 DefaultApiFile = "api"
39 func NewLocalShell() *Shell {
40 baseDir := os.Getenv(EnvDir)
42 baseDir = DefaultPathRoot
45 baseDir, err := homedir.Expand(baseDir)
50 apiFile := path.Join(baseDir, DefaultApiFile)
52 if _, err := os.Stat(apiFile); err != nil {
56 api, err := ioutil.ReadFile(apiFile)
61 return NewShell(strings.TrimSpace(string(api)))
64 func NewShell(url string) *Shell {
66 Transport: &gohttp.Transport{
67 Proxy: gohttp.ProxyFromEnvironment,
68 DisableKeepAlives: true,
72 return NewShellWithClient(url, c)
75 func NewShellWithClient(url string, c *gohttp.Client) *Shell {
76 if a, err := ma.NewMultiaddr(url); err == nil {
77 _, host, err := manet.DialArgs(a)
85 // We don't support redirects.
86 sh.httpcli.CheckRedirect = func(_ *gohttp.Request, _ []*gohttp.Request) error {
87 return fmt.Errorf("unexpected redirect")
92 func (s *Shell) SetTimeout(d time.Duration) {
96 func (s *Shell) Request(command string, args ...string) *RequestBuilder {
97 return &RequestBuilder{
104 type IdOutput struct {
109 ProtocolVersion string
112 // ID gets information about a given peer. Arguments:
114 // peer: peer.ID of the node to look up. If no peer is specified,
115 // return information about the local peer.
116 func (s *Shell) ID(peer ...string) (*IdOutput, error) {
118 return nil, fmt.Errorf("Too many peer arguments")
122 if err := s.Request("id", peer...).Exec(context.Background(), &out); err != nil {
128 // Cat the content at the given path. Callers need to drain and close the returned reader after usage.
129 func (s *Shell) Cat(path string) (io.ReadCloser, error) {
130 resp, err := s.Request("cat", path).Send(context.Background())
134 if resp.Error != nil {
135 return nil, resp.Error
138 return resp.Output, nil
149 // List entries at the given path
150 func (s *Shell) List(path string) ([]*LsLink, error) {
151 var out struct{ Objects []LsObject }
152 err := s.Request("ls", path).Exec(context.Background(), &out)
156 if len(out.Objects) != 1 {
157 return nil, errors.New("bad response from server")
159 return out.Objects[0].Links, nil
169 type LsObject struct {
174 // Pin the given path
175 func (s *Shell) Pin(path string) error {
176 return s.Request("pin/add", path).
177 Option("recursive", true).
178 Exec(context.Background(), nil)
181 // Unpin the given path
182 func (s *Shell) Unpin(path string) error {
183 return s.Request("pin/rm", path).
184 Option("recursive", true).
185 Exec(context.Background(), nil)
190 RecursivePin = "recursive"
191 IndirectPin = "indirect"
194 type PinInfo struct {
198 // Pins returns a map of the pin hashes to their info (currently just the
199 // pin type, one of DirectPin, RecursivePin, or IndirectPin. A map is returned
200 // instead of a slice because it is easier to do existence lookup by map key
201 // than unordered array searching. The map is likely to be more useful to a
202 // client than a flat list.
203 func (s *Shell) Pins() (map[string]PinInfo, error) {
204 var raw struct{ Keys map[string]PinInfo }
205 return raw.Keys, s.Request("pin/ls").Exec(context.Background(), &raw)
208 type PeerInfo struct {
213 func (s *Shell) FindPeer(peer string) (*PeerInfo, error) {
214 var peers struct{ Responses []PeerInfo }
215 err := s.Request("dht/findpeer", peer).Exec(context.Background(), &peers)
219 if len(peers.Responses) == 0 {
220 return nil, errors.New("peer not found")
222 return &peers.Responses[0], nil
225 func (s *Shell) Refs(hash string, recursive bool) (<-chan string, error) {
226 resp, err := s.Request("refs", hash).
227 Option("recursive", recursive).
228 Send(context.Background())
233 if resp.Error != nil {
235 return nil, resp.Error
238 out := make(chan string)
245 dec := json.NewDecoder(resp.Output)
247 err := dec.Decode(&ref)
251 if len(ref.Ref) > 0 {
260 func (s *Shell) Patch(root, action string, args ...string) (string, error) {
262 return out.Hash, s.Request("object/patch/"+action, root).
264 Exec(context.Background(), &out)
267 func (s *Shell) PatchData(root string, set bool, data interface{}) (string, error) {
269 switch d := data.(type) {
273 read = bytes.NewReader(d)
275 read = strings.NewReader(d)
277 return "", fmt.Errorf("unrecognized type: %#v", data)
285 fr := files.NewReaderFile(read)
286 slf := files.NewSliceDirectory([]files.DirEntry{files.FileEntry("", fr)})
287 fileReader := files.NewMultiFileReader(slf, true)
290 return out.Hash, s.Request("object/patch/"+cmd, root).
292 Exec(context.Background(), &out)
295 func (s *Shell) PatchLink(root, path, childhash string, create bool) (string, error) {
297 return out.Hash, s.Request("object/patch/add-link", root, path, childhash).
298 Option("create", create).
299 Exec(context.Background(), &out)
302 func (s *Shell) Get(hash, outdir string) error {
303 resp, err := s.Request("get", hash).Option("create", true).Send(context.Background())
309 if resp.Error != nil {
313 extractor := &tar.Extractor{Path: outdir}
314 return extractor.Extract(resp.Output)
317 func (s *Shell) NewObject(template string) (string, error) {
319 req := s.Request("object/new")
321 req.Arguments(template)
323 return out.Hash, req.Exec(context.Background(), &out)
326 func (s *Shell) ResolvePath(path string) (string, error) {
330 err := s.Request("resolve", path).Exec(context.Background(), &out)
335 return strings.TrimPrefix(out.Path, "/ipfs/"), nil
338 // returns ipfs version and commit sha
339 func (s *Shell) Version() (string, string, error) {
345 if err := s.Request("version").Exec(context.Background(), &ver); err != nil {
348 return ver.Version, ver.Commit, nil
351 func (s *Shell) IsUp() bool {
352 _, _, err := s.Version()
356 func (s *Shell) BlockStat(path string) (string, int, error) {
362 if err := s.Request("block/stat", path).Exec(context.Background(), &inf); err != nil {
365 return inf.Key, inf.Size, nil
368 func (s *Shell) BlockGet(path string) ([]byte, error) {
369 resp, err := s.Request("block/get", path).Send(context.Background())
375 if resp.Error != nil {
376 return nil, resp.Error
379 return ioutil.ReadAll(resp.Output)
382 func (s *Shell) BlockPut(block []byte, format, mhtype string, mhlen int) (string, error) {
387 fr := files.NewBytesFile(block)
388 slf := files.NewSliceDirectory([]files.DirEntry{files.FileEntry("", fr)})
389 fileReader := files.NewMultiFileReader(slf, true)
391 return out.Key, s.Request("block/put").
392 Option("mhtype", mhtype).
393 Option("format", format).
394 Option("mhlen", mhlen).
396 Exec(context.Background(), &out)
399 type IpfsObject struct {
404 type ObjectLink struct {
409 func (s *Shell) ObjectGet(path string) (*IpfsObject, error) {
411 if err := s.Request("object/get", path).Exec(context.Background(), &obj); err != nil {
417 func (s *Shell) ObjectPut(obj *IpfsObject) (string, error) {
418 var data bytes.Buffer
419 err := json.NewEncoder(&data).Encode(obj)
424 fr := files.NewReaderFile(&data)
425 slf := files.NewSliceDirectory([]files.DirEntry{files.FileEntry("", fr)})
426 fileReader := files.NewMultiFileReader(slf, true)
429 return out.Hash, s.Request("object/put").
431 Exec(context.Background(), &out)
434 func (s *Shell) PubSubSubscribe(topic string) (*PubSubSubscription, error) {
436 resp, err := s.Request("pubsub/sub", topic).Send(context.Background())
440 return newPubSubSubscription(resp), nil
443 func (s *Shell) PubSubPublish(topic, data string) (err error) {
444 resp, err := s.Request("pubsub/pub", topic, data).Send(context.Background())
449 if resp.Error != nil {
455 type ObjectStats struct {
464 // ObjectStat gets stats for the DAG object named by key. It returns
465 // the stats of the requested Object or an error.
466 func (s *Shell) ObjectStat(key string) (*ObjectStats, error) {
468 err := s.Request("object/stat", key).Exec(context.Background(), &stat)
475 // ObjectStat gets stats for the DAG object named by key. It returns
476 // the stats of the requested Object or an error.
477 func (s *Shell) StatsBW(ctx context.Context) (*p2pmetrics.Stats, error) {
478 v := &p2pmetrics.Stats{}
479 err := s.Request("stats/bw").Exec(ctx, &v)
483 type SwarmStreamInfo struct {
487 type SwarmConnInfo struct {
492 Streams []SwarmStreamInfo
495 type SwarmConnInfos struct {
496 Peers []SwarmConnInfo
499 // SwarmPeers gets all the swarm peers
500 func (s *Shell) SwarmPeers(ctx context.Context) (*SwarmConnInfos, error) {
501 v := &SwarmConnInfos{}
502 err := s.Request("swarm/peers").Exec(ctx, &v)
506 type swarmConnection struct {
510 // SwarmConnect opens a swarm connection to a specific address.
511 func (s *Shell) SwarmConnect(ctx context.Context, addr ...string) error {
512 var conn *swarmConnection
513 err := s.Request("swarm/connect").