swadhinbiswas feat: initial release of VEET (Universal Linux App Uninstaller & Deep-Clean Residual Purger) · 8/15/2026
5e0888a
78 lines · 1.9 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
package detector

import (
	"context"
	"strings"

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

// NewZypperDetector detects SUSE/openSUSE packages.
func NewZypperDetector(ex Execer) Detector {
	path, _ := ex.LookPath("zypper")
	return &simpleCmdDetector{
		ex:      ex,
		path:    path,
		source:  "zypper",
		listCmd: []string{"zypper", "se", "--installed-only", "-s"},
	}
}

type simpleCmdDetector struct {
	ex      Execer
	path    string
	source  string
	listCmd []string
}

func (d *simpleCmdDetector) Source() string  { return d.source }
func (d *simpleCmdDetector) Available() bool { return d.path != "" }

func (d *simpleCmdDetector) Detect(ctx context.Context) ([]model.AppInfo, error) {
	out, err := Exec(ctx, d.ex, d.listCmd[0], d.listCmd[1:]...)
	if err != nil {
		return nil, err
	}
	var names []string
	versions := map[string]string{}
	for _, line := range strings.Split(out, "\n") {
		// zypper se -s prints: S | Name | Type | Version | Arch | Repository
		fields := strings.Fields(line)
		if len(fields) < 4 || !strings.Contains(fields[0], "i") {
			continue
		}
		names = append(names, fields[1])
		versions[fields[1]] = fields[3]
	}

	sink := &appSink{}
	workerPool(names, 6, func(name string) {
		var sizeKB int64
		var installed string
		if info, err := Exec(ctx, d.ex, "zypper", "info", name); err == nil {
			for _, line := range strings.Split(info, "\n") {
				key, val, ok := strings.Cut(line, ":")
				if !ok {
					continue
				}
				key, val = strings.TrimSpace(key), strings.TrimSpace(val)
				switch key {
				case "Installed Size":
					sizeKB = parseHumanSize(val)
				case "Installed":
					installed = val
				}
			}
		}
		sink.add(model.AppInfo{
			Name:          name,
			Version:       versions[name],
			Source:        "zypper",
			InstalledOn:   parseDate(installed),
			InstallSizeKB: sizeKB,
			Status:        "Installed",
		})
	})
	return sink.apps, nil
}