package torrent import ( "log" "strings" "sync" "github.com/debridmediamanager.com/zurg/pkg/realdebrid" ) type TorrentManager struct { token string torrents []Torrent workerPool chan bool } // NewTorrentManager creates a new torrent manager // it will fetch all torrents and their info in the background // and store them in-memory func NewTorrentManager(token string) *TorrentManager { handler := &TorrentManager{ token: token, workerPool: make(chan bool, 10), } handler.torrents = handler.getAll() for _, torrent := range handler.torrents { go func(id string) { handler.workerPool <- true handler.getInfo(id) // sleep for 1 second to avoid rate limiting <-handler.workerPool }(torrent.ID) } return handler } func (t *TorrentManager) getAll() []Torrent { torrents, err := realdebrid.GetTorrents(t.token) if err != nil { log.Printf("Cannot get torrents: %v\n", err.Error()) return nil } var torrentsV2 []Torrent for _, torrent := range torrents { torrentsV2 = append(torrentsV2, Torrent{ Torrent: torrent, SelectedFiles: nil, }) } return torrentsV2 } func (t *TorrentManager) GetAll() []Torrent { return t.torrents } func (t *TorrentManager) getInfo(torrentID string) *Torrent { info, err := realdebrid.GetTorrentInfo(t.token, torrentID) if err != nil { log.Printf("Cannot get info: %v\n", err.Error()) return nil } var selectedFiles []File for _, file := range info.Files { if file.Selected == 0 { continue } selectedFiles = append(selectedFiles, File{ File: file, Link: "", }) } if len(selectedFiles) != len(info.Links) { type Result struct { Filename string Link string } resultsChan := make(chan Result, len(info.Links)) var wg sync.WaitGroup // Limit concurrency sem := make(chan struct{}, 10) // e.g., 10 concurrent requests for _, link := range info.Links { wg.Add(1) sem <- struct{}{} // Acquire semaphore go func(lnk string) { defer wg.Done() defer func() { <-sem }() // Release semaphore unrestrictFn := func() (*realdebrid.UnrestrictResponse, error) { return realdebrid.UnrestrictLink(t.token, lnk) } resp := realdebrid.RetryUntilOk(unrestrictFn) if resp != nil { resultsChan <- Result{Filename: resp.Filename, Link: resp.Link} } }(link) } go func() { wg.Wait() close(resultsChan) }() for result := range resultsChan { for i := range selectedFiles { if strings.HasSuffix(selectedFiles[i].Path, result.Filename) { selectedFiles[i].Link = result.Link } } } } else { for i, link := range info.Links { selectedFiles[i].Link = link } } torrent := t.getByID(torrentID) if torrent != nil { torrent.SelectedFiles = selectedFiles } log.Println("Fetched info for", info.Filename) return torrent } func (t *TorrentManager) GetInfo(torrentID string) *Torrent { for _, torrent := range t.torrents { if torrent.ID == torrentID { if torrent.SelectedFiles != nil { return t.getInfo(torrentID) } } } return nil } func (t *TorrentManager) getByID(torrentID string) *Torrent { for _, torrent := range t.torrents { if torrent.ID == torrentID { return &torrent } } return nil }