package token import ( "os" "sync" "time" ) // Reader is used to read the token value from a file type Reader struct { Path string // a path to a file containing a token mu sync.Mutex // ensures atomic reads/writes; protects the following fields cached string // the value of the token, a.k.a. the contents of the file mtime time.Time // the mtime of the file when the token was last read } // Read returns the token value from the file only if the file is // expired or the cached value is empty func (r *Reader) Read() (string, error) { changed, err := r.HasChanged() if err != nil { return "", err } if changed || r.GetCached() == "" { if err := r.readFile(); err != nil { return "", err } } return r.GetCached(), nil } // HasChanged checks if the in-memory `value` has changed by comparing // the chached `r.mtime` value with the `mtime` of the file at // `r.path` func (r *Reader) HasChanged() (bool, error) { info, err := os.Stat(r.Path) if err != nil { return false, err } mtime := info.ModTime() r.mu.Lock() defer r.mu.Unlock() return !mtime.Equal(r.mtime), nil } // HasExpired checks if 1 hour has passed since the last recorded `mtime` of // the token file func (r *Reader) HasExpired() bool { r.mu.Lock() defer r.mu.Unlock() return time.Now().After(r.mtime.Add(1 * time.Hour)) } func (r *Reader) GetMtime() time.Time { r.mu.Lock() defer r.mu.Unlock() return r.mtime } // GetCached returns the cached token value func (r *Reader) GetCached() string { r.mu.Lock() defer r.mu.Unlock() return r.cached } func (r *Reader) readFile() error { f, err := os.ReadFile(r.Path) if err != nil { return err } info, err := os.Stat(r.Path) if err != nil { return err } r.mu.Lock() r.mtime = info.ModTime() r.cached = string(f) r.mu.Unlock() return nil }