diff --git a/shell.go b/shell.go index 22324867b..d0c272aec 100644 --- a/shell.go +++ b/shell.go @@ -13,6 +13,7 @@ import ( "os" "path" "strings" + "sync" "time" files "github.com/ipfs/go-ipfs-cmdkit/files" @@ -29,6 +30,16 @@ const ( DefaultPathRoot = "~/" + DefaultPathName DefaultApiFile = "api" EnvDir = "IPFS_PATH" + DefaultGateway = "https://ipfs.io" + DefaultAPIAddr = "/ip4/127.0.0.1/tcp/5001" +) + +var ( + localShell *Shell + LocalShellError error // Reading apiFile may raise this error. + localShellLoadOnce sync.Once + defaultShell *Shell + defaultShellLoadOnce sync.Once ) type Shell struct { @@ -36,29 +47,40 @@ type Shell struct { httpcli *gohttp.Client } -func NewLocalShell() *Shell { +func newLocalShell() { + ipfsAPI := os.Getenv("IPFS_API") + if ipfsAPI != "" { + localShell = NewShell(strings.TrimSpace(ipfsAPI)) + return + } + 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 + if apiFile, err := homedir.Expand(apiFile); err == nil { + if _, err := os.Stat(apiFile); err == nil { + api, err := ioutil.ReadFile(apiFile) + if err != nil { + LocalShellError = err + return + } + localShell = NewShell(strings.TrimSpace(string(api))) + return + } } - api, err := ioutil.ReadFile(apiFile) - if err != nil { - return nil - } + localShell = NewShell(DefaultGateway) +} - return NewShell(strings.TrimSpace(string(api))) +// Try to obtain a new shell from the following sources, returns the first found one. +// The sources are $IPFS_API, api file under $IPFS_PATH or ~/.ipfs and the default gateway. +// Will ignore api file if it does not exist, but may rasie APIFileError if unable to read it. +func NewLocalShell() *Shell { + localShellLoadOnce.Do(newLocalShell) + return localShell } func NewShell(url string) *Shell { @@ -86,6 +108,33 @@ func NewShellWithClient(url string, c *gohttp.Client) *Shell { } } +// Try to obtain a working shell from the urls given in the arguments. +// Returns the first working shell or returns nil when none of the urls works. +func TryNewShell(urls ...string) *Shell { + encountered := map[string]struct{}{} + for _, url := range urls { + if _, ok := encountered[url]; !ok { + encountered[url] = struct{}{} + sh := NewShell(url) + _, _, err := sh.Version() + if err == nil { + return sh + } + } + } + return nil +} + +func getDefaultShell() { + defaultShell = TryNewShell(DefaultAPIAddr) +} + +// Get shell from the default api address, may return nil if it is not working. +func DefaultShell() *Shell { + defaultShellLoadOnce.Do(getDefaultShell) + return defaultShell +} + func (s *Shell) SetTimeout(d time.Duration) { s.httpcli.Timeout = d } diff --git a/shell_test.go b/shell_test.go index 535f1fd52..43c227afa 100644 --- a/shell_test.go +++ b/shell_test.go @@ -6,6 +6,7 @@ import ( "crypto/md5" "fmt" "io" + "os" "testing" "time" @@ -244,6 +245,40 @@ func TestDagPut(t *testing.T) { is.Equal(c, "zdpuAt47YjE9XTgSxUBkiYCbmnktKajQNheQBGASHj3FfYf8M") } +func TestNewLocalShell(t *testing.T) { + is := is.New(t) + shell1 := NewLocalShell() + err := LocalShellError + is.Nil(err) + is.NotNil(shell1) + ipfs_api := "mySimpleTest" + os.Setenv("IPFS_API", ipfs_api) + shell2 := NewLocalShell() + is.Nil(err) + is.NotNil(shell2) + // NewLocalShell is guarded by a sync.Once + is.Equal(shell1, shell2) + // run newLocalShell one more time to make environmental variable take effects. + newLocalShell() + shell3 := NewLocalShell() + is.Equal(shell3.url, ipfs_api) +} + +func TestDefaultShell(t *testing.T) { + is := is.New(t) + sh := DefaultShell() + is.NotNil(sh) + is.OK(sh.IsUp()) +} + +func TestTryNewShell(t *testing.T) { + is := is.New(t) + sh := TryNewShell(DefaultGateway, DefaultAPIAddr) + is.NotNil(sh) + is.Equal(sh.url, DefaultGateway) + is.OK(sh.IsUp()) + sh = TryNewShell("some-nonexistent-URL") + is.Nil(sh) func TestStatsBW(t *testing.T) { is := is.New(t) s := NewShell(shellUrl)