Torrent Queuing for Botched torrent (#83)
* Implement a queue for handling failed torrent * Add checks for getting slots * Few other cleanups, change some function names
This commit is contained in:
+94
-72
@@ -2,6 +2,7 @@ package debrid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirrobot01/decypharr/internal/config"
|
||||
"github.com/sirrobot01/decypharr/internal/logger"
|
||||
@@ -13,25 +14,34 @@ import (
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/providers/torbox"
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/store"
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Debrid struct {
|
||||
cache *store.Cache // Could be nil if not using WebDAV
|
||||
client types.Client // HTTP client for making requests to the debrid service
|
||||
}
|
||||
|
||||
func (de *Debrid) Client() types.Client {
|
||||
return de.client
|
||||
}
|
||||
|
||||
func (de *Debrid) Cache() *store.Cache {
|
||||
return de.cache
|
||||
}
|
||||
|
||||
type Storage struct {
|
||||
clients map[string]types.Client
|
||||
clientsLock sync.Mutex
|
||||
caches map[string]*store.Cache
|
||||
cachesLock sync.Mutex
|
||||
LastUsed string
|
||||
debrids map[string]*Debrid
|
||||
mu sync.RWMutex
|
||||
lastUsed string
|
||||
}
|
||||
|
||||
func NewStorage() *Storage {
|
||||
cfg := config.Get()
|
||||
clients := make(map[string]types.Client)
|
||||
|
||||
_logger := logger.Default()
|
||||
|
||||
caches := make(map[string]*store.Cache)
|
||||
debrids := make(map[string]*Debrid)
|
||||
|
||||
for _, dc := range cfg.Debrids {
|
||||
client, err := createDebridClient(dc)
|
||||
@@ -39,89 +49,100 @@ func NewStorage() *Storage {
|
||||
_logger.Error().Err(err).Str("Debrid", dc.Name).Msg("failed to connect to debrid client")
|
||||
continue
|
||||
}
|
||||
_log := client.GetLogger()
|
||||
var cache *store.Cache
|
||||
_log := client.Logger()
|
||||
if dc.UseWebDav {
|
||||
caches[dc.Name] = store.NewDebridCache(dc, client)
|
||||
cache = store.NewDebridCache(dc, client)
|
||||
_log.Info().Msg("Debrid Service started with WebDAV")
|
||||
} else {
|
||||
_log.Info().Msg("Debrid Service started")
|
||||
}
|
||||
clients[dc.Name] = client
|
||||
debrids[dc.Name] = &Debrid{
|
||||
cache: cache,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
d := &Storage{
|
||||
clients: clients,
|
||||
LastUsed: "",
|
||||
caches: caches,
|
||||
debrids: debrids,
|
||||
lastUsed: "",
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Storage) GetClient(name string) types.Client {
|
||||
d.clientsLock.Lock()
|
||||
defer d.clientsLock.Unlock()
|
||||
client, exists := d.clients[name]
|
||||
if !exists {
|
||||
return nil
|
||||
func (d *Storage) Debrid(name string) *Debrid {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
if debrid, exists := d.debrids[name]; exists {
|
||||
return debrid
|
||||
}
|
||||
return client
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Storage) Debrids() map[string]*Debrid {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
debridsCopy := make(map[string]*Debrid)
|
||||
for name, debrid := range d.debrids {
|
||||
if debrid != nil {
|
||||
debridsCopy[name] = debrid
|
||||
}
|
||||
}
|
||||
return debridsCopy
|
||||
}
|
||||
|
||||
func (d *Storage) Client(name string) types.Client {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
if client, exists := d.debrids[name]; exists {
|
||||
return client.client
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Storage) Reset() {
|
||||
d.clientsLock.Lock()
|
||||
d.clients = make(map[string]types.Client)
|
||||
d.clientsLock.Unlock()
|
||||
|
||||
d.cachesLock.Lock()
|
||||
d.caches = make(map[string]*store.Cache)
|
||||
d.cachesLock.Unlock()
|
||||
d.LastUsed = ""
|
||||
d.mu.Lock()
|
||||
d.debrids = make(map[string]*Debrid)
|
||||
d.mu.Unlock()
|
||||
d.lastUsed = ""
|
||||
}
|
||||
|
||||
func (d *Storage) GetClients() map[string]types.Client {
|
||||
d.clientsLock.Lock()
|
||||
defer d.clientsLock.Unlock()
|
||||
func (d *Storage) Clients() map[string]types.Client {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
clientsCopy := make(map[string]types.Client)
|
||||
for name, client := range d.clients {
|
||||
clientsCopy[name] = client
|
||||
for name, debrid := range d.debrids {
|
||||
if debrid != nil && debrid.client != nil {
|
||||
clientsCopy[name] = debrid.client
|
||||
}
|
||||
}
|
||||
return clientsCopy
|
||||
}
|
||||
|
||||
func (d *Storage) GetCaches() map[string]*store.Cache {
|
||||
d.clientsLock.Lock()
|
||||
defer d.clientsLock.Unlock()
|
||||
func (d *Storage) Caches() map[string]*store.Cache {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
cachesCopy := make(map[string]*store.Cache)
|
||||
for name, cache := range d.caches {
|
||||
cachesCopy[name] = cache
|
||||
for name, debrid := range d.debrids {
|
||||
if debrid != nil && debrid.cache != nil {
|
||||
cachesCopy[name] = debrid.cache
|
||||
}
|
||||
}
|
||||
return cachesCopy
|
||||
}
|
||||
|
||||
func (d *Storage) FilterClients(filter func(types.Client) bool) map[string]types.Client {
|
||||
d.clientsLock.Lock()
|
||||
defer d.clientsLock.Unlock()
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
filteredClients := make(map[string]types.Client)
|
||||
for name, client := range d.clients {
|
||||
if filter(client) {
|
||||
filteredClients[name] = client
|
||||
for name, client := range d.debrids {
|
||||
if client != nil && filter(client.client) {
|
||||
filteredClients[name] = client.client
|
||||
}
|
||||
}
|
||||
return filteredClients
|
||||
}
|
||||
|
||||
func (d *Storage) FilterCaches(filter func(*store.Cache) bool) map[string]*store.Cache {
|
||||
d.cachesLock.Lock()
|
||||
defer d.cachesLock.Unlock()
|
||||
filteredCaches := make(map[string]*store.Cache)
|
||||
for name, cache := range d.caches {
|
||||
if filter(cache) {
|
||||
filteredCaches[name] = cache
|
||||
}
|
||||
}
|
||||
return filteredCaches
|
||||
}
|
||||
|
||||
func createDebridClient(dc config.Debrid) (types.Client, error) {
|
||||
switch dc.Name {
|
||||
case "realdebrid":
|
||||
@@ -137,7 +158,7 @@ func createDebridClient(dc config.Debrid) (types.Client, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func ProcessTorrent(ctx context.Context, store *Storage, selectedDebrid string, magnet *utils.Magnet, a *arr.Arr, isSymlink, overrideDownloadUncached bool) (*types.Torrent, error) {
|
||||
func Process(ctx context.Context, store *Storage, selectedDebrid string, magnet *utils.Magnet, a *arr.Arr, isSymlink, overrideDownloadUncached bool) (*types.Torrent, error) {
|
||||
|
||||
debridTorrent := &types.Torrent{
|
||||
InfoHash: magnet.InfoHash,
|
||||
@@ -149,7 +170,7 @@ func ProcessTorrent(ctx context.Context, store *Storage, selectedDebrid string,
|
||||
}
|
||||
|
||||
clients := store.FilterClients(func(c types.Client) bool {
|
||||
if selectedDebrid != "" && c.GetName() != selectedDebrid {
|
||||
if selectedDebrid != "" && c.Name() != selectedDebrid {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -173,9 +194,9 @@ func ProcessTorrent(ctx context.Context, store *Storage, selectedDebrid string,
|
||||
}
|
||||
|
||||
for index, db := range clients {
|
||||
_logger := db.GetLogger()
|
||||
_logger := db.Logger()
|
||||
_logger.Info().
|
||||
Str("Debrid", db.GetName()).
|
||||
Str("Debrid", db.Name()).
|
||||
Str("Arr", a.Name).
|
||||
Str("Hash", debridTorrent.InfoHash).
|
||||
Str("Name", debridTorrent.Name).
|
||||
@@ -191,8 +212,8 @@ func ProcessTorrent(ctx context.Context, store *Storage, selectedDebrid string,
|
||||
continue
|
||||
}
|
||||
dbt.Arr = a
|
||||
_logger.Info().Str("id", dbt.Id).Msgf("Torrent: %s submitted to %s", dbt.Name, db.GetName())
|
||||
store.LastUsed = index
|
||||
_logger.Info().Str("id", dbt.Id).Msgf("Torrent: %s submitted to %s", dbt.Name, db.Name())
|
||||
store.lastUsed = index
|
||||
|
||||
torrent, err := db.CheckStatus(dbt, isSymlink)
|
||||
if err != nil && torrent != nil && torrent.Id != "" {
|
||||
@@ -201,18 +222,19 @@ func ProcessTorrent(ctx context.Context, store *Storage, selectedDebrid string,
|
||||
_ = db.DeleteTorrent(id)
|
||||
}(torrent.Id)
|
||||
}
|
||||
return torrent, err
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
if torrent == nil {
|
||||
errs = append(errs, fmt.Errorf("torrent %s returned nil after checking status", dbt.Name))
|
||||
continue
|
||||
}
|
||||
return torrent, nil
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
return nil, fmt.Errorf("failed to process torrent: no clients available")
|
||||
}
|
||||
if len(errs) == 1 {
|
||||
return nil, fmt.Errorf("failed to process torrent: %w", errs[0])
|
||||
} else {
|
||||
errStrings := make([]string, 0, len(errs))
|
||||
for _, err := range errs {
|
||||
errStrings = append(errStrings, err.Error())
|
||||
}
|
||||
return nil, fmt.Errorf("failed to process torrent: %s", strings.Join(errStrings, ", "))
|
||||
}
|
||||
joinedErrors := errors.Join(errs...)
|
||||
return nil, fmt.Errorf("failed to process torrent: %w", joinedErrors)
|
||||
}
|
||||
|
||||
@@ -18,17 +18,18 @@ import (
|
||||
)
|
||||
|
||||
type AllDebrid struct {
|
||||
Name string
|
||||
name string
|
||||
Host string `json:"host"`
|
||||
APIKey string
|
||||
accounts map[string]types.Account
|
||||
DownloadUncached bool
|
||||
client *request.Client
|
||||
|
||||
MountPath string
|
||||
logger zerolog.Logger
|
||||
checkCached bool
|
||||
addSamples bool
|
||||
MountPath string
|
||||
logger zerolog.Logger
|
||||
checkCached bool
|
||||
addSamples bool
|
||||
minimumFreeSlot int
|
||||
}
|
||||
|
||||
func (ad *AllDebrid) GetProfile() (*types.Profile, error) {
|
||||
@@ -59,7 +60,7 @@ func New(dc config.Debrid) (*AllDebrid, error) {
|
||||
}
|
||||
}
|
||||
return &AllDebrid{
|
||||
Name: "alldebrid",
|
||||
name: "alldebrid",
|
||||
Host: "http://api.alldebrid.com/v4.1",
|
||||
APIKey: dc.APIKey,
|
||||
accounts: accounts,
|
||||
@@ -69,14 +70,15 @@ func New(dc config.Debrid) (*AllDebrid, error) {
|
||||
logger: logger.New(dc.Name),
|
||||
checkCached: dc.CheckCached,
|
||||
addSamples: dc.AddSamples,
|
||||
minimumFreeSlot: dc.MinimumFreeSlot,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ad *AllDebrid) GetName() string {
|
||||
return ad.Name
|
||||
func (ad *AllDebrid) Name() string {
|
||||
return ad.name
|
||||
}
|
||||
|
||||
func (ad *AllDebrid) GetLogger() zerolog.Logger {
|
||||
func (ad *AllDebrid) Logger() zerolog.Logger {
|
||||
return ad.logger
|
||||
}
|
||||
|
||||
@@ -204,7 +206,7 @@ func (ad *AllDebrid) GetTorrent(torrentId string) (*types.Torrent, error) {
|
||||
OriginalFilename: name,
|
||||
Files: make(map[string]types.File),
|
||||
InfoHash: data.Hash,
|
||||
Debrid: ad.Name,
|
||||
Debrid: ad.name,
|
||||
MountPath: ad.MountPath,
|
||||
Added: time.Unix(data.CompletionDate, 0).Format(time.RFC3339),
|
||||
}
|
||||
@@ -244,7 +246,7 @@ func (ad *AllDebrid) UpdateTorrent(t *types.Torrent) error {
|
||||
t.OriginalFilename = name
|
||||
t.Folder = name
|
||||
t.MountPath = ad.MountPath
|
||||
t.Debrid = ad.Name
|
||||
t.Debrid = ad.name
|
||||
t.Bytes = data.Size
|
||||
t.Seeders = data.Seeders
|
||||
t.Added = time.Unix(data.CompletionDate, 0).Format(time.RFC3339)
|
||||
@@ -406,7 +408,7 @@ func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) {
|
||||
OriginalFilename: magnet.Filename,
|
||||
Files: make(map[string]types.File),
|
||||
InfoHash: magnet.Hash,
|
||||
Debrid: ad.Name,
|
||||
Debrid: ad.name,
|
||||
MountPath: ad.MountPath,
|
||||
Added: time.Unix(magnet.CompletionDate, 0).Format(time.RFC3339),
|
||||
})
|
||||
@@ -444,3 +446,9 @@ func (ad *AllDebrid) ResetActiveDownloadKeys() {
|
||||
func (ad *AllDebrid) DeleteDownloadLink(linkId string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *AllDebrid) GetAvailableSlots() (int, error) {
|
||||
// This function is a placeholder for AllDebrid
|
||||
//TODO: Implement the logic to check available slots for AllDebrid
|
||||
return 0, fmt.Errorf("GetAvailableSlots not implemented for AllDebrid")
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
type DebridLink struct {
|
||||
Name string
|
||||
name string
|
||||
Host string `json:"host"`
|
||||
APIKey string
|
||||
accounts map[string]types.Account
|
||||
@@ -56,7 +56,7 @@ func New(dc config.Debrid) (*DebridLink, error) {
|
||||
}
|
||||
}
|
||||
return &DebridLink{
|
||||
Name: "debridlink",
|
||||
name: "debridlink",
|
||||
Host: "https://debrid-link.com/api/v2",
|
||||
APIKey: dc.APIKey,
|
||||
accounts: accounts,
|
||||
@@ -73,11 +73,11 @@ func (dl *DebridLink) GetProfile() (*types.Profile, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (dl *DebridLink) GetName() string {
|
||||
return dl.Name
|
||||
func (dl *DebridLink) Name() string {
|
||||
return dl.name
|
||||
}
|
||||
|
||||
func (dl *DebridLink) GetLogger() zerolog.Logger {
|
||||
func (dl *DebridLink) Logger() zerolog.Logger {
|
||||
return dl.logger
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ func (dl *DebridLink) GetTorrent(torrentId string) (*types.Torrent, error) {
|
||||
Filename: name,
|
||||
OriginalFilename: name,
|
||||
MountPath: dl.MountPath,
|
||||
Debrid: dl.Name,
|
||||
Debrid: dl.name,
|
||||
Added: time.Unix(t.Created, 0).Format(time.RFC3339),
|
||||
}
|
||||
cfg := config.Get()
|
||||
@@ -288,7 +288,7 @@ func (dl *DebridLink) SubmitMagnet(t *types.Torrent) (*types.Torrent, error) {
|
||||
t.Filename = name
|
||||
t.OriginalFilename = name
|
||||
t.MountPath = dl.MountPath
|
||||
t.Debrid = dl.Name
|
||||
t.Debrid = dl.name
|
||||
t.Added = time.Unix(data.Created, 0).Format(time.RFC3339)
|
||||
for _, f := range data.Files {
|
||||
file := types.File{
|
||||
@@ -428,7 +428,7 @@ func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) {
|
||||
OriginalFilename: t.Name,
|
||||
InfoHash: t.HashString,
|
||||
Files: make(map[string]types.File),
|
||||
Debrid: dl.Name,
|
||||
Debrid: dl.name,
|
||||
MountPath: dl.MountPath,
|
||||
Added: time.Unix(t.Created, 0).Format(time.RFC3339),
|
||||
}
|
||||
@@ -476,3 +476,8 @@ func (dl *DebridLink) ResetActiveDownloadKeys() {
|
||||
func (dl *DebridLink) DeleteDownloadLink(linkId string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dl *DebridLink) GetAvailableSlots() (int, error) {
|
||||
//TODO: Implement the logic to check available slots for DebridLink
|
||||
return 0, fmt.Errorf("GetAvailableSlots not implemented for DebridLink")
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
type RealDebrid struct {
|
||||
Name string
|
||||
name string
|
||||
Host string `json:"host"`
|
||||
|
||||
APIKey string
|
||||
@@ -41,10 +41,12 @@ type RealDebrid struct {
|
||||
logger zerolog.Logger
|
||||
UnpackRar bool
|
||||
|
||||
rarSemaphore chan struct{}
|
||||
checkCached bool
|
||||
addSamples bool
|
||||
Profile *types.Profile
|
||||
rarSemaphore chan struct{}
|
||||
checkCached bool
|
||||
addSamples bool
|
||||
Profile *types.Profile
|
||||
minimumFreeSlot int // Minimum number of active pots to maintain (used for cached stuffs, etc.)
|
||||
|
||||
}
|
||||
|
||||
func New(dc config.Debrid) (*RealDebrid, error) {
|
||||
@@ -71,7 +73,7 @@ func New(dc config.Debrid) (*RealDebrid, error) {
|
||||
}
|
||||
|
||||
r := &RealDebrid{
|
||||
Name: "realdebrid",
|
||||
name: "realdebrid",
|
||||
Host: "https://api.real-debrid.com/rest/1.0",
|
||||
APIKey: dc.APIKey,
|
||||
accounts: accounts,
|
||||
@@ -98,6 +100,7 @@ func New(dc config.Debrid) (*RealDebrid, error) {
|
||||
rarSemaphore: make(chan struct{}, 2),
|
||||
checkCached: dc.CheckCached,
|
||||
addSamples: dc.AddSamples,
|
||||
minimumFreeSlot: dc.MinimumFreeSlot,
|
||||
}
|
||||
|
||||
if _, err := r.GetProfile(); err != nil {
|
||||
@@ -107,11 +110,11 @@ func New(dc config.Debrid) (*RealDebrid, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RealDebrid) GetName() string {
|
||||
return r.Name
|
||||
func (r *RealDebrid) Name() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
func (r *RealDebrid) GetLogger() zerolog.Logger {
|
||||
func (r *RealDebrid) Logger() zerolog.Logger {
|
||||
return r.logger
|
||||
}
|
||||
|
||||
@@ -337,15 +340,30 @@ func (r *RealDebrid) addTorrent(t *types.Torrent) (*types.Torrent, error) {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/x-bittorrent")
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
resp, err := r.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(resp, &data); err != nil {
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||
// Handle multiple_downloads
|
||||
|
||||
if resp.StatusCode == 509 {
|
||||
return nil, utils.TooManyActiveDownloadsError
|
||||
}
|
||||
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("realdebrid API error: Status: %d || Body: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
if err = json.Unmarshal(bodyBytes, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Id = data.Id
|
||||
t.Debrid = r.Name
|
||||
t.Debrid = r.name
|
||||
t.MountPath = r.MountPath
|
||||
return t, nil
|
||||
}
|
||||
@@ -357,15 +375,30 @@ func (r *RealDebrid) addMagnet(t *types.Torrent) (*types.Torrent, error) {
|
||||
}
|
||||
var data AddMagnetSchema
|
||||
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
resp, err := r.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(resp, &data); err != nil {
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||
// Handle multiple_downloads
|
||||
|
||||
if resp.StatusCode == 509 {
|
||||
return nil, utils.TooManyActiveDownloadsError
|
||||
}
|
||||
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("realdebrid API error: Status: %d || Body: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
if err = json.Unmarshal(bodyBytes, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Id = data.Id
|
||||
t.Debrid = r.Name
|
||||
t.Debrid = r.name
|
||||
t.MountPath = r.MountPath
|
||||
return t, nil
|
||||
}
|
||||
@@ -384,7 +417,7 @@ func (r *RealDebrid) GetTorrent(torrentId string) (*types.Torrent, error) {
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, request.TorrentNotFoundError
|
||||
return nil, utils.TorrentNotFoundError
|
||||
}
|
||||
return nil, fmt.Errorf("realdebrid API error: Status: %d || Body: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
@@ -406,7 +439,7 @@ func (r *RealDebrid) GetTorrent(torrentId string) (*types.Torrent, error) {
|
||||
Filename: data.Filename,
|
||||
OriginalFilename: data.OriginalFilename,
|
||||
Links: data.Links,
|
||||
Debrid: r.Name,
|
||||
Debrid: r.name,
|
||||
MountPath: r.MountPath,
|
||||
}
|
||||
t.Files = r.getTorrentFiles(t, data) // Get selected files
|
||||
@@ -427,7 +460,7 @@ func (r *RealDebrid) UpdateTorrent(t *types.Torrent) error {
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return request.TorrentNotFoundError
|
||||
return utils.TorrentNotFoundError
|
||||
}
|
||||
return fmt.Errorf("realdebrid API error: Status: %d || Body: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
@@ -447,7 +480,7 @@ func (r *RealDebrid) UpdateTorrent(t *types.Torrent) error {
|
||||
t.OriginalFilename = data.OriginalFilename
|
||||
t.Links = data.Links
|
||||
t.MountPath = r.MountPath
|
||||
t.Debrid = r.Name
|
||||
t.Debrid = r.name
|
||||
t.Added = data.Added
|
||||
t.Files, _ = r.getSelectedFiles(t, data) // Get selected files
|
||||
|
||||
@@ -478,7 +511,7 @@ func (r *RealDebrid) CheckStatus(t *types.Torrent, isSymlink bool) (*types.Torre
|
||||
t.Seeders = data.Seeders
|
||||
t.Links = data.Links
|
||||
t.Status = status
|
||||
t.Debrid = r.Name
|
||||
t.Debrid = r.name
|
||||
t.MountPath = r.MountPath
|
||||
if status == "waiting_files_selection" {
|
||||
t.Files = r.getTorrentFiles(t, data)
|
||||
@@ -499,6 +532,9 @@ func (r *RealDebrid) CheckStatus(t *types.Torrent, isSymlink bool) (*types.Torre
|
||||
return t, err
|
||||
}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
if res.StatusCode == 509 {
|
||||
return nil, utils.TooManyActiveDownloadsError
|
||||
}
|
||||
return t, fmt.Errorf("realdebrid API error: Status: %d", res.StatusCode)
|
||||
}
|
||||
} else if status == "downloaded" {
|
||||
@@ -593,7 +629,7 @@ func (r *RealDebrid) CheckLink(link string) error {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return request.HosterUnavailableError // File has been removed
|
||||
return utils.HosterUnavailableError // File has been removed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -622,17 +658,17 @@ func (r *RealDebrid) _getDownloadLink(file *types.File) (*types.DownloadLink, er
|
||||
}
|
||||
switch data.ErrorCode {
|
||||
case 19:
|
||||
return nil, request.HosterUnavailableError // File has been removed
|
||||
return nil, utils.HosterUnavailableError // File has been removed
|
||||
case 23:
|
||||
return nil, request.TrafficExceededError
|
||||
return nil, utils.TrafficExceededError
|
||||
case 24:
|
||||
return nil, request.HosterUnavailableError // Link has been nerfed
|
||||
return nil, utils.HosterUnavailableError // Link has been nerfed
|
||||
case 34:
|
||||
return nil, request.TrafficExceededError // traffic exceeded
|
||||
return nil, utils.TrafficExceededError // traffic exceeded
|
||||
case 35:
|
||||
return nil, request.HosterUnavailableError
|
||||
return nil, utils.HosterUnavailableError
|
||||
case 36:
|
||||
return nil, request.TrafficExceededError // traffic exceeded
|
||||
return nil, utils.TrafficExceededError // traffic exceeded
|
||||
default:
|
||||
return nil, fmt.Errorf("realdebrid API error: Status: %d || Code: %d", resp.StatusCode, data.ErrorCode)
|
||||
}
|
||||
@@ -674,7 +710,7 @@ func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
|
||||
downloadLink, err := r._getDownloadLink(file)
|
||||
retries := 0
|
||||
if err != nil {
|
||||
if errors.Is(err, request.TrafficExceededError) {
|
||||
if errors.Is(err, utils.TrafficExceededError) {
|
||||
// Retries generating
|
||||
retries = 5
|
||||
} else {
|
||||
@@ -688,7 +724,7 @@ func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
|
||||
if err == nil {
|
||||
return downloadLink, nil
|
||||
}
|
||||
if !errors.Is(err, request.TrafficExceededError) {
|
||||
if !errors.Is(err, utils.TrafficExceededError) {
|
||||
return nil, err
|
||||
}
|
||||
// Add a delay before retrying
|
||||
@@ -750,7 +786,7 @@ func (r *RealDebrid) getTorrents(offset int, limit int) (int, []*types.Torrent,
|
||||
Links: t.Links,
|
||||
Files: make(map[string]types.File),
|
||||
InfoHash: t.Hash,
|
||||
Debrid: r.Name,
|
||||
Debrid: r.name,
|
||||
MountPath: r.MountPath,
|
||||
Added: t.Added.Format(time.RFC3339),
|
||||
})
|
||||
@@ -941,3 +977,17 @@ func (r *RealDebrid) GetProfile() (*types.Profile, error) {
|
||||
}
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
func (r *RealDebrid) GetAvailableSlots() (int, error) {
|
||||
url := fmt.Sprintf("%s/torrents/activeCount", r.Host)
|
||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
resp, err := r.client.MakeRequest(req)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
var data AvailableSlotsResponse
|
||||
if json.Unmarshal(resp, &data) != nil {
|
||||
return 0, fmt.Errorf("error unmarshalling available slots response: %w", err)
|
||||
}
|
||||
return data.TotalSlots - data.ActiveSlots - r.minimumFreeSlot, nil // Ensure we maintain minimum active pots
|
||||
}
|
||||
|
||||
@@ -151,3 +151,8 @@ type profileResponse struct {
|
||||
Premium int `json:"premium"`
|
||||
Expiration time.Time `json:"expiration"`
|
||||
}
|
||||
|
||||
type AvailableSlotsResponse struct {
|
||||
ActiveSlots int `json:"nb"`
|
||||
TotalSlots int `json:"limit"`
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
type Torbox struct {
|
||||
Name string
|
||||
name string
|
||||
Host string `json:"host"`
|
||||
APIKey string
|
||||
accounts map[string]types.Account
|
||||
@@ -67,7 +67,7 @@ func New(dc config.Debrid) (*Torbox, error) {
|
||||
}
|
||||
|
||||
return &Torbox{
|
||||
Name: "torbox",
|
||||
name: "torbox",
|
||||
Host: "https://api.torbox.app/v1",
|
||||
APIKey: dc.APIKey,
|
||||
accounts: accounts,
|
||||
@@ -80,11 +80,11 @@ func New(dc config.Debrid) (*Torbox, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tb *Torbox) GetName() string {
|
||||
return tb.Name
|
||||
func (tb *Torbox) Name() string {
|
||||
return tb.name
|
||||
}
|
||||
|
||||
func (tb *Torbox) GetLogger() zerolog.Logger {
|
||||
func (tb *Torbox) Logger() zerolog.Logger {
|
||||
return tb.logger
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ func (tb *Torbox) SubmitMagnet(torrent *types.Torrent) (*types.Torrent, error) {
|
||||
torrentId := strconv.Itoa(dt.Id)
|
||||
torrent.Id = torrentId
|
||||
torrent.MountPath = tb.MountPath
|
||||
torrent.Debrid = tb.Name
|
||||
torrent.Debrid = tb.name
|
||||
|
||||
return torrent, nil
|
||||
}
|
||||
@@ -215,7 +215,7 @@ func (tb *Torbox) GetTorrent(torrentId string) (*types.Torrent, error) {
|
||||
Filename: data.Name,
|
||||
OriginalFilename: data.Name,
|
||||
MountPath: tb.MountPath,
|
||||
Debrid: tb.Name,
|
||||
Debrid: tb.name,
|
||||
Files: make(map[string]types.File),
|
||||
Added: data.CreatedAt.Format(time.RFC3339),
|
||||
}
|
||||
@@ -250,7 +250,7 @@ func (tb *Torbox) GetTorrent(torrentId string) (*types.Torrent, error) {
|
||||
}
|
||||
|
||||
t.OriginalFilename = strings.Split(cleanPath, "/")[0]
|
||||
t.Debrid = tb.Name
|
||||
t.Debrid = tb.name
|
||||
|
||||
return t, nil
|
||||
}
|
||||
@@ -279,7 +279,7 @@ func (tb *Torbox) UpdateTorrent(t *types.Torrent) error {
|
||||
t.Filename = name
|
||||
t.OriginalFilename = name
|
||||
t.MountPath = tb.MountPath
|
||||
t.Debrid = tb.Name
|
||||
t.Debrid = tb.name
|
||||
cfg := config.Get()
|
||||
for _, f := range data.Files {
|
||||
fileName := filepath.Base(f.Name)
|
||||
@@ -311,7 +311,7 @@ func (tb *Torbox) UpdateTorrent(t *types.Torrent) error {
|
||||
}
|
||||
|
||||
t.OriginalFilename = strings.Split(cleanPath, "/")[0]
|
||||
t.Debrid = tb.Name
|
||||
t.Debrid = tb.name
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -470,3 +470,8 @@ func (tb *Torbox) ResetActiveDownloadKeys() {
|
||||
func (tb *Torbox) DeleteDownloadLink(linkId string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tb *Torbox) GetAvailableSlots() (int, error) {
|
||||
//TODO: Implement the logic to check available slots for Torbox
|
||||
return 0, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ func NewDebridCache(dc config.Debrid, client types.Client) *Cache {
|
||||
customFolders = append(customFolders, name)
|
||||
|
||||
}
|
||||
_log := logger.New(fmt.Sprintf("%s-webdav", client.GetName()))
|
||||
_log := logger.New(fmt.Sprintf("%s-webdav", client.Name()))
|
||||
c := &Cache{
|
||||
dir: filepath.Join(cfg.Path, "cache", dc.Name), // path to save cache files
|
||||
|
||||
@@ -248,7 +248,7 @@ func (c *Cache) Start(ctx context.Context) error {
|
||||
go c.repairWorker(ctx)
|
||||
|
||||
cfg := config.Get()
|
||||
name := c.client.GetName()
|
||||
name := c.client.Name()
|
||||
addr := cfg.BindAddress + ":" + cfg.Port + cfg.URLBase + "webdav/" + name + "/"
|
||||
c.logger.Info().Msgf("%s WebDav server running at %s", name, addr)
|
||||
|
||||
@@ -379,7 +379,7 @@ func (c *Cache) Sync(ctx context.Context) error {
|
||||
|
||||
totalTorrents := len(torrents)
|
||||
|
||||
c.logger.Info().Msgf("%d torrents found from %s", totalTorrents, c.client.GetName())
|
||||
c.logger.Info().Msgf("%d torrents found from %s", totalTorrents, c.client.Name())
|
||||
|
||||
newTorrents := make([]*types.Torrent, 0)
|
||||
idStore := make(map[string]struct{}, totalTorrents)
|
||||
@@ -719,7 +719,7 @@ func (c *Cache) Add(t *types.Torrent) error {
|
||||
|
||||
}
|
||||
|
||||
func (c *Cache) GetClient() types.Client {
|
||||
func (c *Cache) Client() types.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
@@ -866,6 +866,6 @@ func (c *Cache) RemoveFile(torrentId string, filename string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) GetLogger() zerolog.Logger {
|
||||
func (c *Cache) Logger() zerolog.Logger {
|
||||
return c.logger
|
||||
}
|
||||
|
||||
@@ -3,12 +3,11 @@ package store
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirrobot01/decypharr/internal/utils"
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sirrobot01/decypharr/internal/request"
|
||||
)
|
||||
|
||||
type linkCache struct {
|
||||
@@ -146,7 +145,7 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (strin
|
||||
c.logger.Trace().Msgf("Getting download link for %s(%s)", filename, file.Link)
|
||||
downloadLink, err := c.client.GetDownloadLink(ct.Torrent, &file)
|
||||
if err != nil {
|
||||
if errors.Is(err, request.HosterUnavailableError) {
|
||||
if errors.Is(err, utils.HosterUnavailableError) {
|
||||
newCt, err := c.reInsertTorrent(ct)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to reinsert torrent: %w", err)
|
||||
@@ -166,7 +165,7 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (strin
|
||||
}
|
||||
c.updateDownloadLink(downloadLink)
|
||||
return "", nil
|
||||
} else if errors.Is(err, request.TrafficExceededError) {
|
||||
} else if errors.Is(err, utils.TrafficExceededError) {
|
||||
// This is likely a fair usage limit error
|
||||
return "", err
|
||||
} else {
|
||||
|
||||
@@ -28,7 +28,7 @@ func mergeFiles(torrents ...CachedTorrent) map[string]types.File {
|
||||
|
||||
func (c *Cache) GetIngests() ([]types.IngestData, error) {
|
||||
torrents := c.GetTorrents()
|
||||
debridName := c.client.GetName()
|
||||
debridName := c.client.Name()
|
||||
var ingests []types.IngestData
|
||||
for _, torrent := range torrents {
|
||||
ingests = append(ingests, types.IngestData{
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirrobot01/decypharr/internal/request"
|
||||
"github.com/sirrobot01/decypharr/internal/utils"
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
"sync"
|
||||
@@ -98,7 +97,7 @@ func (c *Cache) GetBrokenFiles(t *CachedTorrent, filenames []string) []string {
|
||||
} else {
|
||||
// Check if file.Link not in the downloadLink Cache
|
||||
if err := c.client.CheckLink(f.Link); err != nil {
|
||||
if errors.Is(err, request.HosterUnavailableError) {
|
||||
if errors.Is(err, utils.HosterUnavailableError) {
|
||||
brokenFiles = append(brokenFiles, f.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ type Client interface {
|
||||
UpdateTorrent(torrent *Torrent) error
|
||||
GetTorrent(torrentId string) (*Torrent, error)
|
||||
GetTorrents() ([]*Torrent, error)
|
||||
GetName() string
|
||||
GetLogger() zerolog.Logger
|
||||
Name() string
|
||||
Logger() zerolog.Logger
|
||||
GetDownloadingStatus() []string
|
||||
GetDownloads() (map[string]DownloadLink, error)
|
||||
CheckLink(link string) error
|
||||
@@ -26,4 +26,5 @@ type Client interface {
|
||||
ResetActiveDownloadKeys()
|
||||
DeleteDownloadLink(linkId string) error
|
||||
GetProfile() (*Profile, error)
|
||||
GetAvailableSlots() (int, error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user