11package disk_cache
22
33import (
4+ "bytes"
45 "crypto/sha256"
56 "encoding/hex"
67 "encoding/json"
@@ -12,11 +13,51 @@ import (
1213 "slices"
1314 "time"
1415
16+ "golang.org/x/sys/unix"
17+
1518 "github.com/kovidgoyal/kitty/tools/utils"
1619)
1720
1821var _ = fmt .Print
1922
23+ type file_state struct {
24+ Size int64
25+ ModTime time.Time
26+ Inode uint64
27+ }
28+
29+ func (s * file_state ) equal (o * file_state ) bool {
30+ return o != nil && s .Size == o .Size && s .ModTime .Equal (o .ModTime ) && s .Inode == o .Inode
31+ }
32+
33+ func get_file_state (fi fs.FileInfo ) * file_state {
34+ // The Sys() method returns the underlying data source (can be nil).
35+ // For Unix-like systems, it's a *syscall.Stat_t.
36+ stat , ok := fi .Sys ().(* unix.Stat_t )
37+ if ! ok {
38+ // For non-Unix systems, you might not have an inode.
39+ // In that case, you can fall back to using only size and mod time.
40+ return & file_state {
41+ Size : fi .Size (),
42+ ModTime : fi .ModTime (),
43+ Inode : 0 , // Inode not available
44+ }
45+ }
46+ return & file_state {
47+ Size : fi .Size (),
48+ ModTime : fi .ModTime (),
49+ Inode : stat .Ino ,
50+ }
51+ }
52+
53+ func get_file_state_from_path (path string ) (* file_state , error ) {
54+ if s , err := os .Stat (path ); err != nil {
55+ return nil , err
56+ } else {
57+ return get_file_state (s ), nil
58+ }
59+ }
60+
2061func new_disk_cache (path string , max_size int64 ) (dc * DiskCache , err error ) {
2162 if path , err = filepath .Abs (path ); err != nil {
2263 return
@@ -92,16 +133,20 @@ func (dc *DiskCache) write_entries_if_dirty() (err error) {
92133 if ! dc .entries_dirty {
93134 return
94135 }
136+ path := dc .entries_path ()
95137 defer func () {
96138 if err == nil {
97139 dc .entries_dirty = false
98- dc .entries_mod_time = time .Now ()
140+ if s , serr := get_file_state_from_path (path ); serr == nil {
141+ dc .entries_last_read_state = s
142+ }
99143 }
100144 }()
101145 if d , err := json .Marshal (dc .entries ); err != nil {
102146 return err
103147 } else {
104- return os .WriteFile (dc .entries_path (), d , 0o600 )
148+ // use an atomic write so that the inode number changes
149+ return utils .AtomicWriteFile (path , bytes .NewReader (d ), 0o600 )
105150 }
106151}
107152
@@ -156,14 +201,15 @@ func (dc *DiskCache) rebuild_entries() error {
156201}
157202
158203func (dc * DiskCache ) ensure_entries () error {
159- needed := dc .entry_map == nil
204+ needed := dc .entry_map == nil || dc . entries_last_read_state == nil
160205 path := dc .entries_path ()
161- var stat_result fs. FileInfo
206+ var fstate * file_state
162207 if ! needed {
163- if s , err := os .Stat (path ); err == nil && s .ModTime ().After (dc .entries_mod_time ) {
164- needed = true
165- stat_result = s
166- dc .entries_mod_time = s .ModTime ()
208+ if s , err := get_file_state_from_path (path ); err == nil {
209+ fstate = s
210+ if ! s .equal (dc .entries_last_read_state ) {
211+ needed = true
212+ }
167213 }
168214 }
169215 if needed {
@@ -181,11 +227,12 @@ func (dc *DiskCache) ensure_entries() error {
181227 // corrupted data
182228 dc .rebuild_entries ()
183229 } else {
184- if stat_result == nil {
185- if s , err := os . Stat (path ); err == nil {
186- dc . entries_mod_time = s . ModTime ()
230+ if fstate == nil {
231+ if s , err := get_file_state_from_path (path ); err == nil {
232+ fstate = s
187233 }
188234 }
235+ dc .entries_last_read_state = fstate
189236 }
190237 dc .entry_map = make (map [string ]* Entry )
191238 for _ , e := range dc .entries .SortedEntries {
0 commit comments