swadhinbiswas feat: live scan matrix, safety vacuuming & theme engine · 8/17/2026
ba4f98c
125 lines · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package detector

import (
	"context"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/swadhinbiswas/veet/internal/model"
)

type cacheDetector struct {
	home string
}

// NewCacheDetector surfaces reclaimable caches, shared data and orphaned
// dotfiles under the user's home directory. Always available.
func NewCacheDetector(home string) Detector {
	return &cacheDetector{home: home}
}

func (d *cacheDetector) Source() string  { return "cache" }
func (d *cacheDetector) Available() bool { return d.home != "" }

// skipHome are top-level home entries that must never be reported.
var skipHome = map[string]bool{
	".config": true, ".local": true, ".cache": true, ".ssh": true,
	".gnupg": true, ".mozilla": true, ".npm": true, ".cargo": true,
	".rustup": true, ".go": true, ".pyenv": true, ".nvm": true,
	".node_modules": true, ".bundle": true, ".gem": true, ".vim": true,
	".oh-my-zsh": true, ".tmux": true, ".dbus": true, ".gconf": true,
	".pki": true, ".x": true, ".Trash": true, ".thumbnails": true,
	".pulse": true, ".esd_auth": true, ".ICEauthority": true,
	".Xauthority": true, ".Xsession": true, ".xorgxrdb": true,
}

// knownHomeFiles are regular files that are part of the shell environment.
var knownHomeFiles = map[string]bool{
	".bashrc": true, ".bash_profile": true, ".bash_login": true,
	".profile": true, ".zshrc": true, ".zprofile": true, ".zshenv": true,
	".gitconfig": true, ".gitignore": true, ".xinitrc": true,
	".xprofile": true, ".Xresources": true, ".inputrc": true,
	".wget-hsts": true, ".lesshst": true, ".viminfo": true,
	".python_history": true, ".zsh_history": true, ".bash_history": true,
	".config": true, ".local": true,
}

func (d *cacheDetector) Detect(ctx context.Context) ([]model.AppInfo, error) {
	var apps []model.AppInfo

	scanDir := func(dir string, prefix string) {
		entries, err := os.ReadDir(dir)
		if err != nil {
			return
		}
		for _, e := range entries {
			if ctx.Err() != nil {
				return
			}
			name := e.Name()
			if strings.HasPrefix(name, ".") {
				continue
			}
			path := filepath.Join(dir, name)
			size, err := dirSizeKB(path)
			if err != nil {
				continue
			}
			if size < 16 {
				continue // noise threshold
			}
			rem := model.RemovableFiles{Paths: []string{path}}
			rem.CacheKB = size
			apps = append(apps, model.AppInfo{
				Name:          prefix + name,
				Version:       "-",
				Source:        "cache",
				InstallSizeKB: size,
				Status:        "Installed",
				Removable:     rem,
			})
		}
	}

	scanDir(filepath.Join(d.home, ".cache"), "")
	scanDir(filepath.Join(d.home, ".local", "share"), "share/")
	scanDir(filepath.Join(d.home, ".var", "app"), "flatpak-data/")
	scanDir(filepath.Join(d.home, "snap"), "snap-data/")

	// Orphaned dotfile directories in $HOME.
	if entries, err := os.ReadDir(d.home); err == nil {
		for _, e := range entries {
			if ctx.Err() != nil {
				break
			}
			name := e.Name()
			if !strings.HasPrefix(name, ".") || !e.IsDir() {
				continue
			}
			if skipHome[name] || knownHomeFiles[name] {
				continue
			}
			path := filepath.Join(d.home, name)
			size, err := dirSizeKB(path)
			if err != nil || size < 16 {
				continue
			}
			rem := model.RemovableFiles{Paths: []string{path}}
			rem.ResidualKB = size
			apps = append(apps, model.AppInfo{
				Name:          "." + strings.TrimPrefix(name, "."),
				Version:       "-",
				Source:        "cache",
				InstallSizeKB: size,
				Status:        "Installed",
				Removable:     rem,
			})
		}
	}

	sort.Slice(apps, func(i, j int) bool { return apps[i].InstallSizeKB > apps[j].InstallSizeKB })
	return apps, nil
}