Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/cache/store.go
3432 views
1
package cache
2
3
import (
4
"errors"
5
"fmt"
6
"os"
7
"path/filepath"
8
)
9
10
//go:generate mockgen -destination=storemocks_test.go -package=cache_test github.com/kardolus/chatgpt-cli/cache Store
11
type Store interface {
12
Get(key string) ([]byte, error)
13
Set(key string, value []byte) error
14
Delete(key string) error
15
}
16
17
func NewFileStore(baseDir string) *FileStore {
18
return &FileStore{
19
baseDir: baseDir,
20
}
21
}
22
23
// Ensure FileStore implements the Store interface
24
var _ Store = &FileStore{}
25
26
type FileStore struct {
27
baseDir string
28
}
29
30
func (f *FileStore) Get(key string) ([]byte, error) {
31
path := f.pathForKey(key)
32
b, err := os.ReadFile(path)
33
if err != nil {
34
return nil, err
35
}
36
return b, nil
37
}
38
39
func (f *FileStore) Set(key string, value []byte) error {
40
if err := f.ensureBaseDir(); err != nil {
41
return err
42
}
43
44
dst := f.pathForKey(key)
45
46
// Write to a temp file in the same directory so rename is atomic.
47
tmp, err := os.CreateTemp(f.baseDir, fmt.Sprintf(".%s.*.tmp", key))
48
if err != nil {
49
return err
50
}
51
tmpName := tmp.Name()
52
53
// Clean up temp file on failure.
54
defer func() {
55
_ = tmp.Close()
56
_ = os.Remove(tmpName)
57
}()
58
59
if _, err := tmp.Write(value); err != nil {
60
return err
61
}
62
if err := tmp.Sync(); err != nil {
63
return err
64
}
65
if err := tmp.Close(); err != nil {
66
return err
67
}
68
69
// Atomic replace on POSIX; on Windows, Rename may fail if dst exists.
70
// Best effort: remove dst first if needed.
71
if err := os.Rename(tmpName, dst); err != nil {
72
if errors.Is(err, os.ErrExist) || errors.Is(err, os.ErrPermission) {
73
_ = os.Remove(dst)
74
return os.Rename(tmpName, dst)
75
}
76
return err
77
}
78
79
return nil
80
}
81
82
func (f *FileStore) Delete(key string) error {
83
path := f.pathForKey(key)
84
err := os.Remove(path)
85
if err != nil && errors.Is(err, os.ErrNotExist) {
86
return nil
87
}
88
return err
89
}
90
91
func (f *FileStore) ensureBaseDir() error {
92
// 0700: single-user CLI cache
93
return os.MkdirAll(f.baseDir, 0o700)
94
}
95
96
func (f *FileStore) pathForKey(key string) string {
97
// key should already be a safe filename (yours is sha256 hex), so no sanitization needed.
98
return filepath.Join(f.baseDir, key+".json")
99
}
100
101