detached 0acc806

refactor(cli): use FaviconService exports instead of file cache

- Remove old FaviconCache usage from browse.go
- Update dmenu to get favicons from FaviconService exports
- Dmenu queries {dataDir}/webkit/favicons-export/{hash}.png
- Remove legacy cache cleanup from root.go initialization
- Use 7-day expiry check for favicon freshness
commit
0acc806d6777822bfc3ac2b11cb5659fdae2a8ce
author
bnema · · 2025-10-18 10:54 UTC
parent
c8f47a3
signature
GPG Unverified2B82207D370127A0 The key is unknown
3 files · +21 -33
M internal/cli/browse.go +0 -7
@@ -9,7 +9,6 @@ import (
99 "os/exec"
1010 "strings"
1111
12- "github.com/bnema/dumber/internal/cache"
1312 "github.com/bnema/dumber/internal/config"
1413
1514 "github.com/spf13/cobra"
@@ -161,12 +160,6 @@ func updateFavicon(ctx context.Context, cli *CLI, pageURL string) {
161160 // Update in database using the new sqlc-generated method
162161 faviconNullString := sql.NullString{String: faviconURL, Valid: true}
163162 if err := cli.Queries.UpdateHistoryFavicon(ctx, faviconNullString, pageURL); err != nil {
164- // Silently fail - favicon is not critical
165163 return
166164 }
167-
168- // Also cache the favicon for dmenu use
169- if faviconCache, err := cache.NewFaviconCache(); err == nil {
170- faviconCache.CacheAsync(faviconURL)
171- }
172165 }
@@ -9,7 +9,6 @@ import (
9 "os/exec"9 "os/exec"
10 "strings"10 "strings"
11 11
12- "github.com/bnema/dumber/internal/cache"
13 "github.com/bnema/dumber/internal/config"12 "github.com/bnema/dumber/internal/config"
14 13
15 "github.com/spf13/cobra"14 "github.com/spf13/cobra"
@@ -161,12 +160,6 @@ func updateFavicon(ctx context.Context, cli *CLI, pageURL string) {
161 // Update in database using the new sqlc-generated method160 // Update in database using the new sqlc-generated method
162 faviconNullString := sql.NullString{String: faviconURL, Valid: true}161 faviconNullString := sql.NullString{String: faviconURL, Valid: true}
163 if err := cli.Queries.UpdateHistoryFavicon(ctx, faviconNullString, pageURL); err != nil {162 if err := cli.Queries.UpdateHistoryFavicon(ctx, faviconNullString, pageURL); err != nil {
164- // Silently fail - favicon is not critical
165 return163 return
166 }164 }
167-
168- // Also cache the favicon for dmenu use
169- if faviconCache, err := cache.NewFaviconCache(); err == nil {
170- faviconCache.CacheAsync(faviconURL)
171- }
172 }165 }
M internal/cli/dmenu.go +21 -18
@@ -8,8 +8,11 @@ import (
88 "os"
99 "sort"
1010 "strings"
11+ "time"
1112
1213 "github.com/bnema/dumber/internal/cache"
14+ "github.com/bnema/dumber/internal/config"
15+ "github.com/bnema/dumber/internal/services"
1316 "github.com/spf13/cobra"
1417 )
1518
@@ -63,11 +66,10 @@ func generateOptions(cli *CLI) error {
6366 cacheConfig.MaxResults = 50 // Match the old maxHistoryEntries
6467 cacheManager := cache.NewCacheManager(cli.Queries, cacheConfig)
6568
66- // Initialize favicon cache
67- faviconCache, err := cache.NewFaviconCache()
69+ // Get data directory for favicon exports
70+ dataDir, err := config.GetDataDir()
6871 if err != nil {
69- // Continue without favicon cache if it fails
70- faviconCache = nil
72+ dataDir = "" // Continue without favicons if data dir fails
7173 }
7274
7375 // Get top entries from cache (this is blazingly fast!)
@@ -139,7 +141,7 @@ func generateOptions(cli *CLI) error {
139141
140142 // Output options to stdout with icon specifications
141143 for _, option := range options {
142- iconName := getIconName(option, faviconCache)
144+ iconName := getIconName(option, dataDir)
143145 if iconName != "" {
144146 fmt.Printf("%s\x00icon\x1f%s\n", option.Display, iconName)
145147 } else {
@@ -157,11 +159,10 @@ func generateOptions(cli *CLI) error {
157159 func generateOptionsFallback(cli *CLI) error {
158160 ctx := context.Background()
159161
160- // Initialize favicon cache
161- faviconCache, err := cache.NewFaviconCache()
162+ // Get data directory for favicon exports
163+ dataDir, err := config.GetDataDir()
162164 if err != nil {
163- // Continue without favicon cache if it fails
164- faviconCache = nil
165+ dataDir = "" // Continue without favicons if data dir fails
165166 }
166167
167168 const maxHistoryEntries = 50
@@ -240,7 +241,7 @@ func generateOptionsFallback(cli *CLI) error {
240241
241242 // Output options to stdout with icon specifications
242243 for _, option := range options {
243- iconName := getIconName(option, faviconCache)
244+ iconName := getIconName(option, dataDir)
244245 if iconName != "" {
245246 fmt.Printf("%s\x00icon\x1f%s\n", option.Display, iconName)
246247 } else {
@@ -344,19 +345,21 @@ func parseSelection(selection string) string {
344345 }
345346
346347 // getIconName determines the appropriate icon name for a dmenu option
347-func getIconName(option DmenuOption, faviconCache *cache.FaviconCache) string {
348+func getIconName(option DmenuOption, dataDir string) string {
348349 if option.Type != historyType {
349350 return ""
350351 }
351352
352- // Only use cached favicons - no system theme fallbacks for consistent sizing
353- if option.FaviconURL != "" && faviconCache != nil {
354- cachedPath := faviconCache.GetCachedPath(option.FaviconURL)
355- if cachedPath != "" {
356- return cachedPath
353+ // Get exported favicon path from FaviconService
354+ // FaviconService exports all favicons as 32x32 PNG files for CLI access
355+ if option.Value != "" {
356+ faviconPath := services.GetExportedFaviconPath(dataDir, option.Value)
357+ // Check if file exists and is recent (within 7 days)
358+ if info, err := os.Stat(faviconPath); err == nil {
359+ if time.Since(info.ModTime()) < 7*24*time.Hour {
360+ return faviconPath
361+ }
357362 }
358- // Start async download for next time
359- faviconCache.CacheAsync(option.FaviconURL)
360363 }
361364
362365 // No favicon available - return empty string to show no icon
@@ -8,8 +8,11 @@ import (
8 "os"8 "os"
9 "sort"9 "sort"
10 "strings"10 "strings"
11+ "time"
11 12
12 "github.com/bnema/dumber/internal/cache"13 "github.com/bnema/dumber/internal/cache"
14+ "github.com/bnema/dumber/internal/config"
15+ "github.com/bnema/dumber/internal/services"
13 "github.com/spf13/cobra"16 "github.com/spf13/cobra"
14 )17 )
15 18
@@ -63,11 +66,10 @@ func generateOptions(cli *CLI) error {
63 cacheConfig.MaxResults = 50 // Match the old maxHistoryEntries66 cacheConfig.MaxResults = 50 // Match the old maxHistoryEntries
64 cacheManager := cache.NewCacheManager(cli.Queries, cacheConfig)67 cacheManager := cache.NewCacheManager(cli.Queries, cacheConfig)
65 68
66- // Initialize favicon cache69+ // Get data directory for favicon exports
67- faviconCache, err := cache.NewFaviconCache()70+ dataDir, err := config.GetDataDir()
68 if err != nil {71 if err != nil {
69- // Continue without favicon cache if it fails72+ dataDir = "" // Continue without favicons if data dir fails
70- faviconCache = nil
71 }73 }
72 74
73 // Get top entries from cache (this is blazingly fast!)75 // Get top entries from cache (this is blazingly fast!)
@@ -139,7 +141,7 @@ func generateOptions(cli *CLI) error {
139 141
140 // Output options to stdout with icon specifications142 // Output options to stdout with icon specifications
141 for _, option := range options {143 for _, option := range options {
142- iconName := getIconName(option, faviconCache)144+ iconName := getIconName(option, dataDir)
143 if iconName != "" {145 if iconName != "" {
144 fmt.Printf("%s\x00icon\x1f%s\n", option.Display, iconName)146 fmt.Printf("%s\x00icon\x1f%s\n", option.Display, iconName)
145 } else {147 } else {
@@ -157,11 +159,10 @@ func generateOptions(cli *CLI) error {
157 func generateOptionsFallback(cli *CLI) error {159 func generateOptionsFallback(cli *CLI) error {
158 ctx := context.Background()160 ctx := context.Background()
159 161
160- // Initialize favicon cache162+ // Get data directory for favicon exports
161- faviconCache, err := cache.NewFaviconCache()163+ dataDir, err := config.GetDataDir()
162 if err != nil {164 if err != nil {
163- // Continue without favicon cache if it fails165+ dataDir = "" // Continue without favicons if data dir fails
164- faviconCache = nil
165 }166 }
166 167
167 const maxHistoryEntries = 50168 const maxHistoryEntries = 50
@@ -240,7 +241,7 @@ func generateOptionsFallback(cli *CLI) error {
240 241
241 // Output options to stdout with icon specifications242 // Output options to stdout with icon specifications
242 for _, option := range options {243 for _, option := range options {
243- iconName := getIconName(option, faviconCache)244+ iconName := getIconName(option, dataDir)
244 if iconName != "" {245 if iconName != "" {
245 fmt.Printf("%s\x00icon\x1f%s\n", option.Display, iconName)246 fmt.Printf("%s\x00icon\x1f%s\n", option.Display, iconName)
246 } else {247 } else {
@@ -344,19 +345,21 @@ func parseSelection(selection string) string {
344 }345 }
345 346
346 // getIconName determines the appropriate icon name for a dmenu option347 // getIconName determines the appropriate icon name for a dmenu option
347-func getIconName(option DmenuOption, faviconCache *cache.FaviconCache) string {348+func getIconName(option DmenuOption, dataDir string) string {
348 if option.Type != historyType {349 if option.Type != historyType {
349 return ""350 return ""
350 }351 }
351 352
352- // Only use cached favicons - no system theme fallbacks for consistent sizing353+ // Get exported favicon path from FaviconService
353- if option.FaviconURL != "" && faviconCache != nil {354+ // FaviconService exports all favicons as 32x32 PNG files for CLI access
354- cachedPath := faviconCache.GetCachedPath(option.FaviconURL)355+ if option.Value != "" {
355- if cachedPath != "" {356+ faviconPath := services.GetExportedFaviconPath(dataDir, option.Value)
356- return cachedPath357+ // Check if file exists and is recent (within 7 days)
358+ if info, err := os.Stat(faviconPath); err == nil {
359+ if time.Since(info.ModTime()) < 7*24*time.Hour {
360+ return faviconPath
361+ }
357 }362 }
358- // Start async download for next time
359- faviconCache.CacheAsync(option.FaviconURL)
360 }363 }
361 364
362 // No favicon available - return empty string to show no icon365 // No favicon available - return empty string to show no icon
M internal/cli/root.go +0 -8
@@ -8,7 +8,6 @@ import (
88 "os"
99 "path/filepath"
1010
11- "github.com/bnema/dumber/internal/cache"
1211 "github.com/bnema/dumber/internal/config"
1312 "github.com/bnema/dumber/internal/db"
1413 "github.com/bnema/dumber/internal/services"
@@ -77,13 +76,6 @@ func NewCLI() (*CLI, error) {
7776 queries := db.New(database)
7877 parserService := services.NewParserService(cfg, queries)
7978
80- // Clean old cached favicons on startup (async, non-blocking)
81- go func() {
82- if faviconCache, err := cache.NewFaviconCache(); err == nil {
83- faviconCache.CleanOld()
84- }
85- }()
86-
8779 return &CLI{
8880 DB: database,
8981 Queries: queries,
@@ -8,7 +8,6 @@ import (
8 "os"8 "os"
9 "path/filepath"9 "path/filepath"
10 10
11- "github.com/bnema/dumber/internal/cache"
12 "github.com/bnema/dumber/internal/config"11 "github.com/bnema/dumber/internal/config"
13 "github.com/bnema/dumber/internal/db"12 "github.com/bnema/dumber/internal/db"
14 "github.com/bnema/dumber/internal/services"13 "github.com/bnema/dumber/internal/services"
@@ -77,13 +76,6 @@ func NewCLI() (*CLI, error) {
77 queries := db.New(database)76 queries := db.New(database)
78 parserService := services.NewParserService(cfg, queries)77 parserService := services.NewParserService(cfg, queries)
79 78
80- // Clean old cached favicons on startup (async, non-blocking)
81- go func() {
82- if faviconCache, err := cache.NewFaviconCache(); err == nil {
83- faviconCache.CleanOld()
84- }
85- }()
86-
87 return &CLI{79 return &CLI{
88 DB: database,80 DB: database,
89 Queries: queries,81 Queries: queries,

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close