feat(actions): add more macro variables (#157)

* feat(actions): add more macro variables

* feat: add more macros

* feat: add more tests
This commit is contained in:
Ludvig Lundgren 2022-03-04 20:29:53 +01:00 committed by GitHub
parent e0e4bf6202
commit 5a45851677
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 252 additions and 68 deletions

View file

@ -13,7 +13,7 @@ import (
"github.com/rs/zerolog/log"
)
func (s *service) deluge(action domain.Action, torrentFile string) error {
func (s *service) deluge(action domain.Action, release domain.Release) error {
log.Debug().Msgf("action Deluge: %v", action.Name)
var err error
@ -40,12 +40,12 @@ func (s *service) deluge(action domain.Action, torrentFile string) error {
switch client.Type {
case "DELUGE_V1":
if err = delugeV1(client, settings, action, torrentFile); err != nil {
if err = delugeV1(client, settings, action, release); err != nil {
return err
}
case "DELUGE_V2":
if err = delugeV2(client, settings, action, torrentFile); err != nil {
if err = delugeV2(client, settings, action, release); err != nil {
return err
}
}
@ -135,7 +135,7 @@ func (s *service) delugeCheckRulesCanDownload(action domain.Action) (bool, error
return true, nil
}
func delugeV1(client *domain.DownloadClient, settings delugeClient.Settings, action domain.Action, torrentFile string) error {
func delugeV1(client *domain.DownloadClient, settings delugeClient.Settings, action domain.Action, release domain.Release) error {
deluge := delugeClient.NewV1(settings)
@ -148,27 +148,37 @@ func delugeV1(client *domain.DownloadClient, settings delugeClient.Settings, act
defer deluge.Close()
t, err := ioutil.ReadFile(torrentFile)
t, err := ioutil.ReadFile(release.TorrentTmpFile)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not read torrent file: %v", torrentFile)
log.Error().Stack().Err(err).Msgf("could not read torrent file: %v", release.TorrentTmpFile)
return err
}
// encode file to base64 before sending to deluge
encodedFile := base64.StdEncoding.EncodeToString(t)
if encodedFile == "" {
log.Error().Stack().Err(err).Msgf("could not encode torrent file: %v", torrentFile)
log.Error().Stack().Err(err).Msgf("could not encode torrent file: %v", release.TorrentTmpFile)
return err
}
// set options
options := delugeClient.Options{}
// macros handle args and replace vars
m := NewMacro(release)
if action.Paused {
options.AddPaused = &action.Paused
}
if action.SavePath != "" {
options.DownloadLocation = &action.SavePath
// parse and replace values in argument string before continuing
savePathArgs, err := m.Parse(action.SavePath)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not parse macro: %v", action.SavePath)
return err
}
options.DownloadLocation = &savePathArgs
}
if action.LimitDownloadSpeed > 0 {
maxDL := int(action.LimitDownloadSpeed)
@ -181,9 +191,9 @@ func delugeV1(client *domain.DownloadClient, settings delugeClient.Settings, act
log.Trace().Msgf("action Deluge options: %+v", options)
torrentHash, err := deluge.AddTorrentFile(torrentFile, encodedFile, &options)
torrentHash, err := deluge.AddTorrentFile(release.TorrentTmpFile, encodedFile, &options)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not add torrent %v to client: %v", torrentFile, client.Name)
log.Error().Stack().Err(err).Msgf("could not add torrent %v to client: %v", release.TorrentTmpFile, client.Name)
return err
}
@ -194,9 +204,16 @@ func delugeV1(client *domain.DownloadClient, settings delugeClient.Settings, act
return err
}
// parse and replace values in argument string before continuing
labelArgs, err := m.Parse(action.Label)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not parse macro: %v", action.Label)
return err
}
if p != nil {
// TODO first check if label exists, if not, add it, otherwise set
err = p.SetTorrentLabel(torrentHash, action.Label)
err = p.SetTorrentLabel(torrentHash, labelArgs)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not set label: %v on client: %v", action.Label, client.Name)
return err
@ -209,7 +226,7 @@ func delugeV1(client *domain.DownloadClient, settings delugeClient.Settings, act
return nil
}
func delugeV2(client *domain.DownloadClient, settings delugeClient.Settings, action domain.Action, torrentFile string) error {
func delugeV2(client *domain.DownloadClient, settings delugeClient.Settings, action domain.Action, release domain.Release) error {
deluge := delugeClient.NewV2(settings)
@ -222,27 +239,37 @@ func delugeV2(client *domain.DownloadClient, settings delugeClient.Settings, act
defer deluge.Close()
t, err := ioutil.ReadFile(torrentFile)
t, err := ioutil.ReadFile(release.TorrentTmpFile)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not read torrent file: %v", torrentFile)
log.Error().Stack().Err(err).Msgf("could not read torrent file: %v", release.TorrentTmpFile)
return err
}
// encode file to base64 before sending to deluge
encodedFile := base64.StdEncoding.EncodeToString(t)
if encodedFile == "" {
log.Error().Stack().Err(err).Msgf("could not encode torrent file: %v", torrentFile)
log.Error().Stack().Err(err).Msgf("could not encode torrent file: %v", release.TorrentTmpFile)
return err
}
// set options
options := delugeClient.Options{}
// macros handle args and replace vars
m := NewMacro(release)
if action.Paused {
options.AddPaused = &action.Paused
}
if action.SavePath != "" {
options.DownloadLocation = &action.SavePath
// parse and replace values in argument string before continuing
savePathArgs, err := m.Parse(action.SavePath)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not parse macro: %v", action.SavePath)
return err
}
options.DownloadLocation = &savePathArgs
}
if action.LimitDownloadSpeed > 0 {
maxDL := int(action.LimitDownloadSpeed)
@ -255,9 +282,9 @@ func delugeV2(client *domain.DownloadClient, settings delugeClient.Settings, act
log.Trace().Msgf("action Deluge options: %+v", options)
torrentHash, err := deluge.AddTorrentFile(torrentFile, encodedFile, &options)
torrentHash, err := deluge.AddTorrentFile(release.TorrentTmpFile, encodedFile, &options)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not add torrent %v to client: %v", torrentFile, client.Name)
log.Error().Stack().Err(err).Msgf("could not add torrent %v to client: %v", release.TorrentTmpFile, client.Name)
return err
}
@ -268,9 +295,16 @@ func delugeV2(client *domain.DownloadClient, settings delugeClient.Settings, act
return err
}
// parse and replace values in argument string before continuing
labelArgs, err := m.Parse(action.Label)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not parse macro: %v", action.Label)
return err
}
if p != nil {
// TODO first check if label exists, if not, add it, otherwise set
err = p.SetTorrentLabel(torrentHash, action.Label)
err = p.SetTorrentLabel(torrentHash, labelArgs)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not set label: %v on client: %v", action.Label, client.Name)
return err

View file

@ -10,7 +10,7 @@ import (
"github.com/rs/zerolog/log"
)
func (s *service) execCmd(release domain.Release, action domain.Action, torrentFile string) {
func (s *service) execCmd(release domain.Release, action domain.Action) {
log.Debug().Msgf("action exec: %v release: %v", action.Name, release.TorrentName)
// check if program exists
@ -21,11 +21,7 @@ func (s *service) execCmd(release domain.Release, action domain.Action, torrentF
}
// handle args and replace vars
m := Macro{
TorrentName: release.TorrentName,
TorrentPathName: torrentFile,
TorrentUrl: release.TorrentURL,
}
m := NewMacro(release)
// parse and replace values in argument string before continuing
parsedArgs, err := m.Parse(action.ExecArgs)
@ -46,7 +42,7 @@ func (s *service) execCmd(release domain.Release, action domain.Action, torrentF
output, err := command.CombinedOutput()
if err != nil {
// everything other than exit 0 is considered an error
log.Error().Stack().Err(err).Msgf("command: %v args: %v failed, torrent: %v", cmd, parsedArgs, torrentFile)
log.Error().Stack().Err(err).Msgf("command: %v args: %v failed, torrent: %v", cmd, parsedArgs, release.TorrentTmpFile)
}
log.Trace().Msgf("executed command: '%v'", string(output))

View file

@ -3,12 +3,53 @@ package action
import (
"bytes"
"text/template"
"time"
"github.com/autobrr/autobrr/internal/domain"
)
type Macro struct {
TorrentName string
TorrentPathName string
TorrentHash string
TorrentUrl string
Indexer string
Resolution string
Source string
HDR string
Season int
Episode int
Year int
Month int
Day int
Hour int
Minute int
Second int
}
func NewMacro(release domain.Release) Macro {
currentTime := time.Now()
ma := Macro{
TorrentName: release.TorrentName,
TorrentUrl: release.TorrentURL,
TorrentPathName: release.TorrentTmpFile,
TorrentHash: release.TorrentHash,
Indexer: release.Indexer,
Resolution: release.Resolution,
Source: release.Source,
HDR: release.HDR,
Season: release.Season,
Episode: release.Episode,
Year: currentTime.Year(),
Month: int(currentTime.Month()),
Day: currentTime.Day(),
Hour: currentTime.Hour(),
Minute: currentTime.Minute(),
Second: currentTime.Second(),
}
return ma
}
// Parse takes a string and replaces valid vars

View file

@ -1,14 +1,21 @@
package action
import (
"fmt"
"github.com/autobrr/autobrr/internal/domain"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestMacros_Parse(t *testing.T) {
currentTime := time.Now()
type fields struct {
TorrentName string
TorrentPathName string
TorrentUrl string
Indexer string
}
type args struct {
text string
@ -16,43 +23,60 @@ func TestMacros_Parse(t *testing.T) {
tests := []struct {
name string
fields fields
release domain.Release
args args
want string
wantErr bool
}{
{
name: "test_ok",
fields: fields{TorrentPathName: "/tmp/a-temporary-file.torrent"},
name: "test_ok",
release: domain.Release{
TorrentName: "This movie 2021",
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
Indexer: "mock1",
},
args: args{text: "Print mee {{.TorrentPathName}}"},
want: "Print mee /tmp/a-temporary-file.torrent",
wantErr: false,
},
{
name: "test_bad",
fields: fields{TorrentPathName: "/tmp/a-temporary-file.torrent"},
name: "test_bad",
release: domain.Release{
TorrentName: "This movie 2021",
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
Indexer: "mock1",
},
args: args{text: "Print mee {{TorrentPathName}}"},
want: "",
wantErr: true,
},
{
name: "test_program_arg",
fields: fields{TorrentPathName: "/tmp/a-temporary-file.torrent"},
name: "test_program_arg",
release: domain.Release{
TorrentName: "This movie 2021",
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
Indexer: "mock1",
},
args: args{text: "add {{.TorrentPathName}} --category test"},
want: "add /tmp/a-temporary-file.torrent --category test",
wantErr: false,
},
{
name: "test_program_arg_bad",
fields: fields{TorrentPathName: "/tmp/a-temporary-file.torrent"},
name: "test_program_arg_bad",
release: domain.Release{
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
Indexer: "mock1",
},
args: args{text: "add {{.TorrenttPathName}} --category test"},
want: "",
wantErr: true,
},
{
name: "test_program_arg",
fields: fields{
TorrentName: "This movie 2021",
TorrentPathName: "/tmp/a-temporary-file.torrent",
release: domain.Release{
TorrentName: "This movie 2021",
TorrentTmpFile: "/tmp/a-temporary-file.torrent",
Indexer: "mock1",
},
args: args{text: "add {{.TorrentPathName}} --category test --other {{.TorrentName}}"},
want: "add /tmp/a-temporary-file.torrent --category test --other This movie 2021",
@ -60,31 +84,87 @@ func TestMacros_Parse(t *testing.T) {
},
{
name: "test_args_long",
fields: fields{
release: domain.Release{
TorrentName: "This movie 2021",
TorrentUrl: "https://some.site/download/fakeid",
TorrentURL: "https://some.site/download/fakeid",
Indexer: "mock1",
},
args: args{text: "{{.TorrentName}} {{.TorrentUrl}} SOME_LONG_TOKEN"},
want: "This movie 2021 https://some.site/download/fakeid SOME_LONG_TOKEN",
wantErr: false,
},
{
name: "test_args_long_1",
release: domain.Release{
TorrentName: "This movie 2021",
TorrentURL: "https://some.site/download/fakeid",
Indexer: "mock1",
},
args: args{text: "{{.Indexer}} {{.TorrentName}} {{.TorrentUrl}} SOME_LONG_TOKEN"},
want: "mock1 This movie 2021 https://some.site/download/fakeid SOME_LONG_TOKEN",
wantErr: false,
},
{
name: "test_args_category",
release: domain.Release{
TorrentName: "This movie 2021",
TorrentURL: "https://some.site/download/fakeid",
Indexer: "mock1",
},
args: args{text: "{{.Indexer}}-race"},
want: "mock1-race",
wantErr: false,
},
{
name: "test_args_category_year",
release: domain.Release{
TorrentName: "This movie 2021",
TorrentURL: "https://some.site/download/fakeid",
Indexer: "mock1",
},
args: args{text: "{{.Indexer}}-{{.Year}}-race"},
want: fmt.Sprintf("mock1-%v-race", currentTime.Year()),
wantErr: false,
},
{
name: "test_args_category_year",
release: domain.Release{
TorrentName: "This movie 2021",
TorrentURL: "https://some.site/download/fakeid",
Indexer: "mock1",
Resolution: "2160p",
HDR: "DV",
},
args: args{text: "movies-{{.Resolution}}{{ if .HDR }}-{{.HDR}}{{ end }}"},
want: "movies-2160p-DV",
wantErr: false,
},
{
name: "test_args_category_and_if",
release: domain.Release{
TorrentName: "This movie 2021",
TorrentURL: "https://some.site/download/fakeid",
Indexer: "mock1",
Resolution: "2160p",
HDR: "HDR",
},
args: args{text: "movies-{{.Resolution}}{{ if .HDR }}-{{.HDR}}{{ end }}"},
want: "movies-2160p-HDR",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := Macro{
TorrentPathName: tt.fields.TorrentPathName,
TorrentUrl: tt.fields.TorrentUrl,
TorrentName: tt.fields.TorrentName,
}
m := NewMacro(tt.release)
got, err := m.Parse(tt.args.text)
assert.Equal(t, currentTime.Year(), m.Year)
if (err != nil) != tt.wantErr {
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Parse() got = %v, want %v", got, tt.want)
}
assert.Equal(t, tt.want, got)
})
}
}

View file

@ -5,31 +5,56 @@ import (
"strconv"
"time"
"github.com/rs/zerolog/log"
"github.com/autobrr/autobrr/internal/domain"
"github.com/autobrr/autobrr/pkg/qbittorrent"
"github.com/rs/zerolog/log"
)
const ReannounceMaxAttempts = 50
const ReannounceInterval = 7000
func (s *service) qbittorrent(qbt *qbittorrent.Client, action domain.Action, torrentFile string, hash string) error {
func (s *service) qbittorrent(qbt *qbittorrent.Client, action domain.Action, release domain.Release) error {
log.Debug().Msgf("action qBittorrent: %v", action.Name)
// macros handle args and replace vars
m := NewMacro(release)
options := map[string]string{}
if action.Paused {
options["paused"] = "true"
}
if action.SavePath != "" {
options["savepath"] = action.SavePath
// parse and replace values in argument string before continuing
actionArgs, err := m.Parse(action.SavePath)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not parse macro: %v", action.SavePath)
return err
}
options["savepath"] = actionArgs
options["autoTMM"] = "false"
}
if action.Category != "" {
options["category"] = action.Category
// parse and replace values in argument string before continuing
categoryArgs, err := m.Parse(action.Category)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not parse macro: %v", action.Category)
return err
}
options["category"] = categoryArgs
}
if action.Tags != "" {
options["tags"] = action.Tags
// parse and replace values in argument string before continuing
tagsArgs, err := m.Parse(action.Tags)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not parse macro: %v", action.Tags)
return err
}
options["tags"] = tagsArgs
}
if action.LimitUploadSpeed > 0 {
options["upLimit"] = strconv.FormatInt(action.LimitUploadSpeed, 10)
@ -40,21 +65,21 @@ func (s *service) qbittorrent(qbt *qbittorrent.Client, action domain.Action, tor
log.Trace().Msgf("action qBittorrent options: %+v", options)
err := qbt.AddTorrentFromFile(torrentFile, options)
err := qbt.AddTorrentFromFile(release.TorrentTmpFile, options)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not add torrent %v to client: %v", torrentFile, qbt.Name)
log.Error().Stack().Err(err).Msgf("could not add torrent %v to client: %v", release.TorrentTmpFile, qbt.Name)
return err
}
if !action.Paused && hash != "" {
err = checkTrackerStatus(qbt, hash)
if !action.Paused && release.TorrentHash != "" {
err = checkTrackerStatus(qbt, release.TorrentHash)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not reannounce torrent: %v", hash)
log.Error().Stack().Err(err).Msgf("could not reannounce torrent: %v", release.TorrentHash)
return err
}
}
log.Info().Msgf("torrent with hash %v successfully added to client: '%v'", hash, qbt.Name)
log.Info().Msgf("torrent with hash %v successfully added to client: '%v'", release.TorrentHash, qbt.Name)
return nil
}

View file

@ -61,7 +61,7 @@ func (s *service) runAction(action domain.Action, release domain.Release) error
}
}
s.execCmd(release, action, release.TorrentTmpFile)
s.execCmd(release, action)
case domain.ActionTypeWatchFolder:
if release.TorrentTmpFile == "" {
@ -71,7 +71,7 @@ func (s *service) runAction(action domain.Action, release domain.Release) error
}
}
s.watchFolder(action.WatchFolder, release.TorrentTmpFile)
s.watchFolder(action, release)
case domain.ActionTypeDelugeV1, domain.ActionTypeDelugeV2:
canDownload, err := s.delugeCheckRulesCanDownload(action)
@ -91,7 +91,7 @@ func (s *service) runAction(action domain.Action, release domain.Release) error
}
}
err = s.deluge(action, release.TorrentTmpFile)
err = s.deluge(action, release)
if err != nil {
log.Error().Stack().Err(err).Msg("error sending torrent to Deluge")
return err
@ -115,7 +115,7 @@ func (s *service) runAction(action domain.Action, release domain.Release) error
}
}
err = s.qbittorrent(client, action, release.TorrentTmpFile, release.TorrentHash)
err = s.qbittorrent(client, action, release)
if err != nil {
log.Error().Stack().Err(err).Msg("error sending torrent to qBittorrent")
return err
@ -215,19 +215,27 @@ func (s *service) test(name string) {
log.Info().Msgf("action TEST: %v", name)
}
func (s *service) watchFolder(dir string, torrentFile string) {
log.Trace().Msgf("action WATCH_FOLDER: %v file: %v", dir, torrentFile)
func (s *service) watchFolder(action domain.Action, release domain.Release) {
m := NewMacro(release)
// parse and replace values in argument string before continuing
watchFolderArgs, err := m.Parse(action.WatchFolder)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not parse macro: %v", action.WatchFolder)
}
log.Trace().Msgf("action WATCH_FOLDER: %v file: %v", watchFolderArgs, release.TorrentTmpFile)
// Open original file
original, err := os.Open(torrentFile)
original, err := os.Open(release.TorrentTmpFile)
if err != nil {
log.Error().Stack().Err(err).Msgf("could not open temp file '%v'", torrentFile)
log.Error().Stack().Err(err).Msgf("could not open temp file '%v'", release.TorrentTmpFile)
return
}
defer original.Close()
_, tmpFileName := path.Split(torrentFile)
fullFileName := path.Join(dir, tmpFileName+".torrent")
_, tmpFileName := path.Split(release.TorrentTmpFile)
fullFileName := path.Join(watchFolderArgs, tmpFileName+".torrent")
// Create new file
newFile, err := os.Create(fullFileName)