filebrowser/http/upload_cache_memory.go

86 lines
2.3 KiB
Go

package fbhttp
import (
"context"
"fmt"
"os"
"time"
"github.com/jellydator/ttlcache/v3"
)
const uploadCacheTTL = 3 * time.Minute
// UploadCache is an interface for tracking active uploads.
// Allows for different backends (e.g. in-memory or redis)
// to support both single instance and multi replica deployments.
type UploadCache interface {
// Register stores an upload with its expected file size
Register(filePath string, fileSize int64)
// Complete removes an upload from the cache
Complete(filePath string)
// GetLength returns the expected file size for an active upload
GetLength(filePath string) (int64, error)
// Touch refreshes the TTL for an active upload
Touch(filePath string)
// Close cleans up any resources
Close()
}
// memoryUploadCache is an upload cache for single replica deployments
type memoryUploadCache struct {
cache *ttlcache.Cache[string, int64]
}
func newMemoryUploadCache() *memoryUploadCache {
cache := ttlcache.New[string, int64]()
cache.OnEviction(func(_ context.Context, reason ttlcache.EvictionReason, item *ttlcache.Item[string, int64]) {
if reason == ttlcache.EvictionReasonExpired {
fmt.Printf("deleting incomplete upload file: \"%s\"\n", item.Key())
os.Remove(item.Key())
}
})
go cache.Start()
return &memoryUploadCache{cache: cache}
}
func (c *memoryUploadCache) Register(filePath string, fileSize int64) {
c.cache.Set(filePath, fileSize, uploadCacheTTL)
}
func (c *memoryUploadCache) Complete(filePath string) {
c.cache.Delete(filePath)
}
func (c *memoryUploadCache) GetLength(filePath string) (int64, error) {
item := c.cache.Get(filePath)
if item == nil {
return 0, fmt.Errorf("no active upload found for the given path")
}
return item.Value(), nil
}
func (c *memoryUploadCache) Touch(filePath string) {
c.cache.Touch(filePath)
}
func (c *memoryUploadCache) Close() {
c.cache.Stop()
}
// NewUploadCache creates a new upload cache.
// If redisURL is empty, an in-memory cache will be used (suitable for single instance deployments).
// Otherwise, Redis will be used for the cache (suitable for multi-instance deployments).
// The redisURL can include credentials, e.g. redis://user:pass@host:port
func NewUploadCache(redisURL string) (UploadCache, error) {
if redisURL != "" {
return newRedisUploadCache(redisURL)
}
return newMemoryUploadCache(), nil
}