11package main
22
33import (
4+ "context"
45 "crypto/rand"
56 "encoding/base64"
7+ "errors"
68 "io"
9+ "io/fs"
10+ "log/slog"
711 "os"
812 "path/filepath"
13+ "strings"
14+ "time"
915)
1016
1117type DiskStore struct {
@@ -15,6 +21,70 @@ type DiskStore struct {
1521// IdByteLength must not be less than 3, or the sharding logic will panic (minimum 5 for sensible file names)
1622const IdByteLength = 6
1723
24+ func NewDiskStore (ctx context.Context , config Config ) * DiskStore {
25+ store := & DiskStore {BaseDir : config .StoragePath }
26+ go store .sweep (ctx , config .SweepInterval )
27+ return store
28+ }
29+
30+ func (s * DiskStore ) sweep (ctx context.Context , sweepInterval time.Duration ) {
31+ ticker := time .NewTicker (sweepInterval )
32+ defer ticker .Stop ()
33+ for {
34+ select {
35+ case <- ctx .Done ():
36+ return
37+ case <- ticker .C :
38+ s .performCleanup ()
39+ }
40+ }
41+ }
42+
43+ func (s * DiskStore ) performCleanup () {
44+ metaDir := filepath .Join (s .BaseDir , ".meta" )
45+ err := filepath .WalkDir (metaDir , func (path string , d fs.DirEntry , err error ) error {
46+ if err != nil {
47+ slog .Warn ("sweep: walk error" , "path" , path , "error" , err )
48+ return nil
49+ }
50+ if d .IsDir () {
51+ return nil
52+ }
53+ data , err := os .ReadFile (path )
54+ if err != nil {
55+ slog .Warn ("sweep: failed to read meta file" , "path" , path , "error" , err )
56+ return nil
57+ }
58+ expiry , err := time .Parse (time .RFC3339 , string (data ))
59+ if err != nil {
60+ slog .Warn ("sweep: failed to parse meta file" , "path" , path , "error" , err )
61+ return nil
62+ }
63+ if ! time .Now ().After (expiry ) {
64+ return nil
65+ }
66+ rel , err := filepath .Rel (metaDir , path )
67+ if err != nil {
68+ return nil
69+ }
70+ parts := strings .Split (rel , string (filepath .Separator ))
71+ if len (parts ) != 3 {
72+ return nil
73+ }
74+ id := parts [0 ] + parts [1 ] + parts [2 ]
75+ if err := s .DeleteFile (id ); err != nil && ! errors .Is (err , os .ErrNotExist ) {
76+ slog .Warn ("sweep: failed to delete expired file" , "id" , id , "error" , err )
77+ }
78+ if err := s .DeleteMeta (id ); err != nil && ! errors .Is (err , os .ErrNotExist ) {
79+ slog .Warn ("sweep: failed to delete expired meta" , "id" , id , "error" , err )
80+ }
81+ return nil
82+ })
83+ if err != nil && ! errors .Is (err , os .ErrNotExist ) {
84+ slog .Warn ("sweep failed" , "error" , err )
85+ }
86+ }
87+
1888func (s * DiskStore ) SaveFile (r io.Reader , ext string ) (string , error ) {
1989 for {
2090 id , err := generateId (IdByteLength )
@@ -68,6 +138,51 @@ func (s *DiskStore) GetFile(id string) (string, bool) {
68138 return matches [0 ], true
69139}
70140
141+ func (s * DiskStore ) DeleteFile (id string ) error {
142+ path , ok := s .GetFile (id )
143+ if ! ok {
144+ return os .ErrNotExist
145+ }
146+ if err := os .Remove (path ); err != nil && ! errors .Is (err , os .ErrNotExist ) {
147+ return err
148+ }
149+ dir := filepath .Dir (path )
150+ _ = os .Remove (dir )
151+ _ = os .Remove (filepath .Dir (dir ))
152+ return nil
153+ }
154+
155+ func (s * DiskStore ) GetMeta (id string ) (time.Time , error ) {
156+ contents , err := os .ReadFile (s .metaPath (id ))
157+ if err != nil {
158+ return time.Time {}, err
159+ }
160+ return time .Parse (time .RFC3339 , string (contents ))
161+ }
162+
163+ func (s * DiskStore ) WriteMeta (id string , expiry time.Duration ) error {
164+ path := s .metaPath (id )
165+ if err := os .MkdirAll (filepath .Dir (path ), 0755 ); err != nil {
166+ return err
167+ }
168+ return os .WriteFile (path , []byte (time .Now ().Add (expiry ).Format (time .RFC3339 )), 0644 )
169+ }
170+
171+ func (s * DiskStore ) DeleteMeta (id string ) error {
172+ path := s .metaPath (id )
173+ if err := os .Remove (path ); err != nil && ! errors .Is (err , os .ErrNotExist ) {
174+ return err
175+ }
176+ dir := filepath .Dir (path )
177+ _ = os .Remove (dir )
178+ _ = os .Remove (filepath .Dir (dir ))
179+ return nil
180+ }
181+
182+ func (s * DiskStore ) metaPath (id string ) string {
183+ return filepath .Join (s .BaseDir , ".meta" , id [:2 ], id [2 :4 ], id [4 :])
184+ }
185+
71186func generateId (length int ) (string , error ) {
72187 buffer := make ([]byte , length )
73188 if _ , err := rand .Read (buffer ); err != nil {
0 commit comments