Hotfix for download link generation and account switching
This commit is contained in:
@@ -298,40 +298,7 @@ func New(options ...ClientOption) *Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Configure proxy if needed
|
// Configure proxy if needed
|
||||||
if client.proxy != "" {
|
SetProxy(transport, client.proxy)
|
||||||
if strings.HasPrefix(client.proxy, "socks5://") {
|
|
||||||
// Handle SOCKS5 proxy
|
|
||||||
socksURL, err := url.Parse(client.proxy)
|
|
||||||
if err != nil {
|
|
||||||
client.logger.Error().Msgf("Failed to parse SOCKS5 proxy URL: %v", err)
|
|
||||||
} else {
|
|
||||||
auth := &proxy.Auth{}
|
|
||||||
if socksURL.User != nil {
|
|
||||||
auth.User = socksURL.User.Username()
|
|
||||||
password, _ := socksURL.User.Password()
|
|
||||||
auth.Password = password
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer, err := proxy.SOCKS5("tcp", socksURL.Host, auth, proxy.Direct)
|
|
||||||
if err != nil {
|
|
||||||
client.logger.Error().Msgf("Failed to create SOCKS5 dialer: %v", err)
|
|
||||||
} else {
|
|
||||||
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return dialer.Dial(network, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
proxyURL, err := url.Parse(client.proxy)
|
|
||||||
if err != nil {
|
|
||||||
client.logger.Error().Msgf("Failed to parse proxy URL: %v", err)
|
|
||||||
} else {
|
|
||||||
transport.Proxy = http.ProxyURL(proxyURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
transport.Proxy = http.ProxyFromEnvironment
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the transport to the client
|
// Set the transport to the client
|
||||||
client.client.Transport = transport
|
client.client.Transport = transport
|
||||||
@@ -417,3 +384,41 @@ func isRetryableError(err error) bool {
|
|||||||
// Not a retryable error
|
// Not a retryable error
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetProxy(transport *http.Transport, proxyURL string) {
|
||||||
|
if proxyURL != "" {
|
||||||
|
if strings.HasPrefix(proxyURL, "socks5://") {
|
||||||
|
// Handle SOCKS5 proxy
|
||||||
|
socksURL, err := url.Parse(proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
auth := &proxy.Auth{}
|
||||||
|
if socksURL.User != nil {
|
||||||
|
auth.User = socksURL.User.Username()
|
||||||
|
password, _ := socksURL.User.Password()
|
||||||
|
auth.Password = password
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer, err := proxy.SOCKS5("tcp", socksURL.Host, auth, proxy.Direct)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return dialer.Dial(network, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_proxy, err := url.Parse(proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
transport.Proxy = http.ProxyURL(_proxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transport.Proxy = http.ProxyFromEnvironment
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ func (ad *AllDebrid) GetFileDownloadLinks(t *types.Torrent) error {
|
|||||||
for _, file := range t.Files {
|
for _, file := range t.Files {
|
||||||
go func(file types.File) {
|
go func(file types.File) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
link, err := ad.GetDownloadLink(t, &file)
|
link, _, err := ad.GetDownloadLink(t, &file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errCh <- err
|
errCh <- err
|
||||||
return
|
return
|
||||||
@@ -336,7 +336,7 @@ func (ad *AllDebrid) GetFileDownloadLinks(t *types.Torrent) error {
|
|||||||
links[link.Link] = link
|
links[link.Link] = link
|
||||||
}
|
}
|
||||||
// Update the files with download links
|
// Update the files with download links
|
||||||
ad.accounts.SetDownloadLinks(links)
|
ad.accounts.SetDownloadLinks(nil, links)
|
||||||
|
|
||||||
// Check for errors
|
// Check for errors
|
||||||
for err := range errCh {
|
for err := range errCh {
|
||||||
@@ -349,7 +349,7 @@ func (ad *AllDebrid) GetFileDownloadLinks(t *types.Torrent) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, error) {
|
func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, *types.Account, error) {
|
||||||
url := fmt.Sprintf("%s/link/unlock", ad.Host)
|
url := fmt.Sprintf("%s/link/unlock", ad.Host)
|
||||||
query := gourl.Values{}
|
query := gourl.Values{}
|
||||||
query.Add("link", file.Link)
|
query.Add("link", file.Link)
|
||||||
@@ -357,19 +357,19 @@ func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
|
|||||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||||
resp, err := ad.client.MakeRequest(req)
|
resp, err := ad.client.MakeRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
var data DownloadLink
|
var data DownloadLink
|
||||||
if err = json.Unmarshal(resp, &data); err != nil {
|
if err = json.Unmarshal(resp, &data); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.Error != nil {
|
if data.Error != nil {
|
||||||
return nil, fmt.Errorf("error getting download link: %s", data.Error.Message)
|
return nil, nil, fmt.Errorf("error getting download link: %s", data.Error.Message)
|
||||||
}
|
}
|
||||||
link := data.Data.Link
|
link := data.Data.Link
|
||||||
if link == "" {
|
if link == "" {
|
||||||
return nil, fmt.Errorf("download link is empty")
|
return nil, nil, fmt.Errorf("download link is empty")
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
return &types.DownloadLink{
|
return &types.DownloadLink{
|
||||||
@@ -380,7 +380,7 @@ func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
|
|||||||
Filename: file.Name,
|
Filename: file.Name,
|
||||||
Generated: now,
|
Generated: now,
|
||||||
ExpiresAt: now.Add(ad.autoExpiresLinksAfter),
|
ExpiresAt: now.Add(ad.autoExpiresLinksAfter),
|
||||||
}, nil
|
}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) {
|
func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) {
|
||||||
@@ -416,8 +416,8 @@ func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) {
|
|||||||
return torrents, nil
|
return torrents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AllDebrid) GetDownloadLinks() (map[string]*types.DownloadLink, error) {
|
func (ad *AllDebrid) RefreshDownloadLinks() error {
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AllDebrid) GetDownloadingStatus() []string {
|
func (ad *AllDebrid) GetDownloadingStatus() []string {
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ func (dl *DebridLink) UpdateTorrent(t *types.Torrent) error {
|
|||||||
t.Files[f.Name] = file
|
t.Files[f.Name] = file
|
||||||
}
|
}
|
||||||
|
|
||||||
dl.accounts.SetDownloadLinks(links)
|
dl.accounts.SetDownloadLinks(nil, links)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,8 +308,7 @@ func (dl *DebridLink) SubmitMagnet(t *types.Torrent) (*types.Torrent, error) {
|
|||||||
file.DownloadLink = link
|
file.DownloadLink = link
|
||||||
t.Files[f.Name] = file
|
t.Files[f.Name] = file
|
||||||
}
|
}
|
||||||
|
dl.accounts.SetDownloadLinks(nil, links)
|
||||||
dl.accounts.SetDownloadLinks(links)
|
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
@@ -353,11 +352,11 @@ func (dl *DebridLink) GetFileDownloadLinks(t *types.Torrent) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dl *DebridLink) GetDownloadLinks() (map[string]*types.DownloadLink, error) {
|
func (dl *DebridLink) RefreshDownloadLinks() error {
|
||||||
return nil, nil
|
return 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, *types.Account, error) {
|
||||||
return dl.accounts.GetDownloadLink(file.Link)
|
return dl.accounts.GetDownloadLink(file.Link)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,7 +451,7 @@ func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) {
|
|||||||
}
|
}
|
||||||
torrents = append(torrents, torrent)
|
torrents = append(torrents, torrent)
|
||||||
}
|
}
|
||||||
dl.accounts.SetDownloadLinks(links)
|
dl.accounts.SetDownloadLinks(nil, links)
|
||||||
|
|
||||||
return torrents, nil
|
return torrents, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ func (r *RealDebrid) handleRarArchive(t *types.Torrent, data torrentInfo, select
|
|||||||
|
|
||||||
r.logger.Info().Msgf("RAR file detected, unpacking: %s", t.Name)
|
r.logger.Info().Msgf("RAR file detected, unpacking: %s", t.Name)
|
||||||
linkFile := &types.File{TorrentId: t.Id, Link: data.Links[0]}
|
linkFile := &types.File{TorrentId: t.Id, Link: data.Links[0]}
|
||||||
downloadLinkObj, err := r.GetDownloadLink(t, linkFile)
|
downloadLinkObj, account, err := r.GetDownloadLink(t, linkFile)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.Debug().Err(err).Msgf("Error getting download link for RAR file: %s. Falling back to single file representation.", t.Name)
|
r.logger.Debug().Err(err).Msgf("Error getting download link for RAR file: %s. Falling back to single file representation.", t.Name)
|
||||||
@@ -244,6 +244,7 @@ func (r *RealDebrid) handleRarArchive(t *types.Torrent, data torrentInfo, select
|
|||||||
return r.handleRarFallback(t, data)
|
return r.handleRarFallback(t, data)
|
||||||
}
|
}
|
||||||
r.logger.Info().Msgf("Unpacked RAR archive for torrent: %s with %d files", t.Name, len(files))
|
r.logger.Info().Msgf("Unpacked RAR archive for torrent: %s with %d files", t.Name, len(files))
|
||||||
|
r.accounts.SetDownloadLink(account, downloadLinkObj)
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,7 +589,7 @@ func (r *RealDebrid) GetFileDownloadLinks(t *types.Torrent) error {
|
|||||||
go func(file types.File) {
|
go func(file types.File) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
link, err := r.GetDownloadLink(t, &file)
|
link, account, err := r.GetDownloadLink(t, &file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
if firstErr == nil {
|
if firstErr == nil {
|
||||||
@@ -607,6 +608,7 @@ func (r *RealDebrid) GetFileDownloadLinks(t *types.Torrent) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
file.DownloadLink = link
|
file.DownloadLink = link
|
||||||
|
r.accounts.SetDownloadLink(account, link)
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
files[file.Name] = file
|
files[file.Name] = file
|
||||||
@@ -622,7 +624,6 @@ func (r *RealDebrid) GetFileDownloadLinks(t *types.Torrent) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add links to cache
|
// Add links to cache
|
||||||
r.accounts.SetDownloadLinks(links)
|
|
||||||
t.Files = files
|
t.Files = files
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -643,7 +644,7 @@ func (r *RealDebrid) CheckLink(link string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) _getDownloadLink(file *types.File) (*types.DownloadLink, error) {
|
func (r *RealDebrid) getDownloadLink(account *types.Account, 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
|
_link := file.Link
|
||||||
if strings.HasPrefix(file.Link, "https://real-debrid.com/d/") && len(file.Link) > 39 {
|
if strings.HasPrefix(file.Link, "https://real-debrid.com/d/") && len(file.Link) > 39 {
|
||||||
@@ -653,6 +654,7 @@ func (r *RealDebrid) _getDownloadLink(file *types.File) (*types.DownloadLink, er
|
|||||||
"link": {_link},
|
"link": {_link},
|
||||||
}
|
}
|
||||||
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
|
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
|
||||||
|
r.downloadClient.SetHeader("Authorization", fmt.Sprintf("Bearer %s", account.Token))
|
||||||
resp, err := r.downloadClient.Do(req)
|
resp, err := r.downloadClient.Do(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -709,16 +711,14 @@ func (r *RealDebrid) _getDownloadLink(file *types.File) (*types.DownloadLink, er
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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, *types.Account, error) {
|
||||||
|
|
||||||
accounts := r.accounts.Active()
|
accounts := r.accounts.Active()
|
||||||
|
|
||||||
for _, account := range accounts {
|
for _, account := range accounts {
|
||||||
r.downloadClient.SetHeader("Authorization", fmt.Sprintf("Bearer %s", account.Token))
|
downloadLink, err := r.getDownloadLink(account, file)
|
||||||
downloadLink, err := r._getDownloadLink(file)
|
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return downloadLink, nil
|
return downloadLink, account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
retries := 0
|
retries := 0
|
||||||
@@ -727,16 +727,16 @@ func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
|
|||||||
retries = 5
|
retries = 5
|
||||||
} else {
|
} else {
|
||||||
// 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, account, err
|
||||||
}
|
}
|
||||||
backOff := 1 * time.Second
|
backOff := 1 * time.Second
|
||||||
for retries > 0 {
|
for retries > 0 {
|
||||||
downloadLink, err = r._getDownloadLink(file)
|
downloadLink, err = r.getDownloadLink(account, file)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return downloadLink, nil
|
return downloadLink, account, nil
|
||||||
}
|
}
|
||||||
if !errors.Is(err, utils.TrafficExceededError) {
|
if !errors.Is(err, utils.TrafficExceededError) {
|
||||||
return nil, err
|
return nil, account, err
|
||||||
}
|
}
|
||||||
// Add a delay before retrying
|
// Add a delay before retrying
|
||||||
time.Sleep(backOff)
|
time.Sleep(backOff)
|
||||||
@@ -744,7 +744,7 @@ func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (*types
|
|||||||
retries--
|
retries--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("realdebrid API error: download link not found")
|
return nil, nil, fmt.Errorf("realdebrid API error: download link not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) getTorrents(offset int, limit int) (int, []*types.Torrent, error) {
|
func (r *RealDebrid) getTorrents(offset int, limit int) (int, []*types.Torrent, error) {
|
||||||
@@ -841,48 +841,47 @@ func (r *RealDebrid) GetTorrents() ([]*types.Torrent, error) {
|
|||||||
return allTorrents, nil
|
return allTorrents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) GetDownloadLinks() (map[string]*types.DownloadLink, error) {
|
func (r *RealDebrid) RefreshDownloadLinks() error {
|
||||||
links := make(map[string]*types.DownloadLink)
|
accounts := r.accounts.All()
|
||||||
offset := 0
|
|
||||||
limit := 1000
|
|
||||||
|
|
||||||
accounts := r.accounts.Active()
|
for _, account := range accounts {
|
||||||
|
if account == nil || account.Token == "" {
|
||||||
if len(accounts) < 1 {
|
continue
|
||||||
// No active download keys. It's likely that the key has reached bandwidth limit
|
|
||||||
return links, fmt.Errorf("no active download keys")
|
|
||||||
}
|
|
||||||
activeAccount := accounts[0]
|
|
||||||
r.downloadClient.SetHeader("Authorization", fmt.Sprintf("Bearer %s", activeAccount.Token))
|
|
||||||
for {
|
|
||||||
dl, err := r._getDownloads(offset, limit)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
if len(dl) == 0 {
|
offset := 0
|
||||||
break
|
limit := 1000
|
||||||
}
|
links := make(map[string]*types.DownloadLink)
|
||||||
|
for {
|
||||||
for _, d := range dl {
|
dl, err := r.getDownloadLinks(account, offset, limit)
|
||||||
if _, exists := links[d.Link]; exists {
|
if err != nil {
|
||||||
// This is ordered by date, so we can skip the rest
|
break
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
links[d.Link] = &d
|
if len(dl) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range dl {
|
||||||
|
if _, exists := links[d.Link]; exists {
|
||||||
|
// This is ordered by date, so we can skip the rest
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
links[d.Link] = &d
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += len(dl)
|
||||||
}
|
}
|
||||||
|
r.accounts.SetDownloadLinks(account, links)
|
||||||
offset += len(dl)
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
return links, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) _getDownloads(offset int, limit int) ([]types.DownloadLink, error) {
|
func (r *RealDebrid) getDownloadLinks(account *types.Account, offset int, limit int) ([]types.DownloadLink, error) {
|
||||||
url := fmt.Sprintf("%s/downloads?limit=%d", r.Host, limit)
|
url := fmt.Sprintf("%s/downloads?limit=%d", r.Host, limit)
|
||||||
if offset > 0 {
|
if offset > 0 {
|
||||||
url = fmt.Sprintf("%s&offset=%d", url, offset)
|
url = fmt.Sprintf("%s&offset=%d", url, offset)
|
||||||
}
|
}
|
||||||
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||||
|
r.downloadClient.SetHeader("Authorization", fmt.Sprintf("Bearer %s", account.Token))
|
||||||
resp, err := r.downloadClient.MakeRequest(req)
|
resp, err := r.downloadClient.MakeRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ func (tb *Torbox) GetFileDownloadLinks(t *types.Torrent) error {
|
|||||||
for _, file := range t.Files {
|
for _, file := range t.Files {
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
link, err := tb.GetDownloadLink(t, &file)
|
link, _, err := tb.GetDownloadLink(t, &file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errCh <- err
|
errCh <- err
|
||||||
return
|
return
|
||||||
@@ -439,7 +439,7 @@ func (tb *Torbox) GetFileDownloadLinks(t *types.Torrent) error {
|
|||||||
// Collect download links
|
// Collect download links
|
||||||
for link := range linkCh {
|
for link := range linkCh {
|
||||||
if link != nil {
|
if link != nil {
|
||||||
tb.accounts.SetDownloadLink(link.Link, link)
|
tb.accounts.SetDownloadLink(nil, link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,7 +454,7 @@ func (tb *Torbox) GetFileDownloadLinks(t *types.Torrent) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, error) {
|
func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.DownloadLink, *types.Account, error) {
|
||||||
url := fmt.Sprintf("%s/api/torrents/requestdl/", tb.Host)
|
url := fmt.Sprintf("%s/api/torrents/requestdl/", tb.Host)
|
||||||
query := gourl.Values{}
|
query := gourl.Values{}
|
||||||
query.Add("torrent_id", t.Id)
|
query.Add("torrent_id", t.Id)
|
||||||
@@ -470,7 +470,7 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.Do
|
|||||||
Str("torrent_id", t.Id).
|
Str("torrent_id", t.Id).
|
||||||
Str("file_id", file.Id).
|
Str("file_id", file.Id).
|
||||||
Msg("Failed to make request to Torbox API")
|
Msg("Failed to make request to Torbox API")
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var data DownloadLinksResponse
|
var data DownloadLinksResponse
|
||||||
@@ -480,7 +480,7 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.Do
|
|||||||
Str("torrent_id", t.Id).
|
Str("torrent_id", t.Id).
|
||||||
Str("file_id", file.Id).
|
Str("file_id", file.Id).
|
||||||
Msg("Failed to unmarshal Torbox API response")
|
Msg("Failed to unmarshal Torbox API response")
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.Data == nil {
|
if data.Data == nil {
|
||||||
@@ -491,7 +491,7 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.Do
|
|||||||
Interface("error", data.Error).
|
Interface("error", data.Error).
|
||||||
Str("detail", data.Detail).
|
Str("detail", data.Detail).
|
||||||
Msg("Torbox API returned no data")
|
Msg("Torbox API returned no data")
|
||||||
return nil, fmt.Errorf("error getting download links")
|
return nil, nil, fmt.Errorf("error getting download links")
|
||||||
}
|
}
|
||||||
|
|
||||||
link := *data.Data
|
link := *data.Data
|
||||||
@@ -500,7 +500,7 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.Do
|
|||||||
Str("torrent_id", t.Id).
|
Str("torrent_id", t.Id).
|
||||||
Str("file_id", file.Id).
|
Str("file_id", file.Id).
|
||||||
Msg("Torbox API returned empty download link")
|
Msg("Torbox API returned empty download link")
|
||||||
return nil, fmt.Errorf("error getting download links")
|
return nil, nil, fmt.Errorf("error getting download links")
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@@ -512,7 +512,7 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (*types.Do
|
|||||||
ExpiresAt: now.Add(tb.autoExpiresLinksAfter),
|
ExpiresAt: now.Add(tb.autoExpiresLinksAfter),
|
||||||
}
|
}
|
||||||
|
|
||||||
return downloadLink, nil
|
return downloadLink, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *Torbox) GetDownloadingStatus() []string {
|
func (tb *Torbox) GetDownloadingStatus() []string {
|
||||||
@@ -607,8 +607,8 @@ func (tb *Torbox) GetDownloadUncached() bool {
|
|||||||
return tb.DownloadUncached
|
return tb.DownloadUncached
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *Torbox) GetDownloadLinks() (map[string]*types.DownloadLink, error) {
|
func (tb *Torbox) RefreshDownloadLinks() error {
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *Torbox) CheckLink(link string) error {
|
func (tb *Torbox) CheckLink(link string) error {
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/sirrobot01/decypharr/internal/request"
|
||||||
"github.com/sirrobot01/decypharr/pkg/rclone"
|
"github.com/sirrobot01/decypharr/pkg/rclone"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -40,6 +43,20 @@ const (
|
|||||||
WebdavUseHash WebDavFolderNaming = "infohash"
|
WebdavUseHash WebDavFolderNaming = "infohash"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var streamingTransport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
MaxIdleConns: 200,
|
||||||
|
MaxIdleConnsPerHost: 100,
|
||||||
|
MaxConnsPerHost: 200,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
ResponseHeaderTimeout: 60 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
ForceAttemptHTTP2: false,
|
||||||
|
TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper),
|
||||||
|
}
|
||||||
|
|
||||||
type CachedTorrent struct {
|
type CachedTorrent struct {
|
||||||
*types.Torrent
|
*types.Torrent
|
||||||
AddedOn time.Time `json:"added_on"`
|
AddedOn time.Time `json:"added_on"`
|
||||||
@@ -81,9 +98,8 @@ type Cache struct {
|
|||||||
|
|
||||||
listingDebouncer *utils.Debouncer[bool]
|
listingDebouncer *utils.Debouncer[bool]
|
||||||
// monitors
|
// monitors
|
||||||
repairRequest sync.Map
|
repairRequest sync.Map
|
||||||
failedToReinsert sync.Map
|
failedToReinsert sync.Map
|
||||||
downloadLinkRequests sync.Map
|
|
||||||
|
|
||||||
// repair
|
// repair
|
||||||
repairChan chan RepairRequest
|
repairChan chan RepairRequest
|
||||||
@@ -108,6 +124,7 @@ type Cache struct {
|
|||||||
config config.Debrid
|
config config.Debrid
|
||||||
customFolders []string
|
customFolders []string
|
||||||
mounter *rclone.Mount
|
mounter *rclone.Mount
|
||||||
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDebridCache(dc config.Debrid, client types.Client, mounter *rclone.Mount) *Cache {
|
func NewDebridCache(dc config.Debrid, client types.Client, mounter *rclone.Mount) *Cache {
|
||||||
@@ -153,6 +170,16 @@ func NewDebridCache(dc config.Debrid, client types.Client, mounter *rclone.Mount
|
|||||||
|
|
||||||
}
|
}
|
||||||
_log := logger.New(fmt.Sprintf("%s-webdav", client.Name()))
|
_log := logger.New(fmt.Sprintf("%s-webdav", client.Name()))
|
||||||
|
if dc.Proxy != "" {
|
||||||
|
|
||||||
|
}
|
||||||
|
transport := streamingTransport
|
||||||
|
request.SetProxy(transport, dc.Proxy)
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Timeout: 0,
|
||||||
|
}
|
||||||
|
|
||||||
c := &Cache{
|
c := &Cache{
|
||||||
dir: filepath.Join(cfg.Path, "cache", dc.Name), // path to save cache files
|
dir: filepath.Join(cfg.Path, "cache", dc.Name), // path to save cache files
|
||||||
|
|
||||||
@@ -171,7 +198,8 @@ func NewDebridCache(dc config.Debrid, client types.Client, mounter *rclone.Mount
|
|||||||
customFolders: customFolders,
|
customFolders: customFolders,
|
||||||
mounter: mounter,
|
mounter: mounter,
|
||||||
|
|
||||||
ready: make(chan struct{}),
|
ready: make(chan struct{}),
|
||||||
|
httpClient: httpClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.listingDebouncer = utils.NewDebouncer[bool](100*time.Millisecond, func(refreshRclone bool) {
|
c.listingDebouncer = utils.NewDebouncer[bool](100*time.Millisecond, func(refreshRclone bool) {
|
||||||
@@ -225,7 +253,6 @@ func (c *Cache) Reset() {
|
|||||||
c.invalidDownloadLinks = sync.Map{}
|
c.invalidDownloadLinks = sync.Map{}
|
||||||
c.repairRequest = sync.Map{}
|
c.repairRequest = sync.Map{}
|
||||||
c.failedToReinsert = sync.Map{}
|
c.failedToReinsert = sync.Map{}
|
||||||
c.downloadLinkRequests = sync.Map{}
|
|
||||||
|
|
||||||
// 5. Rebuild the listing debouncer
|
// 5. Rebuild the listing debouncer
|
||||||
c.listingDebouncer = utils.NewDebouncer[bool](
|
c.listingDebouncer = utils.NewDebouncer[bool](
|
||||||
@@ -904,3 +931,7 @@ func (c *Cache) Logger() zerolog.Logger {
|
|||||||
func (c *Cache) GetConfig() config.Debrid {
|
func (c *Cache) GetConfig() config.Debrid {
|
||||||
return c.config
|
return c.config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cache) Download(req *http.Request) (*http.Response, error) {
|
||||||
|
return c.httpClient.Do(req)
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,31 +36,15 @@ func (c *Cache) GetDownloadLink(torrentName, filename, fileLink string) (string,
|
|||||||
return dl, nil
|
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)
|
|
||||||
|
|
||||||
dl, err := c.fetchDownloadLink(torrentName, filename, fileLink)
|
dl, err := c.fetchDownloadLink(torrentName, filename, fileLink)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.Complete("", err)
|
|
||||||
c.downloadLinkRequests.Delete(fileLink)
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if dl == nil || dl.DownloadLink == "" {
|
if dl == nil || dl.DownloadLink == "" {
|
||||||
err = fmt.Errorf("download link is empty for %s in torrent %s", filename, torrentName)
|
err = fmt.Errorf("download link is empty for %s in torrent %s", filename, torrentName)
|
||||||
req.Complete("", err)
|
|
||||||
c.downloadLinkRequests.Delete(fileLink)
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
req.Complete(dl.DownloadLink, err)
|
|
||||||
c.downloadLinkRequests.Delete(fileLink)
|
|
||||||
return dl.DownloadLink, err
|
return dl.DownloadLink, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,10 +86,11 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (*type
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.logger.Trace().Msgf("Getting download link for %s(%s)", filename, file.Link)
|
c.logger.Trace().Msgf("Getting download link for %s(%s)", filename, file.Link)
|
||||||
downloadLink, err := c.client.GetDownloadLink(ct.Torrent, &file)
|
downloadLink, account, err := c.client.GetDownloadLink(ct.Torrent, &file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, utils.HosterUnavailableError) {
|
if errors.Is(err, utils.HosterUnavailableError) {
|
||||||
c.logger.Trace().
|
c.logger.Trace().
|
||||||
|
Str("account", account.Username).
|
||||||
Str("filename", filename).
|
Str("filename", filename).
|
||||||
Str("torrent_id", ct.Id).
|
Str("torrent_id", ct.Id).
|
||||||
Msg("Hoster unavailable, attempting to reinsert torrent")
|
Msg("Hoster unavailable, attempting to reinsert torrent")
|
||||||
@@ -120,7 +105,7 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (*type
|
|||||||
return nil, 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, account, err = c.client.GetDownloadLink(ct.Torrent, &file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("retry failed to get download link: %w", err)
|
return nil, fmt.Errorf("retry failed to get download link: %w", err)
|
||||||
}
|
}
|
||||||
@@ -140,7 +125,7 @@ func (c *Cache) fetchDownloadLink(torrentName, filename, fileLink string) (*type
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set link to cache
|
// Set link to cache
|
||||||
go c.client.Accounts().SetDownloadLink(fileLink, downloadLink)
|
go c.client.Accounts().SetDownloadLink(account, downloadLink)
|
||||||
return downloadLink, nil
|
return downloadLink, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +138,7 @@ func (c *Cache) GetFileDownloadLinks(t CachedTorrent) {
|
|||||||
|
|
||||||
func (c *Cache) checkDownloadLink(link string) (string, error) {
|
func (c *Cache) checkDownloadLink(link string) (string, error) {
|
||||||
|
|
||||||
dl, err := c.client.Accounts().GetDownloadLink(link)
|
dl, _, err := c.client.Accounts().GetDownloadLink(link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -168,7 +153,7 @@ func (c *Cache) MarkDownloadLinkAsInvalid(link, downloadLink, reason string) {
|
|||||||
// Remove the download api key from active
|
// Remove the download api key from active
|
||||||
if reason == "bandwidth_exceeded" {
|
if reason == "bandwidth_exceeded" {
|
||||||
// Disable the account
|
// Disable the account
|
||||||
_, account, err := c.client.Accounts().GetDownloadLinkWithAccount(link)
|
account, err := c.client.Accounts().GetAccountFromLink(link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,14 +243,10 @@ func (c *Cache) refreshDownloadLinks(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
defer c.downloadLinksRefreshMu.Unlock()
|
defer c.downloadLinksRefreshMu.Unlock()
|
||||||
|
|
||||||
links, err := c.client.GetDownloadLinks()
|
if err := c.client.RefreshDownloadLinks(); 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
|
||||||
}
|
}
|
||||||
|
|
||||||
c.client.Accounts().SetDownloadLinks(links)
|
|
||||||
|
|
||||||
c.logger.Debug().Msgf("Refreshed download %d links", c.client.Accounts().GetLinksCount())
|
c.logger.Debug().Msgf("Refreshed download %d links", c.client.Accounts().GetLinksCount())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import (
|
|||||||
"github.com/sirrobot01/decypharr/internal/config"
|
"github.com/sirrobot01/decypharr/internal/config"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Accounts struct {
|
type Accounts struct {
|
||||||
current *Account
|
current atomic.Value
|
||||||
accounts sync.Map // map[string]*Account // key is token
|
accounts sync.Map // map[string]*Account // key is token
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ func NewAccounts(debridConf config.Debrid) *Accounts {
|
|||||||
current = account
|
current = account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.current = current
|
a.setCurrent(current)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +35,7 @@ type Account struct {
|
|||||||
Debrid string // e.g., "realdebrid", "torbox", etc.
|
Debrid string // e.g., "realdebrid", "torbox", etc.
|
||||||
Order int
|
Order int
|
||||||
Disabled bool
|
Disabled bool
|
||||||
|
InUse bool
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
links map[string]*DownloadLink
|
links map[string]*DownloadLink
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@@ -75,25 +76,53 @@ func (a *Accounts) All() []*Account {
|
|||||||
return allAccounts
|
return allAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Accounts) getCurrent() *Account {
|
||||||
|
if acc := a.current.Load(); acc != nil {
|
||||||
|
if current, ok := acc.(*Account); ok {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Accounts) Current() *Account {
|
func (a *Accounts) Current() *Account {
|
||||||
if a.current != nil && !a.current.Disabled {
|
current := a.getCurrent()
|
||||||
current := a.current
|
if current != nil && !current.Disabled {
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
activeAccounts := a.Active()
|
activeAccounts := a.Active()
|
||||||
if len(activeAccounts) == 0 {
|
if len(activeAccounts) == 0 {
|
||||||
return a.current
|
return current
|
||||||
}
|
}
|
||||||
a.current = activeAccounts[0]
|
current = activeAccounts[0]
|
||||||
|
a.setCurrent(current)
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
return a.current
|
func (a *Accounts) setCurrent(account *Account) {
|
||||||
|
if account == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Set every account InUse to false
|
||||||
|
a.accounts.Range(func(key, value interface{}) bool {
|
||||||
|
acc, ok := value.(*Account)
|
||||||
|
if ok {
|
||||||
|
acc.InUse = false
|
||||||
|
a.accounts.Store(key, acc)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
account.InUse = true
|
||||||
|
a.current.Store(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Accounts) Disable(account *Account) {
|
func (a *Accounts) Disable(account *Account) {
|
||||||
account.Disabled = true
|
account.Disabled = true
|
||||||
a.accounts.Store(account.Token, account)
|
a.accounts.Store(account.Token, account)
|
||||||
|
|
||||||
if a.current.Equals(account) {
|
current := a.getCurrent()
|
||||||
|
|
||||||
|
if current.Equals(account) {
|
||||||
var newCurrent *Account
|
var newCurrent *Account
|
||||||
|
|
||||||
a.accounts.Range(func(key, value interface{}) bool {
|
a.accounts.Range(func(key, value interface{}) bool {
|
||||||
@@ -104,66 +133,67 @@ func (a *Accounts) Disable(account *Account) {
|
|||||||
}
|
}
|
||||||
return true // Continue the loop
|
return true // Continue the loop
|
||||||
})
|
})
|
||||||
a.current = newCurrent
|
a.setCurrent(newCurrent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Accounts) Reset() {
|
func (a *Accounts) Reset() {
|
||||||
|
var current *Account
|
||||||
a.accounts.Range(func(key, value interface{}) bool {
|
a.accounts.Range(func(key, value interface{}) bool {
|
||||||
acc, ok := value.(*Account)
|
acc, ok := value.(*Account)
|
||||||
if ok {
|
if ok {
|
||||||
acc.resetDownloadLinks()
|
acc.resetDownloadLinks()
|
||||||
acc.Disabled = false
|
acc.Disabled = false
|
||||||
a.accounts.Store(key, acc)
|
a.accounts.Store(key, acc)
|
||||||
if a.current == nil {
|
if current == nil {
|
||||||
a.current = acc
|
current = acc
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
a.setCurrent(current)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Accounts) GetDownloadLink(fileLink string) (*DownloadLink, error) {
|
func (a *Accounts) GetDownloadLink(fileLink string) (*DownloadLink, *Account, error) {
|
||||||
if a.Current() == nil {
|
current := a.Current()
|
||||||
return nil, NoActiveAccountsError
|
if current == nil {
|
||||||
|
return nil, nil, NoActiveAccountsError
|
||||||
}
|
}
|
||||||
dl, ok := a.Current().getLink(fileLink)
|
dl, ok := current.getLink(fileLink)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, NoDownloadLinkError
|
return nil, current, NoDownloadLinkError
|
||||||
}
|
}
|
||||||
if dl.ExpiresAt.IsZero() || dl.ExpiresAt.Before(time.Now()) {
|
if err := dl.Valid(); err != nil {
|
||||||
return nil, DownloadLinkExpiredError
|
return nil, current, err
|
||||||
}
|
}
|
||||||
if dl.DownloadLink == "" {
|
return dl, current, nil
|
||||||
return nil, EmptyDownloadLinkError
|
|
||||||
}
|
|
||||||
return dl, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Accounts) GetDownloadLinkWithAccount(fileLink string) (*DownloadLink, *Account, error) {
|
func (a *Accounts) GetAccountFromLink(fileLink string) (*Account, error) {
|
||||||
currentAccount := a.Current()
|
currentAccount := a.Current()
|
||||||
if currentAccount == nil {
|
if currentAccount == nil {
|
||||||
return nil, nil, NoActiveAccountsError
|
return nil, NoActiveAccountsError
|
||||||
}
|
}
|
||||||
dl, ok := currentAccount.getLink(fileLink)
|
dl, ok := currentAccount.getLink(fileLink)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, NoDownloadLinkError
|
return nil, NoDownloadLinkError
|
||||||
}
|
|
||||||
if dl.ExpiresAt.IsZero() || dl.ExpiresAt.Before(time.Now()) {
|
|
||||||
return nil, currentAccount, DownloadLinkExpiredError
|
|
||||||
}
|
}
|
||||||
if dl.DownloadLink == "" {
|
if dl.DownloadLink == "" {
|
||||||
return nil, currentAccount, EmptyDownloadLinkError
|
return currentAccount, EmptyDownloadLinkError
|
||||||
}
|
}
|
||||||
return dl, currentAccount, nil
|
return currentAccount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Accounts) SetDownloadLink(fileLink string, dl *DownloadLink) {
|
// SetDownloadLink sets the download link for the current account
|
||||||
if a.Current() == nil {
|
func (a *Accounts) SetDownloadLink(account *Account, dl *DownloadLink) {
|
||||||
|
if dl == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.Current().setLink(fileLink, dl)
|
if account == nil {
|
||||||
|
account = a.getCurrent()
|
||||||
|
}
|
||||||
|
account.setLink(dl.Link, dl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Accounts) DeleteDownloadLink(fileLink string) {
|
func (a *Accounts) DeleteDownloadLink(fileLink string) {
|
||||||
@@ -180,11 +210,12 @@ func (a *Accounts) GetLinksCount() int {
|
|||||||
return a.Current().LinksCount()
|
return a.Current().LinksCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Accounts) SetDownloadLinks(links map[string]*DownloadLink) {
|
func (a *Accounts) SetDownloadLinks(account *Account, links map[string]*DownloadLink) {
|
||||||
if a.Current() == nil {
|
if account == nil {
|
||||||
return
|
account = a.Current()
|
||||||
}
|
}
|
||||||
a.Current().setLinks(links)
|
account.setLinks(links)
|
||||||
|
a.accounts.Store(account.Token, account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Accounts) Update(account *Account) {
|
func (a *Accounts) Update(account *Account) {
|
||||||
@@ -241,10 +272,8 @@ func (a *Account) LinksCount() int {
|
|||||||
func (a *Account) setLinks(links map[string]*DownloadLink) {
|
func (a *Account) setLinks(links map[string]*DownloadLink) {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
now := time.Now()
|
|
||||||
for _, dl := range links {
|
for _, dl := range links {
|
||||||
if !dl.ExpiresAt.IsZero() && dl.ExpiresAt.Before(now) {
|
if err := dl.Valid(); err != nil {
|
||||||
// Expired, continue
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
a.links[a.sliceFileLink(dl.Link)] = dl
|
a.links[a.sliceFileLink(dl.Link)] = dl
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ type Client interface {
|
|||||||
SubmitMagnet(tr *Torrent) (*Torrent, error)
|
SubmitMagnet(tr *Torrent) (*Torrent, error)
|
||||||
CheckStatus(tr *Torrent) (*Torrent, error)
|
CheckStatus(tr *Torrent) (*Torrent, error)
|
||||||
GetFileDownloadLinks(tr *Torrent) error
|
GetFileDownloadLinks(tr *Torrent) error
|
||||||
GetDownloadLink(tr *Torrent, file *File) (*DownloadLink, error)
|
GetDownloadLink(tr *Torrent, file *File) (*DownloadLink, *Account, error)
|
||||||
DeleteTorrent(torrentId string) error
|
DeleteTorrent(torrentId string) error
|
||||||
IsAvailable(infohashes []string) map[string]bool
|
IsAvailable(infohashes []string) map[string]bool
|
||||||
GetDownloadUncached() bool
|
GetDownloadUncached() bool
|
||||||
@@ -18,7 +18,7 @@ type Client interface {
|
|||||||
Name() string
|
Name() string
|
||||||
Logger() zerolog.Logger
|
Logger() zerolog.Logger
|
||||||
GetDownloadingStatus() []string
|
GetDownloadingStatus() []string
|
||||||
GetDownloadLinks() (map[string]*DownloadLink, error)
|
RefreshDownloadLinks() error
|
||||||
CheckLink(link string) error
|
CheckLink(link string) error
|
||||||
GetMountPath() string
|
GetMountPath() string
|
||||||
Accounts() *Accounts // Returns the active download account/token
|
Accounts() *Accounts // Returns the active download account/token
|
||||||
|
|||||||
@@ -179,6 +179,16 @@ type DownloadLink struct {
|
|||||||
ExpiresAt time.Time
|
ExpiresAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DownloadLink) String() string {
|
func (dl *DownloadLink) Valid() error {
|
||||||
return d.DownloadLink
|
if dl.DownloadLink == "" {
|
||||||
|
return EmptyDownloadLinkError
|
||||||
|
}
|
||||||
|
if dl.ExpiresAt.IsZero() || dl.ExpiresAt.Before(time.Now()) {
|
||||||
|
return DownloadLinkExpiredError
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dl *DownloadLink) String() string {
|
||||||
|
return dl.DownloadLink
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
|
|||||||
accountDetail := map[string]any{
|
accountDetail := map[string]any{
|
||||||
"order": account.Order,
|
"order": account.Order,
|
||||||
"disabled": account.Disabled,
|
"disabled": account.Disabled,
|
||||||
|
"in_use": account.InUse,
|
||||||
"token_masked": maskedToken,
|
"token_masked": maskedToken,
|
||||||
"username": account.Username,
|
"username": account.Username,
|
||||||
"traffic_used": account.TrafficUsed,
|
"traffic_used": account.TrafficUsed,
|
||||||
|
|||||||
@@ -371,6 +371,10 @@
|
|||||||
const statusBadge = account.disabled ?
|
const statusBadge = account.disabled ?
|
||||||
'<span class="badge badge-error badge-sm">Disabled</span>' :
|
'<span class="badge badge-error badge-sm">Disabled</span>' :
|
||||||
'<span class="badge badge-success badge-sm">Active</span>';
|
'<span class="badge badge-success badge-sm">Active</span>';
|
||||||
|
|
||||||
|
const inUseBadge = account.in_use ?
|
||||||
|
'<span class="badge badge-info badge-sm">In Use</span>' :
|
||||||
|
'';
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
<div class="card bg-base-100 compact">
|
<div class="card bg-base-100 compact">
|
||||||
@@ -380,6 +384,7 @@
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h5 class="font-medium text-sm">Account #${account.order + 1}</h5>
|
<h5 class="font-medium text-sm">Account #${account.order + 1}</h5>
|
||||||
${statusBadge}
|
${statusBadge}
|
||||||
|
${inUseBadge}
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-base-content/70 mt-1">${account.username || 'No username'}</p>
|
<p class="text-xs text-base-content/70 mt-1">${account.username || 'No username'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package webdav
|
package webdav
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -12,25 +11,6 @@ import (
|
|||||||
"github.com/sirrobot01/decypharr/pkg/debrid/store"
|
"github.com/sirrobot01/decypharr/pkg/debrid/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
var streamingTransport = &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
MaxIdleConns: 200,
|
|
||||||
MaxIdleConnsPerHost: 100,
|
|
||||||
MaxConnsPerHost: 200,
|
|
||||||
IdleConnTimeout: 90 * time.Second,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
ResponseHeaderTimeout: 60 * time.Second,
|
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
|
||||||
DisableKeepAlives: true,
|
|
||||||
ForceAttemptHTTP2: false,
|
|
||||||
TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper),
|
|
||||||
}
|
|
||||||
|
|
||||||
var sharedClient = &http.Client{
|
|
||||||
Transport: streamingTransport,
|
|
||||||
Timeout: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
type streamError struct {
|
type streamError struct {
|
||||||
Err error
|
Err error
|
||||||
StatusCode int
|
StatusCode int
|
||||||
@@ -176,7 +156,7 @@ func (f *File) streamWithRetry(w http.ResponseWriter, r *http.Request, retryCoun
|
|||||||
return &streamError{Err: fmt.Errorf("invalid range"), StatusCode: http.StatusRequestedRangeNotSatisfiable}
|
return &streamError{Err: fmt.Errorf("invalid range"), StatusCode: http.StatusRequestedRangeNotSatisfiable}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := sharedClient.Do(upstreamReq)
|
resp, err := f.cache.Download(upstreamReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &streamError{Err: err, StatusCode: http.StatusServiceUnavailable}
|
return &streamError{Err: err, StatusCode: http.StatusServiceUnavailable}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user