v0.33.1 9577fee

check_runtime_dependencies.go RawHistoryFolder

1// Package usecase contains application business logic.
2package usecase
3
4import (
5	"context"
6	"strings"
7
8	"github.com/bnema/dumber/internal/application/port"
9	"github.com/bnema/dumber/internal/logging"
10)
11
12const (
13	defaultMinGTK4Version      = "4.20"
14	defaultMinWebKitGTKVersion = "2.50"
15	defaultMinGLibVersion      = "2.84"
16)
17
18type RuntimeDependencyID string
19
20const (
21	RuntimeDependencyGTK4      RuntimeDependencyID = "gtk4"
22	RuntimeDependencyWebKitGTK RuntimeDependencyID = "webkitgtk-6.0"
23)
24
25// RuntimeDependencyStatus contains the result of checking a runtime dependency.
26type RuntimeDependencyStatus struct {
27	ID            RuntimeDependencyID
28	PkgConfigName string
29	DisplayName   string
30
31	Installed bool
32	Version   string
33
34	RequiredVersion  string
35	MeetsRequirement bool
36
37	Error string
38}
39
40// CheckRuntimeDependenciesUseCase validates runtime requirements for the GUI browser.
41type CheckRuntimeDependenciesUseCase struct {
42	probe port.RuntimeVersionProbe
43}
44
45// NewCheckRuntimeDependenciesUseCase creates a new use case.
46func NewCheckRuntimeDependenciesUseCase(probe port.RuntimeVersionProbe) *CheckRuntimeDependenciesUseCase {
47	return &CheckRuntimeDependenciesUseCase{probe: probe}
48}
49
50// CheckRuntimeDependenciesInput contains options for runtime dependency checks.
51type CheckRuntimeDependenciesInput struct {
52	// Prefix optionally points to a custom runtime prefix (e.g. /opt/webkitgtk).
53	// Implementations may use this to influence pkg-config search paths.
54	Prefix string
55
56	// Min versions. If empty, defaults are used.
57	MinGTK4Version      string
58	MinWebKitGTKVersion string
59	MinGLibVersion      string
60}
61
62// CheckRuntimeDependenciesOutput contains the result of the runtime dependency checks.
63type CheckRuntimeDependenciesOutput struct {
64	Prefix string
65	OK     bool
66	Checks []RuntimeDependencyStatus
67}
68
69// Execute checks WebKitGTK and GTK runtime versions.
70func (uc *CheckRuntimeDependenciesUseCase) Execute(
71	ctx context.Context,
72	input CheckRuntimeDependenciesInput,
73) (*CheckRuntimeDependenciesOutput, error) {
74	log := logging.FromContext(ctx).With().Str("component", "runtime-check").Logger()
75
76	minGTK := input.MinGTK4Version
77	if minGTK == "" {
78		minGTK = defaultMinGTK4Version
79	}
80	minWebKit := input.MinWebKitGTKVersion
81	if minWebKit == "" {
82		minWebKit = defaultMinWebKitGTKVersion
83	}
84	minGLib := input.MinGLibVersion
85	if minGLib == "" {
86		minGLib = defaultMinGLibVersion
87	}
88
89	checks := []RuntimeDependencyStatus{
90		{
91			ID:              RuntimeDependencyGTK4,
92			PkgConfigName:   "gtk4",
93			DisplayName:     "GTK4",
94			RequiredVersion: minGTK,
95		},
96		{
97			ID:              RuntimeDependencyWebKitGTK,
98			PkgConfigName:   "webkitgtk-6.0",
99			DisplayName:     "WebKitGTK 6.0",
100			RequiredVersion: minWebKit,
101		},
102		{
103			ID:              "glib-2.0",
104			PkgConfigName:   "glib-2.0",
105			DisplayName:     "GLib",
106			RequiredVersion: minGLib,
107		},
108		{
109			ID:              "gio-2.0",
110			PkgConfigName:   "gio-2.0",
111			DisplayName:     "GIO",
112			RequiredVersion: minGLib,
113		},
114	}
115
116	allOK := true
117	for i := range checks {
118		status := &checks[i]
119
120		version, err := uc.probe.PkgConfigModVersion(ctx, status.PkgConfigName, input.Prefix)
121		if err != nil {
122			status.Installed = false
123			status.MeetsRequirement = false
124			status.Error = err.Error()
125			allOK = false
126			continue
127		}
128
129		status.Installed = true
130		status.Version = strings.TrimSpace(version)
131
132		cmp, ok := compareVersion(status.Version, status.RequiredVersion)
133		if !ok {
134			status.MeetsRequirement = false
135			status.Error = "could not parse version"
136			allOK = false
137			continue
138		}
139
140		status.MeetsRequirement = cmp >= 0
141		if !status.MeetsRequirement {
142			allOK = false
143		}
144	}
145
146	log.Debug().Bool("ok", allOK).Str("prefix", input.Prefix).Msg("runtime dependency check complete")
147	return &CheckRuntimeDependenciesOutput{Prefix: input.Prefix, OK: allOK, Checks: checks}, nil
148}
149
150// compareVersion compares two version strings.
151// Returns 1 if a > b, 0 if a == b, -1 if a < b. ok is false if either cannot be parsed.
152func compareVersion(a, b string) (cmp int, ok bool) {
153	av, ok := parseVersionPrefix(a)
154	if !ok {
155		return 0, false
156	}
157	bv, ok := parseVersionPrefix(b)
158	if !ok {
159		return 0, false
160	}
161
162	maxLen := max(len(bv), len(av))
163
164	for i := range maxLen {
165		x := 0
166		if i < len(av) {
167			x = av[i]
168		}
169		y := 0
170		if i < len(bv) {
171			y = bv[i]
172		}
173		switch {
174		case x > y:
175			return 1, true
176		case x < y:
177			return -1, true
178		}
179	}
180	return 0, true
181}
182
183// parseVersionPrefix parses a dotted numeric version prefix (e.g. 4.20.3).
184// It stops at the first non-digit/dot after a numeric segment.
185func parseVersionPrefix(s string) ([]int, bool) {
186	var parts []int
187	cur := 0
188	inNum := false
189
190loop:
191	for i := 0; i < len(s); i++ {
192		c := s[i]
193		switch {
194		case c >= '0' && c <= '9':
195			inNum = true
196			cur = cur*10 + int(c-'0')
197		case c == '.':
198			if !inNum {
199				return nil, false
200			}
201			parts = append(parts, cur)
202			cur = 0
203			inNum = false
204		default:
205			break loop
206		}
207	}
208
209	if inNum {
210		parts = append(parts, cur)
211	}
212	if len(parts) == 0 {
213		return nil, false
214	}
215	return parts, true
216}

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