1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
17
18 package cacher
19
20 import (
21 "context"
22 "io"
23 "log"
24 "os"
25 "path/filepath"
26
27 "perkeep.org/internal/osutil"
28 "perkeep.org/pkg/blob"
29 "perkeep.org/pkg/blobserver"
30 "perkeep.org/pkg/blobserver/localdisk"
31
32 "go4.org/syncutil/singleflight"
33 )
34
35
36
37 func NewCachingFetcher(cache blobserver.Cache, fetcher blob.Fetcher) *CachingFetcher {
38 return &CachingFetcher{c: cache, sf: fetcher}
39 }
40
41
42 type CachingFetcher struct {
43 c blobserver.Cache
44 sf blob.Fetcher
45
46
47
48
49 cacheHitHook func(br blob.Ref, rc io.ReadCloser) (io.ReadCloser, error)
50
51 g singleflight.Group
52 }
53
54
55
56
57
58
59 func (cf *CachingFetcher) SetCacheHitHook(fn func(br blob.Ref, rc io.ReadCloser) (io.ReadCloser, error)) {
60 cf.cacheHitHook = fn
61 }
62
63 func (cf *CachingFetcher) Fetch(ctx context.Context, br blob.Ref) (content io.ReadCloser, size uint32, err error) {
64 content, size, err = cf.c.Fetch(ctx, br)
65 if err == nil {
66 if cf.cacheHitHook != nil {
67 rc, err := cf.cacheHitHook(br, content)
68 if err != nil {
69 content.Close()
70 return nil, 0, err
71 }
72 content = rc
73 }
74 return
75 }
76 if err = cf.faultIn(ctx, br); err != nil {
77 return
78 }
79 return cf.c.Fetch(ctx, br)
80 }
81
82 func (cf *CachingFetcher) faultIn(ctx context.Context, br blob.Ref) error {
83 _, err := cf.g.Do(br.String(), func() (interface{}, error) {
84 sblob, _, err := cf.sf.Fetch(ctx, br)
85 if err != nil {
86 return nil, err
87 }
88 defer sblob.Close()
89 _, err = blobserver.Receive(ctx, cf.c, br, sblob)
90 return nil, err
91 })
92 return err
93 }
94
95
96
97
98 type DiskCache struct {
99 *CachingFetcher
100
101
102
103 Root string
104
105 cleanAll bool
106 }
107
108
109
110
111 func NewDiskCache(fetcher blob.Fetcher) (*DiskCache, error) {
112 cacheDir := filepath.Join(osutil.CacheDir(), "blobs")
113 if !osutil.DirExists(cacheDir) {
114 if err := os.Mkdir(cacheDir, 0700); err != nil {
115 log.Printf("Warning: failed to make %s: %v; using tempdir instead", cacheDir, err)
116 cacheDir, err = os.MkdirTemp("", "camlicache")
117 if err != nil {
118 return nil, err
119 }
120 }
121 }
122
123
124 diskcache, err := localdisk.New(cacheDir)
125 if err != nil {
126 return nil, err
127 }
128 dc := &DiskCache{
129 CachingFetcher: NewCachingFetcher(diskcache, fetcher),
130 Root: cacheDir,
131 }
132 return dc, nil
133 }
134
135
136 func (dc *DiskCache) Clean() {
137
138 if dc.cleanAll {
139 os.RemoveAll(dc.Root)
140 }
141 }
142
143 var (
144 _ blob.Fetcher = (*CachingFetcher)(nil)
145 _ blob.Fetcher = (*DiskCache)(nil)
146 )