...
1 package filecache
2
3 import (
4 "encoding/hex"
5 "errors"
6 "io"
7 "os"
8 "path"
9 "sync"
10 )
11
12
13 func New(dir string) Cache {
14 return newFileCache(dir)
15 }
16
17 func newFileCache(dir string) *fileCache {
18 return &fileCache{dirPath: dir}
19 }
20
21
22
23
24 type fileCache struct {
25 dirPath string
26 mux sync.RWMutex
27 }
28
29 type fileReadCloser struct {
30 *os.File
31 fc *fileCache
32 }
33
34 func (fc *fileCache) path(key Key) string {
35 return path.Join(fc.dirPath, hex.EncodeToString(key[:]))
36 }
37
38 func (fc *fileCache) Get(key Key) (content io.ReadCloser, ok bool, err error) {
39
40 fc.mux.RLock()
41 unlock := fc.mux.RUnlock
42 defer func() {
43 if unlock != nil {
44 unlock()
45 }
46 }()
47
48 f, err := os.Open(fc.path(key))
49 if errors.Is(err, os.ErrNotExist) {
50 return nil, false, nil
51 } else if err != nil {
52 return nil, false, err
53 } else {
54
55 unlock = nil
56 return &fileReadCloser{File: f, fc: fc}, true, nil
57 }
58 }
59
60
61 func (f *fileReadCloser) Close() (err error) {
62 defer f.fc.mux.RUnlock()
63 err = f.File.Close()
64 return
65 }
66
67 func (fc *fileCache) Add(key Key, content io.Reader) (err error) {
68
69 fc.mux.Lock()
70 defer fc.mux.Unlock()
71
72
73 path := fc.path(key)
74 file, err := os.Create(path + ".tmp")
75 if err != nil {
76 return
77 }
78 defer func() {
79 if err != nil {
80 _ = os.Remove(file.Name())
81 }
82 }()
83 defer file.Close()
84 if _, err = io.Copy(file, content); err != nil {
85 return
86 }
87 if err = file.Sync(); err != nil {
88 return
89 }
90 if err = file.Close(); err != nil {
91 return
92 }
93 err = os.Rename(file.Name(), path)
94 return
95 }
96
97 func (fc *fileCache) Delete(key Key) (err error) {
98
99 fc.mux.Lock()
100 defer fc.mux.Unlock()
101
102 err = os.Remove(fc.path(key))
103 if errors.Is(err, os.ErrNotExist) {
104 err = nil
105 }
106 return
107 }
108
View as plain text