diff --git a/engine/local/local.go b/engine/local/local.go index e046c4db..1581d5d0 100644 --- a/engine/local/local.go +++ b/engine/local/local.go @@ -54,7 +54,11 @@ type API struct { Logf func(format string, v ...interface{}) // Each piece of the API can take a handle here. - *Value + *Value // TODO: Rename to ValueImpl? + + // VarDirImpl is the implementation for the VarDir API's. The API's are + // the collection of public methods that exist on this struct. + *VarDirImpl } // Init initializes the API before first use. It returns itself so it can be @@ -67,6 +71,13 @@ func (obj *API) Init() *API { Logf: obj.Logf, }) + obj.VarDirImpl = &VarDirImpl{} + obj.VarDirImpl.Init(&VarDirInit{ + Prefix: obj.Prefix, + Debug: obj.Debug, + Logf: obj.Logf, + }) + return obj } @@ -332,3 +343,81 @@ func valueRemove(ctx context.Context, prefix, key string) error { } return nil // ignore not found errors } + +// VarDirInit are the init values that the VarDir API needs to work correctly. +type VarDirInit struct { + Prefix string + Debug bool + Logf func(format string, v ...interface{}) +} + +// VarDirImpl is the implementation for the VarDir API's. The API's are the +// collection of public methods that exist on this struct. +type VarDirImpl struct { + init *VarDirInit + mutex *sync.Mutex + prefix string + prefixExists bool // is it okay to use the prefix? +} + +// Init runs some initialization code for the VarDir API. +func (obj *VarDirImpl) Init(init *VarDirInit) { + obj.init = init + obj.mutex = &sync.Mutex{} + obj.prefix = fmt.Sprintf("%s/", path.Join(obj.init.Prefix, "vardir")) +} + +// VarDir returns a directory rooted at the internal prefix. +func (obj *VarDirImpl) VarDir(ctx context.Context, reldir string) (string, error) { + if strings.HasPrefix(reldir, "/") { + return "", fmt.Errorf("path must be relative") + } + if !strings.HasSuffix(reldir, "/") { + return "", fmt.Errorf("path must be a dir") + } + // NOTE: The above checks ensure we don't get either "" or "/" as input! + + prefix, err := obj.getPrefix() + if err != nil { + return "", err + } + + result := fmt.Sprintf("%s/", path.Join(prefix, reldir)) + + // TODO: Should we mkdir this? + obj.mutex.Lock() + defer obj.mutex.Unlock() + if err := os.MkdirAll(result, 0755); err != nil { + return "", err + } + + return result, nil +} + +// getPrefix gets the prefix dir to use, or errors if it can't make one. It +// makes it on first use, and returns quickly from any future calls to it. +func (obj *VarDirImpl) getPrefix() (string, error) { + // NOTE: Moving this mutex to just below the first early return, would + // be a benign race, but as it turns out, it's possible that a compiler + // would see this behaviour as "undefined" and things might not work as + // intended. It could perhaps be replaced with a sync/atomic primitive + // if we wanted better performance here. + obj.mutex.Lock() + defer obj.mutex.Unlock() + + if obj.prefixExists { // former race read + return obj.prefix, nil + } + + // MkdirAll instead of Mkdir because we have no idea if the parent + // local/ directory was already made yet or not. (If at all.) If path is + // already a directory, MkdirAll does nothing and returns nil. (Good!) + // TODO: I hope MkdirAll is thread-safe on path creation in case another + // future local API tries to make the base (parent) directory too! + if err := os.MkdirAll(obj.prefix, 0755); err != nil { + return "", err + } + obj.prefixExists = true // former race write + + return obj.prefix, nil +}