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"
26 DefaultPathName = ".ipfs"
27 DefaultPathRoot = "~/" + DefaultPathName
28 DefaultApiFile = "api"
37 func NewLocalShell() *Shell {
38 baseDir := os.Getenv(EnvDir)
40 baseDir = DefaultPathRoot
43 baseDir, err := homedir.Expand(baseDir)
48 apiFile := path.Join(baseDir, DefaultApiFile)
50 if _, err := os.Stat(apiFile); err != nil {
54 api, err := ioutil.ReadFile(apiFile)
59 return NewShell(strings.TrimSpace(string(api)))
62 func NewShell(url string) *Shell {
64 Transport: &gohttp.Transport{
65 Proxy: gohttp.ProxyFromEnvironment,
66 DisableKeepAlives: true,
70 return NewShellWithClient(url, c)
73 func NewShellWithClient(url string, c *gohttp.Client) *Shell {
74 if a, err := ma.NewMultiaddr(url); err == nil {
75 _, host, err := manet.DialArgs(a)
83 // We don't support redirects.
84 sh.httpcli.CheckRedirect = func(_ *gohttp.Request, _ []*gohttp.Request) error {
85 return fmt.Errorf("unexpected redirect")
90 func (s *Shell) SetTimeout(d time.Duration) {
94 func (s *Shell) Request(command string, args ...string) *RequestBuilder {
95 return &RequestBuilder{
102 type IdOutput struct {
107 ProtocolVersion string
110 // ID gets information about a given peer. Arguments:
112 // peer: peer.ID of the node to look up. If no peer is specified,
113 // return information about the local peer.
114 func (s *Shell) ID(peer ...string) (*IdOutput, error) {
116 return nil, fmt.Errorf("Too many peer arguments")
120 if err := s.Request("id", peer...).Exec(context.Background(), &out); err != nil {
126 // Cat the content at the given path. Callers need to drain and close the returned reader after usage.
127 func (s *Shell) Cat(path string) (io.ReadCloser, error) {
128 resp, err := s.Request("cat", path).Send(context.Background())
132 if resp.Error != nil {
133 return nil, resp.Error
136 return resp.Output, nil
147 // List entries at the given path
148 func (s *Shell) List(path string) ([]*LsLink, error) {
149 var out struct{ Objects []LsObject }
150 err := s.Request("ls", path).Exec(context.Background(), &out)
154 if len(out.Objects) != 1 {
155 return nil, errors.New("bad response from server")
157 return out.Objects[0].Links, nil
167 type LsObject struct {
172 // Pin the given path
173 func (s *Shell) Pin(path string) error {
174 return s.Request("pin/add", path).
175 Option("recursive", true).
176 Exec(context.Background(), nil)
179 // Unpin the given path
180 func (s *Shell) Unpin(path string) error {
181 return s.Request("pin/rm", path).
182 Option("recursive", true).
183 Exec(context.Background(), nil)
188 RecursivePin = "recursive"
189 IndirectPin = "indirect"
192 type PinInfo struct {
196 // Pins returns a map of the pin hashes to their info (currently just the
197 // pin type, one of DirectPin, RecursivePin, or IndirectPin. A map is returned
198 // instead of a slice because it is easier to do existence lookup by map key
199 // than unordered array searching. The map is likely to be more useful to a
200 // client than a flat list.
201 func (s *Shell) Pins() (map[string]PinInfo, error) {
202 var raw struct{ Keys map[string]PinInfo }
203 return raw.Keys, s.Request("pin/ls").Exec(context.Background(), &raw)
206 type PeerInfo struct {
211 func (s *Shell) FindPeer(peer string) (*PeerInfo, error) {
212 var peers struct{ Responses []PeerInfo }
213 err := s.Request("dht/findpeer", peer).Exec(context.Background(), &peers)
217 if len(peers.Responses) == 0 {
218 return nil, errors.New("peer not found")
220 return &peers.Responses[0], nil
223 func (s *Shell) Refs(hash string, recursive bool) (<-chan string, error) {
224 resp, err := s.Request("refs", hash).
225 Option("recursive", recursive).
226 Send(context.Background())
231 if resp.Error != nil {
233 return nil, resp.Error
236 out := make(chan string)
243 dec := json.NewDecoder(resp.Output)
245 err := dec.Decode(&ref)
249 if len(ref.Ref) > 0 {
258 func (s *Shell) Patch(root, action string, args ...string) (string, error) {
260 return out.Hash, s.Request("object/patch/"+action, root).
262 Exec(context.Background(), &out)
265 func (s *Shell) PatchData(root string, set bool, data interface{}) (string, error) {
267 switch d := data.(type) {
271 read = bytes.NewReader(d)
273 read = strings.NewReader(d)
275 return "", fmt.Errorf("unrecognized type: %#v", data)
283 fr := files.NewReaderFile(read)
284 slf := files.NewSliceDirectory([]files.DirEntry{files.FileEntry("", fr)})
285 fileReader := files.NewMultiFileReader(slf, true)
288 return out.Hash, s.Request("object/patch/"+cmd, root).
290 Exec(context.Background(), &out)
293 func (s *Shell) PatchLink(root, path, childhash string, create bool) (string, error) {
295 return out.Hash, s.Request("object/patch/add-link", root, path, childhash).
296 Option("create", create).
297 Exec(context.Background(), &out)
300 func (s *Shell) Get(hash, outdir string) error {
301 resp, err := s.Request("get", hash).Option("create", true).Send(context.Background())
307 if resp.Error != nil {
311 extractor := &tar.Extractor{Path: outdir}
312 return extractor.Extract(resp.Output)
315 func (s *Shell) NewObject(template string) (string, error) {
317 req := s.Request("object/new")
319 req.Arguments(template)
321 return out.Hash, req.Exec(context.Background(), &out)
324 func (s *Shell) ResolvePath(path string) (string, error) {
328 err := s.Request("resolve", path).Exec(context.Background(), &out)
333 return strings.TrimPrefix(out.Path, "/ipfs/"), nil
336 // returns ipfs version and commit sha
337 func (s *Shell) Version() (string, string, error) {
343 if err := s.Request("version").Exec(context.Background(), &ver); err != nil {
346 return ver.Version, ver.Commit, nil
349 func (s *Shell) IsUp() bool {
350 _, _, err := s.Version()
354 func (s *Shell) BlockStat(path string) (string, int, error) {
360 if err := s.Request("block/stat", path).Exec(context.Background(), &inf); err != nil {
363 return inf.Key, inf.Size, nil
366 func (s *Shell) BlockGet(path string) ([]byte, error) {
367 resp, err := s.Request("block/get", path).Send(context.Background())
373 if resp.Error != nil {
374 return nil, resp.Error
377 return ioutil.ReadAll(resp.Output)
380 func (s *Shell) BlockPut(block []byte, format, mhtype string, mhlen int) (string, error) {
385 fr := files.NewBytesFile(block)
386 slf := files.NewSliceDirectory([]files.DirEntry{files.FileEntry("", fr)})
387 fileReader := files.NewMultiFileReader(slf, true)
389 return out.Key, s.Request("block/put").
390 Option("mhtype", mhtype).
391 Option("format", format).
392 Option("mhlen", mhlen).
394 Exec(context.Background(), &out)
397 type IpfsObject struct {
402 type ObjectLink struct {
407 func (s *Shell) ObjectGet(path string) (*IpfsObject, error) {
409 if err := s.Request("object/get", path).Exec(context.Background(), &obj); err != nil {
415 func (s *Shell) ObjectPut(obj *IpfsObject) (string, error) {
416 var data bytes.Buffer
417 err := json.NewEncoder(&data).Encode(obj)
422 fr := files.NewReaderFile(&data)
423 slf := files.NewSliceDirectory([]files.DirEntry{files.FileEntry("", fr)})
424 fileReader := files.NewMultiFileReader(slf, true)
427 return out.Hash, s.Request("object/put").
429 Exec(context.Background(), &out)
432 func (s *Shell) PubSubSubscribe(topic string) (*PubSubSubscription, error) {
434 resp, err := s.Request("pubsub/sub", topic).Send(context.Background())
438 return newPubSubSubscription(resp), nil
441 func (s *Shell) PubSubPublish(topic, data string) (err error) {
442 resp, err := s.Request("pubsub/pub", topic, data).Send(context.Background())
447 if resp.Error != nil {
453 type ObjectStats struct {
462 // ObjectStat gets stats for the DAG object named by key. It returns
463 // the stats of the requested Object or an error.
464 func (s *Shell) ObjectStat(key string) (*ObjectStats, error) {
466 err := s.Request("object/stat", key).Exec(context.Background(), &stat)
473 // ObjectStat gets stats for the DAG object named by key. It returns
474 // the stats of the requested Object or an error.
476 func (s *Shell) StatsBW(ctx context.Context) (*p2pmetrics.Stats, error) {
477 v := &p2pmetrics.Stats{}
478 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").