From e0531098d8aef3c8612b82a74b1b48e1e4b92fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20O=2E=20Sel=C3=A5sdal?= Date: Sat, 4 Jul 2026 19:30:25 +0200 Subject: [PATCH] Add file duration --- main.go | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index b686d13..3d5e7b7 100644 --- a/main.go +++ b/main.go @@ -3,9 +3,11 @@ package main import ( "crypto/rand" "encoding/json" + "errors" "fmt" "html/template" "io" + "io/fs" "log" "mime" "net/http" @@ -27,6 +29,17 @@ func main() { if uploadDir == "" { uploadDir = "/uploads" } + var fileExpires time.Duration = 7 * 24 * time.Hour + + dryRun := strings.ToLower(os.Getenv("DRYRUN_CLEAN")) != "false" + fileDuration := os.Getenv("FILE_DURATION") + if fileDuration != "" { + expires, err := time.ParseDuration(fileDuration) + if err != nil { + log.Fatalf("FILE_DURATION %s is invalid: %v", fileDuration, err) + } + fileExpires = expires + } port := os.Getenv("PORT") if port == "" { @@ -40,6 +53,13 @@ func main() { http.HandleFunc("/", handleRequest) log.Printf("Starting server on port %s, upload directory: %s", port, uploadDir) + log.Printf("Files expires after: %v (DRYRUN_CLEAN=%v)", fileExpires, dryRun) + if fileExpires != 0 { + go cleanupProcedure(fileExpires, dryRun) + } else { + log.Print("Cleaning old files is disabled") + } + if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatalf("Server failed: %v", err) } @@ -261,7 +281,7 @@ func getHost(r *http.Request) string { // Check standard Forwarded header (RFC 7239) if forwarded := r.Header.Get("Forwarded"); forwarded != "" { - // Parse "Forwarded: for=...; host=example.com; proto=https" + // Parse "Forwarded: for=...; host=example.com; proto=https" parts := strings.SplitSeq(forwarded, ";") for part := range parts { part = strings.TrimSpace(part) @@ -325,3 +345,95 @@ func serveFrontpage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") indexHTML.Execute(w, pageData) } + +// Cleanup + +func isDirEmpty(name string) (bool, error) { + f, err := os.Open(name) + if err != nil { + return false, err + } + defer f.Close() + + // Read exactly one entry. + _, err = f.ReadDir(1) + if errors.Is(err, io.EOF) { + // EOF means there are no entries, so the directory is empty. + return true, nil + } + + // If err is nil, an entry was found (not empty). + // If err is something else, a real error occurred. + return false, err +} + +func cleanupProcedure(duration time.Duration, dryRun bool) { + var oldFiles []string + var emptyDirs []string + + deleteIfTooOld := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return nil + } + if d.IsDir() { + return nil + } + + info, err := d.Info() + if err != nil { + log.Printf("Error cleaning file '%s' : %v", d.Name(), err) + return nil + } + age := time.Since(info.ModTime()) + if age >= duration { + oldFiles = append(oldFiles, path) + } + return nil + } + + findEmptyDirs := func(path string, d fs.DirEntry, err error) error { + if !d.IsDir() { + return nil + } + empty, err := isDirEmpty(path) + if empty { + emptyDirs = append(emptyDirs, path) + } else if err != nil { + log.Printf("Error cleaning directory '%s' : %v", path, err) + } + + return nil + } + + for { + + filepath.WalkDir(uploadDir, deleteIfTooOld) + for _, file := range oldFiles { + if !dryRun { + err := os.Remove(file) + if err != nil { + log.Printf("Failed to remove expired file '%s' : %v", file, err) + } else { + log.Printf("Removed expired file '%s'", file) + } + } else { + log.Printf("Dry run: Would remove expired file '%s'", file) + } + } + + filepath.WalkDir(uploadDir, findEmptyDirs) + for _, file := range emptyDirs { + if !dryRun { + err := os.Remove(file) + if err != nil { + log.Printf("Failed to remove empty folder '%s' : %v", file, err) + } else { + log.Printf("Removed empty folder '%s'", file) + } + } else { + log.Printf("Dry run: Would remove empty folder '%s'", file) + } + } + time.Sleep(6 * time.Hour) + } +}