Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generic cache #992

Merged
merged 1 commit into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions action.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
shlex "github.com/rsteube/carapace-shlex"
"github.com/rsteube/carapace/internal/cache"
"github.com/rsteube/carapace/internal/common"
pkgcache "github.com/rsteube/carapace/pkg/cache"
"github.com/rsteube/carapace/pkg/cache/key"
"github.com/rsteube/carapace/pkg/match"
"github.com/rsteube/carapace/pkg/style"
pkgtraverse "github.com/rsteube/carapace/pkg/traverse"
Expand All @@ -31,7 +31,7 @@ type ActionMap map[string]Action
type CompletionCallback func(c Context) Action

// Cache cashes values of a CompletionCallback for given duration and keys.
func (a Action) Cache(timeout time.Duration, keys ...pkgcache.Key) Action {
func (a Action) Cache(timeout time.Duration, keys ...key.Key) Action {
if a.callback != nil { // only relevant for callback actions
cachedCallback := a.callback
_, file, line, _ := runtime.Caller(1) // generate uid from wherever Cache() was called
Expand All @@ -41,14 +41,14 @@ func (a Action) Cache(timeout time.Duration, keys ...pkgcache.Key) Action {
return cachedCallback(c)
}

if cached, err := cache.Load(cacheFile, timeout); err == nil {
if cached, err := cache.LoadE(cacheFile, timeout); err == nil {
return Action{meta: cached.Meta, rawValues: cached.Values}
}

invokedAction := (Action{callback: cachedCallback}).Invoke(c)
if invokedAction.action.meta.Messages.IsEmpty() {
if cacheFile, err := cache.File(file, line, keys...); err == nil { // regenerate as cache keys might have changed due to invocation
_ = cache.Write(cacheFile, invokedAction.export())
_ = cache.WriteE(cacheFile, invokedAction.export())
}
}
return invokedAction.ToA()
Expand Down
4 changes: 2 additions & 2 deletions example/cmd/modifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"time"

"github.com/rsteube/carapace"
"github.com/rsteube/carapace/pkg/cache"
"github.com/rsteube/carapace/pkg/cache/key"
"github.com/rsteube/carapace/pkg/condition"
"github.com/rsteube/carapace/pkg/style"
"github.com/rsteube/carapace/pkg/traverse"
Expand Down Expand Up @@ -85,7 +85,7 @@ func init() {
return carapace.ActionValues(
time.Now().Format("15:04:05"),
)
}).Cache(10*time.Second, cache.String(c.Parts[0]))
}).Cache(10*time.Second, key.String(c.Parts[0]))
default:
return carapace.ActionValues()
}
Expand Down
38 changes: 24 additions & 14 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,41 @@ import (
"github.com/rsteube/carapace/internal/env"
"github.com/rsteube/carapace/internal/export"
"github.com/rsteube/carapace/internal/uid"
"github.com/rsteube/carapace/pkg/cache"
"github.com/rsteube/carapace/pkg/cache/key"
"github.com/rsteube/carapace/pkg/xdg"
)

