diff --git a/cli/lakeflags/datadir.go b/cli/lakeflags/datadir.go new file mode 100644 index 0000000000..d88020276d --- /dev/null +++ b/cli/lakeflags/datadir.go @@ -0,0 +1,36 @@ +package lakeflags + +import ( + "os" + "path/filepath" + "runtime" +) + +var defaultDataDir string + +func init() { + defaultDataDir = getDefaultDataDir() +} + +// getDefaultDataDir returns the default data directory for the current user. +// Derived from https://github.com/btcsuite/btcd/blob/master/btcutil/appdata.go +func getDefaultDataDir() string { + // Resolve the XDG data home directory if set. + if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" { + return filepath.Join(xdgDataHome, "zed") + } + if runtime.GOOS == "windows" { + if appData := os.Getenv("LOCALAPPDATA"); appData != "" { + return filepath.Join(appData, "zed") + } + } + if homeDir, _ := os.UserHomeDir(); homeDir != "" { + // Follow the XDG spec which states: + // If $XDG_DATA_HOME is either not set or empty, a default equal to + // $HOME/.local/share should be used. + return filepath.Join(homeDir, ".local", "share", "zed") + } + // Return an empty string which will cause an error if a default data + // directory cannot be found. + return "" +} diff --git a/cli/lakeflags/flags.go b/cli/lakeflags/flags.go index b4678fba3c..bb6efb3c56 100644 --- a/cli/lakeflags/flags.go +++ b/cli/lakeflags/flags.go @@ -5,6 +5,7 @@ import ( "errors" "flag" "fmt" + "net" "os" "path/filepath" "strings" @@ -21,11 +22,8 @@ var ErrNoHEAD = errors.New("HEAD not specified: indicate with -use or run the \" type Flags struct { ConfigDir string - // LakeSpecified is set to true if the lake is explicitly set via either - // command line flag or environment variable. - LakeSpecified bool - Lake string - Quiet bool + Lake string + Quiet bool } func (l *Flags) SetFlags(fs *flag.FlagSet) { @@ -35,20 +33,19 @@ func (l *Flags) SetFlags(fs *flag.FlagSet) { dir = filepath.Join(dir, ".zed") } fs.StringVar(&l.ConfigDir, "configdir", dir, "configuration and credentials directory") - l.Lake = "http://localhost:9867" if s, ok := os.LookupEnv("ZED_LAKE"); ok { l.Lake = strings.TrimRight(s, "/") - l.LakeSpecified = true + } else { + l.Lake = defaultDataDir } fs.Func("lake", fmt.Sprintf("lake location (env ZED_LAKE) (default %s)", l.Lake), func(s string) error { l.Lake = strings.TrimRight(s, "/") - l.LakeSpecified = true return nil }) } func (l *Flags) Connection() (*client.Connection, error) { - uri, err := l.URI() + uri, err := l.ClientURI() if err != nil { return nil, err } @@ -63,7 +60,7 @@ func (l *Flags) Connection() (*client.Connection, error) { } func (l *Flags) Open(ctx context.Context) (api.Interface, error) { - uri, err := l.URI() + uri, err := l.ClientURI() if err != nil { return nil, err } @@ -95,3 +92,27 @@ func (l *Flags) URI() (*storage.URI, error) { } return u, err } + +// ClientURI returns the URI of the lake to connect to. If the lake path is +// the defaultDataDir, it first checks if a service is running on port 9867 +// and if so, uses http://localhost:9867 as the lake location. +func (l *Flags) ClientURI() (*storage.URI, error) { + u, err := l.URI() + if err != nil { + return nil, err + } + // If the lake is the defaultDataDir, first check if a service is running + // on port 9867 and if so, use this as the lake location. + if u.String() == storage.MustParseURI(defaultDataDir).String() && portInUse("9867") { + u = storage.MustParseURI("http://localhost:9867") + } + return u, nil +} + +func portInUse(port string) bool { + ln, err := net.Listen("tcp", ":"+port) + if err == nil { + ln.Close() + } + return err != nil +} diff --git a/cmd/zed/serve/command.go b/cmd/zed/serve/command.go index 4caf08458c..d6e42c8174 100644 --- a/cmd/zed/serve/command.go +++ b/cmd/zed/serve/command.go @@ -73,17 +73,12 @@ func (c *Command) Run(args []string) error { return err } defer cleanup() - if !c.LakeFlags.LakeSpecified { - c.LakeFlags.Lake = "" - } - uri, err := c.LakeFlags.URI() - if err != nil { + if c.conf.Root, err = c.LakeFlags.URI(); err != nil { return err } - if api.IsLakeService(uri.String()) { + if api.IsLakeService(c.conf.Root.String()) { return errors.New("serve command available for local lakes only") } - c.conf.Root = uri if c.rootContentFile != "" { f, err := fs.Open(c.rootContentFile) if err != nil { diff --git a/cmd/zed/use/command.go b/cmd/zed/use/command.go index 527123c247..6923aeecb7 100644 --- a/cmd/zed/use/command.go +++ b/cmd/zed/use/command.go @@ -84,7 +84,7 @@ func (c *Command) Run(args []string) error { return errors.New("default pool and branch unset") } fmt.Printf("HEAD at %s\n", head) - if u, err := c.LakeFlags.URI(); err == nil { + if u, err := c.LakeFlags.ClientURI(); err == nil { fmt.Printf("Lake at %s\n", u) } return nil diff --git a/cmd/zed/ztests/no-lake-location.yaml b/cmd/zed/ztests/no-lake-location.yaml index cedc088f87..0fade08661 100644 --- a/cmd/zed/ztests/no-lake-location.yaml +++ b/cmd/zed/ztests/no-lake-location.yaml @@ -1,6 +1,6 @@ script: | ! zed ls -lake '' - ! zed serve + ! zed serve -lake '' outputs: - name: stderr diff --git a/cmd/zed/ztests/xdg-data-home.yaml b/cmd/zed/ztests/xdg-data-home.yaml new file mode 100644 index 0000000000..0077704ed5 --- /dev/null +++ b/cmd/zed/ztests/xdg-data-home.yaml @@ -0,0 +1,8 @@ +script: | + export XDG_DATA_HOME=path/to/lake + zed init + +outputs: + - name: stdout + data: | + lake created: path/to/lake/zed diff --git a/compiler/ztests/from-error.yaml b/compiler/ztests/from-error.yaml index 209544a444..c40e85b392 100644 --- a/compiler/ztests/from-error.yaml +++ b/compiler/ztests/from-error.yaml @@ -1,5 +1,5 @@ script: | - ! zc -C -s 'from p' + ! zc -lake='' -C -s 'from p' echo === >&2 export ZED_LAKE=test zed init