- Speed up repairs when checking links \n

- Remove run on start for repairs since it causes issues \n
- Add support for arr-specific debrid
- Support for queuing system
- Support for no-op when sending torrents to debrid
This commit is contained in:
Mukhtar Akere
2025-06-14 16:09:28 +01:00
parent 3efda45304
commit a539aa53bd
28 changed files with 428 additions and 355 deletions

View File

@@ -158,7 +158,7 @@ func createDebridClient(dc config.Debrid) (types.Client, error) {
}
}
func Process(ctx context.Context, store *Storage, selectedDebrid string, magnet *utils.Magnet, a *arr.Arr, isSymlink, overrideDownloadUncached bool) (*types.Torrent, error) {
func Process(ctx context.Context, store *Storage, selectedDebrid string, magnet *utils.Magnet, a *arr.Arr, action string, overrideDownloadUncached bool) (*types.Torrent, error) {
debridTorrent := &types.Torrent{
InfoHash: magnet.InfoHash,
@@ -200,6 +200,7 @@ func Process(ctx context.Context, store *Storage, selectedDebrid string, magnet
Str("Arr", a.Name).
Str("Hash", debridTorrent.InfoHash).
Str("Name", debridTorrent.Name).
Str("Action", action).
Msg("Processing torrent")
if !overrideDownloadUncached && a.DownloadUncached == nil {
@@ -215,7 +216,7 @@ func Process(ctx context.Context, store *Storage, selectedDebrid string, magnet
_logger.Info().Str("id", dbt.Id).Msgf("Torrent: %s submitted to %s", dbt.Name, db.Name())
store.lastUsed = index
torrent, err := db.CheckStatus(dbt, isSymlink)
torrent, err := db.CheckStatus(dbt)
if err != nil && torrent != nil && torrent.Id != "" {
// Delete the torrent if it was not downloaded
go func(id string) {

View File

@@ -259,7 +259,7 @@ func (ad *AllDebrid) UpdateTorrent(t *types.Torrent) error {
return nil
}
func (ad *AllDebrid) CheckStatus(torrent *types.Torrent, isSymlink bool) (*types.Torrent, error) {
func (ad *AllDebrid) CheckStatus(torrent *types.Torrent) (*types.Torrent, error) {
for {
err := ad.UpdateTorrent(torrent)
@@ -269,13 +269,7 @@ func (ad *AllDebrid) CheckStatus(torrent *types.Torrent, isSymlink bool) (*types
status := torrent.Status
if status == "downloaded" {
ad.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name)
if !isSymlink {
if err = ad.GetFileDownloadLinks(torrent); err != nil {
return torrent, err
}
}
break
return torrent, nil
} else if utils.Contains(ad.GetDownloadingStatus(), status) {
if !torrent.DownloadUncached {
return torrent, fmt.Errorf("torrent: %s not cached", torrent.Name)
@@ -288,7 +282,6 @@ func (ad *AllDebrid) CheckStatus(torrent *types.Torrent, isSymlink bool) (*types
}
}
return torrent, nil
}
func (ad *AllDebrid) DeleteTorrent(torrentId string) error {

View File

@@ -316,7 +316,7 @@ func (dl *DebridLink) SubmitMagnet(t *types.Torrent) (*types.Torrent, error) {
return t, nil
}
func (dl *DebridLink) CheckStatus(torrent *types.Torrent, isSymlink bool) (*types.Torrent, error) {
func (dl *DebridLink) CheckStatus(torrent *types.Torrent) (*types.Torrent, error) {
for {
err := dl.UpdateTorrent(torrent)
if err != nil || torrent == nil {
@@ -325,11 +325,7 @@ func (dl *DebridLink) CheckStatus(torrent *types.Torrent, isSymlink bool) (*type
status := torrent.Status
if status == "downloaded" {
dl.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name)
if err = dl.GetFileDownloadLinks(torrent); err != nil {
return torrent, err
}
break
return torrent, nil
} else if utils.Contains(dl.GetDownloadingStatus(), status) {
if !torrent.DownloadUncached {
return torrent, fmt.Errorf("torrent: %s not cached", torrent.Name)
@@ -342,7 +338,6 @@ func (dl *DebridLink) CheckStatus(torrent *types.Torrent, isSymlink bool) (*type
}
}
return torrent, nil
}
func (dl *DebridLink) DeleteTorrent(torrentId string) error {

View File

@@ -0,0 +1 @@
package realdebrid

View File

@@ -468,7 +468,7 @@ func (r *RealDebrid) UpdateTorrent(t *types.Torrent) error {
return nil
}
func (r *RealDebrid) CheckStatus(t *types.Torrent, isSymlink bool) (*types.Torrent, error) {
func (r *RealDebrid) CheckStatus(t *types.Torrent) (*types.Torrent, error) {
url := fmt.Sprintf("%s/torrents/info/%s", r.Host, t.Id)
req, _ := http.NewRequest(http.MethodGet, url, nil)
for {
@@ -525,12 +525,7 @@ func (r *RealDebrid) CheckStatus(t *types.Torrent, isSymlink bool) (*types.Torre
}
r.logger.Info().Msgf("Torrent: %s downloaded to RD", t.Name)
if !isSymlink {
if err = r.GetFileDownloadLinks(t); err != nil {
return t, err
}
}
break
return t, nil
} else if utils.Contains(r.GetDownloadingStatus(), status) {
if !t.DownloadUncached {
return t, fmt.Errorf("torrent: %s not cached", t.Name)
@@ -541,7 +536,6 @@ func (r *RealDebrid) CheckStatus(t *types.Torrent, isSymlink bool) (*types.Torre
}
}
return t, nil
}
func (r *RealDebrid) DeleteTorrent(torrentId string) error {
@@ -555,63 +549,55 @@ func (r *RealDebrid) DeleteTorrent(torrentId string) error {
}
func (r *RealDebrid) GetFileDownloadLinks(t *types.Torrent) error {
filesCh := make(chan types.File, len(t.Files))
errCh := make(chan error, len(t.Files))
linksCh := make(chan *types.DownloadLink)
var wg sync.WaitGroup
wg.Add(len(t.Files))
for _, f := range t.Files {
var mu sync.Mutex
var firstErr error
files := make(map[string]types.File)
links := make(map[string]*types.DownloadLink)
_files := t.GetFiles()
wg.Add(len(_files))
for _, f := range _files {
go func(file types.File) {
defer wg.Done()
link, err := r.GetDownloadLink(t, &file)
if err != nil {
errCh <- err
mu.Lock()
if firstErr == nil {
firstErr = err
}
mu.Unlock()
return
}
if link == nil {
errCh <- fmt.Errorf("realdebrid API error: download link not found for file %s", file.Name)
mu.Lock()
if firstErr == nil {
firstErr = fmt.Errorf("realdebrid API error: download link not found for file %s", file.Name)
}
mu.Unlock()
return
}
linksCh <- link
file.DownloadLink = link
filesCh <- file
mu.Lock()
files[file.Name] = file
links[link.Link] = link
mu.Unlock()
}(f)
}
go func() {
wg.Wait()
close(filesCh)
close(linksCh)
close(errCh)
}()
wg.Wait()
// Collect results
files := make(map[string]types.File, len(t.Files))
for file := range filesCh {
files[file.Name] = file
}
// Collect download links
links := make(map[string]*types.DownloadLink)
for link := range linksCh {
if link == nil {
continue
}
links[link.Link] = link
if firstErr != nil {
return firstErr
}
// Add links to cache
r.accounts.SetDownloadLinks(links)
// Check for errors
for err := range errCh {
if err != nil {
return err // Return the first error encountered
}
}
t.Files = files
return nil
}

View File

@@ -312,7 +312,7 @@ func (tb *Torbox) UpdateTorrent(t *types.Torrent) error {
return nil
}
func (tb *Torbox) CheckStatus(torrent *types.Torrent, isSymlink bool) (*types.Torrent, error) {
func (tb *Torbox) CheckStatus(torrent *types.Torrent) (*types.Torrent, error) {
for {
err := tb.UpdateTorrent(torrent)
@@ -322,12 +322,7 @@ func (tb *Torbox) CheckStatus(torrent *types.Torrent, isSymlink bool) (*types.To
status := torrent.Status
if status == "downloaded" {
tb.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name)
if !isSymlink {
if err = tb.GetFileDownloadLinks(torrent); err != nil {
return torrent, err
}
}
break
return torrent, nil
} else if utils.Contains(tb.GetDownloadingStatus(), status) {
if !torrent.DownloadUncached {
return torrent, fmt.Errorf("torrent: %s not cached", torrent.Name)
@@ -340,7 +335,6 @@ func (tb *Torbox) CheckStatus(torrent *types.Torrent, isSymlink bool) (*types.To
}
}
return torrent, nil
}
func (tb *Torbox) DeleteTorrent(torrentId string) error {

View File

@@ -34,8 +34,6 @@ func (c *Cache) GetDownloadLink(torrentName, filename, fileLink string) (string,
// Check link cache
if dl, err := c.checkDownloadLink(fileLink); dl != "" && err == nil {
return dl, nil
} else {
c.logger.Trace().Msgf("Download link check failed: %v", err)
}
if req, inFlight := c.downloadLinkRequests.Load(fileLink); inFlight {
@@ -54,6 +52,13 @@ func (c *Cache) GetDownloadLink(torrentName, filename, fileLink string) (string,
c.downloadLinkRequests.Delete(fileLink)
return "", err
}
if dl == nil || dl.DownloadLink == "" {
err = fmt.Errorf("download link is empty for %s in torrent %s", filename, torrentName)
req.Complete("", err)
c.downloadLinkRequests.Delete(fileLink)
return "", err
}
req.Complete(dl.DownloadLink, err)
c.downloadLinkRequests.Delete(fileLink)
return dl.DownloadLink, err

View File

@@ -90,18 +90,25 @@ func (c *Cache) GetBrokenFiles(t *CachedTorrent, filenames []string) []string {
files = t.Files
var wg sync.WaitGroup
wg.Add(len(files))
for _, f := range files {
// Check if file link is still missing
if f.Link == "" {
brokenFiles = append(brokenFiles, f.Name)
} else {
// Check if file.Link not in the downloadLink Cache
if err := c.client.CheckLink(f.Link); err != nil {
if errors.Is(err, utils.HosterUnavailableError) {
brokenFiles = append(brokenFiles, f.Name)
go func(f types.File) {
defer wg.Done()
if f.Link == "" {
brokenFiles = append(brokenFiles, f.Name)
} else {
// Check if file.Link not in the downloadLink Cache
if err := c.client.CheckLink(f.Link); err != nil {
if errors.Is(err, utils.HosterUnavailableError) {
brokenFiles = append(brokenFiles, f.Name)
}
}
}
}
}(f)
}
// Try to reinsert the torrent if it's broken
@@ -202,7 +209,7 @@ func (c *Cache) reInsertTorrent(ct *CachedTorrent) (*CachedTorrent, error) {
return ct, fmt.Errorf("failed to submit magnet: empty torrent")
}
newTorrent.DownloadUncached = false // Set to false, avoid re-downloading
newTorrent, err = c.client.CheckStatus(newTorrent, true)
newTorrent, err = c.client.CheckStatus(newTorrent)
if err != nil {
if newTorrent != nil && newTorrent.Id != "" {
// Delete the torrent if it was not downloaded

View File

@@ -6,7 +6,7 @@ import (
type Client interface {
SubmitMagnet(tr *Torrent) (*Torrent, error)
CheckStatus(tr *Torrent, isSymlink bool) (*Torrent, error)
CheckStatus(tr *Torrent) (*Torrent, error)
GetFileDownloadLinks(tr *Torrent) error
GetDownloadLink(tr *Torrent, file *File) (*DownloadLink, error)
DeleteTorrent(torrentId string) error