From e0dddff176a01d410904b6750b3395de4f7e54db Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Mon, 14 Sep 2026 21:39:00 +0200 Subject: plan 9: keyword cache keys on extension, max-read and Unicode tables, trims removed keywords --- internal/kwcache/kwcache.go | 53 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 10 deletions(-) (limited to 'internal/kwcache/kwcache.go') diff --git a/internal/kwcache/kwcache.go b/internal/kwcache/kwcache.go index b61cfb1..c9af7b8 100644 --- a/internal/kwcache/kwcache.go +++ b/internal/kwcache/kwcache.go @@ -19,15 +19,18 @@ import ( ) // version is the on-disk format; a file of any other version loads empty. -const version = 1 +// 2: the extension is part of a file's ID. +const version = 2 // ID is how a file is recognised: the same inode with the same size and -// modification time is taken to hold the same content. A move or rename -// within one filesystem keeps it. +// modification time is taken to hold the same content, and the same +// extension is read by the same extractor. A move within one filesystem +// keeps it; a rename that changes the extension does not (review M6). type ID struct { Dev, Ino uint64 Size int64 - MTime int64 // Unix nanoseconds + MTime int64 // Unix nanoseconds + Ext string // lower case, with its dot; "" for none } // entry is what is known about one file: every keyword it was checked @@ -58,6 +61,7 @@ type diskFile struct { Ino uint64 `json:"ino"` Size int64 `json:"size"` MTime int64 `json:"mtime"` + Ext string `json:"ext"` Set int `json:"keywords"` // index into diskCache.Keywords Hits []int `json:"hits"` // indices into that keyword list } @@ -106,7 +110,7 @@ func Load(path, fingerprint string) (*Cache, error) { } hits[set[h]] = true } - c.old[ID{Dev: f.Dev, Ino: f.Ino, Size: f.Size, MTime: f.MTime}] = entry{checked: set, hits: hits} + c.old[ID{Dev: f.Dev, Ino: f.Ino, Size: f.Size, MTime: f.MTime, Ext: f.Ext}] = entry{checked: set, hits: hits} } c.existed = true return c, nil @@ -154,14 +158,22 @@ func (c *Cache) Store(id ID, answers map[string]bool) { // Save writes the entries of the files in present, from this run or loaded, // to path, and drops every other: the cache only ever describes files -// still in the directory. The directory is created 0700 and the file -// written 0600 under a temporary name, then renamed into place. A cache -// with nothing to write and no file on disk writes nothing. -func (c *Cache) Save(path string, present []ID) error { +// still in the directory. Each entry keeps only the keywords in keywords - +// the directory's current ones - so a keyword removed from the +// configuration leaves the cache too, and an entry left with none is +// dropped. The directory is made private (0700, tightened if it already +// existed) and the file written 0600 under a temporary name, then renamed +// into place. A cache with nothing to write and no file on disk writes +// nothing. +func (c *Cache) Save(path string, present []ID, keywords []string) error { c.mu.Lock() defer c.mu.Unlock() d := diskCache{Version: version, Fingerprint: c.fingerprint, Keywords: [][]string{}, Files: []diskFile{}} + current := make(map[string]bool, len(keywords)) + for _, k := range keywords { + current[k] = true + } sets := map[string]int{} seen := map[ID]bool{} for _, id := range present { @@ -176,6 +188,10 @@ func (c *Cache) Save(path string, present []ID) error { if !ok { continue } + e = trimmed(e, current) + if len(e.checked) == 0 { + continue + } key := strings.Join(e.checked, "\x00") set, ok := sets[key] if !ok { @@ -183,7 +199,7 @@ func (c *Cache) Save(path string, present []ID) error { sets[key] = set d.Keywords = append(d.Keywords, e.checked) } - f := diskFile{Dev: id.Dev, Ino: id.Ino, Size: id.Size, MTime: id.MTime, Set: set, Hits: []int{}} + f := diskFile{Dev: id.Dev, Ino: id.Ino, Size: id.Size, MTime: id.MTime, Ext: id.Ext, Set: set, Hits: []int{}} for i, k := range e.checked { if e.hits[k] { f.Hits = append(f.Hits, i) @@ -209,6 +225,9 @@ func (c *Cache) Save(path string, present []ID) error { if err := os.MkdirAll(dir, 0o700); err != nil { return err } + if err := os.Chmod(dir, 0o700); err != nil { + return err + } tmp, err := os.CreateTemp(dir, ".kwcache-*") if err != nil { return err @@ -229,3 +248,17 @@ func (c *Cache) Save(path string, present []ID) error { c.existed = true return nil } + +// trimmed returns e keeping only the keywords in current. +func trimmed(e entry, current map[string]bool) entry { + out := entry{hits: map[string]bool{}} + for _, k := range e.checked { + if current[k] { + out.checked = append(out.checked, k) + if e.hits[k] { + out.hits[k] = true + } + } + } + return out +} -- cgit v1.3