feat(cli): add --filters flag to purge command for filter cache management
- commit
- 0639fb7b5691a75ebbc11001595cfcd7295406a5
- author
- brice · · 2025-12-18 17:58 UTC
- committer
- brice · · 2025-12-18 21:48 UTC
- parent
- 7505dfa
- signature
- unsigned
1 file · +88 -1
M internal/cli/cmd/purge.go +88 -1
| @@ -18,6 +18,7 @@ var ( | |||
| 18 | 18 | purgeForce bool | |
| 19 | 19 | purgeAll bool | |
| 20 | 20 | purgeDesktop bool | |
| 21 | + | purgeFilters bool | |
| 21 | 22 | ) | |
| 22 | 23 | ||
| 23 | 24 | var purgeCmd = &cobra.Command{ | |
| @@ -32,7 +33,8 @@ This will delete: | |||
| 32 | 33 | - ~/.cache/dumber/ (cache) | |
| 33 | 34 | ||
| 34 | 35 | Use --force to skip confirmation prompt. | |
| 35 | - | Use --desktop to also remove the desktop file from ~/.local/share/applications/`, | |
| 36 | + | Use --desktop to also remove the desktop file from ~/.local/share/applications/ | |
| 37 | + | Use --filters to only remove content filter cache (for re-downloading filters)`, | |
| 36 | 38 | RunE: runPurge, | |
| 37 | 39 | } | |
| 38 | 40 | ||
| @@ -41,6 +43,7 @@ func init() { | |||
| 41 | 43 | purgeCmd.Flags().BoolVarP(&purgeForce, "force", "f", false, "skip confirmation prompt") | |
| 42 | 44 | purgeCmd.Flags().BoolVar(&purgeAll, "all", true, "remove all directories (default)") | |
| 43 | 45 | purgeCmd.Flags().BoolVar(&purgeDesktop, "desktop", false, "also remove desktop file") | |
| 46 | + | purgeCmd.Flags().BoolVar(&purgeFilters, "filters", false, "only remove content filter cache") | |
| 44 | 47 | } | |
| 45 | 48 | ||
| 46 | 49 | func runPurge(cmd *cobra.Command, args []string) error { | |
| @@ -49,6 +52,11 @@ func runPurge(cmd *cobra.Command, args []string) error { | |||
| 49 | 52 | return fmt.Errorf("get home dir: %w", err) | |
| 50 | 53 | } | |
| 51 | 54 | ||
| 55 | + | // Handle --filters flag (only purge filter cache) | |
| 56 | + | if purgeFilters { | |
| 57 | + | return runPurgeFilters(home) | |
| 58 | + | } | |
| 59 | + | ||
| 52 | 60 | // Build list of directories to remove | |
| 53 | 61 | dirs := []struct { | |
| 54 | 62 | path string | |
| @@ -225,3 +233,82 @@ func formatSize(bytes int64) string { | |||
| 225 | 233 | } | |
| 226 | 234 | return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) | |
| 227 | 235 | } | |
| 236 | + | ||
| 237 | + | // runPurgeFilters removes only the content filter cache. | |
| 238 | + | func runPurgeFilters(home string) error { | |
| 239 | + | dataDir := getXDGPath("XDG_DATA_HOME", home, ".local/share", "dumber") | |
| 240 | + | filterDirs := []struct { | |
| 241 | + | path string | |
| 242 | + | desc string | |
| 243 | + | }{ | |
| 244 | + | {filepath.Join(dataDir, "filters", "json"), "filter JSON cache"}, | |
| 245 | + | {filepath.Join(dataDir, "filters", "store"), "compiled filters"}, | |
| 246 | + | } | |
| 247 | + | ||
| 248 | + | // Check which directories exist | |
| 249 | + | var existing []struct { | |
| 250 | + | path string | |
| 251 | + | desc string | |
| 252 | + | size int64 | |
| 253 | + | } | |
| 254 | + | ||
| 255 | + | for _, d := range filterDirs { | |
| 256 | + | if info, err := os.Stat(d.path); err == nil && info.IsDir() { | |
| 257 | + | size := getDirSize(d.path) | |
| 258 | + | existing = append(existing, struct { | |
| 259 | + | path string | |
| 260 | + | desc string | |
| 261 | + | size int64 | |
| 262 | + | }{d.path, d.desc, size}) | |
| 263 | + | } | |
| 264 | + | } | |
| 265 | + | ||
| 266 | + | if len(existing) == 0 { | |
| 267 | + | fmt.Println("No filter cache found.") | |
| 268 | + | return nil | |
| 269 | + | } | |
| 270 | + | ||
| 271 | + | // Show what will be deleted | |
| 272 | + | fmt.Println("The following filter cache will be removed:") | |
| 273 | + | fmt.Println() | |
| 274 | + | for _, d := range existing { | |
| 275 | + | fmt.Printf(" • %s (%s, %s)\n", d.path, d.desc, formatSize(d.size)) | |
| 276 | + | } | |
| 277 | + | fmt.Println() | |
| 278 | + | ||
| 279 | + | // Confirm unless --force | |
| 280 | + | if !purgeForce { | |
| 281 | + | fmt.Print("Are you sure? [y/N]: ") | |
| 282 | + | reader := bufio.NewReader(os.Stdin) | |
| 283 | + | response, _ := reader.ReadString('\n') | |
| 284 | + | response = strings.TrimSpace(strings.ToLower(response)) | |
| 285 | + | ||
| 286 | + | if response != "y" && response != "yes" { | |
| 287 | + | fmt.Println("Aborted.") | |
| 288 | + | return nil | |
| 289 | + | } | |
| 290 | + | } | |
| 291 | + | ||
| 292 | + | // Remove directories | |
| 293 | + | var errors []string | |
| 294 | + | for _, d := range existing { | |
| 295 | + | if err := os.RemoveAll(d.path); err != nil { | |
| 296 | + | errors = append(errors, fmt.Sprintf("%s: %v", d.path, err)) | |
| 297 | + | } else { | |
| 298 | + | fmt.Printf("✓ Removed %s\n", d.path) | |
| 299 | + | } | |
| 300 | + | } | |
| 301 | + | ||
| 302 | + | if len(errors) > 0 { | |
| 303 | + | fmt.Println() | |
| 304 | + | fmt.Println("Errors:") | |
| 305 | + | for _, e := range errors { | |
| 306 | + | fmt.Printf(" ✗ %s\n", e) | |
| 307 | + | } | |
| 308 | + | return fmt.Errorf("failed to remove some items") | |
| 309 | + | } | |
| 310 | + | ||
| 311 | + | fmt.Println() | |
| 312 | + | fmt.Println("Filter cache purged. Filters will be re-downloaded on next browser start.") | |
| 313 | + | return nil | |
| 314 | + | } | |
| @@ -18,6 +18,7 @@ var ( | |||||
| 18 | purgeForce bool | 18 | purgeForce bool | ||
| 19 | purgeAll bool | 19 | purgeAll bool | ||
| 20 | purgeDesktop bool | 20 | purgeDesktop bool | ||
| 21 | + | purgeFilters bool | |||
| 21 | ) | 22 | ) | ||
| 22 | 23 | ||||
| 23 | var purgeCmd = &cobra.Command{ | 24 | var purgeCmd = &cobra.Command{ | ||
| @@ -32,7 +33,8 @@ This will delete: | |||||
| 32 | - ~/.cache/dumber/ (cache) | 33 | - ~/.cache/dumber/ (cache) | ||
| 33 | 34 | ||||
| 34 | Use --force to skip confirmation prompt. | 35 | Use --force to skip confirmation prompt. | ||
| 35 | - | Use --desktop to also remove the desktop file from ~/.local/share/applications/`, | 36 | + | Use --desktop to also remove the desktop file from ~/.local/share/applications/ |
| 37 | + | Use --filters to only remove content filter cache (for re-downloading filters)`, | |||
| 36 | RunE: runPurge, | 38 | RunE: runPurge, | ||
| 37 | } | 39 | } | ||
| 38 | 40 | ||||
| @@ -41,6 +43,7 @@ func init() { | |||||
| 41 | purgeCmd.Flags().BoolVarP(&purgeForce, "force", "f", false, "skip confirmation prompt") | 43 | purgeCmd.Flags().BoolVarP(&purgeForce, "force", "f", false, "skip confirmation prompt") | ||
| 42 | purgeCmd.Flags().BoolVar(&purgeAll, "all", true, "remove all directories (default)") | 44 | purgeCmd.Flags().BoolVar(&purgeAll, "all", true, "remove all directories (default)") | ||
| 43 | purgeCmd.Flags().BoolVar(&purgeDesktop, "desktop", false, "also remove desktop file") | 45 | purgeCmd.Flags().BoolVar(&purgeDesktop, "desktop", false, "also remove desktop file") | ||
| 46 | + | purgeCmd.Flags().BoolVar(&purgeFilters, "filters", false, "only remove content filter cache") | |||
| 44 | } | 47 | } | ||
| 45 | 48 | ||||
| 46 | func runPurge(cmd *cobra.Command, args []string) error { | 49 | func runPurge(cmd *cobra.Command, args []string) error { | ||
| @@ -49,6 +52,11 @@ func runPurge(cmd *cobra.Command, args []string) error { | |||||
| 49 | return fmt.Errorf("get home dir: %w", err) | 52 | return fmt.Errorf("get home dir: %w", err) | ||
| 50 | } | 53 | } | ||
| 51 | 54 | ||||
| 55 | + | // Handle --filters flag (only purge filter cache) | |||
| 56 | + | if purgeFilters { | |||
| 57 | + | return runPurgeFilters(home) | |||
| 58 | + | } | |||
| 59 | + | ||||
| 52 | // Build list of directories to remove | 60 | // Build list of directories to remove | ||
| 53 | dirs := []struct { | 61 | dirs := []struct { | ||
| 54 | path string | 62 | path string | ||
| @@ -225,3 +233,82 @@ func formatSize(bytes int64) string { | |||||
| 225 | } | 233 | } | ||
| 226 | return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) | 234 | return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) | ||
| 227 | } | 235 | } | ||
| 236 | + | ||||
| 237 | + | // runPurgeFilters removes only the content filter cache. | |||
| 238 | + | func runPurgeFilters(home string) error { | |||
| 239 | + | dataDir := getXDGPath("XDG_DATA_HOME", home, ".local/share", "dumber") | |||
| 240 | + | filterDirs := []struct { | |||
| 241 | + | path string | |||
| 242 | + | desc string | |||
| 243 | + | }{ | |||
| 244 | + | {filepath.Join(dataDir, "filters", "json"), "filter JSON cache"}, | |||
| 245 | + | {filepath.Join(dataDir, "filters", "store"), "compiled filters"}, | |||
| 246 | + | } | |||
| 247 | + | ||||
| 248 | + | // Check which directories exist | |||
| 249 | + | var existing []struct { | |||
| 250 | + | path string | |||
| 251 | + | desc string | |||
| 252 | + | size int64 | |||
| 253 | + | } | |||
| 254 | + | ||||
| 255 | + | for _, d := range filterDirs { | |||
| 256 | + | if info, err := os.Stat(d.path); err == nil && info.IsDir() { | |||
| 257 | + | size := getDirSize(d.path) | |||
| 258 | + | existing = append(existing, struct { | |||
| 259 | + | path string | |||
| 260 | + | desc string | |||
| 261 | + | size int64 | |||
| 262 | + | }{d.path, d.desc, size}) | |||
| 263 | + | } | |||
| 264 | + | } | |||
| 265 | + | ||||
| 266 | + | if len(existing) == 0 { | |||
| 267 | + | fmt.Println("No filter cache found.") | |||
| 268 | + | return nil | |||
| 269 | + | } | |||
| 270 | + | ||||
| 271 | + | // Show what will be deleted | |||
| 272 | + | fmt.Println("The following filter cache will be removed:") | |||
| 273 | + | fmt.Println() | |||
| 274 | + | for _, d := range existing { | |||
| 275 | + | fmt.Printf(" • %s (%s, %s)\n", d.path, d.desc, formatSize(d.size)) | |||
| 276 | + | } | |||
| 277 | + | fmt.Println() | |||
| 278 | + | ||||
| 279 | + | // Confirm unless --force | |||
| 280 | + | if !purgeForce { | |||
| 281 | + | fmt.Print("Are you sure? [y/N]: ") | |||
| 282 | + | reader := bufio.NewReader(os.Stdin) | |||
| 283 | + | response, _ := reader.ReadString('\n') | |||
| 284 | + | response = strings.TrimSpace(strings.ToLower(response)) | |||
| 285 | + | ||||
| 286 | + | if response != "y" && response != "yes" { | |||
| 287 | + | fmt.Println("Aborted.") | |||
| 288 | + | return nil | |||
| 289 | + | } | |||
| 290 | + | } | |||
| 291 | + | ||||
| 292 | + | // Remove directories | |||
| 293 | + | var errors []string | |||
| 294 | + | for _, d := range existing { | |||
| 295 | + | if err := os.RemoveAll(d.path); err != nil { | |||
| 296 | + | errors = append(errors, fmt.Sprintf("%s: %v", d.path, err)) | |||
| 297 | + | } else { | |||
| 298 | + | fmt.Printf("✓ Removed %s\n", d.path) | |||
| 299 | + | } | |||
| 300 | + | } | |||
| 301 | + | ||||
| 302 | + | if len(errors) > 0 { | |||
| 303 | + | fmt.Println() | |||
| 304 | + | fmt.Println("Errors:") | |||
| 305 | + | for _, e := range errors { | |||
| 306 | + | fmt.Printf(" ✗ %s\n", e) | |||
| 307 | + | } | |||
| 308 | + | return fmt.Errorf("failed to remove some items") | |||
| 309 | + | } | |||
| 310 | + | ||||
| 311 | + | fmt.Println() | |||
| 312 | + | fmt.Println("Filter cache purged. Filters will be re-downloaded on next browser start.") | |||
| 313 | + | return nil | |||
| 314 | + | } | |||