feat(transcode): add cache pruning and config options

* Added config option to set size of transcode cache and cadence to enforce that sizing via ejection.

* Added cache eject to contrib/config.

* Added error return for CacheEject(). Changed to use WalkDir() instead of Walk().

* Lint fix.

* Added universal lock for cache eject.

* Removed accidentally committed binary.
This commit is contained in:
brian-doherty
2024-09-15 09:04:28 -05:00
committed by GitHub
parent bfa0e130d4
commit bcb613c79c
5 changed files with 86 additions and 3 deletions

View File

@@ -5,9 +5,12 @@ import (
"crypto/md5"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"sort"
"sync"
"time"
)
const perm = 0o644
@@ -15,16 +18,21 @@ const perm = 0o644
type CachingTranscoder struct {
cachePath string
transcoder Transcoder
limitMB int
locks keyedMutex
cleanLock sync.RWMutex
}
var _ Transcoder = (*CachingTranscoder)(nil)
func NewCachingTranscoder(t Transcoder, cachePath string) *CachingTranscoder {
return &CachingTranscoder{transcoder: t, cachePath: cachePath}
func NewCachingTranscoder(t Transcoder, cachePath string, limitMB int) *CachingTranscoder {
return &CachingTranscoder{transcoder: t, cachePath: cachePath, limitMB: limitMB}
}
func (t *CachingTranscoder) Transcode(ctx context.Context, profile Profile, in string, out io.Writer) error {
t.cleanLock.RLock()
defer t.cleanLock.RUnlock()
// don't try cache partial transcodes
if profile.Seek() > 0 {
return t.transcoder.Transcode(ctx, profile, in, out)
@@ -52,6 +60,7 @@ func (t *CachingTranscoder) Transcode(ctx context.Context, profile Profile, in s
if i, err := cf.Stat(); err == nil && i.Size() > 0 {
_, _ = io.Copy(out, cf)
_ = os.Chtimes(path, time.Now(), time.Now()) // Touch for LRU cache purposes
return nil
}
@@ -64,6 +73,55 @@ func (t *CachingTranscoder) Transcode(ctx context.Context, profile Profile, in s
return nil
}
func (t *CachingTranscoder) CacheEject() error {
t.cleanLock.Lock()
defer t.cleanLock.Unlock()
// Delete LRU cache files that exceed size limit. Use last modified time.
type file struct {
path string
info os.FileInfo
}
var files []file
var total int64 = 0
err := filepath.WalkDir(t.cachePath, func(path string, de fs.DirEntry, err error) error {
if err != nil {
return err
}
if !de.IsDir() {
info, err := de.Info()
if err != nil {
return fmt.Errorf("walk cache path for eject: %w", err)
}
files = append(files, file{path, info})
total += info.Size()
}
return nil
})
if err != nil {
return fmt.Errorf("walk cache path for eject: %w", err)
}
sort.Slice(files, func(i, j int) bool {
return files[i].info.ModTime().Before(files[j].info.ModTime())
})
for total > int64(t.limitMB)*1024*1024 {
curFile := files[0]
files = files[1:]
total -= curFile.info.Size()
err = os.Remove(curFile.path)
if err != nil {
return fmt.Errorf("remove cache file: %w", err)
}
}
return nil
}
func cacheKey(cmd string, args []string) string {
// the cache is invalid whenever transcode command (which includes the
// absolute filepath, bit rate args, replay gain args, etc.) changes