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() return c.torrents.getAll()
} }
func (c *Cache) TotalTorrents() int {
return c.torrents.getAllCount()
}
func (c *Cache) GetTorrentByName(name string) *CachedTorrent { func (c *Cache) GetTorrentByName(name string) *CachedTorrent {
if torrent, ok := c.torrents.getByName(name); ok { if torrent, ok := c.torrents.getByName(name); ok {
return &torrent return &torrent

View File

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

View File

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

View File

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

View File

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