From 22e2edeb7589ca0a2d2f9c3118a8cf4824047d0c Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 13 Jul 2018 17:51:39 +0800 Subject: [PATCH 1/3] Add DefaultShell as required by #69 --- shell.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/shell.go b/shell.go index 2a46737db..1e7832989 100644 --- a/shell.go +++ b/shell.go @@ -84,6 +84,42 @@ func NewShellWithClient(url string, c *gohttp.Client) *Shell { } } +// Get shell from environmental variables, default api path or gateway. +func DefaultShell() (*Shell, error) { + urls := make([]string, 1) + if ipfsAPI := os.Getenv("IPFS_API"); ipfsAPI != "" { + urls = append(urls, ipfsAPI) + } + ipfsPath := os.Getenv("IPFS_PATH") + if ipfsPath == "" { + if homePath, err := homedir.Dir(); err == nil { + ipfsPath = homePath + } + } + if ipfsPath != "" { + apifile := path.Join(ipfsPath, ".ipfs", "api") + if data, err := ioutil.ReadFile(apifile); err == nil { + url := strings.Trim(string(data), "\n\t ") + urls = append(urls, url) + } + } + urls = append(urls, "/ip4/127.0.0.1/tcp/5001", "https://ipfs.io") + + // do not repeat encountered addresses + encountered := map[string]bool{} + for _, url := range urls { + if encountered[url] != true { + encountered[url] = true + sh := NewShell(url) + _, _, err := sh.Version() + if err == nil { + return sh, nil + } + } + } + return nil, errors.New("No default node is working") +} + func (s *Shell) SetTimeout(d time.Duration) { s.httpcli.Timeout = d } From a48d7d7d0cd66b0219f0ee3becfed39a0c7f354b Mon Sep 17 00:00:00 2001 From: B YI Date: Sat, 14 Jul 2018 00:13:50 +0800 Subject: [PATCH 2/3] use sync.Once to obtain default shell add functions to obtain new shells from environmental variables, default gateway and the default api file. --- shell.go | 110 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 85 insertions(+), 25 deletions(-) diff --git a/shell.go b/shell.go index 1e7832989..a78bd9f17 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" @@ -27,6 +28,13 @@ const ( DefaultPathRoot = "~/" + DefaultPathName DefaultApiFile = "api" EnvDir = "IPFS_PATH" + DefaultGateway = "https://ipfs.io" + DefaultAPIAddr = "/ip4/127.0.0.1/tcp/5001" +) + +var ( + defaultShellAddr string + defaultShellAddrLoadOnce sync.Once ) type Shell struct { @@ -84,40 +92,92 @@ func NewShellWithClient(url string, c *gohttp.Client) *Shell { } } -// Get shell from environmental variables, default api path or gateway. -func DefaultShell() (*Shell, error) { - urls := make([]string, 1) - if ipfsAPI := os.Getenv("IPFS_API"); ipfsAPI != "" { - urls = append(urls, ipfsAPI) - } - ipfsPath := os.Getenv("IPFS_PATH") - if ipfsPath == "" { - if homePath, err := homedir.Dir(); err == nil { - ipfsPath = homePath +// Load all shell urls with error checking +func NewShellWithCheck(urls ...string) (*Shell, error) { + 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, nil + } } } - if ipfsPath != "" { - apifile := path.Join(ipfsPath, ".ipfs", "api") + return nil, errors.New(fmt.Sprintf("No working server in %v", urls)) +} + +func NewShellFromDefaultGateway() (*Shell, error) { + return NewShellWithCheck(DefaultGateway) +} + +// Get all shell urls from api files +func newShellFromAPIFiles(files ...string) (urls []string) { + for _, apifile := range files { if data, err := ioutil.ReadFile(apifile); err == nil { url := strings.Trim(string(data), "\n\t ") urls = append(urls, url) } } - urls = append(urls, "/ip4/127.0.0.1/tcp/5001", "https://ipfs.io") + return +} - // do not repeat encountered addresses - encountered := map[string]bool{} - for _, url := range urls { - if encountered[url] != true { - encountered[url] = true - sh := NewShell(url) - _, _, err := sh.Version() - if err == nil { - return sh, nil - } - } +func NewShellFromAPIFiles(files ...string) (*Shell, error) { + return NewShellWithCheck(newShellFromAPIFiles(files...)...) +} + +// Get api url from ~/.ipfs/api +func newShellFromDefaultAPIFile() (urls []string) { + apifile, err := homedir.Expand(path.Join(DefaultPathRoot, DefaultApiFile)) + if err != nil { + return urls + } + return newShellFromAPIFiles(apifile) +} + +func NewShellFromDefaultAPIFile() (*Shell, error) { + return NewShellWithCheck(newShellFromDefaultAPIFile()...) +} + +// get all shell urls from environmental variable IPFS_API and IPFS_PATH +func newShellFromEnv() (urls []string) { + if ipfsAPI := os.Getenv("IPFS_API"); ipfsAPI != "" { + urls = append(urls, ipfsAPI) } - return nil, errors.New("No default node is working") + if ipfsPath := os.Getenv("IPFS_PATH"); ipfsPath != "" { + apifile := path.Join(ipfsPath, DefaultApiFile) + urls = append(urls, newShellFromAPIFiles(apifile)...) + } + return +} + +func NewShellFromEnv() (*Shell, error) { + return NewShellWithCheck(newShellFromEnv()...) +} + +// Load working default shell address once +func defaultShell() { + fmt.Println("ran defaultShell") + var urls []string + urls = append(urls, newShellFromEnv()...) + urls = append(urls, newShellFromDefaultAPIFile()...) + urls = append(urls, DefaultAPIAddr, DefaultGateway) + if sh, err := NewShellWithCheck(urls...); err == nil { + defaultShellAddr = sh.url + } +} + +func DefaultShell() *Shell { + // Load once and cache working default shell address + defaultShellAddrLoadOnce.Do(defaultShell) + return NewShell(defaultShellAddr) +} + +func DefaultShellWithCheck() (*Shell, error) { + // Load once and cache working default shell address + defaultShellAddrLoadOnce.Do(defaultShell) + return NewShellWithCheck(defaultShellAddr) } func (s *Shell) SetTimeout(d time.Duration) { From ede2fd1075a1d3f757fe27b10448c084a8213c6f Mon Sep 17 00:00:00 2001 From: B YI Date: Sat, 28 Jul 2018 12:02:55 +0800 Subject: [PATCH 3/3] reuse NewLocalShell, add DefautlShell Try to reuse NewLocalShell to return a local Shell add DefaultShell to return a shell from default api address add test functions to NewLocalShell, DefaultShell and TryNewShell --- shell.go | 127 ++++++++++++++++---------------------------------- shell_test.go | 37 +++++++++++++++ 2 files changed, 77 insertions(+), 87 deletions(-) diff --git a/shell.go b/shell.go index a78bd9f17..c1720ce8e 100644 --- a/shell.go +++ b/shell.go @@ -33,8 +33,11 @@ const ( ) var ( - defaultShellAddr string - defaultShellAddrLoadOnce sync.Once + localShell *Shell + LocalShellError error // Reading apiFile may raise this error. + localShellLoadOnce sync.Once + defaultShell *Shell + defaultShellLoadOnce sync.Once ) type Shell struct { @@ -42,29 +45,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 { @@ -92,8 +106,9 @@ func NewShellWithClient(url string, c *gohttp.Client) *Shell { } } -// Load all shell urls with error checking -func NewShellWithCheck(urls ...string) (*Shell, error) { +// 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 { @@ -101,83 +116,21 @@ func NewShellWithCheck(urls ...string) (*Shell, error) { sh := NewShell(url) _, _, err := sh.Version() if err == nil { - return sh, nil + return sh } } } - return nil, errors.New(fmt.Sprintf("No working server in %v", urls)) -} - -func NewShellFromDefaultGateway() (*Shell, error) { - return NewShellWithCheck(DefaultGateway) -} - -// Get all shell urls from api files -func newShellFromAPIFiles(files ...string) (urls []string) { - for _, apifile := range files { - if data, err := ioutil.ReadFile(apifile); err == nil { - url := strings.Trim(string(data), "\n\t ") - urls = append(urls, url) - } - } - return -} - -func NewShellFromAPIFiles(files ...string) (*Shell, error) { - return NewShellWithCheck(newShellFromAPIFiles(files...)...) -} - -// Get api url from ~/.ipfs/api -func newShellFromDefaultAPIFile() (urls []string) { - apifile, err := homedir.Expand(path.Join(DefaultPathRoot, DefaultApiFile)) - if err != nil { - return urls - } - return newShellFromAPIFiles(apifile) -} - -func NewShellFromDefaultAPIFile() (*Shell, error) { - return NewShellWithCheck(newShellFromDefaultAPIFile()...) -} - -// get all shell urls from environmental variable IPFS_API and IPFS_PATH -func newShellFromEnv() (urls []string) { - if ipfsAPI := os.Getenv("IPFS_API"); ipfsAPI != "" { - urls = append(urls, ipfsAPI) - } - if ipfsPath := os.Getenv("IPFS_PATH"); ipfsPath != "" { - apifile := path.Join(ipfsPath, DefaultApiFile) - urls = append(urls, newShellFromAPIFiles(apifile)...) - } - return -} - -func NewShellFromEnv() (*Shell, error) { - return NewShellWithCheck(newShellFromEnv()...) + return nil } -// Load working default shell address once -func defaultShell() { - fmt.Println("ran defaultShell") - var urls []string - urls = append(urls, newShellFromEnv()...) - urls = append(urls, newShellFromDefaultAPIFile()...) - urls = append(urls, DefaultAPIAddr, DefaultGateway) - if sh, err := NewShellWithCheck(urls...); err == nil { - defaultShellAddr = sh.url - } +func getDefaultShell() { + defaultShell = TryNewShell(DefaultAPIAddr) } +// Get shell from the default api address, may return nil if it is not working. func DefaultShell() *Shell { - // Load once and cache working default shell address - defaultShellAddrLoadOnce.Do(defaultShell) - return NewShell(defaultShellAddr) -} - -func DefaultShellWithCheck() (*Shell, error) { - // Load once and cache working default shell address - defaultShellAddrLoadOnce.Do(defaultShell) - return NewShellWithCheck(defaultShellAddr) + defaultShellLoadOnce.Do(getDefaultShell) + return defaultShell } func (s *Shell) SetTimeout(d time.Duration) { diff --git a/shell_test.go b/shell_test.go index 26b43eaa1..68cba9c5e 100644 --- a/shell_test.go +++ b/shell_test.go @@ -5,6 +5,7 @@ import ( "crypto/md5" "fmt" "io" + "os" "testing" "time" @@ -242,3 +243,39 @@ func TestDagPut(t *testing.T) { is.Nil(err) 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) +}