Add Download Progress tracking; early errors for invalid debrid torrent status (#35)

This commit is contained in:
Mukhtar Akere
2025-01-31 23:45:49 +01:00
committed by GitHub
parent 64995d0bf3
commit 297715bf6e
9 changed files with 81 additions and 40 deletions
+3 -3
View File
@@ -197,9 +197,7 @@ func (r *DebridLink) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er
return torrent, err return torrent, err
} }
status := torrent.Status status := torrent.Status
if status == "error" || status == "dead" || status == "magnet_error" { if status == "downloaded" {
return torrent, fmt.Errorf("torrent: %s has error", torrent.Name)
} else if status == "downloaded" {
r.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name) r.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name)
if !isSymlink { if !isSymlink {
err = r.GetDownloadLinks(torrent) err = r.GetDownloadLinks(torrent)
@@ -216,6 +214,8 @@ func (r *DebridLink) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er
// Break out of the loop if the torrent is downloading. // Break out of the loop if the torrent is downloading.
// This is necessary to prevent infinite loop since we moved to sync downloading and async processing // This is necessary to prevent infinite loop since we moved to sync downloading and async processing
break break
} else {
return torrent, fmt.Errorf("torrent: %s has error", torrent.Name)
} }
} }
+3 -3
View File
@@ -183,9 +183,7 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er
torrent.Status = status torrent.Status = status
torrent.Debrid = r torrent.Debrid = r
downloadingStatus := []string{"downloading", "magnet_conversion", "queued", "compressing", "uploading"} downloadingStatus := []string{"downloading", "magnet_conversion", "queued", "compressing", "uploading"}
if status == "error" || status == "dead" || status == "magnet_error" { if status == "waiting_files_selection" {
return torrent, fmt.Errorf("torrent: %s has error: %s", torrent.Name, status)
} else if status == "waiting_files_selection" {
files := GetTorrentFiles(data) files := GetTorrentFiles(data)
torrent.Files = files torrent.Files = files
if len(files) == 0 { if len(files) == 0 {
@@ -222,6 +220,8 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er
// Break out of the loop if the torrent is downloading. // Break out of the loop if the torrent is downloading.
// This is necessary to prevent infinite loop since we moved to sync downloading and async processing // This is necessary to prevent infinite loop since we moved to sync downloading and async processing
break break
} else {
return torrent, fmt.Errorf("torrent: %s has error: %s", torrent.Name, status)
} }
} }
+3 -3
View File
@@ -206,9 +206,7 @@ func (r *Torbox) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error)
return tb, err return tb, err
} }
status := torrent.Status status := torrent.Status
if status == "error" || status == "dead" || status == "magnet_error" { if status == "downloaded" {
return torrent, fmt.Errorf("torrent: %s has error", torrent.Name)
} else if status == "downloaded" {
r.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name) r.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name)
if !isSymlink { if !isSymlink {
err = r.GetDownloadLinks(torrent) err = r.GetDownloadLinks(torrent)
@@ -225,6 +223,8 @@ func (r *Torbox) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error)
// Break out of the loop if the torrent is downloading. // Break out of the loop if the torrent is downloading.
// This is necessary to prevent infinite loop since we moved to sync downloading and async processing // This is necessary to prevent infinite loop since we moved to sync downloading and async processing
break break
} else {
return torrent, fmt.Errorf("torrent: %s has error", torrent.Name)
} }
} }
+5 -2
View File
@@ -6,6 +6,7 @@ import (
"github.com/sirrobot01/debrid-blackhole/pkg/arr" "github.com/sirrobot01/debrid-blackhole/pkg/arr"
"os" "os"
"path/filepath" "path/filepath"
"sync"
) )
type Arr struct { type Arr struct {
@@ -45,8 +46,10 @@ type Torrent struct {
Links []string `json:"links"` Links []string `json:"links"`
DownloadLinks []TorrentDownloadLinks `json:"download_links"` DownloadLinks []TorrentDownloadLinks `json:"download_links"`
Debrid Service Debrid Service `json:"-"`
Arr *arr.Arr Arr *arr.Arr `json:"arr"`
Mu sync.Mutex `json:"-"`
SizeDownloaded int64 `json:"-"` // This is used for local download
} }
type TorrentDownloadLinks struct { type TorrentDownloadLinks struct {
+17 -16
View File
@@ -2,7 +2,6 @@ package downloaders
import ( import (
"crypto/tls" "crypto/tls"
"fmt"
"github.com/cavaliergopher/grab/v3" "github.com/cavaliergopher/grab/v3"
"net/http" "net/http"
"time" "time"
@@ -21,35 +20,37 @@ func GetGrabClient() *grab.Client {
} }
} }
func NormalGrab(client *grab.Client, url, filename string) error { func NormalGrab(client *grab.Client, url, filename string, progressCallback func(int64)) error {
req, err := grab.NewRequest(filename, url) req, err := grab.NewRequest(filename, url)
if err != nil { if err != nil {
return err return err
} }
resp := client.Do(req) resp := client.Do(req)
if err := resp.Err(); err != nil {
return err
}
t := time.NewTicker(2 * time.Second) t := time.NewTicker(time.Second)
defer t.Stop() defer t.Stop()
var lastReported int64
Loop: Loop:
for { for {
select { select {
case <-t.C: case <-t.C:
fmt.Printf(" %s: transferred %d / %d bytes (%.2f%%)", current := resp.BytesComplete()
resp.Filename, if current != lastReported {
resp.BytesComplete(), if progressCallback != nil {
resp.Size(), progressCallback(current - lastReported)
100*resp.Progress()) }
lastReported = current
}
case <-resp.Done: case <-resp.Done:
// download is complete
break Loop break Loop
} }
} }
if err := resp.Err(); err != nil {
return err // Report final bytes
if progressCallback != nil {
progressCallback(resp.BytesComplete() - lastReported)
} }
return nil
return resp.Err()
} }
-2
View File
@@ -96,8 +96,6 @@
} else { } else {
alert(`Successfully added ${result.results.length} torrents!`); alert(`Successfully added ${result.results.length} torrents!`);
} }
document.getElementById('magnetURI').value = '';
} catch (error) { } catch (error) {
alert(`Error adding downloads: ${error.message}`); alert(`Error adding downloads: ${error.message}`);
} finally { } finally {
+42 -8
View File
@@ -11,7 +11,8 @@ import (
"time" "time"
) )
func (q *QBit) processManualFiles(debridTorrent *debrid.Torrent) (string, error) { func (q *QBit) ProcessManualFile(torrent *Torrent) (string, error) {
debridTorrent := torrent.DebridTorrent
q.logger.Info().Msgf("Downloading %d files...", len(debridTorrent.DownloadLinks)) q.logger.Info().Msgf("Downloading %d files...", len(debridTorrent.DownloadLinks))
torrentPath := common.RemoveExtension(debridTorrent.OriginalFilename) torrentPath := common.RemoveExtension(debridTorrent.OriginalFilename)
parent := common.RemoveInvalidChars(filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentPath)) parent := common.RemoveInvalidChars(filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentPath))
@@ -20,14 +21,38 @@ func (q *QBit) processManualFiles(debridTorrent *debrid.Torrent) (string, error)
// add previous error to the error and return // add previous error to the error and return
return "", fmt.Errorf("failed to create directory: %s: %v", parent, err) return "", fmt.Errorf("failed to create directory: %s: %v", parent, err)
} }
q.downloadFiles(debridTorrent, parent) q.downloadFiles(torrent, parent)
return torrentPath, nil return torrentPath, nil
} }
func (q *QBit) downloadFiles(debridTorrent *debrid.Torrent, parent string) { func (q *QBit) downloadFiles(torrent *Torrent, parent string) {
debridTorrent := torrent.DebridTorrent
var wg sync.WaitGroup var wg sync.WaitGroup
semaphore := make(chan struct{}, 5) semaphore := make(chan struct{}, 5)
client := downloaders.GetHTTPClient() totalSize := int64(0)
for _, file := range debridTorrent.Files {
totalSize += file.Size
}
debridTorrent.Mu.Lock()
debridTorrent.SizeDownloaded = 0 // Reset downloaded bytes
debridTorrent.Progress = 0 // Reset progress
debridTorrent.Mu.Unlock()
client := downloaders.GetGrabClient()
progressCallback := func(downloaded int64) {
debridTorrent.Mu.Lock()
defer debridTorrent.Mu.Unlock()
torrent.Mu.Lock()
defer torrent.Mu.Unlock()
// Update total downloaded bytes
debridTorrent.SizeDownloaded += downloaded
// Calculate overall progress
if totalSize > 0 {
debridTorrent.Progress = float64(debridTorrent.SizeDownloaded) / float64(totalSize) * 100
}
q.UpdateTorrentMin(torrent, debridTorrent)
}
for _, link := range debridTorrent.DownloadLinks { for _, link := range debridTorrent.DownloadLinks {
if link.DownloadLink == "" { if link.DownloadLink == "" {
q.logger.Info().Msgf("No download link found for %s", link.Filename) q.logger.Info().Msgf("No download link found for %s", link.Filename)
@@ -38,11 +63,19 @@ func (q *QBit) downloadFiles(debridTorrent *debrid.Torrent, parent string) {
go func(link debrid.TorrentDownloadLinks) { go func(link debrid.TorrentDownloadLinks) {
defer wg.Done() defer wg.Done()
defer func() { <-semaphore }() defer func() { <-semaphore }()
err := downloaders.NormalHTTP(client, link.DownloadLink, filepath.Join(parent, link.Filename)) filename := link.Filename
err := downloaders.NormalGrab(
client,
link.DownloadLink,
filepath.Join(parent, filename),
progressCallback,
)
if err != nil { if err != nil {
q.logger.Info().Msgf("Error downloading %s: %v", link.DownloadLink, err) q.logger.Error().Msgf("Failed to download %s: %v", filename, err)
} else { } else {
q.logger.Info().Msgf("Downloaded %s successfully", link.DownloadLink) q.logger.Info().Msgf("Downloaded %s", filename)
} }
}(link) }(link)
} }
@@ -50,7 +83,8 @@ func (q *QBit) downloadFiles(debridTorrent *debrid.Torrent, parent string) {
q.logger.Info().Msgf("Downloaded all files for %s", debridTorrent.Name) q.logger.Info().Msgf("Downloaded all files for %s", debridTorrent.Name)
} }
func (q *QBit) ProcessSymlink(debridTorrent *debrid.Torrent) (string, error) { func (q *QBit) ProcessSymlink(torrent *Torrent) (string, error) {
debridTorrent := torrent.DebridTorrent
var wg sync.WaitGroup var wg sync.WaitGroup
files := debridTorrent.Files files := debridTorrent.Files
ready := make(chan debrid.TorrentFile, len(files)) ready := make(chan debrid.TorrentFile, len(files))
+6 -1
View File
@@ -1,6 +1,9 @@
package shared package shared
import "github.com/sirrobot01/debrid-blackhole/pkg/debrid" import (
"github.com/sirrobot01/debrid-blackhole/pkg/debrid"
"sync"
)
type BuildInfo struct { type BuildInfo struct {
Libtorrent string `json:"libtorrent"` Libtorrent string `json:"libtorrent"`
@@ -219,6 +222,8 @@ type Torrent struct {
UploadedSession int64 `json:"uploaded_session,omitempty"` UploadedSession int64 `json:"uploaded_session,omitempty"`
Upspeed int `json:"upspeed,omitempty"` Upspeed int `json:"upspeed,omitempty"`
Source string `json:"source,omitempty"` Source string `json:"source,omitempty"`
Mu sync.Mutex `json:"-"`
} }
func (t *Torrent) IsReady() bool { func (t *Torrent) IsReady() bool {
+2 -2
View File
@@ -112,9 +112,9 @@ func (q *QBit) ProcessFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr
) )
debridTorrent.Arr = arr debridTorrent.Arr = arr
if isSymlink { if isSymlink {
torrentPath, err = q.ProcessSymlink(debridTorrent) torrentPath, err = q.ProcessSymlink(torrent)
} else { } else {
torrentPath, err = q.processManualFiles(debridTorrent) torrentPath, err = q.ProcessManualFile(torrent)
} }
if err != nil { if err != nil {
q.MarkAsFailed(torrent) q.MarkAsFailed(torrent)