Skip to content

Commit ab12610

Browse files
committed
Merge branch 'beta'
2 parents 705de2d + 1d19be9 commit ab12610

File tree

21 files changed

+240
-458
lines changed

21 files changed

+240
-458
lines changed

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,5 @@ body:
7272
label: Trace Logs have been provided as applicable
7373
description: Trace logs are **generally required** and are not optional for all bug reports and contain `trace`. Info logs are invalid for bug reports and do not contain `debug` nor `trace`
7474
options:
75-
- label: I have read and followed the steps in the wiki link above and provided the required trace logs - the logs contain `trace` - that are relevant and show this issue.
75+
- label: I have read and followed the steps in the documentation link and provided the required trace logs - the logs contain `trace` - that are relevant and show this issue.
7676
required: true

docs/docs/guides/rclone.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,26 @@ Create a `config.json` file in `/opt/decypharr/` with your Decypharr configurati
6262

6363
```
6464

65+
### Docker Compose Setup
66+
67+
- Check your current user and group IDs by running `id -u` and `id -g` in your terminal. You can use these values to set the `PUID` and `PGID` environment variables in the Docker Compose file.
68+
- You should also set `user` to your user ID and group ID in the Docker Compose file to ensure proper file permissions.
69+
6570
Create a `docker-compose.yml` file with the following content:
6671

6772
```yaml
6873
services:
6974
decypharr:
7075
image: cy01/blackhole:latest
7176
container_name: decypharr
77+
user: "${PUID:-1000}:${PGID:-1000}"
7278
volumes:
7379
- /mnt/:/mnt:rslave
7480
- /opt/decypharr/:/app
7581
environment:
7682
- UMASK=002
83+
- PUID=1000 # Replace with your user ID
84+
- PGID=1000 # Replace with your group ID
7785
ports:
7886
- "8282:8282/tcp"
7987
restart: unless-stopped

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ require (
1414
github.com/robfig/cron/v3 v3.0.1
1515
github.com/rs/zerolog v1.33.0
1616
github.com/stanNthe5/stringbuf v0.0.3
17+
go.uber.org/ratelimit v0.3.1
1718
golang.org/x/crypto v0.33.0
1819
golang.org/x/net v0.35.0
1920
golang.org/x/sync v0.12.0
20-
golang.org/x/time v0.8.0
2121
gopkg.in/natefinch/lumberjack.v2 v2.2.1
2222
)
2323

