- IMplement multi-download api tokens
- Move things around a bit
This commit is contained in:
@@ -277,9 +277,15 @@ func (c *Config) updateDebrid(d Debrid) Debrid {
|
|||||||
workers := runtime.NumCPU() * 50
|
workers := runtime.NumCPU() * 50
|
||||||
perDebrid := workers / len(c.Debrids)
|
perDebrid := workers / len(c.Debrids)
|
||||||
|
|
||||||
if len(d.DownloadAPIKeys) == 0 {
|
var downloadKeys []string
|
||||||
d.DownloadAPIKeys = append(d.DownloadAPIKeys, d.APIKey)
|
|
||||||
|
if len(d.DownloadAPIKeys) > 0 {
|
||||||
|
downloadKeys = d.DownloadAPIKeys
|
||||||
|
} else {
|
||||||
|
// If no download API keys are specified, use the main API key
|
||||||
|
downloadKeys = []string{d.APIKey}
|
||||||
}
|
}
|
||||||
|
d.DownloadAPIKeys = downloadKeys
|
||||||
|
|
||||||
if !d.UseWebDav {
|
if !d.UseWebDav {
|
||||||
return d
|
return d
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ type AllDebrid struct {
|
|||||||
name string
|
name string
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
APIKey string
|
APIKey string
|
||||||
accounts map[string]types.Account
|
accounts *types.Accounts
|
||||||
|
autoExpiresLinksAfter time.Duration
|
||||||
DownloadUncached bool
|
DownloadUncached bool
|
||||||
client *request.Client
|
client *request.Client
|
||||||
|
|
||||||
@@ -50,21 +51,17 @@ func New(dc config.Debrid) (*AllDebrid, error) {
|
|||||||
request.WithProxy(dc.Proxy),
|
request.WithProxy(dc.Proxy),
|
||||||
)
|
)
|
||||||
|
|
||||||
accounts := make(map[string]types.Account)
|
autoExpiresLinksAfter, err := time.ParseDuration(dc.AutoExpireLinksAfter)
|
||||||
for idx, key := range dc.DownloadAPIKeys {
|
if autoExpiresLinksAfter == 0 || err != nil {
|
||||||
id := strconv.Itoa(idx)
|
autoExpiresLinksAfter = 48 * time.Hour
|
||||||
accounts[id] = types.Account{
|
|
||||||
Name: key,
|
|
||||||
ID: id,
|
|
||||||
Token: key,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return &AllDebrid{
|
return &AllDebrid{
|
||||||
name: "alldebrid",
|
name: "alldebrid",
|
||||||
Host: "http://api.alldebrid.com/v4.1",
|
Host: "http://api.alldebrid.com/v4.1",
|
||||||
APIKey: dc.APIKey,
|
APIKey: dc.APIKey,
|
||||||
accounts: accounts,
|
accounts: types.NewAccounts(dc),
|
||||||
DownloadUncached: dc.DownloadUncached,
|
DownloadUncached: dc.DownloadUncached,
|
||||||
|
autoExpiresLinksAfter: autoExpiresLinksAfter,
|
||||||
client: client,
|
client: client,
|
||||||
MountPath: dc.Folder,
|
MountPath: dc.Folder,
|
||||||
logger: logger.New(dc.Name),
|
logger: logger.New(dc.Name),
|
||||||
@@ -273,8 +270,8 @@ func (ad *AllDebrid) CheckStatus(torrent *types.Torrent, isSymlink bool) (*types
|
|||||||
if status == "downloaded" {
|
if status == "downloaded" {
|
||||||
ad.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name)
|
ad.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name)
|
||||||
if !isSymlink {
|
if !isSymlink {
|
||||||
err = ad.GenerateDownloadLinks(torrent)
|
|
||||||
if err != nil {
|
if err = ad.GetFileDownloadLinks(torrent); err != nil {
|
||||||
return torrent, err
|
return torrent, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,8 +301,9 @@ func (ad *AllDebrid) DeleteTorrent(torrentId string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AllDebrid) GenerateDownloadLinks(t *types.Torrent) error {
|
func (ad *AllDebrid) GetFileDownloadLinks(t *types.Torrent) error {
|
||||||
filesCh := make(chan types.File, len(t.Files))
|
filesCh := make(chan types.File, len(t.Files))
|
||||||
|
linksCh := make(chan *types.DownloadLink, len(t.Files))
|
||||||
errCh := make(chan error, len(t.Files))
|
errCh := make(chan error, len(t.Files))
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@@ -318,17 +316,19 @@ func (ad *AllDebrid) GenerateDownloadLinks(t *types.Torrent) error {
|
|||||||
errCh <- err
|
errCh <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
file.DownloadLink = link
|
|
||||||
if link != nil {
|
if link != nil {
|
||||||
errCh <- fmt.Errorf("download link is empty")
|
errCh <- fmt.Errorf("download link is empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
linksCh <- link
|
||||||
|
file.DownloadLink = link
|
||||||
filesCh <- file
|
filesCh <- file
|
||||||
}(file)
|
}(file)
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(filesCh)
|
close(filesCh)
|
||||||
|
close(linksCh)
|
||||||
close(errCh)
|
close(errCh)
|
||||||
}()
|
}()
|
||||||
files := make(map[string]types.File, len(t.Files))
|
files := make(map[string]types.File, len(t.Files))
|
||||||
@@ -336,10 +336,22 @@ func (ad *AllDebrid) GenerateDownloadLinks(t *types.Torrent) error {
|
|||||||
files[file.Name] = file
|
files[file.Name] = file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect download links
|
||||||
|
links := make(map[string]*types.DownloadLink, len(t.Files))
|
||||||
|
|
||||||
|
for link := range linksCh {
|
||||||
|
if link == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
links[link.Link] = link
|
||||||
|
}
|
||||||
|
// Update the files with download links
|
||||||
|
ad.accounts.SetDownloadLinks(links)
|
||||||
|
|
||||||
// Check for errors
|
// Check for errors
|
||||||
for err := range errCh {
|
for err := range errCh {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // Return the first error encountered
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,21 +381,18 @@ func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
|
|||||||
if link == "" {
|
if link == "" {
|
||||||
return nil, fmt.Errorf("download link is empty")
|
return nil, fmt.Errorf("download link is empty")
|
||||||
}
|
}
|
||||||
|
now := time.Now()
|
||||||
return &types.DownloadLink{
|
return &types.DownloadLink{
|
||||||
Link: file.Link,
|
Link: file.Link,
|
||||||
DownloadLink: link,
|
DownloadLink: link,
|
||||||
Id: data.Data.Id,
|
Id: data.Data.Id,
|
||||||
Size: file.Size,
|
Size: file.Size,
|
||||||
Filename: file.Name,
|
Filename: file.Name,
|
||||||
Generated: time.Now(),
|
Generated: now,
|
||||||
AccountId: "0",
|
ExpiresAt: now.Add(ad.autoExpiresLinksAfter),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AllDebrid) GetCheckCached() bool {
|
|
||||||
return ad.checkCached
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) {
|
func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) {
|
||||||
url := fmt.Sprintf("%s/magnet/status?status=ready", ad.Host)
|
url := fmt.Sprintf("%s/magnet/status?status=ready", ad.Host)
|
||||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||||
@@ -417,7 +426,7 @@ func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) {
|
|||||||
return torrents, nil
|
return torrents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AllDebrid) GetDownloads() (map[string]types.DownloadLink, error) {
|
func (ad *AllDebrid) GetDownloadLinks() (map[string]*types.DownloadLink, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,12 +446,6 @@ func (ad *AllDebrid) GetMountPath() string {
|
|||||||
return ad.MountPath
|
return ad.MountPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AllDebrid) DisableAccount(accountId string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ad *AllDebrid) ResetActiveDownloadKeys() {
|
|
||||||
|
|
||||||
}
|
|
||||||
func (ad *AllDebrid) DeleteDownloadLink(linkId string) error {
|
func (ad *AllDebrid) DeleteDownloadLink(linkId string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -452,3 +455,7 @@ func (ad *AllDebrid) GetAvailableSlots() (int, error) {
|
|||||||
//TODO: Implement the logic to check available slots for AllDebrid
|
//TODO: Implement the logic to check available slots for AllDebrid
|
||||||
return 0, fmt.Errorf("GetAvailableSlots not implemented for AllDebrid")
|
return 0, fmt.Errorf("GetAvailableSlots not implemented for AllDebrid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ad *AllDebrid) Accounts() *types.Accounts {
|
||||||
|
return ad.accounts
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/sirrobot01/decypharr/internal/request"
|
"github.com/sirrobot01/decypharr/internal/request"
|
||||||
"github.com/sirrobot01/decypharr/internal/utils"
|
"github.com/sirrobot01/decypharr/internal/utils"
|
||||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -21,10 +20,12 @@ type DebridLink struct {
|
|||||||
name string
|
name string
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
APIKey string
|
APIKey string
|
||||||
accounts map[string]types.Account
|
accounts *types.Accounts
|
||||||
DownloadUncached bool
|
DownloadUncached bool
|
||||||
client *request.Client
|
client *request.Client
|
||||||
|
|
||||||
|
autoExpiresLinksAfter time.Duration
|
||||||
|
|
||||||
MountPath string
|
MountPath string
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
checkCached bool
|
checkCached bool
|
||||||
@@ -46,21 +47,17 @@ func New(dc config.Debrid) (*DebridLink, error) {
|
|||||||
request.WithProxy(dc.Proxy),
|
request.WithProxy(dc.Proxy),
|
||||||
)
|
)
|
||||||
|
|
||||||
accounts := make(map[string]types.Account)
|
autoExpiresLinksAfter, err := time.ParseDuration(dc.AutoExpireLinksAfter)
|
||||||
for idx, key := range dc.DownloadAPIKeys {
|
if autoExpiresLinksAfter == 0 || err != nil {
|
||||||
id := strconv.Itoa(idx)
|
autoExpiresLinksAfter = 48 * time.Hour
|
||||||
accounts[id] = types.Account{
|
|
||||||
Name: key,
|
|
||||||
ID: id,
|
|
||||||
Token: key,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return &DebridLink{
|
return &DebridLink{
|
||||||
name: "debridlink",
|
name: "debridlink",
|
||||||
Host: "https://debrid-link.com/api/v2",
|
Host: "https://debrid-link.com/api/v2",
|
||||||
APIKey: dc.APIKey,
|
APIKey: dc.APIKey,
|
||||||
accounts: accounts,
|
accounts: types.NewAccounts(dc),
|
||||||
DownloadUncached: dc.DownloadUncached,
|
DownloadUncached: dc.DownloadUncached,
|
||||||
|
autoExpiresLinksAfter: autoExpiresLinksAfter,
|
||||||
client: client,
|
client: client,
|
||||||
MountPath: dc.Folder,
|
MountPath: dc.Folder,
|
||||||
logger: logger.New(dc.Name),
|
logger: logger.New(dc.Name),
|
||||||
@@ -177,13 +174,6 @@ func (dl *DebridLink) GetTorrent(torrentId string) (*types.Torrent, error) {
|
|||||||
Name: f.Name,
|
Name: f.Name,
|
||||||
Size: f.Size,
|
Size: f.Size,
|
||||||
Path: f.Name,
|
Path: f.Name,
|
||||||
DownloadLink: &types.DownloadLink{
|
|
||||||
Filename: f.Name,
|
|
||||||
Link: f.DownloadURL,
|
|
||||||
DownloadLink: f.DownloadURL,
|
|
||||||
Generated: time.Now(),
|
|
||||||
AccountId: "0",
|
|
||||||
},
|
|
||||||
Link: f.DownloadURL,
|
Link: f.DownloadURL,
|
||||||
}
|
}
|
||||||
torrent.Files[file.Name] = file
|
torrent.Files[file.Name] = file
|
||||||
@@ -233,6 +223,8 @@ func (dl *DebridLink) UpdateTorrent(t *types.Torrent) error {
|
|||||||
t.OriginalFilename = name
|
t.OriginalFilename = name
|
||||||
t.Added = time.Unix(data.Created, 0).Format(time.RFC3339)
|
t.Added = time.Unix(data.Created, 0).Format(time.RFC3339)
|
||||||
cfg := config.Get()
|
cfg := config.Get()
|
||||||
|
links := make(map[string]*types.DownloadLink)
|
||||||
|
now := time.Now()
|
||||||
for _, f := range data.Files {
|
for _, f := range data.Files {
|
||||||
if !cfg.IsSizeAllowed(f.Size) {
|
if !cfg.IsSizeAllowed(f.Size) {
|
||||||
continue
|
continue
|
||||||
@@ -243,17 +235,21 @@ func (dl *DebridLink) UpdateTorrent(t *types.Torrent) error {
|
|||||||
Name: f.Name,
|
Name: f.Name,
|
||||||
Size: f.Size,
|
Size: f.Size,
|
||||||
Path: f.Name,
|
Path: f.Name,
|
||||||
DownloadLink: &types.DownloadLink{
|
Link: f.DownloadURL,
|
||||||
|
}
|
||||||
|
link := &types.DownloadLink{
|
||||||
Filename: f.Name,
|
Filename: f.Name,
|
||||||
Link: f.DownloadURL,
|
Link: f.DownloadURL,
|
||||||
DownloadLink: f.DownloadURL,
|
DownloadLink: f.DownloadURL,
|
||||||
Generated: time.Now(),
|
Generated: now,
|
||||||
AccountId: "0",
|
ExpiresAt: now.Add(dl.autoExpiresLinksAfter),
|
||||||
},
|
|
||||||
Link: f.DownloadURL,
|
|
||||||
}
|
}
|
||||||
|
links[file.Link] = link
|
||||||
|
file.DownloadLink = link
|
||||||
t.Files[f.Name] = file
|
t.Files[f.Name] = file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dl.accounts.SetDownloadLinks(links)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +286,9 @@ func (dl *DebridLink) SubmitMagnet(t *types.Torrent) (*types.Torrent, error) {
|
|||||||
t.MountPath = dl.MountPath
|
t.MountPath = dl.MountPath
|
||||||
t.Debrid = dl.name
|
t.Debrid = dl.name
|
||||||
t.Added = time.Unix(data.Created, 0).Format(time.RFC3339)
|
t.Added = time.Unix(data.Created, 0).Format(time.RFC3339)
|
||||||
|
|
||||||
|
links := make(map[string]*types.DownloadLink)
|
||||||
|
now := time.Now()
|
||||||
for _, f := range data.Files {
|
for _, f := range data.Files {
|
||||||
file := types.File{
|
file := types.File{
|
||||||
TorrentId: t.Id,
|
TorrentId: t.Id,
|
||||||
@@ -298,18 +297,22 @@ func (dl *DebridLink) SubmitMagnet(t *types.Torrent) (*types.Torrent, error) {
|
|||||||
Size: f.Size,
|
Size: f.Size,
|
||||||
Path: f.Name,
|
Path: f.Name,
|
||||||
Link: f.DownloadURL,
|
Link: f.DownloadURL,
|
||||||
DownloadLink: &types.DownloadLink{
|
Generated: now,
|
||||||
|
}
|
||||||
|
link := &types.DownloadLink{
|
||||||
Filename: f.Name,
|
Filename: f.Name,
|
||||||
Link: f.DownloadURL,
|
Link: f.DownloadURL,
|
||||||
DownloadLink: f.DownloadURL,
|
DownloadLink: f.DownloadURL,
|
||||||
Generated: time.Now(),
|
Generated: now,
|
||||||
AccountId: "0",
|
ExpiresAt: now.Add(dl.autoExpiresLinksAfter),
|
||||||
},
|
|
||||||
Generated: time.Now(),
|
|
||||||
}
|
}
|
||||||
|
links[file.Link] = link
|
||||||
|
file.DownloadLink = link
|
||||||
t.Files[f.Name] = file
|
t.Files[f.Name] = file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dl.accounts.SetDownloadLinks(links)
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,8 +325,8 @@ func (dl *DebridLink) CheckStatus(torrent *types.Torrent, isSymlink bool) (*type
|
|||||||
status := torrent.Status
|
status := torrent.Status
|
||||||
if status == "downloaded" {
|
if status == "downloaded" {
|
||||||
dl.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name)
|
dl.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name)
|
||||||
err = dl.GenerateDownloadLinks(torrent)
|
|
||||||
if err != nil {
|
if err = dl.GetFileDownloadLinks(torrent); err != nil {
|
||||||
return torrent, err
|
return torrent, err
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@@ -352,27 +355,23 @@ func (dl *DebridLink) DeleteTorrent(torrentId string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dl *DebridLink) GenerateDownloadLinks(t *types.Torrent) error {
|
func (dl *DebridLink) GetFileDownloadLinks(t *types.Torrent) error {
|
||||||
// Download links are already generated
|
// Download links are already generated
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dl *DebridLink) GetDownloads() (map[string]types.DownloadLink, error) {
|
func (dl *DebridLink) GetDownloadLinks() (map[string]*types.DownloadLink, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dl *DebridLink) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, error) {
|
func (dl *DebridLink) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, error) {
|
||||||
return file.DownloadLink, nil
|
return dl.accounts.GetDownloadLink(file.Link)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dl *DebridLink) GetDownloadingStatus() []string {
|
func (dl *DebridLink) GetDownloadingStatus() []string {
|
||||||
return []string{"downloading"}
|
return []string{"downloading"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dl *DebridLink) GetCheckCached() bool {
|
|
||||||
return dl.checkCached
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dl *DebridLink) GetDownloadUncached() bool {
|
func (dl *DebridLink) GetDownloadUncached() bool {
|
||||||
return dl.DownloadUncached
|
return dl.DownloadUncached
|
||||||
}
|
}
|
||||||
@@ -411,6 +410,7 @@ func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data := *res.Value
|
data := *res.Value
|
||||||
|
links := make(map[string]*types.DownloadLink)
|
||||||
|
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return torrents, nil
|
return torrents, nil
|
||||||
@@ -433,6 +433,7 @@ func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) {
|
|||||||
Added: time.Unix(t.Created, 0).Format(time.RFC3339),
|
Added: time.Unix(t.Created, 0).Format(time.RFC3339),
|
||||||
}
|
}
|
||||||
cfg := config.Get()
|
cfg := config.Get()
|
||||||
|
now := time.Now()
|
||||||
for _, f := range t.Files {
|
for _, f := range t.Files {
|
||||||
if !cfg.IsSizeAllowed(f.Size) {
|
if !cfg.IsSizeAllowed(f.Size) {
|
||||||
continue
|
continue
|
||||||
@@ -443,19 +444,23 @@ func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) {
|
|||||||
Name: f.Name,
|
Name: f.Name,
|
||||||
Size: f.Size,
|
Size: f.Size,
|
||||||
Path: f.Name,
|
Path: f.Name,
|
||||||
DownloadLink: &types.DownloadLink{
|
Link: f.DownloadURL,
|
||||||
|
}
|
||||||
|
link := &types.DownloadLink{
|
||||||
Filename: f.Name,
|
Filename: f.Name,
|
||||||
Link: f.DownloadURL,
|
Link: f.DownloadURL,
|
||||||
DownloadLink: f.DownloadURL,
|
DownloadLink: f.DownloadURL,
|
||||||
Generated: time.Now(),
|
Generated: now,
|
||||||
AccountId: "0",
|
ExpiresAt: now.Add(dl.autoExpiresLinksAfter),
|
||||||
},
|
|
||||||
Link: f.DownloadURL,
|
|
||||||
}
|
}
|
||||||
|
links[file.Link] = link
|
||||||
|
file.DownloadLink = link
|
||||||
torrent.Files[f.Name] = file
|
torrent.Files[f.Name] = file
|
||||||
}
|
}
|
||||||
torrents = append(torrents, torrent)
|
torrents = append(torrents, torrent)
|
||||||
}
|
}
|
||||||
|
dl.accounts.SetDownloadLinks(links)
|
||||||
|
|
||||||
return torrents, nil
|
return torrents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,12 +472,6 @@ func (dl *DebridLink) GetMountPath() string {
|
|||||||
return dl.MountPath
|
return dl.MountPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dl *DebridLink) DisableAccount(accountId string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dl *DebridLink) ResetActiveDownloadKeys() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dl *DebridLink) DeleteDownloadLink(linkId string) error {
|
func (dl *DebridLink) DeleteDownloadLink(linkId string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -481,3 +480,7 @@ func (dl *DebridLink) GetAvailableSlots() (int, error) {
|
|||||||
//TODO: Implement the logic to check available slots for DebridLink
|
//TODO: Implement the logic to check available slots for DebridLink
|
||||||
return 0, fmt.Errorf("GetAvailableSlots not implemented for DebridLink")
|
return 0, fmt.Errorf("GetAvailableSlots not implemented for DebridLink")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dl *DebridLink) Accounts() *types.Accounts {
|
||||||
|
return dl.accounts
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
gourl "net/url"
|
gourl "net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -29,13 +28,12 @@ type RealDebrid struct {
|
|||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
|
|
||||||
APIKey string
|
APIKey string
|
||||||
currentDownloadKey string
|
accounts *types.Accounts
|
||||||
accounts map[string]types.Account
|
|
||||||
accountsMutex sync.RWMutex
|
|
||||||
|
|
||||||
DownloadUncached bool
|
DownloadUncached bool
|
||||||
client *request.Client
|
client *request.Client
|
||||||
downloadClient *request.Client
|
downloadClient *request.Client
|
||||||
|
autoExpiresLinksAfter time.Duration
|
||||||
|
|
||||||
MountPath string
|
MountPath string
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
@@ -57,27 +55,18 @@ func New(dc config.Debrid) (*RealDebrid, error) {
|
|||||||
}
|
}
|
||||||
_log := logger.New(dc.Name)
|
_log := logger.New(dc.Name)
|
||||||
|
|
||||||
accounts := make(map[string]types.Account)
|
autoExpiresLinksAfter, err := time.ParseDuration(dc.AutoExpireLinksAfter)
|
||||||
currentDownloadKey := dc.DownloadAPIKeys[0]
|
if autoExpiresLinksAfter == 0 || err != nil {
|
||||||
for idx, key := range dc.DownloadAPIKeys {
|
autoExpiresLinksAfter = 48 * time.Hour
|
||||||
id := strconv.Itoa(idx)
|
|
||||||
accounts[id] = types.Account{
|
|
||||||
Name: key,
|
|
||||||
ID: id,
|
|
||||||
Token: key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadHeaders := map[string]string{
|
|
||||||
"Authorization": fmt.Sprintf("Bearer %s", currentDownloadKey),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &RealDebrid{
|
r := &RealDebrid{
|
||||||
name: "realdebrid",
|
name: "realdebrid",
|
||||||
Host: "https://api.real-debrid.com/rest/1.0",
|
Host: "https://api.real-debrid.com/rest/1.0",
|
||||||
APIKey: dc.APIKey,
|
APIKey: dc.APIKey,
|
||||||
accounts: accounts,
|
accounts: types.NewAccounts(dc),
|
||||||
DownloadUncached: dc.DownloadUncached,
|
DownloadUncached: dc.DownloadUncached,
|
||||||
|
autoExpiresLinksAfter: autoExpiresLinksAfter,
|
||||||
UnpackRar: dc.UnpackRar,
|
UnpackRar: dc.UnpackRar,
|
||||||
client: request.New(
|
client: request.New(
|
||||||
request.WithHeaders(headers),
|
request.WithHeaders(headers),
|
||||||
@@ -88,13 +77,11 @@ func New(dc config.Debrid) (*RealDebrid, error) {
|
|||||||
request.WithProxy(dc.Proxy),
|
request.WithProxy(dc.Proxy),
|
||||||
),
|
),
|
||||||
downloadClient: request.New(
|
downloadClient: request.New(
|
||||||
request.WithHeaders(downloadHeaders),
|
|
||||||
request.WithLogger(_log),
|
request.WithLogger(_log),
|
||||||
request.WithMaxRetries(10),
|
request.WithMaxRetries(10),
|
||||||
request.WithRetryableStatus(429, 447, 502),
|
request.WithRetryableStatus(429, 447, 502),
|
||||||
request.WithProxy(dc.Proxy),
|
request.WithProxy(dc.Proxy),
|
||||||
),
|
),
|
||||||
currentDownloadKey: currentDownloadKey,
|
|
||||||
MountPath: dc.Folder,
|
MountPath: dc.Folder,
|
||||||
logger: logger.New(dc.Name),
|
logger: logger.New(dc.Name),
|
||||||
rarSemaphore: make(chan struct{}, 2),
|
rarSemaphore: make(chan struct{}, 2),
|
||||||
@@ -182,7 +169,6 @@ func (r *RealDebrid) handleRarArchive(t *types.Torrent, data torrentInfo, select
|
|||||||
ByteRange: nil,
|
ByteRange: nil,
|
||||||
Path: t.Name + ".rar",
|
Path: t.Name + ".rar",
|
||||||
Link: data.Links[0],
|
Link: data.Links[0],
|
||||||
AccountId: selectedFiles[0].AccountId,
|
|
||||||
Generated: time.Now(),
|
Generated: time.Now(),
|
||||||
}
|
}
|
||||||
files[file.Name] = file
|
files[file.Name] = file
|
||||||
@@ -219,19 +205,14 @@ func (r *RealDebrid) handleRarArchive(t *types.Torrent, data torrentInfo, select
|
|||||||
fileMap[safeName] = &selectedFiles[i]
|
fileMap[safeName] = &selectedFiles[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
for _, rarFile := range rarFiles {
|
for _, rarFile := range rarFiles {
|
||||||
if file, exists := fileMap[rarFile.Name()]; exists {
|
if file, exists := fileMap[rarFile.Name()]; exists {
|
||||||
file.IsRar = true
|
file.IsRar = true
|
||||||
file.ByteRange = rarFile.ByteRange()
|
file.ByteRange = rarFile.ByteRange()
|
||||||
file.Link = data.Links[0]
|
file.Link = data.Links[0]
|
||||||
file.DownloadLink = &types.DownloadLink{
|
file.Generated = now
|
||||||
Link: data.Links[0],
|
|
||||||
DownloadLink: dlLink,
|
|
||||||
Filename: file.Name,
|
|
||||||
Size: file.Size,
|
|
||||||
Generated: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
files[file.Name] = *file
|
files[file.Name] = *file
|
||||||
} else if !rarFile.IsDirectory {
|
} else if !rarFile.IsDirectory {
|
||||||
r.logger.Warn().Msgf("RAR file %s not found in torrent files", rarFile.Name())
|
r.logger.Warn().Msgf("RAR file %s not found in torrent files", rarFile.Name())
|
||||||
@@ -545,8 +526,7 @@ func (r *RealDebrid) CheckStatus(t *types.Torrent, isSymlink bool) (*types.Torre
|
|||||||
|
|
||||||
r.logger.Info().Msgf("Torrent: %s downloaded to RD", t.Name)
|
r.logger.Info().Msgf("Torrent: %s downloaded to RD", t.Name)
|
||||||
if !isSymlink {
|
if !isSymlink {
|
||||||
err = r.GenerateDownloadLinks(t)
|
if err = r.GetFileDownloadLinks(t); err != nil {
|
||||||
if err != nil {
|
|
||||||
return t, err
|
return t, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -574,9 +554,10 @@ func (r *RealDebrid) DeleteTorrent(torrentId string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) GenerateDownloadLinks(t *types.Torrent) error {
|
func (r *RealDebrid) GetFileDownloadLinks(t *types.Torrent) error {
|
||||||
filesCh := make(chan types.File, len(t.Files))
|
filesCh := make(chan types.File, len(t.Files))
|
||||||
errCh := make(chan error, len(t.Files))
|
errCh := make(chan error, len(t.Files))
|
||||||
|
linksCh := make(chan *types.DownloadLink)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(len(t.Files))
|
wg.Add(len(t.Files))
|
||||||
@@ -589,7 +570,11 @@ func (r *RealDebrid) GenerateDownloadLinks(t *types.Torrent) error {
|
|||||||
errCh <- err
|
errCh <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if link == nil {
|
||||||
|
errCh <- fmt.Errorf("realdebrid API error: download link not found for file %s", file.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
linksCh <- link
|
||||||
file.DownloadLink = link
|
file.DownloadLink = link
|
||||||
filesCh <- file
|
filesCh <- file
|
||||||
}(f)
|
}(f)
|
||||||
@@ -598,6 +583,7 @@ func (r *RealDebrid) GenerateDownloadLinks(t *types.Torrent) error {
|
|||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(filesCh)
|
close(filesCh)
|
||||||
|
close(linksCh)
|
||||||
close(errCh)
|
close(errCh)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -607,6 +593,18 @@ func (r *RealDebrid) GenerateDownloadLinks(t *types.Torrent) error {
|
|||||||
files[file.Name] = file
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add links to cache
|
||||||
|
r.accounts.SetDownloadLinks(links)
|
||||||
|
|
||||||
// Check for errors
|
// Check for errors
|
||||||
for err := range errCh {
|
for err := range errCh {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -636,8 +634,12 @@ func (r *RealDebrid) CheckLink(link string) error {
|
|||||||
|
|
||||||
func (r *RealDebrid) _getDownloadLink(file *types.File) (*types.DownloadLink, error) {
|
func (r *RealDebrid) _getDownloadLink(file *types.File) (*types.DownloadLink, error) {
|
||||||
url := fmt.Sprintf("%s/unrestrict/link/", r.Host)
|
url := fmt.Sprintf("%s/unrestrict/link/", r.Host)
|
||||||
|
_link := file.Link
|
||||||
|
if strings.HasPrefix(_link, "https://real-debrid.com/d/") {
|
||||||
|
_link = file.Link[0:39]
|
||||||
|
}
|
||||||
payload := gourl.Values{
|
payload := gourl.Values{
|
||||||
"link": {file.Link},
|
"link": {_link},
|
||||||
}
|
}
|
||||||
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
|
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
|
||||||
resp, err := r.downloadClient.Do(req)
|
resp, err := r.downloadClient.Do(req)
|
||||||
@@ -684,32 +686,31 @@ func (r *RealDebrid) _getDownloadLink(file *types.File) (*types.DownloadLink, er
|
|||||||
if data.Download == "" {
|
if data.Download == "" {
|
||||||
return nil, fmt.Errorf("realdebrid API error: download link not found")
|
return nil, fmt.Errorf("realdebrid API error: download link not found")
|
||||||
}
|
}
|
||||||
|
now := time.Now()
|
||||||
return &types.DownloadLink{
|
return &types.DownloadLink{
|
||||||
Filename: data.Filename,
|
Filename: data.Filename,
|
||||||
Size: data.Filesize,
|
Size: data.Filesize,
|
||||||
Link: data.Link,
|
Link: data.Link,
|
||||||
DownloadLink: data.Download,
|
DownloadLink: data.Download,
|
||||||
Generated: time.Now(),
|
Generated: now,
|
||||||
|
ExpiresAt: now.Add(r.autoExpiresLinksAfter),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, error) {
|
func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, error) {
|
||||||
|
|
||||||
if r.currentDownloadKey == "" {
|
accounts := r.accounts.All()
|
||||||
// If no download key is set, use the first one
|
|
||||||
accounts := r.getActiveAccounts()
|
for _, account := range accounts {
|
||||||
if len(accounts) < 1 {
|
r.downloadClient.SetHeader("Authorization", fmt.Sprintf("Bearer %s", account.Token))
|
||||||
// No active download keys. It's likely that the key has reached bandwidth limit
|
downloadLink, err := r._getDownloadLink(file)
|
||||||
return nil, fmt.Errorf("no active download keys")
|
|
||||||
}
|
if err == nil {
|
||||||
r.currentDownloadKey = accounts[0].Token
|
return downloadLink, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
r.downloadClient.SetHeader("Authorization", fmt.Sprintf("Bearer %s", r.currentDownloadKey))
|
|
||||||
downloadLink, err := r._getDownloadLink(file)
|
|
||||||
retries := 0
|
retries := 0
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, utils.TrafficExceededError) {
|
if errors.Is(err, utils.TrafficExceededError) {
|
||||||
// Retries generating
|
// Retries generating
|
||||||
retries = 5
|
retries = 5
|
||||||
@@ -717,7 +718,6 @@ func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
|
|||||||
// If the error is not traffic exceeded, return the error
|
// If the error is not traffic exceeded, return the error
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
backOff := 1 * time.Second
|
backOff := 1 * time.Second
|
||||||
for retries > 0 {
|
for retries > 0 {
|
||||||
downloadLink, err = r._getDownloadLink(file)
|
downloadLink, err = r._getDownloadLink(file)
|
||||||
@@ -730,12 +730,10 @@ func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
|
|||||||
// Add a delay before retrying
|
// Add a delay before retrying
|
||||||
time.Sleep(backOff)
|
time.Sleep(backOff)
|
||||||
backOff *= 2 // Exponential backoff
|
backOff *= 2 // Exponential backoff
|
||||||
|
retries--
|
||||||
}
|
}
|
||||||
return downloadLink, nil
|
|
||||||
}
|
}
|
||||||
|
return nil, fmt.Errorf("realdebrid API error: download link not found")
|
||||||
func (r *RealDebrid) GetCheckCached() bool {
|
|
||||||
return r.checkCached
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) getTorrents(offset int, limit int) (int, []*types.Torrent, error) {
|
func (r *RealDebrid) getTorrents(offset int, limit int) (int, []*types.Torrent, error) {
|
||||||
@@ -824,18 +822,19 @@ func (r *RealDebrid) GetTorrents() ([]*types.Torrent, error) {
|
|||||||
return allTorrents, nil
|
return allTorrents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) GetDownloads() (map[string]types.DownloadLink, error) {
|
func (r *RealDebrid) GetDownloadLinks() (map[string]*types.DownloadLink, error) {
|
||||||
links := make(map[string]types.DownloadLink)
|
links := make(map[string]*types.DownloadLink)
|
||||||
offset := 0
|
offset := 0
|
||||||
limit := 1000
|
limit := 1000
|
||||||
|
|
||||||
accounts := r.getActiveAccounts()
|
accounts := r.accounts.All()
|
||||||
|
|
||||||
if len(accounts) < 1 {
|
if len(accounts) < 1 {
|
||||||
// No active download keys. It's likely that the key has reached bandwidth limit
|
// No active download keys. It's likely that the key has reached bandwidth limit
|
||||||
return nil, fmt.Errorf("no active download keys")
|
return links, fmt.Errorf("no active download keys")
|
||||||
}
|
}
|
||||||
r.downloadClient.SetHeader("Authorization", fmt.Sprintf("Bearer %s", accounts[0].Token))
|
activeAccount := accounts[0]
|
||||||
|
r.downloadClient.SetHeader("Authorization", fmt.Sprintf("Bearer %s", activeAccount.Token))
|
||||||
for {
|
for {
|
||||||
dl, err := r._getDownloads(offset, limit)
|
dl, err := r._getDownloads(offset, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -850,11 +849,12 @@ func (r *RealDebrid) GetDownloads() (map[string]types.DownloadLink, error) {
|
|||||||
// This is ordered by date, so we can skip the rest
|
// This is ordered by date, so we can skip the rest
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
links[d.Link] = d
|
links[d.Link] = &d
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += len(dl)
|
offset += len(dl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return links, nil
|
return links, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -880,6 +880,7 @@ func (r *RealDebrid) _getDownloads(offset int, limit int) ([]types.DownloadLink,
|
|||||||
Link: d.Link,
|
Link: d.Link,
|
||||||
DownloadLink: d.Download,
|
DownloadLink: d.Download,
|
||||||
Generated: d.Generated,
|
Generated: d.Generated,
|
||||||
|
ExpiresAt: d.Generated.Add(r.autoExpiresLinksAfter),
|
||||||
Id: d.Id,
|
Id: d.Id,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -899,49 +900,6 @@ func (r *RealDebrid) GetMountPath() string {
|
|||||||
return r.MountPath
|
return r.MountPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) DisableAccount(accountId string) {
|
|
||||||
r.accountsMutex.Lock()
|
|
||||||
defer r.accountsMutex.Unlock()
|
|
||||||
if len(r.accounts) == 1 {
|
|
||||||
r.logger.Info().Msgf("Cannot disable last account: %s", accountId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.currentDownloadKey = ""
|
|
||||||
if value, ok := r.accounts[accountId]; ok {
|
|
||||||
value.Disabled = true
|
|
||||||
r.accounts[accountId] = value
|
|
||||||
r.logger.Info().Msgf("Disabled account Index: %s", value.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RealDebrid) ResetActiveDownloadKeys() {
|
|
||||||
r.accountsMutex.Lock()
|
|
||||||
defer r.accountsMutex.Unlock()
|
|
||||||
for key, value := range r.accounts {
|
|
||||||
value.Disabled = false
|
|
||||||
r.accounts[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RealDebrid) getActiveAccounts() []types.Account {
|
|
||||||
r.accountsMutex.RLock()
|
|
||||||
defer r.accountsMutex.RUnlock()
|
|
||||||
accounts := make([]types.Account, 0)
|
|
||||||
|
|
||||||
for _, value := range r.accounts {
|
|
||||||
if value.Disabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
accounts = append(accounts, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort accounts by ID
|
|
||||||
sort.Slice(accounts, func(i, j int) bool {
|
|
||||||
return accounts[i].ID < accounts[j].ID
|
|
||||||
})
|
|
||||||
return accounts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RealDebrid) DeleteDownloadLink(linkId string) error {
|
func (r *RealDebrid) DeleteDownloadLink(linkId string) error {
|
||||||
url := fmt.Sprintf("%s/downloads/delete/%s", r.Host, linkId)
|
url := fmt.Sprintf("%s/downloads/delete/%s", r.Host, linkId)
|
||||||
req, _ := http.NewRequest(http.MethodDelete, url, nil)
|
req, _ := http.NewRequest(http.MethodDelete, url, nil)
|
||||||
@@ -991,3 +949,7 @@ func (r *RealDebrid) GetAvailableSlots() (int, error) {
|
|||||||
}
|
}
|
||||||
return data.TotalSlots - data.ActiveSlots - r.minimumFreeSlot, nil // Ensure we maintain minimum active pots
|
return data.TotalSlots - data.ActiveSlots - r.minimumFreeSlot, nil // Ensure we maintain minimum active pots
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RealDebrid) Accounts() *types.Accounts {
|
||||||
|
return r.accounts
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ type Torbox struct {
|
|||||||
name string
|
name string
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
APIKey string
|
APIKey string
|
||||||
accounts map[string]types.Account
|
accounts *types.Accounts
|
||||||
|
autoExpiresLinksAfter time.Duration
|
||||||
|
|
||||||
DownloadUncached bool
|
DownloadUncached bool
|
||||||
client *request.Client
|
client *request.Client
|
||||||
|
|
||||||
@@ -55,23 +57,18 @@ func New(dc config.Debrid) (*Torbox, error) {
|
|||||||
request.WithLogger(_log),
|
request.WithLogger(_log),
|
||||||
request.WithProxy(dc.Proxy),
|
request.WithProxy(dc.Proxy),
|
||||||
)
|
)
|
||||||
|
autoExpiresLinksAfter, err := time.ParseDuration(dc.AutoExpireLinksAfter)
|
||||||
accounts := make(map[string]types.Account)
|
if autoExpiresLinksAfter == 0 || err != nil {
|
||||||
for idx, key := range dc.DownloadAPIKeys {
|
autoExpiresLinksAfter = 48 * time.Hour
|
||||||
id := strconv.Itoa(idx)
|
|
||||||
accounts[id] = types.Account{
|
|
||||||
Name: key,
|
|
||||||
ID: id,
|
|
||||||
Token: key,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Torbox{
|
return &Torbox{
|
||||||
name: "torbox",
|
name: "torbox",
|
||||||
Host: "https://api.torbox.app/v1",
|
Host: "https://api.torbox.app/v1",
|
||||||
APIKey: dc.APIKey,
|
APIKey: dc.APIKey,
|
||||||
accounts: accounts,
|
accounts: types.NewAccounts(dc),
|
||||||
DownloadUncached: dc.DownloadUncached,
|
DownloadUncached: dc.DownloadUncached,
|
||||||
|
autoExpiresLinksAfter: autoExpiresLinksAfter,
|
||||||
client: client,
|
client: client,
|
||||||
MountPath: dc.Folder,
|
MountPath: dc.Folder,
|
||||||
logger: _log,
|
logger: _log,
|
||||||
@@ -326,8 +323,7 @@ func (tb *Torbox) CheckStatus(torrent *types.Torrent, isSymlink bool) (*types.To
|
|||||||
if status == "downloaded" {
|
if status == "downloaded" {
|
||||||
tb.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name)
|
tb.logger.Info().Msgf("Torrent: %s downloaded", torrent.Name)
|
||||||
if !isSymlink {
|
if !isSymlink {
|
||||||
err = tb.GenerateDownloadLinks(torrent)
|
if err = tb.GetFileDownloadLinks(torrent); err != nil {
|
||||||
if err != nil {
|
|
||||||
return torrent, err
|
return torrent, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,8 +355,9 @@ func (tb *Torbox) DeleteTorrent(torrentId string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *Torbox) GenerateDownloadLinks(t *types.Torrent) error {
|
func (tb *Torbox) GetFileDownloadLinks(t *types.Torrent) error {
|
||||||
filesCh := make(chan types.File, len(t.Files))
|
filesCh := make(chan types.File, len(t.Files))
|
||||||
|
linkCh := make(chan *types.DownloadLink)
|
||||||
errCh := make(chan error, len(t.Files))
|
errCh := make(chan error, len(t.Files))
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@@ -373,13 +370,17 @@ func (tb *Torbox) GenerateDownloadLinks(t *types.Torrent) error {
|
|||||||
errCh <- err
|
errCh <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if link != nil {
|
||||||
|
linkCh <- link
|
||||||
file.DownloadLink = link
|
file.DownloadLink = link
|
||||||
|
}
|
||||||
filesCh <- file
|
filesCh <- file
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(filesCh)
|
close(filesCh)
|
||||||
|
close(linkCh)
|
||||||
close(errCh)
|
close(errCh)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -389,6 +390,13 @@ func (tb *Torbox) GenerateDownloadLinks(t *types.Torrent) error {
|
|||||||
files[file.Name] = file
|
files[file.Name] = file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect download links
|
||||||
|
for link := range linkCh {
|
||||||
|
if link != nil {
|
||||||
|
tb.accounts.SetDownloadLink(link.Link, link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check for errors
|
// Check for errors
|
||||||
for err := range errCh {
|
for err := range errCh {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -423,12 +431,13 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.Do
|
|||||||
if link == "" {
|
if link == "" {
|
||||||
return nil, fmt.Errorf("error getting download links")
|
return nil, fmt.Errorf("error getting download links")
|
||||||
}
|
}
|
||||||
|
now := time.Now()
|
||||||
return &types.DownloadLink{
|
return &types.DownloadLink{
|
||||||
Link: file.Link,
|
Link: file.Link,
|
||||||
DownloadLink: link,
|
DownloadLink: link,
|
||||||
Id: file.Id,
|
Id: file.Id,
|
||||||
AccountId: "0",
|
Generated: now,
|
||||||
Generated: time.Now(),
|
ExpiresAt: now.Add(tb.autoExpiresLinksAfter),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,10 +445,6 @@ func (tb *Torbox) GetDownloadingStatus() []string {
|
|||||||
return []string{"downloading"}
|
return []string{"downloading"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *Torbox) GetCheckCached() bool {
|
|
||||||
return tb.checkCached
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tb *Torbox) GetTorrents() ([]*types.Torrent, error) {
|
func (tb *Torbox) GetTorrents() ([]*types.Torrent, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -448,7 +453,7 @@ func (tb *Torbox) GetDownloadUncached() bool {
|
|||||||
return tb.DownloadUncached
|
return tb.DownloadUncached
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *Torbox) GetDownloads() (map[string]types.DownloadLink, error) {
|
func (tb *Torbox) GetDownloadLinks() (map[string]*types.DownloadLink, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,13 +465,6 @@ func (tb *Torbox) GetMountPath() string {
|
|||||||
return tb.MountPath
|
return tb.MountPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *Torbox) DisableAccount(accountId string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tb *Torbox) ResetActiveDownloadKeys() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tb *Torbox) DeleteDownloadLink(linkId string) error {
|
func (tb *Torbox) DeleteDownloadLink(linkId string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -475,3 +473,7 @@ func (tb *Torbox) GetAvailableSlots() (int, error) {
|
|||||||
//TODO: Implement the logic to check available slots for Torbox
|
//TODO: Implement the logic to check available slots for Torbox
|
||||||
return 0, fmt.Errorf("not implemented")
|
return 0, fmt.Errorf("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tb *Torbox) Accounts() *types.Accounts {
|
||||||
|
return tb.accounts
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ type Cache struct {
|
|||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
|
|
||||||
torrents *torrentCache
|
torrents *torrentCache
|
||||||
downloadLinks *downloadLinkCache
|
|
||||||
invalidDownloadLinks sync.Map
|
invalidDownloadLinks sync.Map
|
||||||
folderNaming WebDavFolderNaming
|
folderNaming WebDavFolderNaming
|
||||||
|
|
||||||
@@ -93,7 +92,6 @@ type Cache struct {
|
|||||||
workers int
|
workers int
|
||||||
torrentRefreshInterval string
|
torrentRefreshInterval string
|
||||||
downloadLinksRefreshInterval string
|
downloadLinksRefreshInterval string
|
||||||
autoExpiresLinksAfterDuration time.Duration
|
|
||||||
|
|
||||||
// refresh mutex
|
// refresh mutex
|
||||||
downloadLinksRefreshMu sync.RWMutex // for refreshing download links
|
downloadLinksRefreshMu sync.RWMutex // for refreshing download links
|
||||||
@@ -121,10 +119,6 @@ func NewDebridCache(dc config.Debrid, client types.Client) *Cache {
|
|||||||
scheduler = cetSc
|
scheduler = cetSc
|
||||||
}
|
}
|
||||||
|
|
||||||
autoExpiresLinksAfter, err := time.ParseDuration(dc.AutoExpireLinksAfter)
|
|
||||||
if autoExpiresLinksAfter == 0 || err != nil {
|
|
||||||
autoExpiresLinksAfter = 48 * time.Hour
|
|
||||||
}
|
|
||||||
var customFolders []string
|
var customFolders []string
|
||||||
dirFilters := map[string][]directoryFilter{}
|
dirFilters := map[string][]directoryFilter{}
|
||||||
for name, value := range dc.Directories {
|
for name, value := range dc.Directories {
|
||||||
@@ -151,11 +145,9 @@ func NewDebridCache(dc config.Debrid, client types.Client) *Cache {
|
|||||||
client: client,
|
client: client,
|
||||||
logger: _log,
|
logger: _log,
|
||||||
workers: dc.Workers,
|
workers: dc.Workers,
|
||||||
downloadLinks: newDownloadLinkCache(),
|
|
||||||
torrentRefreshInterval: dc.TorrentsRefreshInterval,
|
torrentRefreshInterval: dc.TorrentsRefreshInterval,
|
||||||
downloadLinksRefreshInterval: dc.DownloadLinksRefreshInterval,
|
downloadLinksRefreshInterval: dc.DownloadLinksRefreshInterval,
|
||||||
folderNaming: WebDavFolderNaming(dc.FolderNaming),
|
folderNaming: WebDavFolderNaming(dc.FolderNaming),
|
||||||
autoExpiresLinksAfterDuration: autoExpiresLinksAfter,
|
|
||||||
saveSemaphore: make(chan struct{}, 50),
|
saveSemaphore: make(chan struct{}, 50),
|
||||||
cetScheduler: cetSc,
|
cetScheduler: cetSc,
|
||||||
scheduler: scheduler,
|
scheduler: scheduler,
|
||||||
@@ -202,9 +194,6 @@ func (c *Cache) Reset() {
|
|||||||
// 1. Reset torrent storage
|
// 1. Reset torrent storage
|
||||||
c.torrents.reset()
|
c.torrents.reset()
|
||||||
|
|
||||||
// 2. Reset download-link cache
|
|
||||||
c.downloadLinks.reset()
|
|
||||||
|
|
||||||
// 3. Clear any sync.Maps
|
// 3. Clear any sync.Maps
|
||||||
c.invalidDownloadLinks = sync.Map{}
|
c.invalidDownloadLinks = sync.Map{}
|
||||||
c.repairRequest = sync.Map{}
|
c.repairRequest = sync.Map{}
|
||||||
@@ -714,7 +703,7 @@ func (c *Cache) Add(t *types.Torrent) error {
|
|||||||
c.setTorrent(ct, func(tor CachedTorrent) {
|
c.setTorrent(ct, func(tor CachedTorrent) {
|
||||||
c.RefreshListings(true)
|
c.RefreshListings(true)
|
||||||
})
|
})
|
||||||
go c.GenerateDownloadLinks(ct)
|
go c.GetFileDownloadLinks(ct)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,58 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/sirrobot01/decypharr/internal/utils"
|
"github.com/sirrobot01/decypharr/internal/utils"
|
||||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||||
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type linkCache struct {
|
|
||||||
Id string
|
|
||||||
link string
|
|
||||||
accountId string
|
|
||||||
expiresAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type downloadLinkCache struct {
|
|
||||||
data map[string]linkCache
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDownloadLinkCache() *downloadLinkCache {
|
|
||||||
return &downloadLinkCache{
|
|
||||||
data: make(map[string]linkCache),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *downloadLinkCache) reset() {
|
|
||||||
c.mu.Lock()
|
|
||||||
c.data = make(map[string]linkCache)
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *downloadLinkCache) Load(key string) (linkCache, bool) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
dl, ok := c.data[key]
|
|
||||||
return dl, ok
|
|
||||||
}
|
|
||||||
func (c *downloadLinkCache) Store(key string, value linkCache) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.data[key] = value
|
|
||||||
}
|
|
||||||
func (c *downloadLinkCache) Delete(key string) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
delete(c.data, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *downloadLinkCache) Len() int {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return len(c.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
type downloadLinkRequest struct {
|
type downloadLinkRequest struct {
|
||||||
result string
|
result string
|
||||||
err error
|
err error
|
||||||
@@ -82,8 +32,10 @@ func (r *downloadLinkRequest) Wait() (string, error) {
|
|||||||
|
|
||||||
func (c *Cache) GetDownloadLink(torrentName, filename, fileLink string) (string, error) {
|
func (c *Cache) GetDownloadLink(torrentName, filename, fileLink string) (string, error) {
|
||||||
// Check link cache
|
// Check link cache
|
||||||
if dl := c.checkDownloadLink(fileLink); dl != "" {
|
if dl, err := c.checkDownloadLink(fileLink); dl != "" && err == nil {
|
||||||
return dl, nil
|
return dl, nil
|
||||||
|
} else {
|
||||||
|
c.logger.Trace().Msgf("Download link check failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if req, inFlight := c.downloadLinkRequests.Load(fileLink); inFlight {
|
if req, inFlight := c.downloadLinkRequests.Load(fileLink); inFlight {
|
||||||
@@ -96,34 +48,36 @@ func (c *Cache) GetDownloadLink(torrentName, filename, fileLink string) (string,
|
|||||||
req := newDownloadLinkRequest()
|
req := newDownloadLinkRequest()
|
||||||
c.downloadLinkRequests.Store(fileLink, req)
|
c.downloadLinkRequests.Store(fileLink, req)
|
||||||
|
|
||||||
downloadLink, err := c.fetchDownloadLink(torrentName, filename, fileLink)
|
dl, err := c.fetchDownloadLink(torrentName, filename, fileLink)
|
||||||
|
if err != nil {
|
||||||
// Complete the request and remove it from the map
|
req.Complete("", err)
|
||||||
req.Complete(downloadLink, err)
|
|
||||||
c.downloadLinkRequests.Delete(fileLink)
|
c.downloadLinkRequests.Delete(fileLink)
|
||||||
|
return "", err
|
||||||
return downloadLink, err
|
}
|
||||||
|
req.Complete(dl.DownloadLink, err)
|
||||||
|
c.downloadLinkRequests.Delete(fileLink)
|
||||||
|
return dl.DownloadLink, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (string, error) {
|
func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (*types.DownloadLink, error) {
|
||||||
ct := c.GetTorrentByName(torrentName)
|
ct := c.GetTorrentByName(torrentName)
|
||||||
if ct == nil {
|
if ct == nil {
|
||||||
return "", fmt.Errorf("torrent not found")
|
return nil, fmt.Errorf("torrent not found")
|
||||||
}
|
}
|
||||||
file, ok := ct.GetFile(filename)
|
file, ok := ct.GetFile(filename)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("file %s not found in torrent %s", filename, torrentName)
|
return nil, fmt.Errorf("file %s not found in torrent %s", filename, torrentName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.Link == "" {
|
if file.Link == "" {
|
||||||
// file link is empty, refresh the torrent to get restricted links
|
// file link is empty, refresh the torrent to get restricted links
|
||||||
ct = c.refreshTorrent(file.TorrentId) // Refresh the torrent from the debrid
|
ct = c.refreshTorrent(file.TorrentId) // Refresh the torrent from the debrid
|
||||||
if ct == nil {
|
if ct == nil {
|
||||||
return "", fmt.Errorf("failed to refresh torrent")
|
return nil, fmt.Errorf("failed to refresh torrent")
|
||||||
} else {
|
} else {
|
||||||
file, ok = ct.GetFile(filename)
|
file, ok = ct.GetFile(filename)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("file %s not found in refreshed torrent %s", filename, torrentName)
|
return nil, fmt.Errorf("file %s not found in refreshed torrent %s", filename, torrentName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,12 +87,12 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (strin
|
|||||||
// Try to reinsert the torrent?
|
// Try to reinsert the torrent?
|
||||||
newCt, err := c.reInsertTorrent(ct)
|
newCt, err := c.reInsertTorrent(ct)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to reinsert torrent. %w", err)
|
return nil, fmt.Errorf("failed to reinsert torrent. %w", err)
|
||||||
}
|
}
|
||||||
ct = newCt
|
ct = newCt
|
||||||
file, ok = ct.GetFile(filename)
|
file, ok = ct.GetFile(filename)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("file %s not found in reinserted torrent %s", filename, torrentName)
|
return nil, fmt.Errorf("file %s not found in reinserted torrent %s", filename, torrentName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,93 +102,71 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (strin
|
|||||||
if errors.Is(err, utils.HosterUnavailableError) {
|
if errors.Is(err, utils.HosterUnavailableError) {
|
||||||
newCt, err := c.reInsertTorrent(ct)
|
newCt, err := c.reInsertTorrent(ct)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to reinsert torrent: %w", err)
|
return nil, fmt.Errorf("failed to reinsert torrent: %w", err)
|
||||||
}
|
}
|
||||||
ct = newCt
|
ct = newCt
|
||||||
file, ok = ct.GetFile(filename)
|
file, ok = ct.GetFile(filename)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("file %s not found in reinserted torrent %s", filename, torrentName)
|
return nil, fmt.Errorf("file %s not found in reinserted torrent %s", filename, torrentName)
|
||||||
}
|
}
|
||||||
// Retry getting the download link
|
// Retry getting the download link
|
||||||
downloadLink, err = c.client.GetDownloadLink(ct.Torrent, &file)
|
downloadLink, err = c.client.GetDownloadLink(ct.Torrent, &file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
if downloadLink == nil {
|
if downloadLink == nil {
|
||||||
return "", fmt.Errorf("download link is empty for")
|
return nil, fmt.Errorf("download link is empty for")
|
||||||
}
|
}
|
||||||
c.updateDownloadLink(downloadLink)
|
return nil, nil
|
||||||
return "", nil
|
|
||||||
} else if errors.Is(err, utils.TrafficExceededError) {
|
} else if errors.Is(err, utils.TrafficExceededError) {
|
||||||
// This is likely a fair usage limit error
|
// This is likely a fair usage limit error
|
||||||
return "", err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return "", fmt.Errorf("failed to get download link: %w", err)
|
return nil, fmt.Errorf("failed to get download link: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if downloadLink == nil {
|
if downloadLink == nil {
|
||||||
return "", fmt.Errorf("download link is empty")
|
return nil, fmt.Errorf("download link is empty")
|
||||||
}
|
|
||||||
c.updateDownloadLink(downloadLink)
|
|
||||||
return downloadLink.DownloadLink, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) GenerateDownloadLinks(t CachedTorrent) {
|
// Set link to cache
|
||||||
if err := c.client.GenerateDownloadLinks(t.Torrent); err != nil {
|
go c.client.Accounts().SetDownloadLink(fileLink, downloadLink)
|
||||||
|
return downloadLink, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) GetFileDownloadLinks(t CachedTorrent) {
|
||||||
|
if err := c.client.GetFileDownloadLinks(t.Torrent); err != nil {
|
||||||
c.logger.Error().Err(err).Str("torrent", t.Name).Msg("Failed to generate download links")
|
c.logger.Error().Err(err).Str("torrent", t.Name).Msg("Failed to generate download links")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, file := range t.GetFiles() {
|
|
||||||
if file.DownloadLink != nil {
|
|
||||||
c.updateDownloadLink(file.DownloadLink)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.setTorrent(t, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) updateDownloadLink(dl *types.DownloadLink) {
|
func (c *Cache) checkDownloadLink(link string) (string, error) {
|
||||||
c.downloadLinks.Store(dl.Link, linkCache{
|
|
||||||
Id: dl.Id,
|
|
||||||
link: dl.DownloadLink,
|
|
||||||
expiresAt: time.Now().Add(c.autoExpiresLinksAfterDuration),
|
|
||||||
accountId: dl.AccountId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) checkDownloadLink(link string) string {
|
dl, err := c.client.Accounts().GetDownloadLink(link)
|
||||||
if dl, ok := c.downloadLinks.Load(link); ok {
|
if err != nil {
|
||||||
if dl.expiresAt.After(time.Now()) && !c.IsDownloadLinkInvalid(dl.link) {
|
return "", err
|
||||||
return dl.link
|
|
||||||
}
|
}
|
||||||
|
if !c.downloadLinkIsInvalid(dl.DownloadLink) {
|
||||||
|
return dl.DownloadLink, nil
|
||||||
}
|
}
|
||||||
return ""
|
return "", fmt.Errorf("download link not found for %s", link)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) MarkDownloadLinkAsInvalid(link, downloadLink, reason string) {
|
func (c *Cache) MarkDownloadLinkAsInvalid(link, downloadLink, reason string) {
|
||||||
c.invalidDownloadLinks.Store(downloadLink, reason)
|
c.invalidDownloadLinks.Store(downloadLink, reason)
|
||||||
// Remove the download api key from active
|
// Remove the download api key from active
|
||||||
if reason == "bandwidth_exceeded" {
|
if reason == "bandwidth_exceeded" {
|
||||||
if dl, ok := c.downloadLinks.Load(link); ok {
|
// Disable the account
|
||||||
if dl.accountId != "" && dl.link == downloadLink {
|
_, account, err := c.client.Accounts().GetDownloadLinkWithAccount(link)
|
||||||
c.client.DisableAccount(dl.accountId)
|
if err != nil {
|
||||||
}
|
return
|
||||||
}
|
|
||||||
}
|
|
||||||
c.removeDownloadLink(link)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) removeDownloadLink(link string) {
|
|
||||||
if dl, ok := c.downloadLinks.Load(link); ok {
|
|
||||||
// Delete dl from cache
|
|
||||||
c.downloadLinks.Delete(link)
|
|
||||||
// Delete dl from debrid
|
|
||||||
if dl.Id != "" {
|
|
||||||
_ = c.client.DeleteDownloadLink(dl.Id)
|
|
||||||
}
|
}
|
||||||
|
c.client.Accounts().Disable(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) IsDownloadLinkInvalid(downloadLink string) bool {
|
func (c *Cache) downloadLinkIsInvalid(downloadLink string) bool {
|
||||||
if reason, ok := c.invalidDownloadLinks.Load(downloadLink); ok {
|
if reason, ok := c.invalidDownloadLinks.Load(downloadLink); ok {
|
||||||
c.logger.Debug().Msgf("Download link %s is invalid: %s", downloadLink, reason)
|
c.logger.Debug().Msgf("Download link %s is invalid: %s", downloadLink, reason)
|
||||||
return true
|
return true
|
||||||
@@ -252,5 +184,5 @@ func (c *Cache) GetDownloadByteRange(torrentName, filename string) (*[2]int64, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) GetTotalActiveDownloadLinks() int {
|
func (c *Cache) GetTotalActiveDownloadLinks() int {
|
||||||
return c.downloadLinks.Len()
|
return c.client.Accounts().GetLinksCount()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,24 +241,14 @@ func (c *Cache) refreshDownloadLinks(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
defer c.downloadLinksRefreshMu.Unlock()
|
defer c.downloadLinksRefreshMu.Unlock()
|
||||||
|
|
||||||
downloadLinks, err := c.client.GetDownloads()
|
links, err := c.client.GetDownloadLinks()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error().Err(err).Msg("Failed to get download links")
|
c.logger.Error().Err(err).Msg("Failed to get download links")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for k, v := range downloadLinks {
|
|
||||||
// if link is generated in the last 24 hours, add it to cache
|
c.client.Accounts().SetDownloadLinks(links)
|
||||||
timeSince := time.Since(v.Generated)
|
|
||||||
if timeSince < c.autoExpiresLinksAfterDuration {
|
c.logger.Debug().Msgf("Refreshed download %d links", c.client.Accounts().GetLinksCount())
|
||||||
c.downloadLinks.Store(k, linkCache{
|
|
||||||
Id: v.Id,
|
|
||||||
accountId: v.AccountId,
|
|
||||||
link: v.DownloadLink,
|
|
||||||
expiresAt: v.Generated.Add(c.autoExpiresLinksAfterDuration - timeSince),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
c.downloadLinks.Delete(k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,5 +252,5 @@ func (c *Cache) reInsertTorrent(ct *CachedTorrent) (*CachedTorrent, error) {
|
|||||||
|
|
||||||
func (c *Cache) resetInvalidLinks() {
|
func (c *Cache) resetInvalidLinks() {
|
||||||
c.invalidDownloadLinks = sync.Map{}
|
c.invalidDownloadLinks = sync.Map{}
|
||||||
c.client.ResetActiveDownloadKeys() // Reset the active download keys
|
c.client.Accounts().Reset() // Reset the active download keys
|
||||||
}
|
}
|
||||||
|
|||||||
230
pkg/debrid/types/account.go
Normal file
230
pkg/debrid/types/account.go
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sirrobot01/decypharr/internal/config"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Accounts struct {
|
||||||
|
current *Account
|
||||||
|
accounts []*Account
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccounts(debridConf config.Debrid) *Accounts {
|
||||||
|
accounts := make([]*Account, 0)
|
||||||
|
for idx, token := range debridConf.DownloadAPIKeys {
|
||||||
|
if token == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
account := newAccount(token, idx)
|
||||||
|
accounts = append(accounts, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
var current *Account
|
||||||
|
if len(accounts) > 0 {
|
||||||
|
current = accounts[0]
|
||||||
|
}
|
||||||
|
return &Accounts{
|
||||||
|
accounts: accounts,
|
||||||
|
current: current,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
Order int
|
||||||
|
Disabled bool
|
||||||
|
Token string
|
||||||
|
links map[string]*DownloadLink
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Accounts) All() []*Account {
|
||||||
|
a.mu.RLock()
|
||||||
|
defer a.mu.RUnlock()
|
||||||
|
activeAccounts := make([]*Account, 0)
|
||||||
|
for _, acc := range a.accounts {
|
||||||
|
if !acc.Disabled {
|
||||||
|
activeAccounts = append(activeAccounts, acc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return activeAccounts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Accounts) Current() *Account {
|
||||||
|
a.mu.RLock()
|
||||||
|
if a.current != nil {
|
||||||
|
current := a.current
|
||||||
|
a.mu.RUnlock()
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
a.mu.RUnlock()
|
||||||
|
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
|
||||||
|
// Double-check after acquiring write lock
|
||||||
|
if a.current != nil {
|
||||||
|
return a.current
|
||||||
|
}
|
||||||
|
|
||||||
|
activeAccounts := make([]*Account, 0)
|
||||||
|
for _, acc := range a.accounts {
|
||||||
|
if !acc.Disabled {
|
||||||
|
activeAccounts = append(activeAccounts, acc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(activeAccounts) > 0 {
|
||||||
|
a.current = activeAccounts[0]
|
||||||
|
}
|
||||||
|
return a.current
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Accounts) Disable(account *Account) {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
account.disable()
|
||||||
|
|
||||||
|
if a.current == account {
|
||||||
|
var newCurrent *Account
|
||||||
|
for _, acc := range a.accounts {
|
||||||
|
if !acc.Disabled {
|
||||||
|
newCurrent = acc
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.current = newCurrent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Accounts) Reset() {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
for _, acc := range a.accounts {
|
||||||
|
acc.resetDownloadLinks()
|
||||||
|
acc.Disabled = false
|
||||||
|
}
|
||||||
|
if len(a.accounts) > 0 {
|
||||||
|
a.current = a.accounts[0]
|
||||||
|
} else {
|
||||||
|
a.current = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Accounts) GetDownloadLink(fileLink string) (*DownloadLink, error) {
|
||||||
|
if a.Current() == nil {
|
||||||
|
return nil, NoActiveAccountsError
|
||||||
|
}
|
||||||
|
dl, ok := a.Current().getLink(fileLink)
|
||||||
|
if !ok {
|
||||||
|
return nil, NoDownloadLinkError
|
||||||
|
}
|
||||||
|
if dl.ExpiresAt.IsZero() || dl.ExpiresAt.Before(time.Now()) {
|
||||||
|
return nil, DownloadLinkExpiredError
|
||||||
|
}
|
||||||
|
if dl.DownloadLink == "" {
|
||||||
|
return nil, EmptyDownloadLinkError
|
||||||
|
}
|
||||||
|
return dl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Accounts) GetDownloadLinkWithAccount(fileLink string) (*DownloadLink, *Account, error) {
|
||||||
|
currentAccount := a.Current()
|
||||||
|
if currentAccount == nil {
|
||||||
|
return nil, nil, NoActiveAccountsError
|
||||||
|
}
|
||||||
|
dl, ok := currentAccount.getLink(fileLink)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, NoDownloadLinkError
|
||||||
|
}
|
||||||
|
if dl.ExpiresAt.IsZero() || dl.ExpiresAt.Before(time.Now()) {
|
||||||
|
return nil, currentAccount, DownloadLinkExpiredError
|
||||||
|
}
|
||||||
|
if dl.DownloadLink == "" {
|
||||||
|
return nil, currentAccount, EmptyDownloadLinkError
|
||||||
|
}
|
||||||
|
return dl, currentAccount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Accounts) SetDownloadLink(fileLink string, dl *DownloadLink) {
|
||||||
|
if a.Current() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.Current().setLink(fileLink, dl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Accounts) DeleteDownloadLink(fileLink string) {
|
||||||
|
if a.Current() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.Current().deleteLink(fileLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Accounts) GetLinksCount() int {
|
||||||
|
if a.Current() == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return a.Current().LinksCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Accounts) SetDownloadLinks(links map[string]*DownloadLink) {
|
||||||
|
if a.Current() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.Current().setLinks(links)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAccount(token string, index int) *Account {
|
||||||
|
return &Account{
|
||||||
|
Token: token,
|
||||||
|
Order: index,
|
||||||
|
links: make(map[string]*DownloadLink),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Account) getLink(fileLink string) (*DownloadLink, bool) {
|
||||||
|
a.mu.RLock()
|
||||||
|
defer a.mu.RUnlock()
|
||||||
|
dl, ok := a.links[fileLink[0:39]]
|
||||||
|
return dl, ok
|
||||||
|
}
|
||||||
|
func (a *Account) setLink(fileLink string, dl *DownloadLink) {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
a.links[fileLink[0:39]] = dl
|
||||||
|
}
|
||||||
|
func (a *Account) deleteLink(fileLink string) {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
|
||||||
|
delete(a.links, fileLink[0:39])
|
||||||
|
}
|
||||||
|
func (a *Account) resetDownloadLinks() {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
a.links = make(map[string]*DownloadLink)
|
||||||
|
}
|
||||||
|
func (a *Account) LinksCount() int {
|
||||||
|
a.mu.RLock()
|
||||||
|
defer a.mu.RUnlock()
|
||||||
|
return len(a.links)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Account) disable() {
|
||||||
|
a.Disabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Account) setLinks(links map[string]*DownloadLink) {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
now := time.Now()
|
||||||
|
for _, dl := range links {
|
||||||
|
if !dl.ExpiresAt.IsZero() && dl.ExpiresAt.Before(now) {
|
||||||
|
// Expired, continue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
a.links[dl.Link[0:39]] = dl
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,11 +7,10 @@ import (
|
|||||||
type Client interface {
|
type Client interface {
|
||||||
SubmitMagnet(tr *Torrent) (*Torrent, error)
|
SubmitMagnet(tr *Torrent) (*Torrent, error)
|
||||||
CheckStatus(tr *Torrent, isSymlink bool) (*Torrent, error)
|
CheckStatus(tr *Torrent, isSymlink bool) (*Torrent, error)
|
||||||
GenerateDownloadLinks(tr *Torrent) error
|
GetFileDownloadLinks(tr *Torrent) error
|
||||||
GetDownloadLink(tr *Torrent, file *File) (*DownloadLink, error)
|
GetDownloadLink(tr *Torrent, file *File) (*DownloadLink, error)
|
||||||
DeleteTorrent(torrentId string) error
|
DeleteTorrent(torrentId string) error
|
||||||
IsAvailable(infohashes []string) map[string]bool
|
IsAvailable(infohashes []string) map[string]bool
|
||||||
GetCheckCached() bool
|
|
||||||
GetDownloadUncached() bool
|
GetDownloadUncached() bool
|
||||||
UpdateTorrent(torrent *Torrent) error
|
UpdateTorrent(torrent *Torrent) error
|
||||||
GetTorrent(torrentId string) (*Torrent, error)
|
GetTorrent(torrentId string) (*Torrent, error)
|
||||||
@@ -19,11 +18,10 @@ type Client interface {
|
|||||||
Name() string
|
Name() string
|
||||||
Logger() zerolog.Logger
|
Logger() zerolog.Logger
|
||||||
GetDownloadingStatus() []string
|
GetDownloadingStatus() []string
|
||||||
GetDownloads() (map[string]DownloadLink, error)
|
GetDownloadLinks() (map[string]*DownloadLink, error)
|
||||||
CheckLink(link string) error
|
CheckLink(link string) error
|
||||||
GetMountPath() string
|
GetMountPath() string
|
||||||
DisableAccount(string)
|
Accounts() *Accounts // Returns the active download account/token
|
||||||
ResetActiveDownloadKeys()
|
|
||||||
DeleteDownloadLink(linkId string) error
|
DeleteDownloadLink(linkId string) error
|
||||||
GetProfile() (*Profile, error)
|
GetProfile() (*Profile, error)
|
||||||
GetAvailableSlots() (int, error)
|
GetAvailableSlots() (int, error)
|
||||||
|
|||||||
30
pkg/debrid/types/error.go
Normal file
30
pkg/debrid/types/error.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
var NoActiveAccountsError = &Error{
|
||||||
|
Message: "No active accounts",
|
||||||
|
Code: "no_active_accounts",
|
||||||
|
}
|
||||||
|
|
||||||
|
var NoDownloadLinkError = &Error{
|
||||||
|
Message: "No download link found",
|
||||||
|
Code: "no_download_link",
|
||||||
|
}
|
||||||
|
|
||||||
|
var DownloadLinkExpiredError = &Error{
|
||||||
|
Message: "Download link expired",
|
||||||
|
Code: "download_link_expired",
|
||||||
|
}
|
||||||
|
|
||||||
|
var EmptyDownloadLinkError = &Error{
|
||||||
|
Message: "Download link is empty",
|
||||||
|
Code: "empty_download_link",
|
||||||
|
}
|
||||||
@@ -42,20 +42,6 @@ type Torrent struct {
|
|||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type DownloadLink struct {
|
|
||||||
Filename string `json:"filename"`
|
|
||||||
Link string `json:"link"`
|
|
||||||
DownloadLink string `json:"download_link"`
|
|
||||||
Generated time.Time `json:"generated"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
Id string `json:"id"`
|
|
||||||
AccountId string `json:"account_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DownloadLink) String() string {
|
|
||||||
return d.DownloadLink
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Torrent) GetSymlinkFolder(parent string) string {
|
func (t *Torrent) GetSymlinkFolder(parent string) string {
|
||||||
return filepath.Join(parent, t.Arr.Name, t.Folder)
|
return filepath.Join(parent, t.Arr.Name, t.Folder)
|
||||||
}
|
}
|
||||||
@@ -106,10 +92,10 @@ type File struct {
|
|||||||
ByteRange *[2]int64 `json:"byte_range,omitempty"`
|
ByteRange *[2]int64 `json:"byte_range,omitempty"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Link string `json:"link"`
|
Link string `json:"link"`
|
||||||
DownloadLink *DownloadLink `json:"-"`
|
|
||||||
AccountId string `json:"account_id"`
|
AccountId string `json:"account_id"`
|
||||||
Generated time.Time `json:"generated"`
|
Generated time.Time `json:"generated"`
|
||||||
Deleted bool `json:"deleted"`
|
Deleted bool `json:"deleted"`
|
||||||
|
DownloadLink *DownloadLink `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Torrent) Cleanup(remove bool) {
|
func (t *Torrent) Cleanup(remove bool) {
|
||||||
@@ -121,13 +107,6 @@ func (t *Torrent) Cleanup(remove bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Account struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Disabled bool `json:"disabled"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type IngestData struct {
|
type IngestData struct {
|
||||||
Debrid string `json:"debrid"`
|
Debrid string `json:"debrid"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -149,3 +128,17 @@ type Profile struct {
|
|||||||
BadTorrents int `json:"bad_torrents"`
|
BadTorrents int `json:"bad_torrents"`
|
||||||
ActiveLinks int `json:"active_links"`
|
ActiveLinks int `json:"active_links"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DownloadLink struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Link string `json:"link"`
|
||||||
|
DownloadLink string `json:"download_link"`
|
||||||
|
Generated time.Time `json:"generated"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Id string `json:"id"`
|
||||||
|
ExpiresAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DownloadLink) String() string {
|
||||||
|
return d.DownloadLink
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user