types.go RawHistoryFolder
1package cache
2
3import (
4 "sync"
5 "time"
6 "unsafe"
7
8 "github.com/bnema/dumber/internal/config"
9)
10
11// DmenuFuzzyCache provides high-performance fuzzy search for dmenu history.
12type DmenuFuzzyCache struct {
13 // Pre-computed search indices
14 trigramIndex map[string][]uint32 // 3-char sequences -> entry IDs
15 prefixTrie *Trie // Compressed trie for prefix matching
16 sortedIndex []uint32 // Pre-sorted entry indices by score (highest first)
17
18 // Compact history storage
19 entries []CompactEntry // Minimal memory footprint
20
21 // Metadata
22 version uint32 // Cache format version
23 lastModified int64 // Unix timestamp
24 entryCount uint32 // Number of entries
25
26 // Concurrency control
27 mu sync.RWMutex
28 data unsafe.Pointer // Atomic pointer to cache data //nolint:unused // Reserved for future use
29}
30
31// CompactEntry represents a history entry optimized for memory usage.
32type CompactEntry struct {
33 URL string // Original URL
34 Title string // Page title
35 FaviconURL string // URL to favicon for this entry
36 VisitCount uint16 // Max 65535 visits (sufficient for most use cases)
37 LastVisit uint32 // Days since Unix epoch (saves 4 bytes vs time.Time)
38 Score uint16 // Pre-computed base score (0-65535)
39}
40
41// FuzzyResult contains the results of a fuzzy search query.
42type FuzzyResult struct {
43 Matches []FuzzyMatch // Matching entries
44 QueryTime time.Duration // Time taken to execute query
45}
46
47// FuzzyMatch represents a single fuzzy search match.
48type FuzzyMatch struct {
49 Entry *CompactEntry // Matched entry
50 Score float64 // Final fuzzy match score (0.0-1.0)
51 URLScore float64 // URL similarity score
52 TitleScore float64 // Title similarity score
53 RecencyScore float64 // Recency boost score
54 VisitScore float64 // Visit count boost score
55 MatchType MatchType // Type of match found
56}
57
58// MatchType indicates how the query matched the entry.
59type MatchType uint8
60
61// Match type constants for different matching strategies
62const (
63 MatchTypeExact MatchType = iota // Exact substring match
64 MatchTypePrefix // Prefix match
65 MatchTypeFuzzy // Fuzzy similarity match
66 MatchTypeTrigram // Trigram-based match
67)
68
69// CacheConfig holds configuration for the fuzzy cache.
70type CacheConfig struct {
71 // File paths
72 CacheFile string // Path to binary cache file
73
74 // Performance tuning
75 MaxEntries uint32 // Maximum entries to cache
76 TrigramThreshold uint32 // Minimum trigram matches required
77 ScoreThreshold float64 // Minimum score for results
78 MaxResults int // Maximum results to return
79
80 // Scoring weights
81 URLWeight float64 // Weight for URL matches
82 TitleWeight float64 // Weight for title matches
83 RecencyWeight float64 // Weight for recency
84 VisitWeight float64 // Weight for visit count
85
86 // Cache behavior
87 TTL time.Duration // Cache time-to-live
88 WarmupEnabled bool // Enable background cache warming
89}
90
91// DefaultCacheConfig returns sensible default configuration.
92func DefaultCacheConfig() *CacheConfig {
93 return &CacheConfig{
94 MaxEntries: 10000,
95 TrigramThreshold: 2,
96 ScoreThreshold: 0.2,
97 MaxResults: 50,
98 URLWeight: 0.4,
99 TitleWeight: 0.3,
100 RecencyWeight: 0.2,
101 VisitWeight: 0.1,
102 TTL: 30 * time.Minute,
103 WarmupEnabled: true,
104 }
105}
106
107// GetStateDir returns the state directory using the config package.
108func (c *CacheConfig) GetStateDir() (string, error) {
109 return config.GetStateDir()
110}
111
112// Trie represents a compressed prefix tree for fast prefix matching.
113type Trie struct {
114 Root *TrieNode
115}
116
117// TrieNode represents a node in the prefix trie.
118type TrieNode struct {
119 Children map[rune]*TrieNode // Child nodes
120 Entries []uint32 // Entry IDs that end at this node
121 IsEnd bool // Marks end of a prefix
122}
123
124// Reset clears the FuzzyResult for reuse.
125func (r *FuzzyResult) Reset() {
126 r.Matches = r.Matches[:0]
127 r.QueryTime = 0
128}
129
130// String returns a string representation of the match type.
131func (mt MatchType) String() string {
132 switch mt {
133 case MatchTypeExact:
134 return "exact"
135 case MatchTypePrefix:
136 return "prefix"
137 case MatchTypeFuzzy:
138 return "fuzzy"
139 case MatchTypeTrigram:
140 return "trigram"
141 default:
142 return "unknown"
143 }
144}
145
146// DaysFromTime converts a time.Time to days since Unix epoch.
147func DaysFromTime(t time.Time) uint32 {
148 return uint32(t.Unix() / 86400) // 86400 seconds per day
149}
150
151// TimeFromDays converts days since Unix epoch back to time.Time.
152func TimeFromDays(days uint32) time.Time {
153 return time.Unix(int64(days)*86400, 0)
154}
155
156// NewCompactEntry creates a CompactEntry from database values.
157func NewCompactEntry(url, title, faviconURL string, visitCount int64, lastVisit time.Time) CompactEntry {
158 entry := CompactEntry{
159 URL: url,
160 Title: title,
161 FaviconURL: faviconURL,
162 }
163
164 // Clamp visit count to uint16 max
165 switch {
166 case visitCount > 65535:
167 entry.VisitCount = 65535
168 case visitCount < 0:
169 entry.VisitCount = 0
170 default:
171 entry.VisitCount = uint16(visitCount)
172 }
173
174 entry.LastVisit = DaysFromTime(lastVisit)
175
176 // Pre-compute a base score for sorting
177 entry.Score = entry.calculateBaseScore()
178
179 return entry
180}
181
182// calculateBaseScore computes a base score for the entry based on visits and recency.
183func (e *CompactEntry) calculateBaseScore() uint16 {
184 visitScore := float64(e.VisitCount) / 65535.0 // Normalize to 0-1
185
186 now := time.Now()
187 lastVisit := TimeFromDays(e.LastVisit)
188 daysSince := now.Sub(lastVisit).Hours() / 24
189 recencyScore := 1.0 / (1.0 + daysSince/30.0) // Decay over 30 days
190
191 // Weighted combination
192 baseScore := (visitScore*0.3 + recencyScore*0.7) * 65535.0
193
194 if baseScore > 65535 {
195 return 65535
196 }
197 return uint16(baseScore)
198}
199
200// Display returns a formatted string for dmenu display.
201func (e *CompactEntry) Display() string {
202 if e.Title != "" && e.Title != e.URL {
203 return e.Title
204 }
205 return e.URL
206}