From 30fe65f5faa30e71fbf1619c01e6b2e1ca7efa64 Mon Sep 17 00:00:00 2001 From: event Date: Sat, 10 Jan 2026 21:36:48 +0000 Subject: [PATCH 1/3] Only collect pieces to remove if removal from cache is planned --- server/torr/storage/torrstor/cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/torr/storage/torrstor/cache.go b/server/torr/storage/torrstor/cache.go index 5b8080ab0..466600f35 100644 --- a/server/torr/storage/torrstor/cache.go +++ b/server/torr/storage/torrstor/cache.go @@ -197,8 +197,8 @@ func (c *Cache) cleanPieces() { defer func() { c.isRemove = false }() c.muRemove.Unlock() - remPieces := c.getRemPieces() if c.filled > c.capacity { + remPieces := c.getRemPieces() rems := (c.filled-c.capacity)/c.pieceLength + 1 for _, p := range remPieces { c.removePiece(p) From 2d49f3c1959d932e8965a81e281cae1a7b329028 Mon Sep 17 00:00:00 2001 From: event Date: Sun, 18 Jan 2026 18:06:47 +0000 Subject: [PATCH 2/3] Add new setting to allow applying cache size limit to all torrents This way if cache size is defined to be e.g. 100GB, torrserver would make sure that all caches combined do not exceed 100GB --- server/settings/btsets.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/settings/btsets.go b/server/settings/btsets.go index 3829cbf96..805ea0de9 100644 --- a/server/settings/btsets.go +++ b/server/settings/btsets.go @@ -34,6 +34,7 @@ type BTSets struct { UseDisk bool TorrentsSavePath string RemoveCacheOnDrop bool + OneCacheForAll bool // Torrent ForceEncrypt bool From 6d34c222bd00a1045aab9c2c10e38e884230c129 Mon Sep 17 00:00:00 2001 From: event Date: Sun, 18 Jan 2026 18:10:33 +0000 Subject: [PATCH 3/3] Implement new setting OneCacheForAll Implement storage.cleanPieces which is used when option OneCacheForAll is enabled. It cleans pieces in caches starting with least recently modified --- server/torr/storage/torrstor/cache.go | 10 ++++- server/torr/storage/torrstor/storage.go | 54 ++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/server/torr/storage/torrstor/cache.go b/server/torr/storage/torrstor/cache.go index 466600f35..f7c40904a 100644 --- a/server/torr/storage/torrstor/cache.go +++ b/server/torr/storage/torrstor/cache.go @@ -184,7 +184,7 @@ func (c *Cache) GetState() *state.CacheState { return cState } -func (c *Cache) cleanPieces() { +func (c *Cache) doCleanPieces() { if c.isRemove || c.isClosed { return } @@ -211,6 +211,14 @@ func (c *Cache) cleanPieces() { } } +func (c *Cache) cleanPieces() { + if settings.BTsets.OneCacheForAll { + c.storage.cleanPieces() + } else { + c.doCleanPieces() + } +} + func (c *Cache) getRemPieces() []*Piece { piecesRemove := make([]*Piece, 0) fill := int64(0) diff --git a/server/torr/storage/torrstor/storage.go b/server/torr/storage/torrstor/storage.go index 195fbb7cb..d13a46e48 100644 --- a/server/torr/storage/torrstor/storage.go +++ b/server/torr/storage/torrstor/storage.go @@ -2,8 +2,11 @@ package torrstor import ( "sync" - + "slices" + "os" + "path/filepath" "server/torr/storage" + "server/settings" "github.com/anacrolix/torrent/metainfo" ts "github.com/anacrolix/torrent/storage" @@ -70,3 +73,52 @@ func (s *Storage) GetCache(hash metainfo.Hash) *Cache { } return nil } + +func (s *Storage) cleanPieces() { + s.mu.Lock() + defer s.mu.Unlock() + var filled int64 = 0 + for _, ch := range s.caches { + filled += ch.filled + } + overfill := filled - s.capacity + if overfill < 0 { + return + } + sortCachesByModifiedDate := func(a, b *Cache) int { + aname := filepath.Join(settings.BTsets.TorrentsSavePath, a.hash.HexString()) + bname := filepath.Join(settings.BTsets.TorrentsSavePath, b.hash.HexString()) + ainfo, err := os.Stat(aname) + if err != nil { + return -1 + } + binfo, err := os.Stat(bname) + if err != nil { + return 1 + } + return ainfo.ModTime().Compare(binfo.ModTime()) + } + nonempty := slices.Values(slices.Collect(func(yield func(*Cache) bool) { + for _, c := range s.caches { + if c.filled > 0 { + if !yield(c) { + return + } + } + } + })) + // fill sortedcaches with refs to non-empty caches sorted by respective + // folder modified date in descending order (older first) + sortedcaches := slices.SortedFunc(nonempty, sortCachesByModifiedDate) + for _, c := range sortedcaches { + _cap := c.capacity + c.capacity = max(c.filled - overfill, 0) + overfill -= c.filled + c.doCleanPieces() + overfill += c.filled + c.capacity = _cap + if overfill <= 0 { + return + } + } +}