From 3e34a37c958440f29f129c33206e0ab925320c27 Mon Sep 17 00:00:00 2001 From: Elijah Date: Sat, 23 May 2026 13:41:49 -0700 Subject: [PATCH] feat: re-architect folder uploads with batching and concurrency limits, add TUS stale cleanup routine --- backend/handlers/tus.go | 34 ++++++++++- frontend/src/components/FileExplorer.tsx | 77 ++++++++++++------------ 2 files changed, 73 insertions(+), 38 deletions(-) diff --git a/backend/handlers/tus.go b/backend/handlers/tus.go index 64aa88a..aaa78a6 100644 --- a/backend/handlers/tus.go +++ b/backend/handlers/tus.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" "sync" + "time" "git.elijahkuntz.com/Elijah/drive/config" "git.elijahkuntz.com/Elijah/drive/database" @@ -41,11 +42,42 @@ type tusUpload struct { } func NewTusHandler(db *database.DB, cfg *config.Config) *TusHandler { - return &TusHandler{ + h := &TusHandler{ DB: db, Config: cfg, uploads: make(map[string]*tusUpload), } + go h.cleanupStaleUploads() + return h +} + +func (h *TusHandler) cleanupStaleUploads() { + ticker := time.NewTicker(1 * time.Hour) + for range ticker.C { + tempDir := filepath.Join(h.Config.StorageDir, ".uploads") + entries, err := os.ReadDir(tempDir) + if err != nil { + continue + } + + cutoff := time.Now().Add(-24 * time.Hour) + + for _, entry := range entries { + info, err := entry.Info() + if err != nil { + continue + } + + if info.ModTime().Before(cutoff) { + path := filepath.Join(tempDir, entry.Name()) + os.Remove(path) + + h.mu.Lock() + delete(h.uploads, entry.Name()) + h.mu.Unlock() + } + } + } } // Options returns tus protocol capability headers. diff --git a/frontend/src/components/FileExplorer.tsx b/frontend/src/components/FileExplorer.tsx index 1d5789b..75d69eb 100644 --- a/frontend/src/components/FileExplorer.tsx +++ b/frontend/src/components/FileExplorer.tsx @@ -1855,45 +1855,48 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
- {uploads.map(upload => ( -
-
- { + const percentage = batch.totalBytes > 0 ? (batch.uploadedBytes / batch.totalBytes) * 100 : 0; + return ( +
+
+ + {batch.name} + + + {batch.status === 'complete' ? '✓' : batch.status === 'error' ? '✗' : `${Math.round(percentage)}%`} + +
+ {batch.totalFiles > 1 && ( +
+ {batch.filesUploaded} / {batch.totalFiles} files +
+ )} +
- {getFileDisplayName(upload.name, false)} - - - {upload.status === 'complete' ? '✓' : upload.status === 'error' ? '✗' : `${Math.round(upload.progress)}%`} - + +
+ {batch.error && ( +
+ {batch.error} +
+ )}
-
- -
- {upload.status === 'error' && ( -

- {upload.error} -

- )} -
- ))} + ); + })}
)}