v0.23.2 bfd5eb4

purge_data.go RawHistoryFolder

1package usecase
2
3import (
4	"context"
5	"fmt"
6
7	"github.com/bnema/dumber/internal/application/port"
8	"github.com/bnema/dumber/internal/domain/entity"
9	"github.com/bnema/dumber/internal/logging"
10)
11
12// PurgeDataUseCase handles discovering and purging application data.
13type PurgeDataUseCase struct {
14	fs      port.FileSystem
15	xdg     port.XDGPaths
16	desktop port.DesktopIntegration
17}
18
19// NewPurgeDataUseCase creates a new PurgeDataUseCase.
20func NewPurgeDataUseCase(fs port.FileSystem, xdg port.XDGPaths, desktop port.DesktopIntegration) *PurgeDataUseCase {
21	return &PurgeDataUseCase{fs: fs, xdg: xdg, desktop: desktop}
22}
23
24// GetPurgeTargets returns all available purge targets with their current state.
25func (uc *PurgeDataUseCase) GetPurgeTargets(ctx context.Context) ([]entity.PurgeTarget, error) {
26	configDir, err := uc.xdg.ConfigDir()
27	if err != nil {
28		return nil, err
29	}
30	dataDir, err := uc.xdg.DataDir()
31	if err != nil {
32		return nil, err
33	}
34	stateDir, err := uc.xdg.StateDir()
35	if err != nil {
36		return nil, err
37	}
38	cacheDir, err := uc.xdg.CacheDir()
39	if err != nil {
40		return nil, err
41	}
42	filterJSONDir, err := uc.xdg.FilterJSONDir()
43	if err != nil {
44		return nil, err
45	}
46	filterStoreDir, err := uc.xdg.FilterStoreDir()
47	if err != nil {
48		return nil, err
49	}
50	filterCacheDir, err := uc.xdg.FilterCacheDir()
51	if err != nil {
52		return nil, err
53	}
54
55	baseTargets := []entity.PurgeTarget{
56		{Type: entity.PurgeTargetConfig, Path: configDir, Description: "config"},
57		{Type: entity.PurgeTargetData, Path: dataDir, Description: "data"},
58		{Type: entity.PurgeTargetState, Path: stateDir, Description: "state"},
59		{Type: entity.PurgeTargetCache, Path: cacheDir, Description: "cache"},
60		{Type: entity.PurgeTargetFilterJSON, Path: filterJSONDir, Description: "filter JSON cache"},
61		{Type: entity.PurgeTargetFilterStore, Path: filterStoreDir, Description: "compiled filters"},
62		{Type: entity.PurgeTargetFilterCache, Path: filterCacheDir, Description: "filter cache"},
63	}
64
65	status, statusErr := uc.desktop.GetStatus(ctx)
66	if statusErr != nil {
67		// Desktop status should not block purge targets discovery.
68		logging.FromContext(ctx).Warn().Err(statusErr).Msg("failed to get desktop integration status")
69	}
70
71	if status != nil {
72		baseTargets = append(baseTargets,
73			entity.PurgeTarget{Type: entity.PurgeTargetDesktopFile, Path: status.DesktopFilePath, Description: "desktop file"},
74			entity.PurgeTarget{Type: entity.PurgeTargetIcon, Path: status.IconFilePath, Description: "icon"},
75		)
76	}
77
78	var targets []entity.PurgeTarget
79	for _, t := range baseTargets {
80		if t.Path == "" {
81			t.Exists = false
82			t.Size = 0
83			targets = append(targets, t)
84			continue
85		}
86		exists, err := uc.fs.Exists(ctx, t.Path)
87		if err != nil {
88			return nil, err
89		}
90		t.Exists = exists
91		if !exists {
92			t.Size = 0
93			targets = append(targets, t)
94			continue
95		}
96		size, err := uc.fs.GetSize(ctx, t.Path)
97		if err != nil {
98			return nil, err
99		}
100		t.Size = size
101		targets = append(targets, t)
102	}
103
104	return targets, nil
105}
106
107// PurgeInput specifies which target types to purge.
108type PurgeInput struct {
109	TargetTypes []entity.PurgeTargetType
110}
111
112// PurgeOutput contains the results of the purge operation.
113type PurgeOutput struct {
114	Results      []entity.PurgeResult
115	TotalSize    int64
116	SuccessCount int
117	FailureCount int
118}
119
120// Execute purges the selected target types.
121// Continues on errors, collecting all results.
122func (uc *PurgeDataUseCase) Execute(ctx context.Context, input PurgeInput) (*PurgeOutput, error) {
123	log := logging.FromContext(ctx)
124
125	targets, err := uc.GetPurgeTargets(ctx)
126	if err != nil {
127		return nil, err
128	}
129
130	selected := make(map[entity.PurgeTargetType]struct{}, len(input.TargetTypes))
131	for _, tt := range input.TargetTypes {
132		selected[tt] = struct{}{}
133	}
134
135	out := &PurgeOutput{}
136	for _, t := range targets {
137		if _, ok := selected[t.Type]; !ok {
138			continue
139		}
140		if !t.Exists {
141			continue
142		}
143
144		res := entity.PurgeResult{Target: t}
145		out.TotalSize += t.Size
146
147		switch t.Type {
148		case entity.PurgeTargetDesktopFile:
149			err = uc.desktop.RemoveDesktopFile(ctx)
150		case entity.PurgeTargetIcon:
151			err = uc.desktop.RemoveIcon(ctx)
152		default:
153			err = uc.fs.RemoveAll(ctx, t.Path)
154		}
155
156		if err != nil {
157			res.Success = false
158			res.Error = err
159			out.FailureCount++
160			log.Warn().Err(err).Str("path", t.Path).Int("type", int(t.Type)).Msg("purge target failed")
161		} else {
162			res.Success = true
163			out.SuccessCount++
164			log.Info().Str("path", t.Path).Int("type", int(t.Type)).Msg("purge target removed")
165		}
166
167		out.Results = append(out.Results, res)
168	}
169
170	if out.FailureCount > 0 {
171		return out, fmt.Errorf("failed to remove %d items", out.FailureCount)
172	}
173	return out, nil
174}
175
176// PurgeAll purges all existing targets (for --force mode).
177func (uc *PurgeDataUseCase) PurgeAll(ctx context.Context) (*PurgeOutput, error) {
178	targets, err := uc.GetPurgeTargets(ctx)
179	if err != nil {
180		return nil, err
181	}
182
183	var types []entity.PurgeTargetType
184	for _, t := range targets {
185		types = append(types, t.Type)
186	}
187
188	return uc.Execute(ctx, PurgeInput{TargetTypes: types})
189}

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