mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat(actions): qbit add options content layout and skip hash check (#393)
* feat(actions): qbit content layout and skip hash check * feat(actions): qbit options
This commit is contained in:
parent
db9d048f5d
commit
9508cbb46c
13 changed files with 394 additions and 50 deletions
|
@ -2,7 +2,6 @@ package action
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -102,10 +101,23 @@ func (s *service) qbittorrent(action domain.Action, release domain.Release) ([]s
|
|||
|
||||
func (s *service) prepareQbitOptions(action domain.Action, m domain.Macro) (map[string]string, error) {
|
||||
|
||||
options := map[string]string{}
|
||||
opts := &qbittorrent.TorrentAddOptions{}
|
||||
|
||||
if action.Paused {
|
||||
options["paused"] = "true"
|
||||
opts.Paused = BoolPointer(true)
|
||||
}
|
||||
if action.SkipHashCheck {
|
||||
opts.SkipHashCheck = BoolPointer(true)
|
||||
}
|
||||
if action.ContentLayout != "" {
|
||||
if action.ContentLayout == domain.ActionContentLayoutSubfolderCreate {
|
||||
layout := qbittorrent.ContentLayoutSubfolderCreate
|
||||
opts.ContentLayout = &layout
|
||||
} else if action.ContentLayout == domain.ActionContentLayoutSubfolderNone {
|
||||
layout := qbittorrent.ContentLayoutSubfolderNone
|
||||
opts.ContentLayout = &layout
|
||||
}
|
||||
// if ORIGINAL then leave empty
|
||||
}
|
||||
if action.SavePath != "" {
|
||||
// parse and replace values in argument string before continuing
|
||||
|
@ -114,8 +126,8 @@ func (s *service) prepareQbitOptions(action domain.Action, m domain.Macro) (map[
|
|||
return nil, errors.Wrap(err, "could not parse savepath macro: %v", action.SavePath)
|
||||
}
|
||||
|
||||
options["savepath"] = actionArgs
|
||||
options["autoTMM"] = "false"
|
||||
opts.SavePath = &actionArgs
|
||||
opts.AutoTMM = BoolPointer(false)
|
||||
}
|
||||
if action.Category != "" {
|
||||
// parse and replace values in argument string before continuing
|
||||
|
@ -124,7 +136,7 @@ func (s *service) prepareQbitOptions(action domain.Action, m domain.Macro) (map[
|
|||
return nil, errors.Wrap(err, "could not parse category macro: %v", action.Category)
|
||||
}
|
||||
|
||||
options["category"] = categoryArgs
|
||||
opts.Category = &categoryArgs
|
||||
}
|
||||
if action.Tags != "" {
|
||||
// parse and replace values in argument string before continuing
|
||||
|
@ -133,22 +145,26 @@ func (s *service) prepareQbitOptions(action domain.Action, m domain.Macro) (map[
|
|||
return nil, errors.Wrap(err, "could not parse tags macro: %v", action.Tags)
|
||||
}
|
||||
|
||||
options["tags"] = tagsArgs
|
||||
opts.Tags = &tagsArgs
|
||||
}
|
||||
if action.LimitUploadSpeed > 0 {
|
||||
options["upLimit"] = strconv.FormatInt(action.LimitUploadSpeed*1000, 10)
|
||||
opts.LimitUploadSpeed = &action.LimitUploadSpeed
|
||||
}
|
||||
if action.LimitDownloadSpeed > 0 {
|
||||
options["dlLimit"] = strconv.FormatInt(action.LimitDownloadSpeed*1000, 10)
|
||||
opts.LimitDownloadSpeed = &action.LimitDownloadSpeed
|
||||
}
|
||||
if action.LimitRatio > 0 {
|
||||
options["ratioLimit"] = strconv.FormatFloat(action.LimitRatio, 'r', 2, 64)
|
||||
opts.LimitRatio = &action.LimitRatio
|
||||
}
|
||||
if action.LimitSeedTime > 0 {
|
||||
options["seedingTimeLimit"] = strconv.FormatInt(action.LimitSeedTime, 10)
|
||||
opts.LimitSeedTime = &action.LimitSeedTime
|
||||
}
|
||||
|
||||
return options, nil
|
||||
return opts.Prepare(), nil
|
||||
}
|
||||
|
||||
func BoolPointer(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
func (s *service) qbittorrentCheckRulesCanDownload(action domain.Action, client *domain.DownloadClient, qbt *qbittorrent.Client) ([]string, error) {
|
||||
|
|
|
@ -70,6 +70,8 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) (
|
|||
"save_path",
|
||||
"paused",
|
||||
"ignore_rules",
|
||||
"skip_hash_check",
|
||||
"content_layout",
|
||||
"limit_download_speed",
|
||||
"limit_upload_speed",
|
||||
"limit_ratio",
|
||||
|
@ -103,7 +105,7 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) (
|
|||
for rows.Next() {
|
||||
var a domain.Action
|
||||
|
||||
var execCmd, execArgs, watchFolder, category, tags, label, savePath, webhookHost, webhookType, webhookMethod, webhookData sql.NullString
|
||||
var execCmd, execArgs, watchFolder, category, tags, label, savePath, contentLayout, webhookHost, webhookType, webhookMethod, webhookData sql.NullString
|
||||
var limitUl, limitDl, limitSeedTime sql.NullInt64
|
||||
var limitRatio sql.NullFloat64
|
||||
|
||||
|
@ -111,7 +113,7 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) (
|
|||
// filterID
|
||||
var paused, ignoreRules sql.NullBool
|
||||
|
||||
if err := rows.Scan(&a.ID, &a.Name, &a.Type, &a.Enabled, &execCmd, &execArgs, &watchFolder, &category, &tags, &label, &savePath, &paused, &ignoreRules, &limitDl, &limitUl, &limitRatio, &limitSeedTime, &a.ReAnnounceSkip, &a.ReAnnounceDelete, &a.ReAnnounceInterval, &a.ReAnnounceMaxAttempts, &webhookHost, &webhookType, &webhookMethod, &webhookData, &clientID); err != nil {
|
||||
if err := rows.Scan(&a.ID, &a.Name, &a.Type, &a.Enabled, &execCmd, &execArgs, &watchFolder, &category, &tags, &label, &savePath, &paused, &ignoreRules, &a.SkipHashCheck, &contentLayout, &limitDl, &limitUl, &limitRatio, &limitSeedTime, &a.ReAnnounceSkip, &a.ReAnnounceDelete, &a.ReAnnounceInterval, &a.ReAnnounceMaxAttempts, &webhookHost, &webhookType, &webhookMethod, &webhookData, &clientID); err != nil {
|
||||
return nil, errors.Wrap(err, "error scanning row")
|
||||
}
|
||||
|
||||
|
@ -124,6 +126,7 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) (
|
|||
a.SavePath = savePath.String
|
||||
a.Paused = paused.Bool
|
||||
a.IgnoreRules = ignoreRules.Bool
|
||||
a.ContentLayout = domain.ActionContentLayout(contentLayout.String)
|
||||
|
||||
a.LimitDownloadSpeed = limitDl.Int64
|
||||
a.LimitUploadSpeed = limitUl.Int64
|
||||
|
@ -324,6 +327,7 @@ func (r *ActionRepo) Store(ctx context.Context, action domain.Action) (*domain.A
|
|||
tags := toNullString(action.Tags)
|
||||
label := toNullString(action.Label)
|
||||
savePath := toNullString(action.SavePath)
|
||||
contentLayout := toNullString(string(action.ContentLayout))
|
||||
webhookHost := toNullString(action.WebhookHost)
|
||||
webhookData := toNullString(action.WebhookData)
|
||||
webhookType := toNullString(action.WebhookType)
|
||||
|
@ -351,6 +355,8 @@ func (r *ActionRepo) Store(ctx context.Context, action domain.Action) (*domain.A
|
|||
"save_path",
|
||||
"paused",
|
||||
"ignore_rules",
|
||||
"skip_hash_check",
|
||||
"content_layout",
|
||||
"limit_upload_speed",
|
||||
"limit_download_speed",
|
||||
"limit_ratio",
|
||||
|
@ -379,6 +385,8 @@ func (r *ActionRepo) Store(ctx context.Context, action domain.Action) (*domain.A
|
|||
savePath,
|
||||
action.Paused,
|
||||
action.IgnoreRules,
|
||||
action.SkipHashCheck,
|
||||
contentLayout,
|
||||
limitUL,
|
||||
limitDL,
|
||||
limitRatio,
|
||||
|
@ -418,6 +426,7 @@ func (r *ActionRepo) Update(ctx context.Context, action domain.Action) (*domain.
|
|||
tags := toNullString(action.Tags)
|
||||
label := toNullString(action.Label)
|
||||
savePath := toNullString(action.SavePath)
|
||||
contentLayout := toNullString(string(action.ContentLayout))
|
||||
webhookHost := toNullString(action.WebhookHost)
|
||||
webhookType := toNullString(action.WebhookType)
|
||||
webhookMethod := toNullString(action.WebhookMethod)
|
||||
|
@ -447,6 +456,8 @@ func (r *ActionRepo) Update(ctx context.Context, action domain.Action) (*domain.
|
|||
Set("save_path", savePath).
|
||||
Set("paused", action.Paused).
|
||||
Set("ignore_rules", action.IgnoreRules).
|
||||
Set("skip_hash_check", action.SkipHashCheck).
|
||||
Set("content_layout", contentLayout).
|
||||
Set("limit_upload_speed", limitUL).
|
||||
Set("limit_download_speed", limitDL).
|
||||
Set("limit_ratio", limitRatio).
|
||||
|
@ -507,6 +518,7 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A
|
|||
tags := toNullString(action.Tags)
|
||||
label := toNullString(action.Label)
|
||||
savePath := toNullString(action.SavePath)
|
||||
contentLayout := toNullString(string(action.ContentLayout))
|
||||
webhookHost := toNullString(action.WebhookHost)
|
||||
webhookType := toNullString(action.WebhookType)
|
||||
webhookMethod := toNullString(action.WebhookMethod)
|
||||
|
@ -533,6 +545,8 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A
|
|||
"save_path",
|
||||
"paused",
|
||||
"ignore_rules",
|
||||
"skip_hash_check",
|
||||
"content_layout",
|
||||
"limit_upload_speed",
|
||||
"limit_download_speed",
|
||||
"limit_ratio",
|
||||
|
@ -561,6 +575,8 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A
|
|||
savePath,
|
||||
action.Paused,
|
||||
action.IgnoreRules,
|
||||
action.SkipHashCheck,
|
||||
contentLayout,
|
||||
limitUL,
|
||||
limitDL,
|
||||
limitRatio,
|
||||
|
|
|
@ -78,5 +78,7 @@ func (db *DB) migratePostgres() error {
|
|||
return errors.Wrap(err, "failed to bump schema version")
|
||||
}
|
||||
|
||||
db.log.Info().Msgf("Database schema upgraded to version: %v", len(postgresMigrations))
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
|
|
@ -158,6 +158,8 @@ CREATE TABLE action
|
|||
save_path TEXT,
|
||||
paused BOOLEAN,
|
||||
ignore_rules BOOLEAN,
|
||||
skip_hash_check BOOLEAN DEFAULT false,
|
||||
content_layout TEXT,
|
||||
limit_upload_speed INT,
|
||||
limit_download_speed INT,
|
||||
limit_ratio REAL,
|
||||
|
@ -527,4 +529,11 @@ CREATE INDEX indexer_identifier_index
|
|||
ALTER TABLE filter
|
||||
ADD COLUMN external_webhook_expect_status INTEGER;
|
||||
`,
|
||||
`
|
||||
ALTER TABLE action
|
||||
ADD COLUMN skip_hash_check BOOLEAN DEFAULT FALSE;
|
||||
|
||||
ALTER TABLE action
|
||||
ADD COLUMN content_layout TEXT;
|
||||
`,
|
||||
}
|
||||
|
|
|
@ -98,6 +98,8 @@ func (db *DB) migrateSQLite() error {
|
|||
return errors.Wrap(err, "failed to bump schema version")
|
||||
}
|
||||
|
||||
db.log.Info().Msgf("Database schema upgraded to version: %v", len(sqliteMigrations))
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
|
|
|
@ -158,6 +158,8 @@ CREATE TABLE action
|
|||
save_path TEXT,
|
||||
paused BOOLEAN,
|
||||
ignore_rules BOOLEAN,
|
||||
skip_hash_check BOOLEAN DEFAULT false,
|
||||
content_layout TEXT,
|
||||
limit_upload_speed INT,
|
||||
limit_download_speed INT,
|
||||
limit_ratio REAL,
|
||||
|
@ -847,4 +849,11 @@ CREATE INDEX indexer_identifier_index
|
|||
ALTER TABLE filter
|
||||
ADD COLUMN external_webhook_expect_status INTEGER;
|
||||
`,
|
||||
`
|
||||
ALTER TABLE action
|
||||
ADD COLUMN skip_hash_check BOOLEAN DEFAULT FALSE;
|
||||
|
||||
ALTER TABLE action
|
||||
ADD COLUMN content_layout TEXT;
|
||||
`,
|
||||
}
|
||||
|
|
|
@ -13,35 +13,37 @@ type ActionRepo interface {
|
|||
}
|
||||
|
||||
type Action struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type ActionType `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
ExecCmd string `json:"exec_cmd,omitempty"`
|
||||
ExecArgs string `json:"exec_args,omitempty"`
|
||||
WatchFolder string `json:"watch_folder,omitempty"`
|
||||
Category string `json:"category,omitempty"`
|
||||
Tags string `json:"tags,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
SavePath string `json:"save_path,omitempty"`
|
||||
Paused bool `json:"paused,omitempty"`
|
||||
IgnoreRules bool `json:"ignore_rules,omitempty"`
|
||||
LimitUploadSpeed int64 `json:"limit_upload_speed,omitempty"`
|
||||
LimitDownloadSpeed int64 `json:"limit_download_speed,omitempty"`
|
||||
LimitRatio float64 `json:"limit_ratio,omitempty"`
|
||||
LimitSeedTime int64 `json:"limit_seed_time,omitempty"`
|
||||
ReAnnounceSkip bool `json:"reannounce_skip,omitempty"`
|
||||
ReAnnounceDelete bool `json:"reannounce_delete,omitempty"`
|
||||
ReAnnounceInterval int64 `json:"reannounce_interval,omitempty"`
|
||||
ReAnnounceMaxAttempts int64 `json:"reannounce_max_attempts,omitempty"`
|
||||
WebhookHost string `json:"webhook_host,omitempty"`
|
||||
WebhookType string `json:"webhook_type,omitempty"`
|
||||
WebhookMethod string `json:"webhook_method,omitempty"`
|
||||
WebhookData string `json:"webhook_data,omitempty"`
|
||||
WebhookHeaders []string `json:"webhook_headers,omitempty"`
|
||||
FilterID int `json:"filter_id,omitempty"`
|
||||
ClientID int32 `json:"client_id,omitempty"`
|
||||
Client DownloadClient `json:"client,omitempty"`
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type ActionType `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
ExecCmd string `json:"exec_cmd,omitempty"`
|
||||
ExecArgs string `json:"exec_args,omitempty"`
|
||||
WatchFolder string `json:"watch_folder,omitempty"`
|
||||
Category string `json:"category,omitempty"`
|
||||
Tags string `json:"tags,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
SavePath string `json:"save_path,omitempty"`
|
||||
Paused bool `json:"paused,omitempty"`
|
||||
IgnoreRules bool `json:"ignore_rules,omitempty"`
|
||||
SkipHashCheck bool `json:"skip_hash_check,omitempty"`
|
||||
ContentLayout ActionContentLayout `json:"content_layout,omitempty"`
|
||||
LimitUploadSpeed int64 `json:"limit_upload_speed,omitempty"`
|
||||
LimitDownloadSpeed int64 `json:"limit_download_speed,omitempty"`
|
||||
LimitRatio float64 `json:"limit_ratio,omitempty"`
|
||||
LimitSeedTime int64 `json:"limit_seed_time,omitempty"`
|
||||
ReAnnounceSkip bool `json:"reannounce_skip,omitempty"`
|
||||
ReAnnounceDelete bool `json:"reannounce_delete,omitempty"`
|
||||
ReAnnounceInterval int64 `json:"reannounce_interval,omitempty"`
|
||||
ReAnnounceMaxAttempts int64 `json:"reannounce_max_attempts,omitempty"`
|
||||
WebhookHost string `json:"webhook_host,omitempty"`
|
||||
WebhookType string `json:"webhook_type,omitempty"`
|
||||
WebhookMethod string `json:"webhook_method,omitempty"`
|
||||
WebhookData string `json:"webhook_data,omitempty"`
|
||||
WebhookHeaders []string `json:"webhook_headers,omitempty"`
|
||||
FilterID int `json:"filter_id,omitempty"`
|
||||
ClientID int32 `json:"client_id,omitempty"`
|
||||
Client DownloadClient `json:"client,omitempty"`
|
||||
}
|
||||
|
||||
type ActionType string
|
||||
|
@ -60,3 +62,11 @@ const (
|
|||
ActionTypeLidarr ActionType = "LIDARR"
|
||||
ActionTypeWhisparr ActionType = "WHISPARR"
|
||||
)
|
||||
|
||||
type ActionContentLayout string
|
||||
|
||||
const (
|
||||
ActionContentLayoutOriginal ActionContentLayout = "ORIGINAL"
|
||||
ActionContentLayoutSubfolderNone ActionContentLayout = "SUBFOLDER_NONE"
|
||||
ActionContentLayoutSubfolderCreate ActionContentLayout = "SUBFOLDER_CREATE"
|
||||
)
|
||||
|
|
|
@ -83,14 +83,14 @@ func (s *service) FindByID(ctx context.Context, filterID int) (*domain.Filter, e
|
|||
// find actions and attach
|
||||
actions, err := s.actionRepo.FindByFilterID(ctx, filter.ID)
|
||||
if err != nil {
|
||||
s.log.Error().Msgf("could not find filter actions: %+v", &filter.ID)
|
||||
s.log.Error().Msgf("could not find filter actions for filter id: %v", filter.ID)
|
||||
}
|
||||
filter.Actions = actions
|
||||
|
||||
// find indexers and attach
|
||||
indexers, err := s.indexerSvc.FindByFilterID(ctx, filter.ID)
|
||||
if err != nil {
|
||||
s.log.Error().Err(err).Msgf("could not find indexers for filter: %+v", &filter.Name)
|
||||
s.log.Error().Err(err).Msgf("could not find indexers for filter: %v", filter.Name)
|
||||
return nil, err
|
||||
}
|
||||
filter.Indexers = indexers
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Torrent struct {
|
||||
AddedOn int `json:"added_on"`
|
||||
AmountLeft int `json:"amount_left"`
|
||||
|
@ -216,3 +220,68 @@ type TransferInfo struct {
|
|||
UpInfoSpeed int64 `json:"up_info_speed"`
|
||||
UpRateLimit int64 `json:"up_rate_limit"`
|
||||
}
|
||||
|
||||
type ContentLayout string
|
||||
|
||||
const (
|
||||
ContentLayoutOriginal ContentLayout = "ORIGINAL"
|
||||
ContentLayoutSubfolderNone ContentLayout = "SUBFOLDER_NONE"
|
||||
ContentLayoutSubfolderCreate ContentLayout = "SUBFOLDER_CREATE"
|
||||
)
|
||||
|
||||
type TorrentAddOptions struct {
|
||||
Paused *bool
|
||||
SkipHashCheck *bool
|
||||
ContentLayout *ContentLayout
|
||||
SavePath *string
|
||||
AutoTMM *bool
|
||||
Category *string
|
||||
Tags *string
|
||||
LimitUploadSpeed *int64
|
||||
LimitDownloadSpeed *int64
|
||||
LimitRatio *float64
|
||||
LimitSeedTime *int64
|
||||
}
|
||||
|
||||
func (o *TorrentAddOptions) Prepare() map[string]string {
|
||||
options := map[string]string{}
|
||||
|
||||
if o.Paused != nil {
|
||||
options["paused"] = "true"
|
||||
}
|
||||
if o.SkipHashCheck != nil {
|
||||
options["skip_checking"] = "true"
|
||||
}
|
||||
if o.ContentLayout != nil {
|
||||
if *o.ContentLayout == ContentLayoutSubfolderCreate {
|
||||
options["root_folder"] = "true"
|
||||
} else if *o.ContentLayout == ContentLayoutSubfolderNone {
|
||||
options["root_folder"] = "false"
|
||||
}
|
||||
// if ORIGINAL then leave empty
|
||||
}
|
||||
if o.SavePath != nil && *o.SavePath != "" {
|
||||
options["savepath"] = *o.SavePath
|
||||
options["autoTMM"] = "false"
|
||||
}
|
||||
if o.Category != nil && *o.Category != "" {
|
||||
options["category"] = *o.Category
|
||||
}
|
||||
if o.Tags != nil && *o.Tags != "" {
|
||||
options["tags"] = *o.Tags
|
||||
}
|
||||
if o.LimitUploadSpeed != nil && *o.LimitUploadSpeed > 0 {
|
||||
options["upLimit"] = strconv.FormatInt(*o.LimitUploadSpeed*1000, 10)
|
||||
}
|
||||
if o.LimitDownloadSpeed != nil && *o.LimitDownloadSpeed > 0 {
|
||||
options["dlLimit"] = strconv.FormatInt(*o.LimitDownloadSpeed*1000, 10)
|
||||
}
|
||||
if o.LimitRatio != nil && *o.LimitRatio > 0 {
|
||||
options["ratioLimit"] = strconv.FormatFloat(*o.LimitRatio, 'f', 2, 64)
|
||||
}
|
||||
if o.LimitSeedTime != nil && *o.LimitSeedTime > 0 {
|
||||
options["seedingTimeLimit"] = strconv.FormatInt(*o.LimitSeedTime, 10)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
|
177
pkg/qbittorrent/domain_test.go
Normal file
177
pkg/qbittorrent/domain_test.go
Normal file
|
@ -0,0 +1,177 @@
|
|||
package qbittorrent
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func PtrBool(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
func PtrStr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func PtrInt64(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
func PtrFloat64(f float64) *float64 {
|
||||
return &f
|
||||
}
|
||||
|
||||
func TestTorrentAddOptions_Prepare(t *testing.T) {
|
||||
layoutNone := ContentLayoutSubfolderNone
|
||||
layoutCreate := ContentLayoutSubfolderCreate
|
||||
layoutOriginal := ContentLayoutOriginal
|
||||
type fields struct {
|
||||
Paused *bool
|
||||
SkipHashCheck *bool
|
||||
ContentLayout *ContentLayout
|
||||
SavePath *string
|
||||
AutoTMM *bool
|
||||
Category *string
|
||||
Tags *string
|
||||
LimitUploadSpeed *int64
|
||||
LimitDownloadSpeed *int64
|
||||
LimitRatio *float64
|
||||
LimitSeedTime *int64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want map[string]string
|
||||
}{
|
||||
{
|
||||
name: "test_01",
|
||||
fields: fields{
|
||||
Paused: nil,
|
||||
SkipHashCheck: PtrBool(true),
|
||||
ContentLayout: nil,
|
||||
SavePath: PtrStr("/home/test/torrents"),
|
||||
AutoTMM: nil,
|
||||
Category: PtrStr("test"),
|
||||
Tags: PtrStr("limited,slow"),
|
||||
LimitUploadSpeed: PtrInt64(100000),
|
||||
LimitDownloadSpeed: PtrInt64(100000),
|
||||
LimitRatio: PtrFloat64(2.0),
|
||||
LimitSeedTime: PtrInt64(100),
|
||||
},
|
||||
want: map[string]string{
|
||||
"skip_checking": "true",
|
||||
"autoTMM": "false",
|
||||
"ratioLimit": "2.00",
|
||||
"savepath": "/home/test/torrents",
|
||||
"seedingTimeLimit": "100",
|
||||
"category": "test",
|
||||
"tags": "limited,slow",
|
||||
"upLimit": "100000000",
|
||||
"dlLimit": "100000000",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test_02",
|
||||
fields: fields{
|
||||
Paused: nil,
|
||||
SkipHashCheck: PtrBool(true),
|
||||
ContentLayout: &layoutCreate,
|
||||
SavePath: PtrStr("/home/test/torrents"),
|
||||
AutoTMM: nil,
|
||||
Category: PtrStr("test"),
|
||||
Tags: PtrStr("limited,slow"),
|
||||
LimitUploadSpeed: PtrInt64(100000),
|
||||
LimitDownloadSpeed: PtrInt64(100000),
|
||||
LimitRatio: PtrFloat64(2.0),
|
||||
LimitSeedTime: PtrInt64(100),
|
||||
},
|
||||
want: map[string]string{
|
||||
"skip_checking": "true",
|
||||
"root_folder": "true",
|
||||
"autoTMM": "false",
|
||||
"ratioLimit": "2.00",
|
||||
"savepath": "/home/test/torrents",
|
||||
"seedingTimeLimit": "100",
|
||||
"category": "test",
|
||||
"tags": "limited,slow",
|
||||
"upLimit": "100000000",
|
||||
"dlLimit": "100000000",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test_03",
|
||||
fields: fields{
|
||||
Paused: nil,
|
||||
SkipHashCheck: PtrBool(true),
|
||||
ContentLayout: &layoutNone,
|
||||
SavePath: PtrStr("/home/test/torrents"),
|
||||
AutoTMM: nil,
|
||||
Category: PtrStr("test"),
|
||||
Tags: PtrStr("limited,slow"),
|
||||
LimitUploadSpeed: PtrInt64(100000),
|
||||
LimitDownloadSpeed: PtrInt64(100000),
|
||||
LimitRatio: PtrFloat64(2.0),
|
||||
LimitSeedTime: PtrInt64(100),
|
||||
},
|
||||
want: map[string]string{
|
||||
"skip_checking": "true",
|
||||
"root_folder": "false",
|
||||
"autoTMM": "false",
|
||||
"ratioLimit": "2.00",
|
||||
"savepath": "/home/test/torrents",
|
||||
"seedingTimeLimit": "100",
|
||||
"category": "test",
|
||||
"tags": "limited,slow",
|
||||
"upLimit": "100000000",
|
||||
"dlLimit": "100000000",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test_04",
|
||||
fields: fields{
|
||||
Paused: nil,
|
||||
SkipHashCheck: PtrBool(true),
|
||||
ContentLayout: &layoutOriginal,
|
||||
SavePath: PtrStr("/home/test/torrents"),
|
||||
AutoTMM: nil,
|
||||
Category: PtrStr("test"),
|
||||
Tags: PtrStr("limited,slow"),
|
||||
LimitUploadSpeed: PtrInt64(100000),
|
||||
LimitDownloadSpeed: PtrInt64(100000),
|
||||
LimitRatio: PtrFloat64(2.0),
|
||||
LimitSeedTime: PtrInt64(100),
|
||||
},
|
||||
want: map[string]string{
|
||||
"skip_checking": "true",
|
||||
"autoTMM": "false",
|
||||
"ratioLimit": "2.00",
|
||||
"savepath": "/home/test/torrents",
|
||||
"seedingTimeLimit": "100",
|
||||
"category": "test",
|
||||
"tags": "limited,slow",
|
||||
"upLimit": "100000000",
|
||||
"dlLimit": "100000000",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &TorrentAddOptions{
|
||||
Paused: tt.fields.Paused,
|
||||
SkipHashCheck: tt.fields.SkipHashCheck,
|
||||
ContentLayout: tt.fields.ContentLayout,
|
||||
SavePath: tt.fields.SavePath,
|
||||
AutoTMM: tt.fields.AutoTMM,
|
||||
Category: tt.fields.Category,
|
||||
Tags: tt.fields.Tags,
|
||||
LimitUploadSpeed: tt.fields.LimitUploadSpeed,
|
||||
LimitDownloadSpeed: tt.fields.LimitDownloadSpeed,
|
||||
LimitRatio: tt.fields.LimitRatio,
|
||||
LimitSeedTime: tt.fields.LimitSeedTime,
|
||||
}
|
||||
|
||||
got := o.Prepare()
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -244,6 +244,12 @@ export const ActionTypeNameMap = {
|
|||
"WHISPARR": "Whisparr"
|
||||
};
|
||||
|
||||
export const ActionContentLayoutOptions: SelectGenericOption<ActionContentLayout>[] = [
|
||||
{ label: "Original", description: "Original", value: "ORIGINAL" },
|
||||
{ label: "Create subfolder", description: "Create subfolder", value: "SUBFOLDER_CREATE" },
|
||||
{ label: "Don't create subfolder", description: "Don't create subfolder", value: "SUBFOLDER_NONE" },
|
||||
];
|
||||
|
||||
export interface OptionBasic {
|
||||
label: string;
|
||||
value: string;
|
||||
|
@ -308,6 +314,12 @@ export interface SelectOption {
|
|||
value: NotificationEvent;
|
||||
}
|
||||
|
||||
export interface SelectGenericOption<T> {
|
||||
label: string;
|
||||
description: string;
|
||||
value: T;
|
||||
}
|
||||
|
||||
export const EventOptions: SelectOption[] = [
|
||||
{
|
||||
label: "Push Rejected",
|
||||
|
|
|
@ -27,7 +27,8 @@ import {
|
|||
RELEASE_TYPE_MUSIC_OPTIONS,
|
||||
OTHER_OPTIONS,
|
||||
ORIGIN_OPTIONS,
|
||||
downloadsPerUnitOptions
|
||||
downloadsPerUnitOptions,
|
||||
ActionContentLayoutOptions
|
||||
} from "../../domain/constants";
|
||||
import { queryClient } from "../../App";
|
||||
import { APIClient } from "../../api/APIClient";
|
||||
|
@ -632,6 +633,8 @@ export function FilterActions({ filter, values }: FilterActionsProps) {
|
|||
save_path: "",
|
||||
paused: false,
|
||||
ignore_rules: false,
|
||||
skip_hash_check: false,
|
||||
content_layout: "",
|
||||
limit_upload_speed: 0,
|
||||
limit_download_speed: 0,
|
||||
limit_ratio: 0,
|
||||
|
@ -658,7 +661,7 @@ export function FilterActions({ filter, values }: FilterActionsProps) {
|
|||
<div className="ml-4 mt-4">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-gray-200">Actions</h3>
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Add to download clients or run custom commands.
|
||||
Add to download clients or run custom commands.
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-4 mt-4 flex-shrink-0">
|
||||
|
@ -667,7 +670,7 @@ export function FilterActions({ filter, values }: FilterActionsProps) {
|
|||
className="relative inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 dark:bg-blue-600 hover:bg-indigo-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
onClick={() => push(newAction)}
|
||||
>
|
||||
Add new
|
||||
Add new
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -790,11 +793,11 @@ const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
|
|||
<div className="mt-6 grid grid-cols-12 gap-6">
|
||||
<NumberField
|
||||
name={`actions.${idx}.limit_download_speed`}
|
||||
label="Limit download speed (KB/s)"
|
||||
label="Limit download speed (KiB/s)"
|
||||
/>
|
||||
<NumberField
|
||||
name={`actions.${idx}.limit_upload_speed`}
|
||||
label="Limit upload speed (KB/s)"
|
||||
label="Limit upload speed (KiB/s)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -822,6 +825,21 @@ const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
|
|||
description="Download if max active reached"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-6">
|
||||
<Select
|
||||
name={`actions.${idx}.content_layout`}
|
||||
label="Content Layout"
|
||||
optionDefaultText="Select content layout"
|
||||
options={ActionContentLayoutOptions}
|
||||
/>
|
||||
<div className="mt-2">
|
||||
<SwitchGroup
|
||||
name={`actions.${idx}.skip_hash_check`}
|
||||
label="Skip hash check"
|
||||
description="Add torrent and skip hash check"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsableSection>
|
||||
|
||||
<CollapsableSection title="Advanced" subtitle="Advanced options">
|
||||
|
|
4
web/src/types/Filter.d.ts
vendored
4
web/src/types/Filter.d.ts
vendored
|
@ -77,6 +77,8 @@ interface Action {
|
|||
save_path?: string;
|
||||
paused?: boolean;
|
||||
ignore_rules?: boolean;
|
||||
skip_hash_check: boolean;
|
||||
content_layout?: ActionContentLayout;
|
||||
limit_upload_speed?: number;
|
||||
limit_download_speed?: number;
|
||||
limit_ratio?: number;
|
||||
|
@ -94,4 +96,6 @@ interface Action {
|
|||
client_id?: number;
|
||||
}
|
||||
|
||||
type ActionContentLayout = "ORIGINAL" | "SUBFOLDER_CREATE" | "SUBFOLDER_NONE";
|
||||
|
||||
type ActionType = "TEST" | "EXEC" | "WATCH_FOLDER" | "WEBHOOK" | DownloadClientType;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue