diff --git a/internal/action/qbittorrent.go b/internal/action/qbittorrent.go index 9d1b6cb..48ba8f6 100644 --- a/internal/action/qbittorrent.go +++ b/internal/action/qbittorrent.go @@ -62,6 +62,12 @@ func (s *service) qbittorrent(qbt *qbittorrent.Client, action domain.Action, rel if action.LimitDownloadSpeed > 0 { options["dlLimit"] = strconv.FormatInt(action.LimitDownloadSpeed, 10) } + if action.LimitRatio > 0 { + options["ratioLimit"] = strconv.FormatFloat(action.LimitRatio, 'r', 2, 64) + } + if action.LimitSeedTime > 0 { + options["seedingTimeLimit"] = strconv.FormatInt(action.LimitSeedTime, 10) + } log.Trace().Msgf("action qBittorrent options: %+v", options) diff --git a/internal/database/action.go b/internal/database/action.go index 6df7add..3e5ecd6 100644 --- a/internal/database/action.go +++ b/internal/database/action.go @@ -68,6 +68,8 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) ( "ignore_rules", "limit_download_speed", "limit_upload_speed", + "limit_ratio", + "limit_seed_time", "reannounce_skip", "reannounce_delete", "reannounce_interval", @@ -100,12 +102,14 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) ( var a domain.Action var execCmd, execArgs, watchFolder, category, tags, label, savePath, webhookHost, webhookType, webhookMethod, webhookData sql.NullString - var limitUl, limitDl sql.NullInt64 + var limitUl, limitDl, limitSeedTime sql.NullInt64 + var limitRatio sql.NullFloat64 + var clientID sql.NullInt32 // 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, &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, &limitDl, &limitUl, &limitRatio, &limitSeedTime, &a.ReAnnounceSkip, &a.ReAnnounceDelete, &a.ReAnnounceInterval, &a.ReAnnounceMaxAttempts, &webhookHost, &webhookType, &webhookMethod, &webhookData, &clientID); err != nil { log.Error().Stack().Err(err).Msg("action.findByFilterID: error scanning row") return nil, err } @@ -122,6 +126,8 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) ( a.LimitDownloadSpeed = limitDl.Int64 a.LimitUploadSpeed = limitUl.Int64 + a.LimitRatio = limitRatio.Float64 + a.LimitSeedTime = limitSeedTime.Int64 a.WebhookHost = webhookHost.String a.WebhookType = webhookType.String @@ -206,6 +212,8 @@ func (r *ActionRepo) List(ctx context.Context) ([]domain.Action, error) { "ignore_rules", "limit_download_speed", "limit_upload_speed", + "limit_ratio", + "limit_seed_time", "reannounce_skip", "reannounce_delete", "reannounce_interval", @@ -237,11 +245,12 @@ func (r *ActionRepo) List(ctx context.Context) ([]domain.Action, error) { var a domain.Action var execCmd, execArgs, watchFolder, category, tags, label, savePath, webhookHost, webhookType, webhookMethod, webhookData sql.NullString - var limitUl, limitDl sql.NullInt64 + var limitUl, limitDl, limitSeedTime sql.NullInt64 + var limitRatio sql.NullFloat64 var clientID sql.NullInt32 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, &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, &limitDl, &limitUl, &limitRatio, &limitSeedTime, &a.ReAnnounceSkip, &a.ReAnnounceDelete, &a.ReAnnounceInterval, &a.ReAnnounceMaxAttempts, &webhookHost, &webhookType, &webhookMethod, &webhookData, &clientID); err != nil { log.Error().Stack().Err(err).Msg("action.list: error scanning row") return nil, err } @@ -255,6 +264,8 @@ func (r *ActionRepo) List(ctx context.Context) ([]domain.Action, error) { a.LimitDownloadSpeed = limitDl.Int64 a.LimitUploadSpeed = limitUl.Int64 + a.LimitRatio = limitRatio.Float64 + a.LimitSeedTime = limitSeedTime.Int64 a.WebhookHost = webhookHost.String a.WebhookType = webhookType.String @@ -332,6 +343,8 @@ func (r *ActionRepo) Store(ctx context.Context, action domain.Action) (*domain.A limitDL := toNullInt64(action.LimitDownloadSpeed) limitUL := toNullInt64(action.LimitUploadSpeed) + limitRatio := toNullFloat64(action.LimitRatio) + limitSeedTime := toNullInt64(action.LimitSeedTime) clientID := toNullInt32(action.ClientID) filterID := toNullInt32(int32(action.FilterID)) @@ -352,6 +365,8 @@ func (r *ActionRepo) Store(ctx context.Context, action domain.Action) (*domain.A "ignore_rules", "limit_upload_speed", "limit_download_speed", + "limit_ratio", + "limit_seed_time", "reannounce_skip", "reannounce_delete", "reannounce_interval", @@ -378,6 +393,8 @@ func (r *ActionRepo) Store(ctx context.Context, action domain.Action) (*domain.A action.IgnoreRules, limitUL, limitDL, + limitRatio, + limitSeedTime, action.ReAnnounceSkip, action.ReAnnounceDelete, action.ReAnnounceInterval, @@ -421,6 +438,9 @@ func (r *ActionRepo) Update(ctx context.Context, action domain.Action) (*domain. limitDL := toNullInt64(action.LimitDownloadSpeed) limitUL := toNullInt64(action.LimitUploadSpeed) + limitRatio := toNullFloat64(action.LimitRatio) + limitSeedTime := toNullInt64(action.LimitSeedTime) + clientID := toNullInt32(action.ClientID) filterID := toNullInt32(int32(action.FilterID)) @@ -442,6 +462,8 @@ func (r *ActionRepo) Update(ctx context.Context, action domain.Action) (*domain. Set("ignore_rules", action.IgnoreRules). Set("limit_upload_speed", limitUL). Set("limit_download_speed", limitDL). + Set("limit_ratio", limitRatio). + Set("limit_seed_time", limitSeedTime). Set("reannounce_skip", action.ReAnnounceSkip). Set("reannounce_delete", action.ReAnnounceDelete). Set("reannounce_interval", action.ReAnnounceInterval). @@ -509,6 +531,8 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A limitDL := toNullInt64(action.LimitDownloadSpeed) limitUL := toNullInt64(action.LimitUploadSpeed) + limitRatio := toNullFloat64(action.LimitRatio) + limitSeedTime := toNullInt64(action.LimitSeedTime) clientID := toNullInt32(action.ClientID) queryBuilder := r.db.squirrel. @@ -528,6 +552,8 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A "ignore_rules", "limit_upload_speed", "limit_download_speed", + "limit_ratio", + "limit_seed_time", "reannounce_skip", "reannounce_delete", "reannounce_interval", @@ -554,6 +580,8 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A action.IgnoreRules, limitUL, limitDL, + limitRatio, + limitSeedTime, action.ReAnnounceSkip, action.ReAnnounceDelete, action.ReAnnounceInterval, diff --git a/internal/database/migrate.go b/internal/database/migrate.go index a396a55..2f6063a 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -147,6 +147,8 @@ CREATE TABLE action ignore_rules BOOLEAN, limit_upload_speed INT, limit_download_speed INT, + limit_ratio REAL, + limit_seed_time INT, reannounce_skip BOOLEAN DEFAULT false, reannounce_delete BOOLEAN DEFAULT false, reannounce_interval INTEGER DEFAULT 7, @@ -665,6 +667,13 @@ ALTER TABLE release_action_status_dg_tmp ALTER TABLE "action" ADD COLUMN reannounce_max_attempts INTEGER DEFAULT 50; `, + ` + ALTER TABLE "action" + ADD COLUMN limit_ratio REAL DEFAULT 0; + + ALTER TABLE "action" + ADD COLUMN limit_seed_time INTEGER DEFAULT 0; + `, } const postgresSchema = ` @@ -814,6 +823,8 @@ CREATE TABLE action ignore_rules BOOLEAN, limit_upload_speed INT, limit_download_speed INT, + limit_ratio REAL, + limit_seed_time INT, reannounce_skip BOOLEAN DEFAULT false, reannounce_delete BOOLEAN DEFAULT false, reannounce_interval INTEGER DEFAULT 7, @@ -1085,4 +1096,11 @@ var postgresMigrations = []string{ ALTER TABLE "action" ADD COLUMN reannounce_max_attempts INTEGER DEFAULT 50; `, + ` + ALTER TABLE "action" + ADD COLUMN limit_ratio REAL DEFAULT 0; + + ALTER TABLE "action" + ADD COLUMN limit_seed_time INTEGER DEFAULT 0; + `, } diff --git a/internal/database/utils.go b/internal/database/utils.go index a6a4ba5..6fa3dad 100644 --- a/internal/database/utils.go +++ b/internal/database/utils.go @@ -32,3 +32,10 @@ func toNullInt64(s int64) sql.NullInt64 { Valid: s != 0, } } + +func toNullFloat64(s float64) sql.NullFloat64 { + return sql.NullFloat64{ + Float64: s, + Valid: s != 0, + } +} diff --git a/internal/domain/action.go b/internal/domain/action.go index 0bec9ed..4f79379 100644 --- a/internal/domain/action.go +++ b/internal/domain/action.go @@ -28,6 +28,8 @@ type Action struct { 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"` diff --git a/web/src/components/inputs/input.tsx b/web/src/components/inputs/input.tsx index 1fe2618..30b6630 100644 --- a/web/src/components/inputs/input.tsx +++ b/web/src/components/inputs/input.tsx @@ -131,12 +131,14 @@ interface NumberFieldProps { name: string; label?: string; placeholder?: string; + step?: number; } export const NumberField = ({ name, label, - placeholder + placeholder, + step, }: NumberFieldProps) => (