Add Download Progress tracking; early errors for invalid debrid torrent status (#35)
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user