-
Notifications
You must be signed in to change notification settings - Fork 1
/
scrubber.go
176 lines (147 loc) · 4.32 KB
/
scrubber.go
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Package scrubber provides an easy way to clean up old files in a directory.
package scrubber
import (
"fmt"
"os"
"path/filepath"
"slices"
)
// Scrubber holds the configuration and a filesystem handle.
type Scrubber struct {
config *TomlConfig
fs Filesystem
log logger
pretend bool
}
// TomlConfig holds the complete structure of the scrubber config file.
type TomlConfig struct {
Title string
Directories []directory `toml:"directory"`
}
// Strategy represents an action to take with files.
type Strategy struct {
c *StrategyConfig
dir *directory
action performer
log logger
}
// StrategyConfig holds all specified strategies for a single Directory.
type StrategyConfig struct {
Type StrategyType
Action StrategyAction
Limit string
}
// StrategyType defines how to decide what files should be cleaned up.
type StrategyType string
const (
// StrategyTypeAge makes files past a certain age to be deleted.
StrategyTypeAge StrategyType = "age"
// StrategyTypeSize makes files past a certain size to be deleted.
StrategyTypeSize StrategyType = "size"
)
// StrategyAction represents the action that should be taken for matching files.
type StrategyAction string
const (
// ActionTypeDelete is used to delete old files.
ActionTypeDelete StrategyAction = "delete"
// ActionTypeZip is used to zip old files.
ActionTypeZip StrategyAction = "zip"
)
// processor is the interface that wraps the single method a strategy implementation has to provide.
type processor interface {
process(files []os.FileInfo) ([]os.FileInfo, error)
}
// logger defines the the minimal logging functionality we expect.
type logger interface {
Printf(string, ...interface{})
Fatalf(format string, v ...interface{})
}
// New returns a new instance of Scrubber.
func New(c *TomlConfig, fs Filesystem, log logger, pretend bool) *Scrubber {
return &Scrubber{
config: c,
fs: fs,
log: log,
pretend: pretend,
}
}
// Scrub performs the actual cleanup.
func (s Scrubber) Scrub() error {
for _, configDir := range s.config.Directories {
expandedDirs, err := s.expandDirs(configDir.Path)
if err != nil {
s.log.Printf("[ERROR] Failed to expand path %s: %s", configDir.Path, err)
continue
}
if len(expandedDirs) < 1 {
s.log.Printf("Found no files to process. Skipping %s", configDir.Path)
continue
}
for _, expandedDir := range expandedDirs {
dir := configDir.WithPath(expandedDir)
s.log.Printf("Scanning for files in %s...", dir.Path)
scanner := newDirectoryScanner(&dir, s.fs)
files, err := scanner.getFiles()
if err != nil {
s.log.Printf("[ERROR] Failed to load files in directory %s...: %s", dir.Path, err)
continue
}
// Sort files by modification time.
slices.SortFunc(files, func(i, j os.FileInfo) int {
if i.ModTime().After(j.ModTime()) {
return 0
}
return 1
})
files = scanner.filterFiles(files)
files = ApplyKeepLatest(files, dir.KeepLatest)
if len(files) < 1 {
s.log.Printf("Found no files to process. Skipping %s", dir.Path)
continue
}
s.log.Printf("Found %d files to process", len(files))
for _, strategy := range dir.Strategies {
strategy := strategy
s, err := strategyFromConfig(&strategy, &dir, s.fs, s.log, s.pretend)
if err != nil {
return err
}
_, err = s.process(files)
if err != nil {
return fmt.Errorf("error while processing files: %s", err)
}
}
}
}
return nil
}
// expandDirs expands a Glob pattern and returns all directories.
func (s Scrubber) expandDirs(path string) ([]string, error) {
expandedPaths, err := filepath.Glob(path)
if err != nil {
return nil, err
}
var dirs []string
for _, expandedPath := range expandedPaths {
fileInfo, err := s.fs.Stat(expandedPath)
if err != nil {
return nil, err
}
if fileInfo.IsDir() {
dirs = append(dirs, expandedPath)
}
}
return dirs, nil
}
// strategyFromConfig returns the strategy defined in the configuration file.
func strategyFromConfig(c *StrategyConfig, dir *directory, fs Filesystem, log logger, pretend bool) (processor,
error) {
action := actionFromConfig(c, dir, fs, log, pretend)
switch c.Type {
case StrategyTypeAge:
return newAgeStrategy(c, dir, action, log), nil
case StrategyTypeSize:
return newSizeStrategy(c, dir, action, log), nil
}
return nil, fmt.Errorf("unknown strategy type: %s", c.Type)
}