minor bug fixes; improvements, final-beta-pre-stable
This commit is contained in:
@@ -3,7 +3,6 @@ package alldebrid
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/sirrobot01/decypharr/internal/config"
|
||||
"github.com/sirrobot01/decypharr/internal/logger"
|
||||
@@ -13,7 +12,6 @@ import (
|
||||
"net/http"
|
||||
gourl "net/url"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -23,7 +21,8 @@ type AllDebrid struct {
|
||||
Name string
|
||||
Host string `json:"host"`
|
||||
APIKey string
|
||||
DownloadKeys *xsync.MapOf[string, types.Account]
|
||||
accounts map[string]types.Account
|
||||
accountsMu sync.RWMutex
|
||||
DownloadUncached bool
|
||||
client *request.Client
|
||||
|
||||
@@ -46,20 +45,20 @@ func New(dc config.Debrid) *AllDebrid {
|
||||
request.WithProxy(dc.Proxy),
|
||||
)
|
||||
|
||||
accounts := xsync.NewMapOf[string, types.Account]()
|
||||
accounts := make(map[string]types.Account)
|
||||
for idx, key := range dc.DownloadAPIKeys {
|
||||
id := strconv.Itoa(idx)
|
||||
accounts.Store(id, types.Account{
|
||||
accounts[id] = types.Account{
|
||||
Name: key,
|
||||
ID: id,
|
||||
Token: key,
|
||||
})
|
||||
}
|
||||
}
|
||||
return &AllDebrid{
|
||||
Name: "alldebrid",
|
||||
Host: "http://api.alldebrid.com/v4.1",
|
||||
APIKey: dc.APIKey,
|
||||
DownloadKeys: accounts,
|
||||
accounts: accounts,
|
||||
DownloadUncached: dc.DownloadUncached,
|
||||
client: client,
|
||||
MountPath: dc.Folder,
|
||||
@@ -273,7 +272,7 @@ func (ad *AllDebrid) CheckStatus(torrent *types.Torrent, isSymlink bool) (*types
|
||||
}
|
||||
}
|
||||
break
|
||||
} else if slices.Contains(ad.GetDownloadingStatus(), status) {
|
||||
} else if utils.Contains(ad.GetDownloadingStatus(), status) {
|
||||
if !torrent.DownloadUncached {
|
||||
return torrent, fmt.Errorf("torrent: %s not cached", torrent.Name)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -49,15 +48,6 @@ type CachedTorrent struct {
|
||||
DuplicateIds []string `json:"duplicate_ids"`
|
||||
}
|
||||
|
||||
func (ct *CachedTorrent) addDuplicateId(id string) {
|
||||
if ct.DuplicateIds == nil {
|
||||
ct.DuplicateIds = make([]string, 0)
|
||||
}
|
||||
if !slices.Contains(ct.DuplicateIds, id) {
|
||||
ct.DuplicateIds = append(ct.DuplicateIds, id)
|
||||
}
|
||||
}
|
||||
|
||||
type downloadLinkCache struct {
|
||||
Id string
|
||||
Link string
|
||||
@@ -93,9 +83,8 @@ type Cache struct {
|
||||
folderNaming WebDavFolderNaming
|
||||
|
||||
// monitors
|
||||
repairRequest sync.Map
|
||||
failedToReinsert sync.Map
|
||||
downloadLinkRequests sync.Map
|
||||
repairRequest sync.Map
|
||||
failedToReinsert sync.Map
|
||||
|
||||
// repair
|
||||
repairChan chan RepairRequest
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/realdebrid"
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/torbox"
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func createDebridClient(dc config.Debrid) types.Client {
|
||||
@@ -38,54 +39,69 @@ func ProcessTorrent(d *Engine, magnet *utils.Magnet, a *arr.Arr, isSymlink, over
|
||||
Files: make(map[string]types.File),
|
||||
}
|
||||
|
||||
errs := make([]error, 0)
|
||||
errs := make([]error, 0, len(d.Clients))
|
||||
|
||||
// Override first, arr second, debrid third
|
||||
|
||||
if overrideDownloadUncached {
|
||||
debridTorrent.DownloadUncached = true
|
||||
} else if a.DownloadUncached != nil {
|
||||
// Arr cached is set
|
||||
debridTorrent.DownloadUncached = *a.DownloadUncached
|
||||
} else {
|
||||
debridTorrent.DownloadUncached = false
|
||||
}
|
||||
|
||||
for index, db := range d.Clients {
|
||||
logger := db.GetLogger()
|
||||
logger.Info().Msgf("Processing debrid: %s", db.GetName())
|
||||
|
||||
// Override first, arr second, debrid third
|
||||
|
||||
if overrideDownloadUncached {
|
||||
debridTorrent.DownloadUncached = true
|
||||
} else if a.DownloadUncached != nil {
|
||||
// Arr cached is set
|
||||
debridTorrent.DownloadUncached = *a.DownloadUncached
|
||||
} else {
|
||||
if !overrideDownloadUncached && a.DownloadUncached == nil {
|
||||
debridTorrent.DownloadUncached = db.GetDownloadUncached()
|
||||
}
|
||||
|
||||
logger.Info().Msgf("Torrent Hash: %s", debridTorrent.InfoHash)
|
||||
if db.GetCheckCached() {
|
||||
hash, exists := db.IsAvailable([]string{debridTorrent.InfoHash})[debridTorrent.InfoHash]
|
||||
if !exists || !hash {
|
||||
logger.Info().Msgf("Torrent: %s is not cached", debridTorrent.Name)
|
||||
continue
|
||||
} else {
|
||||
logger.Info().Msgf("Torrent: %s is cached(or downloading)", debridTorrent.Name)
|
||||
}
|
||||
}
|
||||
|
||||
//if db.GetCheckCached() {
|
||||
// hash, exists := db.IsAvailable([]string{debridTorrent.InfoHash})[debridTorrent.InfoHash]
|
||||
// if !exists || !hash {
|
||||
// logger.Info().Msgf("Torrent: %s is not cached", debridTorrent.Name)
|
||||
// continue
|
||||
// } else {
|
||||
// logger.Info().Msgf("Torrent: %s is cached(or downloading)", debridTorrent.Name)
|
||||
// }
|
||||
//}
|
||||
|
||||
dbt, err := db.SubmitMagnet(debridTorrent)
|
||||
if dbt != nil {
|
||||
dbt.Arr = a
|
||||
}
|
||||
if err != nil || dbt == nil || dbt.Id == "" {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
dbt.Arr = a
|
||||
logger.Info().Msgf("Torrent: %s(id=%s) submitted to %s", dbt.Name, dbt.Id, db.GetName())
|
||||
d.LastUsed = index
|
||||
|
||||
torrent, err := db.CheckStatus(dbt, isSymlink)
|
||||
if err != nil && torrent != nil && torrent.Id != "" {
|
||||
// Delete the torrent if it was not downloaded
|
||||
_ = db.DeleteTorrent(torrent.Id)
|
||||
go func(id string) {
|
||||
_ = db.DeleteTorrent(id)
|
||||
}(torrent.Id)
|
||||
}
|
||||
return torrent, err
|
||||
}
|
||||
err := fmt.Errorf("failed to process torrent")
|
||||
for _, e := range errs {
|
||||
err = fmt.Errorf("%w\n%w", err, e)
|
||||
if len(errs) == 0 {
|
||||
return nil, fmt.Errorf("failed to process torrent: no clients available")
|
||||
}
|
||||
return nil, err
|
||||
var errBuilder strings.Builder
|
||||
errBuilder.WriteString("failed to process torrent:")
|
||||
|
||||
for _, e := range errs {
|
||||
if e != nil {
|
||||
errBuilder.WriteString("\n")
|
||||
errBuilder.WriteString(e.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(errBuilder.String())
|
||||
}
|
||||
|
||||
@@ -8,51 +8,14 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type downloadLinkRequest struct {
|
||||
result string
|
||||
err error
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newDownloadLinkRequest() *downloadLinkRequest {
|
||||
return &downloadLinkRequest{
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *downloadLinkRequest) Complete(result string, err error) {
|
||||
r.result = result
|
||||
r.err = err
|
||||
close(r.done)
|
||||
}
|
||||
|
||||
func (r *downloadLinkRequest) Wait() (string, error) {
|
||||
<-r.done
|
||||
return r.result, r.err
|
||||
}
|
||||
|
||||
func (c *Cache) GetDownloadLink(torrentName, filename, fileLink string) (string, error) {
|
||||
// Check link cache
|
||||
if dl := c.checkDownloadLink(fileLink); dl != "" {
|
||||
return dl, nil
|
||||
}
|
||||
|
||||
if req, inFlight := c.downloadLinkRequests.Load(fileLink); inFlight {
|
||||
// Wait for the other request to complete and use its result
|
||||
result := req.(*downloadLinkRequest)
|
||||
return result.Wait()
|
||||
}
|
||||
|
||||
// Create a new request object
|
||||
req := newDownloadLinkRequest()
|
||||
c.downloadLinkRequests.Store(fileLink, req)
|
||||
|
||||
downloadLink, err := c.fetchDownloadLink(torrentName, filename, fileLink)
|
||||
|
||||
// Complete the request and remove it from the map
|
||||
req.Complete(downloadLink, err)
|
||||
c.downloadLinkRequests.Delete(fileLink)
|
||||
|
||||
return downloadLink, err
|
||||
}
|
||||
|
||||
|
||||
@@ -128,23 +128,17 @@ func (c *Cache) refreshTorrents() {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for t := range workChan {
|
||||
select {
|
||||
default:
|
||||
if err := c.ProcessTorrent(t); err != nil {
|
||||
c.logger.Error().Err(err).Msgf("Failed to process new torrent %s", t.Id)
|
||||
errChan <- err
|
||||
}
|
||||
counter++
|
||||
if err := c.ProcessTorrent(t); err != nil {
|
||||
c.logger.Error().Err(err).Msgf("Failed to process new torrent %s", t.Id)
|
||||
errChan <- err
|
||||
}
|
||||
counter++
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for _, t := range newTorrents {
|
||||
select {
|
||||
default:
|
||||
workChan <- t
|
||||
}
|
||||
workChan <- t
|
||||
}
|
||||
close(workChan)
|
||||
wg.Wait()
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/sirrobot01/decypharr/internal/request"
|
||||
"github.com/sirrobot01/decypharr/internal/utils"
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -43,7 +42,7 @@ func (c *Cache) IsTorrentBroken(t *CachedTorrent, filenames []string) bool {
|
||||
files := make(map[string]types.File)
|
||||
if len(filenames) > 0 {
|
||||
for name, f := range t.Files {
|
||||
if slices.Contains(filenames, name) {
|
||||
if utils.Contains(filenames, name) {
|
||||
files[name] = f
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,32 +10,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// resetPropfindResponse resets the propfind response cache for the specified parent directories.
|
||||
func (c *Cache) resetPropfindResponse() error {
|
||||
// Right now, parents are hardcoded
|
||||
parents := []string{"__all__", "torrents"}
|
||||
// Reset only the parent directories
|
||||
// Convert the parents to a keys
|
||||
// This is a bit hacky, but it works
|
||||
// Instead of deleting all the keys, we only delete the parent keys, e.g __all__/ or torrents/
|
||||
keys := make([]string, 0, len(parents))
|
||||
for _, p := range parents {
|
||||
// Construct the key
|
||||
// construct url
|
||||
url := path.Clean(path.Join("/webdav", c.client.GetName(), p))
|
||||
key0 := fmt.Sprintf("propfind:%s:0", url)
|
||||
key1 := fmt.Sprintf("propfind:%s:1", url)
|
||||
keys = append(keys, key0, key1)
|
||||
}
|
||||
|
||||
// Delete the keys
|
||||
for _, k := range keys {
|
||||
c.PropfindResp.Delete(k)
|
||||
}
|
||||
c.logger.Trace().Msgf("Reset XML cache for %s", c.client.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) refreshParentXml() error {
|
||||
parents := []string{"__all__", "torrents"}
|
||||
torrents := c.GetListing()
|
||||
|
||||
@@ -4,15 +4,14 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/sirrobot01/decypharr/internal/config"
|
||||
"github.com/sirrobot01/decypharr/internal/logger"
|
||||
"github.com/sirrobot01/decypharr/internal/request"
|
||||
"github.com/sirrobot01/decypharr/internal/utils"
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
@@ -23,7 +22,8 @@ type DebridLink struct {
|
||||
Name string
|
||||
Host string `json:"host"`
|
||||
APIKey string
|
||||
DownloadKeys *xsync.MapOf[string, types.Account]
|
||||
accounts map[string]types.Account
|
||||
accountsMutex sync.RWMutex
|
||||
DownloadUncached bool
|
||||
client *request.Client
|
||||
|
||||
@@ -286,7 +286,7 @@ func (dl *DebridLink) CheckStatus(torrent *types.Torrent, isSymlink bool) (*type
|
||||
return torrent, err
|
||||
}
|
||||
break
|
||||
} else if slices.Contains(dl.GetDownloadingStatus(), status) {
|
||||
} else if utils.Contains(dl.GetDownloadingStatus(), status) {
|
||||
if !torrent.DownloadUncached {
|
||||
return torrent, fmt.Errorf("torrent: %s not cached", torrent.Name)
|
||||
}
|
||||
@@ -351,20 +351,20 @@ func New(dc config.Debrid) *DebridLink {
|
||||
request.WithProxy(dc.Proxy),
|
||||
)
|
||||
|
||||
accounts := xsync.NewMapOf[string, types.Account]()
|
||||
accounts := make(map[string]types.Account)
|
||||
for idx, key := range dc.DownloadAPIKeys {
|
||||
id := strconv.Itoa(idx)
|
||||
accounts.Store(id, types.Account{
|
||||
accounts[id] = types.Account{
|
||||
Name: key,
|
||||
ID: id,
|
||||
Token: key,
|
||||
})
|
||||
}
|
||||
}
|
||||
return &DebridLink{
|
||||
Name: "debridlink",
|
||||
Host: "https://debrid-link.com/api/v2",
|
||||
APIKey: dc.APIKey,
|
||||
DownloadKeys: accounts,
|
||||
accounts: accounts,
|
||||
DownloadUncached: dc.DownloadUncached,
|
||||
client: client,
|
||||
MountPath: dc.Folder,
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/sirrobot01/decypharr/internal/config"
|
||||
"github.com/sirrobot01/decypharr/internal/logger"
|
||||
@@ -16,7 +15,6 @@ import (
|
||||
"net/http"
|
||||
gourl "net/url"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -30,7 +28,8 @@ type RealDebrid struct {
|
||||
|
||||
APIKey string
|
||||
currentDownloadKey string
|
||||
DownloadKeys *xsync.MapOf[string, types.Account] // index | Account
|
||||
accounts map[string]types.Account
|
||||
accountsMutex sync.RWMutex
|
||||
|
||||
DownloadUncached bool
|
||||
client *request.Client
|
||||
@@ -49,15 +48,15 @@ func New(dc config.Debrid) *RealDebrid {
|
||||
}
|
||||
_log := logger.New(dc.Name)
|
||||
|
||||
accounts := xsync.NewMapOf[string, types.Account]()
|
||||
accounts := make(map[string]types.Account)
|
||||
currentDownloadKey := dc.DownloadAPIKeys[0]
|
||||
for idx, key := range dc.DownloadAPIKeys {
|
||||
id := strconv.Itoa(idx)
|
||||
accounts.Store(id, types.Account{
|
||||
accounts[id] = types.Account{
|
||||
Name: key,
|
||||
ID: id,
|
||||
Token: key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
downloadHeaders := map[string]string{
|
||||
@@ -68,7 +67,7 @@ func New(dc config.Debrid) *RealDebrid {
|
||||
Name: "realdebrid",
|
||||
Host: "https://api.real-debrid.com/rest/1.0",
|
||||
APIKey: dc.APIKey,
|
||||
DownloadKeys: accounts,
|
||||
accounts: accounts,
|
||||
DownloadUncached: dc.DownloadUncached,
|
||||
client: request.New(
|
||||
request.WithHeaders(headers),
|
||||
@@ -381,10 +380,13 @@ func (r *RealDebrid) CheckStatus(t *types.Torrent, isSymlink bool) (*types.Torre
|
||||
}
|
||||
payload := strings.NewReader(p.Encode())
|
||||
req, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/torrents/selectFiles/%s", r.Host, t.Id), payload)
|
||||
_, err = r.client.MakeRequest(req)
|
||||
res, err := r.client.Do(req)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
return t, fmt.Errorf("realdebrid API error: Status: %d", res.StatusCode)
|
||||
}
|
||||
} else if status == "downloaded" {
|
||||
t.Files = getSelectedFiles(t, data) // Get selected files
|
||||
r.logger.Info().Msgf("Torrent: %s downloaded to RD", t.Name)
|
||||
@@ -395,7 +397,7 @@ func (r *RealDebrid) CheckStatus(t *types.Torrent, isSymlink bool) (*types.Torre
|
||||
}
|
||||
}
|
||||
break
|
||||
} else if slices.Contains(r.GetDownloadingStatus(), status) {
|
||||
} else if utils.Contains(r.GetDownloadingStatus(), status) {
|
||||
if !t.DownloadUncached {
|
||||
return t, fmt.Errorf("torrent: %s not cached", t.Name)
|
||||
}
|
||||
@@ -556,12 +558,13 @@ func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
|
||||
if err != nil {
|
||||
if errors.Is(err, request.TrafficExceededError) {
|
||||
// Retries generating
|
||||
retries = 4
|
||||
retries = 5
|
||||
} else {
|
||||
// If the error is not traffic exceeded, return the error
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
backOff := 1 * time.Second
|
||||
for retries > 0 {
|
||||
downloadLink, err = r._getDownloadLink(file)
|
||||
if err == nil {
|
||||
@@ -571,7 +574,8 @@ func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
|
||||
return nil, err
|
||||
}
|
||||
// Add a delay before retrying
|
||||
time.Sleep(5 * time.Second)
|
||||
time.Sleep(backOff)
|
||||
backOff *= 2 // Exponential backoff
|
||||
}
|
||||
return downloadLink, nil
|
||||
}
|
||||
@@ -755,35 +759,42 @@ func (r *RealDebrid) GetMountPath() string {
|
||||
}
|
||||
|
||||
func (r *RealDebrid) DisableAccount(accountId string) {
|
||||
if r.DownloadKeys.Size() == 1 {
|
||||
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.DownloadKeys.Load(accountId); ok {
|
||||
if value, ok := r.accounts[accountId]; ok {
|
||||
value.Disabled = true
|
||||
r.DownloadKeys.Store(accountId, value)
|
||||
r.accounts[accountId] = value
|
||||
r.logger.Info().Msgf("Disabled account Index: %s", value.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RealDebrid) ResetActiveDownloadKeys() {
|
||||
r.DownloadKeys.Range(func(key string, value types.Account) bool {
|
||||
r.accountsMutex.Lock()
|
||||
defer r.accountsMutex.Unlock()
|
||||
for key, value := range r.accounts {
|
||||
value.Disabled = false
|
||||
r.DownloadKeys.Store(key, value)
|
||||
return true
|
||||
})
|
||||
r.accounts[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RealDebrid) getActiveAccounts() []types.Account {
|
||||
r.accountsMutex.RLock()
|
||||
defer r.accountsMutex.RUnlock()
|
||||
accounts := make([]types.Account, 0)
|
||||
r.DownloadKeys.Range(func(key string, value types.Account) bool {
|
||||
|
||||
for _, value := range r.accounts {
|
||||
if value.Disabled {
|
||||
return true
|
||||
continue
|
||||
}
|
||||
accounts = append(accounts, value)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Sort accounts by ID
|
||||
sort.Slice(accounts, func(i, j int) bool {
|
||||
return accounts[i].ID < accounts[j].ID
|
||||
})
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/sirrobot01/decypharr/internal/config"
|
||||
"github.com/sirrobot01/decypharr/internal/logger"
|
||||
@@ -18,7 +17,6 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -29,7 +27,8 @@ type Torbox struct {
|
||||
Name string
|
||||
Host string `json:"host"`
|
||||
APIKey string
|
||||
DownloadKeys *xsync.MapOf[string, types.Account]
|
||||
accounts map[string]types.Account
|
||||
accountsMutex sync.RWMutex
|
||||
DownloadUncached bool
|
||||
client *request.Client
|
||||
|
||||
@@ -53,21 +52,21 @@ func New(dc config.Debrid) *Torbox {
|
||||
request.WithProxy(dc.Proxy),
|
||||
)
|
||||
|
||||
accounts := xsync.NewMapOf[string, types.Account]()
|
||||
accounts := make(map[string]types.Account)
|
||||
for idx, key := range dc.DownloadAPIKeys {
|
||||
id := strconv.Itoa(idx)
|
||||
accounts.Store(id, types.Account{
|
||||
accounts[id] = types.Account{
|
||||
Name: key,
|
||||
ID: id,
|
||||
Token: key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &Torbox{
|
||||
Name: "torbox",
|
||||
Host: "https://api.torbox.app/v1",
|
||||
APIKey: dc.APIKey,
|
||||
DownloadKeys: accounts,
|
||||
accounts: accounts,
|
||||
DownloadUncached: dc.DownloadUncached,
|
||||
client: client,
|
||||
MountPath: dc.Folder,
|
||||
@@ -176,7 +175,7 @@ func getTorboxStatus(status string, finished bool) string {
|
||||
"forcedUP", "allocating", "downloading", "metaDL", "pausedDL",
|
||||
"queuedDL", "checkingDL", "forcedDL", "checkingResumeData", "moving"}
|
||||
switch {
|
||||
case slices.Contains(downloading, status):
|
||||
case utils.Contains(downloading, status):
|
||||
return "downloading"
|
||||
default:
|
||||
return "error"
|
||||
@@ -328,7 +327,7 @@ func (tb *Torbox) CheckStatus(torrent *types.Torrent, isSymlink bool) (*types.To
|
||||
}
|
||||
}
|
||||
break
|
||||
} else if slices.Contains(tb.GetDownloadingStatus(), status) {
|
||||
} else if utils.Contains(tb.GetDownloadingStatus(), status) {
|
||||
if !torrent.DownloadUncached {
|
||||
return torrent, fmt.Errorf("torrent: %s not cached", torrent.Name)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"github.com/cavaliergopher/grab/v3"
|
||||
"github.com/sirrobot01/decypharr/internal/request"
|
||||
"github.com/sirrobot01/decypharr/internal/utils"
|
||||
debrid "github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
debridTypes "github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -102,7 +102,7 @@ func (q *QBit) downloadFiles(torrent *Torrent, parent string) {
|
||||
}
|
||||
wg.Add(1)
|
||||
q.downloadSemaphore <- struct{}{}
|
||||
go func(file debrid.File) {
|
||||
go func(file debridTypes.File) {
|
||||
defer wg.Done()
|
||||
defer func() { <-q.downloadSemaphore }()
|
||||
filename := file.Link
|
||||
@@ -149,7 +149,7 @@ func (q *QBit) ProcessSymlink(torrent *Torrent) (string, error) {
|
||||
return q.createSymlinks(debridTorrent, torrentRclonePath, torrentFolder) // verify cos we're using external webdav
|
||||
}
|
||||
|
||||
func (q *QBit) createSymlinksWebdav(debridTorrent *debrid.Torrent, rclonePath, torrentFolder string) (string, error) {
|
||||
func (q *QBit) createSymlinksWebdav(debridTorrent *debridTypes.Torrent, rclonePath, torrentFolder string) (string, error) {
|
||||
files := debridTorrent.Files
|
||||
symlinkPath := filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentFolder) // /mnt/symlinks/{category}/MyTVShow/
|
||||
err := os.MkdirAll(symlinkPath, os.ModePerm)
|
||||
@@ -157,7 +157,7 @@ func (q *QBit) createSymlinksWebdav(debridTorrent *debrid.Torrent, rclonePath, t
|
||||
return "", fmt.Errorf("failed to create directory: %s: %v", symlinkPath, err)
|
||||
}
|
||||
|
||||
remainingFiles := make(map[string]debrid.File)
|
||||
remainingFiles := make(map[string]debridTypes.File)
|
||||
for _, file := range files {
|
||||
remainingFiles[utils.EscapePath(file.Name)] = file
|
||||
}
|
||||
@@ -214,7 +214,7 @@ func (q *QBit) createSymlinksWebdav(debridTorrent *debrid.Torrent, rclonePath, t
|
||||
return symlinkPath, nil
|
||||
}
|
||||
|
||||
func (q *QBit) createSymlinks(debridTorrent *debrid.Torrent, rclonePath, torrentFolder string) (string, error) {
|
||||
func (q *QBit) createSymlinks(debridTorrent *debridTypes.Torrent, rclonePath, torrentFolder string) (string, error) {
|
||||
files := debridTorrent.Files
|
||||
symlinkPath := filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentFolder) // /mnt/symlinks/{category}/MyTVShow/
|
||||
err := os.MkdirAll(symlinkPath, os.ModePerm)
|
||||
@@ -222,7 +222,7 @@ func (q *QBit) createSymlinks(debridTorrent *debrid.Torrent, rclonePath, torrent
|
||||
return "", fmt.Errorf("failed to create directory: %s: %v", symlinkPath, err)
|
||||
}
|
||||
|
||||
remainingFiles := make(map[string]debrid.File)
|
||||
remainingFiles := make(map[string]debridTypes.File)
|
||||
for _, file := range files {
|
||||
remainingFiles[file.Path] = file
|
||||
}
|
||||
@@ -278,7 +278,7 @@ func (q *QBit) createSymlinks(debridTorrent *debrid.Torrent, rclonePath, torrent
|
||||
return symlinkPath, nil
|
||||
}
|
||||
|
||||
func (q *QBit) getTorrentPath(rclonePath string, debridTorrent *debrid.Torrent) (string, error) {
|
||||
func (q *QBit) getTorrentPath(rclonePath string, debridTorrent *debridTypes.Torrent) (string, error) {
|
||||
for {
|
||||
torrentPath, err := debridTorrent.GetMountFolder(rclonePath)
|
||||
if err == nil {
|
||||
|
||||
@@ -7,14 +7,13 @@ import (
|
||||
"github.com/sirrobot01/decypharr/internal/request"
|
||||
"github.com/sirrobot01/decypharr/internal/utils"
|
||||
"github.com/sirrobot01/decypharr/pkg/arr"
|
||||
db "github.com/sirrobot01/decypharr/pkg/debrid/debrid"
|
||||
debrid "github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/debrid"
|
||||
debridTypes "github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
"github.com/sirrobot01/decypharr/pkg/service"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -56,7 +55,7 @@ func (q *QBit) Process(ctx context.Context, magnet *utils.Magnet, category strin
|
||||
return fmt.Errorf("arr not found in context")
|
||||
}
|
||||
isSymlink := ctx.Value("isSymlink").(bool)
|
||||
debridTorrent, err := db.ProcessTorrent(svc.Debrid, magnet, a, isSymlink, false)
|
||||
debridTorrent, err := debrid.ProcessTorrent(svc.Debrid, magnet, a, isSymlink, false)
|
||||
if err != nil || debridTorrent == nil {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("failed to process torrent")
|
||||
@@ -69,22 +68,27 @@ func (q *QBit) Process(ctx context.Context, magnet *utils.Magnet, category strin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *QBit) ProcessFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr *arr.Arr, isSymlink bool) {
|
||||
func (q *QBit) ProcessFiles(torrent *Torrent, debridTorrent *debridTypes.Torrent, arr *arr.Arr, isSymlink bool) {
|
||||
svc := service.GetService()
|
||||
client := svc.Debrid.GetClient(debridTorrent.Debrid)
|
||||
downloadingStatuses := client.GetDownloadingStatus()
|
||||
for debridTorrent.Status != "downloaded" {
|
||||
q.logger.Debug().Msgf("%s <- (%s) Download Progress: %.2f%%", debridTorrent.Debrid, debridTorrent.Name, debridTorrent.Progress)
|
||||
dbT, err := client.CheckStatus(debridTorrent, isSymlink)
|
||||
if err != nil {
|
||||
if dbT != nil && dbT.Id != "" {
|
||||
// Delete the torrent if it was not downloaded
|
||||
_ = client.DeleteTorrent(dbT.Id)
|
||||
go func() {
|
||||
_ = client.DeleteTorrent(dbT.Id)
|
||||
}()
|
||||
}
|
||||
q.logger.Error().Msgf("Error checking status: %v", err)
|
||||
q.MarkAsFailed(torrent)
|
||||
if err := arr.Refresh(); err != nil {
|
||||
q.logger.Error().Msgf("Error refreshing arr: %v", err)
|
||||
}
|
||||
go func() {
|
||||
if err := arr.Refresh(); err != nil {
|
||||
q.logger.Error().Msgf("Error refreshing arr: %v", err)
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -92,22 +96,24 @@ func (q *QBit) ProcessFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr
|
||||
torrent = q.UpdateTorrentMin(torrent, debridTorrent)
|
||||
|
||||
// Exit the loop for downloading statuses to prevent memory buildup
|
||||
if !slices.Contains(client.GetDownloadingStatus(), debridTorrent.Status) {
|
||||
if debridTorrent.Status == "downloaded" || !utils.Contains(downloadingStatuses, debridTorrent.Status) {
|
||||
break
|
||||
}
|
||||
if !utils.Contains(client.GetDownloadingStatus(), debridTorrent.Status) {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(q.RefreshInterval) * time.Second)
|
||||
}
|
||||
var (
|
||||
torrentSymlinkPath string
|
||||
err error
|
||||
)
|
||||
var torrentSymlinkPath string
|
||||
var err error
|
||||
debridTorrent.Arr = arr
|
||||
|
||||
// Check if debrid supports webdav by checking cache
|
||||
if isSymlink {
|
||||
cache, ok := svc.Debrid.Caches[debridTorrent.Debrid]
|
||||
if ok {
|
||||
cache, useWebdav := svc.Debrid.Caches[debridTorrent.Debrid]
|
||||
if useWebdav {
|
||||
q.logger.Info().Msgf("Using internal webdav for %s", debridTorrent.Debrid)
|
||||
timer := time.Now()
|
||||
// Use webdav to download the file
|
||||
|
||||
if err := cache.AddTorrent(debridTorrent); err != nil {
|
||||
@@ -118,7 +124,6 @@ func (q *QBit) ProcessFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr
|
||||
|
||||
rclonePath := filepath.Join(debridTorrent.MountPath, cache.GetTorrentFolder(debridTorrent)) // /mnt/remote/realdebrid/MyTVShow
|
||||
torrentFolderNoExt := utils.RemoveExtension(debridTorrent.Name)
|
||||
timer := time.Now()
|
||||
torrentSymlinkPath, err = q.createSymlinksWebdav(debridTorrent, rclonePath, torrentFolderNoExt) // /mnt/symlinks/{category}/MyTVShow/
|
||||
q.logger.Debug().Msgf("Adding %s took %s", debridTorrent.Name, time.Since(timer))
|
||||
|
||||
@@ -160,7 +165,7 @@ func (q *QBit) MarkAsFailed(t *Torrent) *Torrent {
|
||||
return t
|
||||
}
|
||||
|
||||
func (q *QBit) UpdateTorrentMin(t *Torrent, debridTorrent *debrid.Torrent) *Torrent {
|
||||
func (q *QBit) UpdateTorrentMin(t *Torrent, debridTorrent *debridTypes.Torrent) *Torrent {
|
||||
if debridTorrent == nil {
|
||||
return t
|
||||
}
|
||||
@@ -202,7 +207,7 @@ func (q *QBit) UpdateTorrentMin(t *Torrent, debridTorrent *debrid.Torrent) *Torr
|
||||
return t
|
||||
}
|
||||
|
||||
func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent {
|
||||
func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debridTypes.Torrent) *Torrent {
|
||||
if debridTorrent == nil {
|
||||
return t
|
||||
}
|
||||
@@ -298,10 +303,10 @@ func (q *QBit) SetTorrentTags(t *Torrent, tags []string) bool {
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(torrentTags, tag) {
|
||||
if !utils.Contains(torrentTags, tag) {
|
||||
torrentTags = append(torrentTags, tag)
|
||||
}
|
||||
if !slices.Contains(q.Tags, tag) {
|
||||
if !utils.Contains(q.Tags, tag) {
|
||||
q.Tags = append(q.Tags, tag)
|
||||
}
|
||||
}
|
||||
@@ -324,7 +329,7 @@ func (q *QBit) AddTags(tags []string) bool {
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(q.Tags, tag) {
|
||||
if !utils.Contains(q.Tags, tag) {
|
||||
q.Tags = append(q.Tags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,73 +5,8 @@ import (
|
||||
"github.com/sirrobot01/decypharr/pkg/arr"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func parseSchedule(schedule string) (time.Duration, error) {
|
||||
if schedule == "" {
|
||||
return time.Hour, nil // default 60m
|
||||
}
|
||||
|
||||
// Check if it's a time-of-day format (HH:MM)
|
||||
if strings.Contains(schedule, ":") {
|
||||
return parseTimeOfDay(schedule)
|
||||
}
|
||||
|
||||
// Otherwise treat as duration interval
|
||||
return parseDurationInterval(schedule)
|
||||
}
|
||||
|
||||
func parseTimeOfDay(schedule string) (time.Duration, error) {
|
||||
now := time.Now()
|
||||
scheduledTime, err := time.Parse("15:04", schedule)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid time format: %s. Use HH:MM in 24-hour format", schedule)
|
||||
}
|
||||
|
||||
// Convert scheduled time to today
|
||||
scheduleToday := time.Date(
|
||||
now.Year(), now.Month(), now.Day(),
|
||||
scheduledTime.Hour(), scheduledTime.Minute(), 0, 0,
|
||||
now.Location(),
|
||||
)
|
||||
|
||||
if scheduleToday.Before(now) {
|
||||
scheduleToday = scheduleToday.Add(24 * time.Hour)
|
||||
}
|
||||
|
||||
return scheduleToday.Sub(now), nil
|
||||
}
|
||||
|
||||
func parseDurationInterval(interval string) (time.Duration, error) {
|
||||
if len(interval) < 2 {
|
||||
return 0, fmt.Errorf("invalid interval format: %s", interval)
|
||||
}
|
||||
|
||||
numStr := interval[:len(interval)-1]
|
||||
unit := interval[len(interval)-1]
|
||||
|
||||
num, err := strconv.Atoi(numStr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid number in interval: %s", numStr)
|
||||
}
|
||||
|
||||
switch unit {
|
||||
case 'm':
|
||||
return time.Duration(num) * time.Minute, nil
|
||||
case 'h':
|
||||
return time.Duration(num) * time.Hour, nil
|
||||
case 'd':
|
||||
return time.Duration(num) * 24 * time.Hour, nil
|
||||
case 's':
|
||||
return time.Duration(num) * time.Second, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid unit in interval: %c", unit)
|
||||
}
|
||||
}
|
||||
|
||||
func fileIsSymlinked(file string) bool {
|
||||
info, err := os.Lstat(file)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,7 +18,7 @@ func (ui *Handler) setupMiddleware(next http.Handler) http.Handler {
|
||||
// strip inco from URL
|
||||
if inco := r.URL.Query().Get("inco"); inco != "" && needsAuth == nil && r.URL.Path == "/config" {
|
||||
// redirect to the same URL without the inco parameter
|
||||
http.Redirect(w, r, fmt.Sprintf("/config"), http.StatusSeeOther)
|
||||
http.Redirect(w, r, "/config", http.StatusSeeOther)
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
@@ -165,7 +165,7 @@ func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.F
|
||||
_path := strings.TrimPrefix(name, rootDir)
|
||||
parts := strings.Split(strings.TrimPrefix(_path, string(os.PathSeparator)), string(os.PathSeparator))
|
||||
|
||||
if len(parts) >= 2 && (slices.Contains(h.getParentItems(), parts[0])) {
|
||||
if len(parts) >= 2 && (utils.Contains(h.getParentItems(), parts[0])) {
|
||||
|
||||
torrentName := parts[1]
|
||||
cachedTorrent := h.cache.GetTorrentByName(torrentName)
|
||||
|
||||
Reference in New Issue
Block a user