2424
require (
2525
github.com/anacrolix/missinggo v1.3.0 // indirect
2626
github.com/anacrolix/missinggo/v2 v2.7.3 // indirect
27+
github.com/benbjohnson/clock v1.3.0 // indirect
2728
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
2829
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
2930
github.com/google/go-cmp v0.6.0 // indirect

go.sum

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CM
3636
github.com/anacrolix/torrent v1.55.0 h1:s9yh/YGdPmbN9dTa+0Inh2dLdrLQRvEAj1jdFW/Hdd8=
3737
github.com/anacrolix/torrent v1.55.0/go.mod h1:sBdZHBSZNj4de0m+EbYg7vvs/G/STubxu/GzzNbojsE=
3838
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
39+
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
40+
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
3941
github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI=
4042
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
4143
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -216,8 +218,12 @@ github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPy
216218
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
217219
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
218220
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
221+
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
222+
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
219223
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
220224
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
225+
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
226+
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
221227
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
222228
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
223229
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
@@ -266,8 +272,6 @@ golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
266272
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
267273
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
268274
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
269-
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
270-
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
271275
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
272276
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
273277
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

internal/config/config.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,19 @@ var (
1919
)
2020

2121
type Debrid struct {
22-
Name string `json:"name,omitempty"`
23-
APIKey string `json:"api_key,omitempty"`
24-
DownloadAPIKeys []string `json:"download_api_keys,omitempty"`
25-
Folder string `json:"folder,omitempty"`
26-
DownloadUncached bool `json:"download_uncached,omitempty"`
27-
CheckCached bool `json:"check_cached,omitempty"`
28-
RateLimit string `json:"rate_limit,omitempty"` // 200/minute or 10/second
29-
Proxy string `json:"proxy,omitempty"`
30-
UnpackRar bool `json:"unpack_rar,omitempty"`
31-
AddSamples bool `json:"add_samples,omitempty"`
32-
MinimumFreeSlot int `json:"minimum_free_slot,omitempty"` // Minimum active pots to use this debrid
22+
Name string `json:"name,omitempty"`
23+
APIKey string `json:"api_key,omitempty"`
24+
DownloadAPIKeys []string `json:"download_api_keys,omitempty"`
25+
Folder string `json:"folder,omitempty"`
26+
DownloadUncached bool `json:"download_uncached,omitempty"`
27+
CheckCached bool `json:"check_cached,omitempty"`
28+
RateLimit string `json:"rate_limit,omitempty"` // 200/minute or 10/second
29+
RepairRateLimit string `json:"repair_rate_limit,omitempty"`
30+
DownloadRateLimit string `json:"download_rate_limit,omitempty"`
31+
Proxy string `json:"proxy,omitempty"`
32+
UnpackRar bool `json:"unpack_rar,omitempty"`
33+
AddSamples bool `json:"add_samples,omitempty"`
34+
MinimumFreeSlot int `json:"minimum_free_slot,omitempty"` // Minimum active pots to use this debrid
3335

3436
UseWebDav bool `json:"use_webdav,omitempty"`
3537
WebDav

internal/request/request.go

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ import (
99
"fmt"
1010
"github.com/rs/zerolog"
1111
"github.com/sirrobot01/decypharr/internal/logger"
12+
"go.uber.org/ratelimit"
1213
"golang.org/x/net/proxy"
13-
"golang.org/x/time/rate"
1414
"io"
15-
"math"
1615
"math/rand"
1716
"net"
1817
"net/http"
@@ -52,7 +51,7 @@ type ClientOption func(*Client)
5251
// Client represents an HTTP client with additional capabilities
5352
type Client struct {
5453
client *http.Client
55-
rateLimiter *rate.Limiter
54+
rateLimiter ratelimit.Limiter
5655
headers map[string]string
5756
headersMu sync.RWMutex
5857
maxRetries int
@@ -84,7 +83,7 @@ func WithRedirectPolicy(policy func(req *http.Request, via []*http.Request) erro
8483
}
8584

8685
// WithRateLimiter sets a rate limiter
87-
func WithRateLimiter(rl *rate.Limiter) ClientOption {
86+
func WithRateLimiter(rl ratelimit.Limiter) ClientOption {
8887
return func(c *Client) {
8988
c.rateLimiter = rl
9089
}
@@ -136,9 +135,11 @@ func WithProxy(proxyURL string) ClientOption {
136135
// doRequest performs a single HTTP request with rate limiting
137136
func (c *Client) doRequest(req *http.Request) (*http.Response, error) {
138137
if c.rateLimiter != nil {
139-
err := c.rateLimiter.Wait(req.Context())
140-
if err != nil {
141-
return nil, fmt.Errorf("rate limiter wait: %w", err)
138+
select {
139+
case <-req.Context().Done():
140+
return nil, req.Context().Err()
141+
default:
142+
c.rateLimiter.Take()
142143
}
143144
}
144145

@@ -339,7 +340,10 @@ func New(options ...ClientOption) *Client {
339340
return client
340341
}
341342

342-
func ParseRateLimit(rateStr string) *rate.Limiter {
343+
func ParseRateLimit(rateStr string) ratelimit.Limiter {
344+
if rateStr == "" {
345+
return nil
346+
}
343347
parts := strings.SplitN(rateStr, "/", 2)
344348
if len(parts) != 2 {
345349
return nil
@@ -351,23 +355,21 @@ func ParseRateLimit(rateStr string) *rate.Limiter {
351355
return nil
352356
}
353357

358+
// Set slack size to 10%
359+
slackSize := count / 10
360+
354361
// normalize unit
355362
unit := strings.ToLower(strings.TrimSpace(parts[1]))
356363
unit = strings.TrimSuffix(unit, "s")
357-
burstSize := int(math.Ceil(float64(count) * 0.1))
358-
if burstSize < 1 {
359-
burstSize = 1
360-
}
361-
if burstSize > count {
362-
burstSize = count
363-
}
364364
switch unit {
365365
case "minute", "min":
366-
return rate.NewLimiter(rate.Limit(float64(count)/60.0), burstSize)
366+
return ratelimit.New(count, ratelimit.Per(time.Minute), ratelimit.WithSlack(slackSize))
367367
case "second", "sec":
368-
return rate.NewLimiter(rate.Limit(float64(count)), burstSize)
368+
return ratelimit.New(count, ratelimit.Per(time.Second), ratelimit.WithSlack(slackSize))
369369
case "hour", "hr":
370-
return rate.NewLimiter(rate.Limit(float64(count)/3600.0), burstSize)
370+
return ratelimit.New(count, ratelimit.Per(time.Hour), ratelimit.WithSlack(slackSize))
371+
case "day", "d":
372+
return ratelimit.New(count, ratelimit.Per(24*time.Hour), ratelimit.WithSlack(slackSize))
371373
default:
372374
return nil
373375
}

internal/utils/regex.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,10 @@ func RemoveInvalidChars(value string) string {
4040
}
4141

4242
func RemoveExtension(value string) string {
43-
loc := mediaRegex.FindStringIndex(value)
44-
if loc != nil {
43+
if loc := mediaRegex.FindStringIndex(value); loc != nil {
4544
return value[:loc[0]]
46-
} else {
47-
return value
4845
}
46+
return value
4947
}
5048

5149
func IsMediaFile(path string) bool {

pkg/debrid/providers/realdebrid/realdebrid.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package realdebrid
22

33
import (
44
"bytes"
5+
"cmp"
56
"encoding/json"
67
"errors"
78
"fmt"
@@ -33,6 +34,7 @@ type RealDebrid struct {
3334
DownloadUncached bool
3435
client *request.Client
3536
downloadClient *request.Client
37+
repairClient *request.Client
3638
autoExpiresLinksAfter time.Duration
3739

3840
MountPath string
@@ -49,6 +51,8 @@ type RealDebrid struct {
4951

5052
func New(dc config.Debrid) (*RealDebrid, error) {
5153
rl := request.ParseRateLimit(dc.RateLimit)
54+
repairRl := request.ParseRateLimit(cmp.Or(dc.RepairRateLimit, dc.RateLimit))
55+
downloadRl := request.ParseRateLimit(cmp.Or(dc.DownloadRateLimit, dc.RateLimit))
5256

5357
headers := map[string]string{
5458
"Authorization": fmt.Sprintf("Bearer %s", dc.APIKey),
@@ -77,11 +81,20 @@ func New(dc config.Debrid) (*RealDebrid, error) {
7781
request.WithProxy(dc.Proxy),
7882
),
7983
downloadClient: request.New(
84+
request.WithRateLimiter(downloadRl),
8085
request.WithLogger(_log),
8186
request.WithMaxRetries(10),
8287
request.WithRetryableStatus(429, 447, 502),
8388
request.WithProxy(dc.Proxy),
8489
),
90+
repairClient: request.New(
91+
request.WithRateLimiter(repairRl),
92+
request.WithHeaders(headers),
93+
request.WithLogger(_log),
94+
request.WithMaxRetries(4),
95+
request.WithRetryableStatus(429, 502),
96+
request.WithProxy(dc.Proxy),
97+
),
8598
MountPath: dc.Folder,
8699
logger: logger.New(dc.Name),
87100
rarSemaphore: make(chan struct{}, 2),
@@ -608,7 +621,7 @@ func (r *RealDebrid) CheckLink(link string) error {
608621
"link": {link},
609622
}
610623
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
611-
resp, err := r.client.Do(req)
624+
resp, err := r.repairClient.Do(req)
612625
if err != nil {
613626
return err
614627
}
@@ -621,7 +634,7 @@ func (r *RealDebrid) CheckLink(link string) error {
621634
func (r *RealDebrid) _getDownloadLink(file *types.File) (*types.DownloadLink, error) {
622635
url := fmt.Sprintf("%s/unrestrict/link/", r.Host)
623636
_link := file.Link
624-
if strings.HasPrefix(_link, "https://real-debrid.com/d/") {
637+
if strings.HasPrefix(file.Link, "https://real-debrid.com/d/") && len(file.Link) > 39 {
625638
_link = file.Link[0:39]
626639
}
627640
payload := gourl.Values{

pkg/debrid/store/cache.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -514,9 +514,9 @@ func (c *Cache) setTorrent(t CachedTorrent, callback func(torrent CachedTorrent)
514514
updatedTorrent.Files = mergedFiles
515515
}
516516
c.torrents.set(torrentName, t, updatedTorrent)
517-
c.SaveTorrent(t)
517+
go c.SaveTorrent(t)
518518
if callback != nil {
519-
callback(updatedTorrent)
519+
go callback(updatedTorrent)
520520
}
521521
}
522522

@@ -702,6 +702,7 @@ func (c *Cache) ProcessTorrent(t *types.Torrent) error {
702702

703703
func (c *Cache) Add(t *types.Torrent) error {
704704
if len(t.Files) == 0 {
705+
c.logger.Warn().Msgf("Torrent %s has no files to add. Refreshing", t.Id)
705706
if err := c.client.UpdateTorrent(t); err != nil {
706707
return fmt.Errorf("failed to update torrent: %w", err)
707708
}

0 commit comments

Comments
 (0)