This repository was archived by the owner on Jan 24, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcache_manager.go
More file actions
190 lines (162 loc) · 4.8 KB
/
Copy pathcache_manager.go
File metadata and controls
190 lines (162 loc) · 4.8 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
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package blobber
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"time"
"github.com/meigma/blobber/internal/cache"
)
// CacheInfo contains statistics about a cache directory.
type CacheInfo struct {
// Path is the absolute path to the cache directory.
Path string
// TotalSize is the sum of all cached blob sizes in bytes.
TotalSize int64
// EntryCount is the number of cached blobs.
EntryCount int
// Entries contains detailed information about each cached blob.
// Sorted by LastAccessed, most recent first.
Entries []CacheEntry
}
// CacheEntry describes a single cached blob.
type CacheEntry struct {
// Digest is the blob's SHA256 digest (sha256:...).
Digest string
// Size is the blob size in bytes.
Size int64
// LastAccessed is when the blob was last accessed.
LastAccessed time.Time
// Complete indicates whether the full blob is cached.
Complete bool
}
// CachePruneOptions configures cache pruning behavior.
type CachePruneOptions struct {
// MaxSize is the maximum total cache size in bytes.
// Entries are evicted LRU until the cache is under this limit.
// Zero means no size limit.
MaxSize int64
// MaxAge is the maximum age for cache entries.
// Entries not accessed within this duration are evicted.
// Zero means no age limit.
MaxAge time.Duration
}
// CachePruneResult contains statistics about a prune operation.
type CachePruneResult struct {
// EntriesRemoved is the number of entries that were evicted.
EntriesRemoved int
// BytesRemoved is the total bytes freed.
BytesRemoved int64
// EntriesRemaining is the number of entries still in cache.
EntriesRemaining int
// BytesRemaining is the total bytes still in cache.
BytesRemaining int64
}
// CacheStats returns statistics about the cache at the given path.
// If the cache directory doesn't exist, returns an empty CacheInfo.
func CacheStats(path string) (*CacheInfo, error) {
absPath, err := resolveCachePath(path)
if err != nil {
return nil, err
}
// Check if cache directory exists
if _, statErr := os.Stat(absPath); os.IsNotExist(statErr) {
return &CacheInfo{Path: absPath}, nil
}
c, err := cache.New(absPath, nil, slog.New(slog.DiscardHandler))
if err != nil {
return nil, fmt.Errorf("open cache: %w", err)
}
entries, err := c.Entries()
if err != nil {
return nil, fmt.Errorf("list entries: %w", err)
}
info := &CacheInfo{
Path: absPath,
EntryCount: len(entries),
Entries: make([]CacheEntry, len(entries)),
}
for i, e := range entries {
info.TotalSize += e.Size
info.Entries[i] = CacheEntry{
Digest: e.Digest,
Size: e.Size,
LastAccessed: e.LastAccessed,
Complete: e.Complete,
}
}
return info, nil
}
// CacheClear removes all entries from the cache at the given path.
// Returns nil if the cache directory doesn't exist.
func CacheClear(path string) error {
absPath, err := resolveCachePath(path)
if err != nil {
return err
}
// Check if cache directory exists
if _, statErr := os.Stat(absPath); os.IsNotExist(statErr) {
return nil
}
c, err := cache.New(absPath, nil, slog.New(slog.DiscardHandler))
if err != nil {
return fmt.Errorf("open cache: %w", err)
}
if err := c.Clear(); err != nil {
return fmt.Errorf("clear cache: %w", err)
}
return nil
}
// CachePrune removes entries based on the provided options.
// Entries exceeding MaxAge are removed first, then LRU eviction
// is performed until TotalSize is under MaxSize.
// Returns nil if the cache directory doesn't exist.
func CachePrune(ctx context.Context, path string, opts CachePruneOptions) (*CachePruneResult, error) {
absPath, err := resolveCachePath(path)
if err != nil {
return nil, err
}
// Check if cache directory exists
if _, statErr := os.Stat(absPath); os.IsNotExist(statErr) {
return &CachePruneResult{}, nil
}
c, err := cache.New(absPath, nil, slog.New(slog.DiscardHandler))
if err != nil {
return nil, fmt.Errorf("open cache: %w", err)
}
result, err := c.Prune(ctx, cache.PruneOptions{
MaxSize: opts.MaxSize,
MaxAge: opts.MaxAge,
})
if err != nil {
return nil, fmt.Errorf("prune cache: %w", err)
}
return &CachePruneResult{
EntriesRemoved: result.EntriesRemoved,
BytesRemoved: result.BytesRemoved,
EntriesRemaining: result.EntriesRemaining,
BytesRemaining: result.BytesRemaining,
}, nil
}
// resolveCachePath expands ~ and converts to absolute path.
func resolveCachePath(path string) (string, error) {
if path == "" {
return "", errors.New("cache path is empty")
}
// Expand ~ to home directory
if path[0] == '~' {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("expand home directory: %w", err)
}
path = filepath.Join(home, path[1:])
}
// Convert to absolute path
absPath, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("resolve path: %w", err)
}
return absPath, nil
}