cleanup torrent cache

This commit is contained in:
Mukhtar Akere
2025-06-14 16:55:45 +01:00
parent a539aa53bd
commit 22280f15cf
5 changed files with 83 additions and 57 deletions

View File

@@ -552,6 +552,10 @@ func (c *Cache) GetTorrents() map[string]CachedTorrent {
return c.torrents.getAll()
}
func (c *Cache) TotalTorrents() int {
return c.torrents.getAllCount()
}
func (c *Cache) GetTorrentByName(name string) *CachedTorrent {
if torrent, ok := c.torrents.getByName(name); ok {
return &torrent

View File

@@ -137,10 +137,10 @@ func (c *Cache) refreshRclone() error {
}
client := &http.Client{
Timeout: 10 * time.Second,
Timeout: 60 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
IdleConnTimeout: 60 * time.Second,
DisableCompression: false,
MaxIdleConnsPerHost: 5,
},

View File

@@ -40,13 +40,22 @@ type directoryFilter struct {
ageThreshold time.Duration // only for last_added
}
type torrents struct {
sync.RWMutex
byID map[string]CachedTorrent
byName map[string]CachedTorrent
}
type folders struct {
sync.RWMutex
listing map[string][]os.FileInfo // folder name to file listing
}
type torrentCache struct {
mu sync.Mutex
byID map[string]CachedTorrent
byName map[string]CachedTorrent
torrents torrents
listing atomic.Value
folderListing map[string][]os.FileInfo
folderListingMu sync.RWMutex
folders folders
directoriesFilters map[string][]directoryFilter
sortNeeded atomic.Bool
}
@@ -62,9 +71,13 @@ type sortableFile struct {
func newTorrentCache(dirFilters map[string][]directoryFilter) *torrentCache {
tc := &torrentCache{
byID: make(map[string]CachedTorrent),
byName: make(map[string]CachedTorrent),
folderListing: make(map[string][]os.FileInfo),
torrents: torrents{
byID: make(map[string]CachedTorrent),
byName: make(map[string]CachedTorrent),
},
folders: folders{
listing: make(map[string][]os.FileInfo),
},
directoriesFilters: dirFilters,
}
@@ -74,41 +87,42 @@ func newTorrentCache(dirFilters map[string][]directoryFilter) *torrentCache {
}
func (tc *torrentCache) reset() {
tc.mu.Lock()
tc.byID = make(map[string]CachedTorrent)
tc.byName = make(map[string]CachedTorrent)
tc.mu.Unlock()
tc.torrents.Lock()
tc.torrents.byID = make(map[string]CachedTorrent)
tc.torrents.byName = make(map[string]CachedTorrent)
tc.torrents.Unlock()
// reset the sorted listing
tc.sortNeeded.Store(false)
tc.listing.Store(make([]os.FileInfo, 0))
// reset any per-folder views
tc.folderListingMu.Lock()
tc.folderListing = make(map[string][]os.FileInfo)
tc.folderListingMu.Unlock()
tc.folders.Lock()
tc.folders.listing = make(map[string][]os.FileInfo)
tc.folders.Unlock()
}
func (tc *torrentCache) getByID(id string) (CachedTorrent, bool) {
tc.mu.Lock()
defer tc.mu.Unlock()
torrent, exists := tc.byID[id]
tc.torrents.RLock()
defer tc.torrents.RUnlock()
torrent, exists := tc.torrents.byID[id]
return torrent, exists
}
func (tc *torrentCache) getByName(name string) (CachedTorrent, bool) {
tc.mu.Lock()
defer tc.mu.Unlock()
torrent, exists := tc.byName[name]
tc.torrents.RLock()
defer tc.torrents.RUnlock()
torrent, exists := tc.torrents.byName[name]
return torrent, exists
}
func (tc *torrentCache) set(name string, torrent, newTorrent CachedTorrent) {
tc.mu.Lock()
tc.torrents.Lock()
// Set the id first
tc.byID[newTorrent.Id] = torrent // This is the unadulterated torrent
tc.byName[name] = newTorrent // This is likely the modified torrent
tc.mu.Unlock()
tc.torrents.byName[name] = torrent
tc.torrents.byID[torrent.Id] = torrent // This is the unadulterated torrent
tc.torrents.Unlock()
tc.sortNeeded.Store(true)
}
@@ -124,12 +138,12 @@ func (tc *torrentCache) getListing() []os.FileInfo {
}
func (tc *torrentCache) getFolderListing(folderName string) []os.FileInfo {
tc.folderListingMu.RLock()
defer tc.folderListingMu.RUnlock()
tc.folders.RLock()
defer tc.folders.RUnlock()
if folderName == "" {
return tc.getListing()
}
if folder, ok := tc.folderListing[folderName]; ok {
if folder, ok := tc.folders.listing[folderName]; ok {
return folder
}
// If folder not found, return empty slice
@@ -138,13 +152,13 @@ func (tc *torrentCache) getFolderListing(folderName string) []os.FileInfo {
func (tc *torrentCache) refreshListing() {
tc.mu.Lock()
all := make([]sortableFile, 0, len(tc.byName))
for name, t := range tc.byName {
tc.torrents.RLock()
all := make([]sortableFile, 0, len(tc.torrents.byName))
for name, t := range tc.torrents.byName {
all = append(all, sortableFile{t.Id, name, t.AddedOn, t.Bytes, t.Bad})
}
tc.sortNeeded.Store(false)
tc.mu.Unlock()
tc.torrents.RUnlock()
sort.Slice(all, func(i, j int) bool {
if all[i].name != all[j].name {
@@ -181,13 +195,13 @@ func (tc *torrentCache) refreshListing() {
})
}
}
tc.folderListingMu.Lock()
tc.folders.Lock()
if len(listing) > 0 {
tc.folderListing["__bad__"] = listing
tc.folders.listing["__bad__"] = listing
} else {
delete(tc.folderListing, "__bad__")
delete(tc.folders.listing, "__bad__")
}
tc.folderListingMu.Unlock()
tc.folders.Unlock()
}()
wg.Done()
@@ -207,13 +221,13 @@ func (tc *torrentCache) refreshListing() {
}
}
tc.folderListingMu.Lock()
tc.folders.Lock()
if len(matched) > 0 {
tc.folderListing[dir] = matched
tc.folders.listing[dir] = matched
} else {
delete(tc.folderListing, dir)
delete(tc.folders.listing, dir)
}
tc.folderListingMu.Unlock()
tc.folders.Unlock()
}(dir, filters)
}
@@ -264,35 +278,41 @@ func (tc *torrentCache) torrentMatchDirectory(filters []directoryFilter, file so
}
func (tc *torrentCache) getAll() map[string]CachedTorrent {
tc.mu.Lock()
defer tc.mu.Unlock()
result := make(map[string]CachedTorrent)
for name, torrent := range tc.byID {
tc.torrents.RLock()
defer tc.torrents.RUnlock()
result := make(map[string]CachedTorrent, len(tc.torrents.byID))
for name, torrent := range tc.torrents.byID {
result[name] = torrent
}
return result
}
func (tc *torrentCache) getAllCount() int {
tc.torrents.RLock()
defer tc.torrents.RUnlock()
return len(tc.torrents.byID)
}
func (tc *torrentCache) getIdMaps() map[string]struct{} {
tc.mu.Lock()
defer tc.mu.Unlock()
res := make(map[string]struct{}, len(tc.byID))
for id := range tc.byID {
tc.torrents.RLock()
defer tc.torrents.RUnlock()
res := make(map[string]struct{}, len(tc.torrents.byID))
for id := range tc.torrents.byID {
res[id] = struct{}{}
}
return res
}
func (tc *torrentCache) removeId(id string) {
tc.mu.Lock()
defer tc.mu.Unlock()
delete(tc.byID, id)
tc.torrents.Lock()
defer tc.torrents.Unlock()
delete(tc.torrents.byID, id)
tc.sortNeeded.Store(true)
}
func (tc *torrentCache) remove(name string) {
tc.mu.Lock()
defer tc.mu.Unlock()
delete(tc.byName, name)
tc.torrents.Lock()
defer tc.torrents.Unlock()
delete(tc.torrents.byName, name)
tc.sortNeeded.Store(true)
}

View File

@@ -293,6 +293,8 @@ func (r *Repair) StopJob(id string) error {
go func() {
if job.Status == JobStarted || job.Status == JobProcessing {
job.Status = JobCancelled
job.BrokenItems = nil
job.ctx = nil // Clear context to prevent further processing
job.CompletedAt = time.Now()
job.Error = "Job was cancelled by user"
r.saveToFile()

View File

@@ -110,7 +110,7 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
cache, ok := caches[debridName]
if ok {
// Get torrent data
profile.LibrarySize = len(cache.GetTorrents())
profile.LibrarySize = cache.TotalTorrents()
profile.BadTorrents = len(cache.GetListing("__bad__"))
profile.ActiveLinks = cache.GetTotalActiveDownloadLinks()