// Write persistests given values to file as json.
func Write(file string, e export.Export) (err error) {
func WriteE(file string, e export.Export) (err error) {
var m []byte
if m, err = json.Marshal(e); err == nil {
err = os.WriteFile(file, m, 0600)
err = Write(file, m)
}
return
}

// Load loads values from file unless modification date exceeds timeout.
func Load(file string, timeout time.Duration) (e export.Export, err error) {
func Write(file string, content []byte) (err error) {
return os.WriteFile(file, content, 0600)
}

func LoadE(file string, timeout time.Duration) (*export.Export, error) { // TODO reference
content, err := Load(file, timeout)
if err != nil {
return nil, err
}

var e export.Export
if err := json.Unmarshal(content, &e); err != nil {
return nil, err
}
return &e, nil
}

func Load(file string, timeout time.Duration) (b []byte, err error) {
var stat os.FileInfo
if stat, err = os.Stat(file); os.IsNotExist(err) || (timeout >= 0 && stat.ModTime().Add(timeout).Before(time.Now())) {
err = errors.New("not exists or timeout exceeded")
} else {
var content []byte
if content, err = os.ReadFile(file); err == nil {
err = json.Unmarshal(content, &e)
}
return nil, errors.New("not exists or timeout exceeded")
}
return
return os.ReadFile(file)
}

// CacheDir creates a cache folder for current user and returns the path.
Expand All @@ -60,7 +70,7 @@ func CacheDir(name string) (dir string, err error) {

// File returns the cache filename for given values
// TODO cleanup
func File(callerFile string, callerLine int, keys ...cache.Key) (file string, err error) {
func File(callerFile string, callerLine int, keys ...key.Key) (file string, err error) {
uid := uidKeys(callerFile, strconv.Itoa(callerLine))
ids := make([]string, 0)
for _, key := range keys {
Expand Down
54 changes: 19 additions & 35 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
@@ -1,46 +1,30 @@
// Package cache provides cache keys
package cache

import (
"crypto/sha1"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
)

// Key provides a cache key.
type Key func() (string, error)
"runtime"
"time"

// String creates a CacheKey for given strings.
func String(s ...string) Key {
return func() (string, error) {
return strings.Join(s, "\n"), nil
}
}
"github.com/rsteube/carapace/internal/cache"
"github.com/rsteube/carapace/pkg/cache/key"
)

// FileChecksum creates a CacheKey for given file.
func FileChecksum(file string) Key {
return func() (checksum string, err error) {
var content []byte
if content, err = os.ReadFile(file); err == nil {
checksum = fmt.Sprintf("%x", sha1.Sum(content))
// Cache caches a function for given duration and keys.
func Cache(timeout time.Duration, keys ...key.Key) func(f func() ([]byte, error)) ([]byte, error) {
return func(f func() ([]byte, error)) ([]byte, error) {
_, file, line, _ := runtime.Caller(1)
cacheFile, err := cache.File(file, line, keys...)
if err != nil {
return nil, err
}
return
}
}

// FileStats creates a CacheKey for given file.
func FileStats(file string) Key {
return func() (checksum string, err error) {
var path string
if path, err = filepath.Abs(file); err == nil {
var info os.FileInfo
if info, err = os.Stat(file); err == nil {
return String(path, strconv.FormatInt(info.Size(), 10), info.ModTime().String())()
content, err := cache.Load(cacheFile, timeout)
if err != nil {
content, err = f()
if err != nil {
return nil, err
}
return content, cache.Write(cacheFile, content)
}
return
return content, nil
}
}
67 changes: 67 additions & 0 deletions pkg/cache/key/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Package cache provides cache keys
package key

import (
"crypto/sha1"
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
)

// Key provides a cache key.
type Key func() (string, error)

// String creates a CacheKey for given strings.
func String(s ...string) Key {
return func() (string, error) {
return strings.Join(s, "\n"), nil
}
}

// FileChecksum creates a CacheKey for given file.
func FileChecksum(file string) Key {
return func() (checksum string, err error) {
var content []byte
if content, err = os.ReadFile(file); err == nil {
checksum = fmt.Sprintf("%x", sha1.Sum(content))
}
return
}
}

// FileStats creates a CacheKey for given file.
func FileStats(file string) Key {
return func() (checksum string, err error) {
var path string
if path, err = filepath.Abs(file); err == nil {
var info os.FileInfo
if info, err = os.Stat(file); err == nil {
return String(path, strconv.FormatInt(info.Size(), 10), info.ModTime().String())()
}
}
return
}
}

func FolderStats(folder string) Key {
return func() (string, error) {
sums := make([]string, 0)
err := filepath.Walk(folder, func(path string, info fs.FileInfo, err error) error {
if !info.IsDir() {
sum, err := String(info.Name(), strconv.FormatInt(info.Size(), 10), info.ModTime().String())()
if err != nil {
return err
}
sums = append(sums, sum)
}
return nil
})
if err != nil {
return "", err
}
return strings.Join(sums, "\n"), nil
}
}
Loading