feat(clients): Transmission support seedtime and ratiolimit (#1211)

* feat(clients): Transmission seedtime ratiolimit

* feat(clients): update client pkg

* feat(clients): update client pkg test

* feat(actions): update transmission
This commit is contained in:
ze0s 2023-10-31 00:38:23 +01:00 committed by GitHub
parent 568e41de24
commit d5d1cecc1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 189 additions and 23 deletions

2
go.mod
View file

@ -23,7 +23,7 @@ require (
github.com/gorilla/sessions v1.2.1
github.com/gosimple/slug v1.13.1
github.com/hashicorp/go-version v1.6.0
github.com/hekmon/transmissionrpc/v2 v2.0.1
github.com/hekmon/transmissionrpc/v3 v3.0.0
github.com/lib/pq v1.10.9
github.com/mattn/go-shellwords v1.0.12
github.com/mmcdole/gofeed v1.2.1

9
go.sum
View file

@ -147,8 +147,6 @@ github.com/ergochat/irc-go v0.4.0 h1:0YibCKfAAtwxQdNjLQd9xpIEPisLcJ45f8FNsMHAuZc
github.com/ergochat/irc-go v0.4.0/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gdm85/go-rencode v0.1.8 h1:7+qxwoQWU1b1nMGcESOyoUR5dzPtRA6yLQpKn7uXmnI=
@ -261,8 +259,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hekmon/cunits/v2 v2.1.0 h1:k6wIjc4PlacNOHwKEMBgWV2/c8jyD4eRMs5mR1BBhI0=
github.com/hekmon/cunits/v2 v2.1.0/go.mod h1:9r1TycXYXaTmEWlAIfFV8JT+Xo59U96yUJAYHxzii2M=
github.com/hekmon/transmissionrpc/v2 v2.0.1 h1:WkILCEdbNy3n/N/w7mi449waMPdH2AA1THyw7TfnN/w=
github.com/hekmon/transmissionrpc/v2 v2.0.1/go.mod h1:+s96Pkg7dIP3h2PT3fzhXPvNb3OdLryh5J8PIvQg3aA=
github.com/hekmon/transmissionrpc/v3 v3.0.0 h1:0Fb11qE0IBh4V4GlOwHNYpqpjcYDp5GouolwrpmcUDQ=
github.com/hekmon/transmissionrpc/v3 v3.0.0/go.mod h1:38SlNhFzinVUuY87wGj3acOmRxeYZAZfrj6Re7UgCDg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
@ -331,8 +329,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/moistari/rls v0.5.9 h1:peRGW+1/HJDUZ76s0v2ukcBLCBUs4/Qf3TKOzRjOOco=
github.com/moistari/rls v0.5.9/go.mod h1:/3P63JjNkaf1MNBoS2tSXqGeqee6l4je+Krakp4ob7c=
github.com/moistari/rls v0.5.10 h1:e2XDxF0/+Riz9TEiTSewuZ7nuNZKOpbY1V2KSJWA2e4=
github.com/moistari/rls v0.5.10/go.mod h1:/3P63JjNkaf1MNBoS2tSXqGeqee6l4je+Krakp4ob7c=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
@ -614,7 +610,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View file

@ -6,13 +6,15 @@ package action
import (
"context"
"fmt"
"net/url"
"strings"
"time"
"github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/errors"
"github.com/autobrr/autobrr/pkg/transmission"
"github.com/hekmon/transmissionrpc/v2"
"github.com/hekmon/transmissionrpc/v3"
)
const (
@ -21,6 +23,7 @@ const (
)
var ErrReannounceTookTooLong = errors.New("ErrReannounceTookTooLong")
var TrTrue = true
func (s *service) transmission(ctx context.Context, action *domain.Action, release domain.Release) ([]string, error) {
s.log.Debug().Msgf("action Transmission: %s", action.Name)
@ -38,10 +41,21 @@ func (s *service) transmission(ctx context.Context, action *domain.Action, relea
return nil, errors.New("could not find client by id: %d", action.ClientID)
}
tbt, err := transmissionrpc.New(client.Host, client.Username, client.Password, &transmissionrpc.AdvancedConfig{
HTTPS: client.TLS,
Port: uint16(client.Port),
UserAgent: "autobrr",
scheme := "http"
if client.TLS {
scheme = "https"
}
u, err := url.Parse(fmt.Sprintf("%s://%s:%d/transmission/rpc", scheme, client.Host, client.Port))
if err != nil {
return nil, err
}
tbt, err := transmission.New(u, &transmission.Config{
UserAgent: "autobrr",
Username: client.Username,
Password: client.Password,
TLSSkipVerify: client.TLSSkipVerify,
})
if err != nil {
return nil, errors.Wrap(err, "error logging into client: %s", client.Host)
@ -74,10 +88,36 @@ func (s *service) transmission(ctx context.Context, action *domain.Action, relea
return nil, errors.Wrap(err, "could not add torrent from magnet %s to client: %s", release.MagnetURI, client.Host)
}
if action.Label != "" {
if action.Label != "" || action.LimitUploadSpeed > 0 || action.LimitDownloadSpeed > 0 || action.LimitRatio > 0 || action.LimitSeedTime > 0 {
p := transmissionrpc.TorrentSetPayload{
IDs: []int64{*torrent.ID},
Labels: []string{action.Label},
IDs: []int64{*torrent.ID},
}
if action.Label != "" {
p.Labels = []string{action.Label}
}
if action.LimitUploadSpeed > 0 {
p.UploadLimit = &action.LimitUploadSpeed
p.UploadLimited = &TrTrue
}
if action.LimitDownloadSpeed > 0 {
p.DownloadLimit = &action.LimitDownloadSpeed
p.DownloadLimited = &TrTrue
}
if action.LimitRatio > 0 {
p.SeedRatioLimit = &action.LimitRatio
ratioMode := transmissionrpc.SeedRatioModeCustom
p.SeedRatioMode = &ratioMode
}
if action.LimitSeedTime > 0 {
t := time.Duration(action.LimitSeedTime) * time.Minute
//p.SeedIdleLimit = &action.LimitSeedTime
p.SeedIdleLimit = &t
// seed idle mode 1
seedIdleMode := int64(1)
p.SeedIdleMode = &seedIdleMode
}
if err := tbt.TorrentSet(ctx, p); err != nil {
@ -112,12 +152,39 @@ func (s *service) transmission(ctx context.Context, action *domain.Action, relea
return nil, errors.Wrap(err, "could not add torrent %s to client: %s", release.TorrentTmpFile, client.Host)
}
if action.Label != "" {
if action.Label != "" || action.LimitUploadSpeed > 0 || action.LimitDownloadSpeed > 0 || action.LimitRatio > 0 || action.LimitSeedTime > 0 {
p := transmissionrpc.TorrentSetPayload{
IDs: []int64{*torrent.ID},
Labels: []string{action.Label},
IDs: []int64{*torrent.ID},
}
if action.Label != "" {
p.Labels = []string{action.Label}
}
if action.LimitUploadSpeed > 0 {
p.UploadLimit = &action.LimitUploadSpeed
p.UploadLimited = &TrTrue
}
if action.LimitDownloadSpeed > 0 {
p.DownloadLimit = &action.LimitDownloadSpeed
p.DownloadLimited = &TrTrue
}
if action.LimitRatio > 0 {
p.SeedRatioLimit = &action.LimitRatio
ratioMode := transmissionrpc.SeedRatioModeCustom
p.SeedRatioMode = &ratioMode
}
if action.LimitSeedTime > 0 {
t := time.Duration(action.LimitSeedTime) * time.Minute
p.SeedIdleLimit = &t
// seed idle mode 1
seedIdleMode := int64(1)
p.SeedIdleMode = &seedIdleMode
}
s.log.Trace().Msgf("transmission torrent set payload: %+v for torrent hash %s client: %s", p, *torrent.HashString, client.Name)
if err := tbt.TorrentSet(ctx, p); err != nil {
return nil, errors.Wrap(err, "could not set label for hash %s to client: %s", *torrent.HashString, client.Host)
}

View file

@ -5,6 +5,8 @@ package download_client
import (
"context"
"fmt"
"net/url"
"time"
"github.com/autobrr/autobrr/internal/domain"
@ -15,13 +17,13 @@ import (
"github.com/autobrr/autobrr/pkg/readarr"
"github.com/autobrr/autobrr/pkg/sabnzbd"
"github.com/autobrr/autobrr/pkg/sonarr"
"github.com/autobrr/autobrr/pkg/transmission"
"github.com/autobrr/autobrr/pkg/whisparr"
"github.com/autobrr/go-deluge"
"github.com/autobrr/go-qbittorrent"
"github.com/autobrr/go-rtorrent"
"github.com/dcarbone/zadapters/zstdlog"
"github.com/hekmon/transmissionrpc/v2"
"github.com/rs/zerolog"
)
@ -175,9 +177,21 @@ func (s *service) testRTorrentConnection(ctx context.Context, client domain.Down
}
func (s *service) testTransmissionConnection(ctx context.Context, client domain.DownloadClient) error {
tbt, err := transmissionrpc.New(client.Host, client.Username, client.Password, &transmissionrpc.AdvancedConfig{
HTTPS: client.TLS,
Port: uint16(client.Port),
scheme := "http"
if client.TLS {
scheme = "https"
}
u, err := url.Parse(fmt.Sprintf("%s://%s:%d/transmission/rpc", scheme, client.Host, client.Port))
if err != nil {
return err
}
tbt, err := transmission.New(u, &transmission.Config{
UserAgent: "autobrr",
Username: client.Username,
Password: client.Password,
TLSSkipVerify: client.TLSSkipVerify,
})
if err != nil {
return errors.Wrap(err, "error logging into client: %v", client.Host)

View file

@ -0,0 +1,58 @@
package transmission
import (
"crypto/tls"
"net/http"
"net/url"
"time"
"github.com/hekmon/transmissionrpc/v3"
)
type Config struct {
UserAgent string
CustomClient *http.Client
Username string
Password string
TLSSkipVerify bool
Timeout int
}
func New(endpoint *url.URL, cfg *Config) (*transmissionrpc.Client, error) {
ct := &customTransport{
Username: cfg.Username,
Password: cfg.Password,
TLSSkipVerify: cfg.TLSSkipVerify,
}
extra := &transmissionrpc.Config{
CustomClient: &http.Client{
Transport: ct,
Timeout: time.Second * 60,
},
UserAgent: cfg.UserAgent,
}
return transmissionrpc.New(endpoint, extra)
}
type customTransport struct {
Username string
Password string
TLSSkipVerify bool
}
func (t *customTransport) RoundTrip(req *http.Request) (*http.Response, error) {
dt := http.DefaultTransport.(*http.Transport).Clone()
if t.TLSSkipVerify {
dt.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
r := req.Clone(req.Context())
if t.Username != "" && t.Password != "" {
r.SetBasicAuth(t.Username, t.Password)
}
return dt.RoundTrip(r)
}

View file

@ -493,6 +493,38 @@ const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
/>
</div>
<CollapsableSection title="Rules" subtitle="client options">
<div className="col-span-12">
<div className="mt-6 grid grid-cols-12 gap-6">
<NumberField
name={`actions.${idx}.limit_download_speed`}
label="Limit download speed (KiB/s)"
placeholder="Takes any number (0 is no limit)"
/>
<NumberField
name={`actions.${idx}.limit_upload_speed`}
label="Limit upload speed (KiB/s)"
placeholder="Takes any number (0 is no limit)"
/>
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<NumberField
name={`actions.${idx}.limit_ratio`}
label="Ratio limit"
placeholder="Takes any number (0 is no limit)"
step={0.25}
isDecimal
/>
<NumberField
name={`actions.${idx}.limit_seed_time`}
label="Seed time limit (minutes)"
placeholder="Takes any number (0 is no limit)"
/>
</div>
</div>
</CollapsableSection>
<CollapsableSection title="Re-announce" subtitle="Re-announce options">
<div className="col-span-12">
<div className="mt-6 grid grid-cols-12 gap-6">