aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2021-09-20 19:19:52 +0200
committerMartin Polden <mpolden@mpolden.no>2021-09-20 19:24:09 +0200
commit4318414c02363e14f755b0003bfda83ee80c32ec (patch)
tree6b6d898c2ce9e2a6cc1cfc1a4b248e7916983b67
parentbfe00452eaebf216bb9c589aa4f06911ceb116e9 (diff)
cmd: Respect replacements when classifying
-rw-r--r--cmd/lftpq/main.go53
-rw-r--r--cmd/lftpq/main_test.go17
-rw-r--r--parser/parser.go12
-rw-r--r--queue/config.go32
-rw-r--r--queue/config_test.go6
-rw-r--r--queue/item.go34
-rw-r--r--queue/item_test.go34
-rw-r--r--queue/queue.go4
-rw-r--r--queue/queue_test.go26
9 files changed, 110 insertions, 108 deletions
diff --git a/cmd/lftpq/main.go b/cmd/lftpq/main.go
index 65837d7..6b752bf 100644
--- a/cmd/lftpq/main.go
+++ b/cmd/lftpq/main.go
@@ -7,10 +7,10 @@ import (
"os"
"os/signal"
"path/filepath"
+ "sort"
"syscall"
"github.com/mpolden/lftpq/lftp"
- "github.com/mpolden/lftpq/parser"
"github.com/mpolden/lftpq/queue"
)
@@ -66,28 +66,7 @@ func (c *CLI) Run() error {
return nil
}
if c.Name != "" {
- name := filepath.Base(c.Name)
- media, parserName, err := parser.Guess(name)
- if err != nil {
- return err
- }
- templateFound := false
- for _, dir := range cfg.LocalDirs {
- if dir.Parser != parserName {
- continue
- }
- path, err := media.PathIn(dir.Template)
- if err != nil {
- return err
- }
- templateFound = true
- fmt.Fprintln(c.stdout, path)
- break
- }
- if !templateFound {
- return fmt.Errorf("no template set for parser: %s", parserName)
- }
- return nil
+ return c.classify(cfg.LocalDirs)
}
var queues []queue.Queue
if c.Import {
@@ -110,6 +89,34 @@ func (c *CLI) Run() error {
return nil
}
+func (c *CLI) classify(dirs []queue.LocalDir) error {
+ name := filepath.Base(c.Name)
+ sortedDirs := make([]queue.LocalDir, len(dirs))
+ copy(sortedDirs, dirs)
+ // Always try show parsers first
+ sort.Slice(sortedDirs, func(i, j int) bool {
+ return sortedDirs[i].Parser == "show" && sortedDirs[j].Parser != "show"
+ })
+ parsed := false
+ for _, dir := range sortedDirs {
+ media, err := dir.Media(name)
+ if err != nil {
+ return err
+ }
+ path, err := media.PathIn(dir.Template)
+ if err != nil {
+ return err
+ }
+ parsed = true
+ fmt.Fprintln(c.stdout, path)
+ break
+ }
+ if !parsed {
+ return fmt.Errorf("parsing failed: %q", name)
+ }
+ return nil
+}
+
func (c *CLI) lockfile() string { return filepath.Join(os.TempDir(), ".lftpqlock") }
func (c *CLI) lock() error {
diff --git a/cmd/lftpq/main_test.go b/cmd/lftpq/main_test.go
index b2de28e..bfe6910 100644
--- a/cmd/lftpq/main_test.go
+++ b/cmd/lftpq/main_test.go
@@ -121,7 +121,7 @@ func TestConfigTest(t *testing.T) {
"Name": "d1",
"Parser": "movie",
"Dir": "/tmp/",
- "Replacements": null
+ "Replacements": []
}
],
"Sites": []
@@ -417,17 +417,28 @@ func TestClassify(t *testing.T) {
"Name": "d1",
"Parser": "movie",
"Dir": "/media/{{ .Year}}/"
+ },
+ {
+ "Name": "d2",
+ "Parser": "show",
+ "Dir": "/media/{{ .Name }}/S{{ .Season | Sprintf \"%02d\" }}/",
+ "Replacements": [
+ {
+ "Pattern": "foo",
+ "Replacement": "Foo"
+ }
+ ]
}
]
}`)
defer os.Remove(cli.Config)
- cli.Name = "/download/foo.2018"
+ cli.Name = "/download/foo.S01E01"
if err := cli.Run(); err != nil {
t.Fatal(err)
}
- want := "/media/2018/foo.2018\n"
+ want := "/media/Foo/S01/foo.S01E01\n"
if got := buf.String(); got != want {
t.Errorf("want %q, got %q", want, got)
}
diff --git a/parser/parser.go b/parser/parser.go
index 3378154..621992d 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -60,18 +60,6 @@ func (m *Media) PathIn(dir *template.Template) (string, error) {
return path, nil
}
-func Guess(s string) (Media, string, error) {
- show, err := Show(s)
- if err != nil {
- movie, err := Movie(s)
- if err != nil {
- return Media{}, "", fmt.Errorf("could not guess media: %q", s)
- }
- return movie, "movie", err
- }
- return show, "show", nil
-}
-
func Default(s string) (Media, error) {
return Media{Release: s}, nil
}
diff --git a/queue/config.go b/queue/config.go
index bff5572..59b07a7 100644
--- a/queue/config.go
+++ b/queue/config.go
@@ -35,6 +35,7 @@ type LocalDir struct {
Dir string
Replacements []Replacement
Template *template.Template `json:"-"`
+ parser parser.Parser
}
type Site struct {
@@ -51,19 +52,24 @@ type Site struct {
SkipExisting bool
SkipFiles bool
LocalDir string
+ localDir LocalDir
Priorities []string
priorities []*regexp.Regexp
PostCommand string
postCommand *exec.Cmd
Merge bool
Skip bool
- itemParser
}
-type itemParser struct {
- parser parser.Parser
- template *template.Template
- replacements []Replacement
+func (d *LocalDir) Media(name string) (parser.Media, error) {
+ m, err := d.parser(filepath.Base(name))
+ if err != nil {
+ return parser.Media{}, err
+ }
+ for _, r := range d.Replacements {
+ m.ReplaceName(r.pattern, r.Replacement)
+ }
+ return m, nil
}
func compilePatterns(patterns []string) ([]*regexp.Regexp, error) {
@@ -129,7 +135,7 @@ func command(cmd string) (*exec.Cmd, error) {
}
func (c *Config) load() error {
- itemParsers := make(map[string]itemParser)
+ localDirs := make(map[string]LocalDir)
for i, d := range c.LocalDirs {
if d.Name == "" {
return fmt.Errorf("invalid local dir name: %q", d.Name)
@@ -137,7 +143,7 @@ func (c *Config) load() error {
if d.Dir == "" {
return fmt.Errorf("invalid local dir path: %q", d.Dir)
}
- if _, ok := itemParsers[d.Name]; ok {
+ if _, ok := localDirs[d.Name]; ok {
return fmt.Errorf("invalid local dir: %q: declared multiple times", d.Name)
}
var parserFunc parser.Parser
@@ -160,12 +166,10 @@ func (c *Config) load() error {
if err != nil {
return err
}
- itemParsers[d.Name] = itemParser{
- parser: parserFunc,
- replacements: replacements,
- template: tmpl,
- }
+ c.LocalDirs[i].parser = parserFunc
+ c.LocalDirs[i].Replacements = replacements
c.LocalDirs[i].Template = tmpl
+ localDirs[d.Name] = c.LocalDirs[i]
}
for i := range c.Sites {
site := &c.Sites[i]
@@ -196,11 +200,11 @@ func (c *Config) load() error {
}
site.postCommand = cmd
- itemParser, ok := itemParsers[site.LocalDir]
+ localDir, ok := localDirs[site.LocalDir]
if !ok {
return fmt.Errorf("site: %q: invalid local dir: %q", site.Name, site.LocalDir)
}
- site.itemParser = itemParser
+ site.localDir = localDir
}
return nil
}
diff --git a/queue/config_test.go b/queue/config_test.go
index 7cf57a2..f5c19cc 100644
--- a/queue/config_test.go
+++ b/queue/config_test.go
@@ -52,13 +52,13 @@ func TestLoad(t *testing.T) {
if len(site.priorities) == 0 {
t.Error("Expected non-empty priorities")
}
- if site.itemParser.template == nil {
+ if site.localDir.Template == nil {
t.Error("Expected template to be compiled")
}
- if site.itemParser.parser == nil {
+ if site.localDir.parser == nil {
t.Error("Expected parser to be set")
}
- if len(site.itemParser.replacements) == 0 {
+ if len(site.localDir.Replacements) == 0 {
t.Error("Expected non-empty replacements")
}
if want := []string{"xargs", "echo"}; !reflect.DeepEqual(want, site.postCommand.Args) {
diff --git a/queue/item.go b/queue/item.go
index 27d3821..7ea6e78 100644
--- a/queue/item.go
+++ b/queue/item.go
@@ -16,7 +16,7 @@ type Item struct {
Media parser.Media
Duplicate bool
Merged bool
- itemParser
+ localDir LocalDir
}
func (i *Item) isEmpty(readDir readDir) bool {
@@ -34,18 +34,6 @@ func (i *Item) reject(reason string) {
i.Reason = reason
}
-func (i *Item) setMedia(dirname string) error {
- m, err := i.itemParser.parser(dirname)
- if err != nil {
- return err
- }
- for _, r := range i.itemParser.replacements {
- m.ReplaceName(r.pattern, r.Replacement)
- }
- i.Media = m
- return nil
-}
-
func (i *Item) duplicates(readDir readDir) []Item {
var items []Item
parent := filepath.Join(i.LocalPath, "..")
@@ -56,7 +44,7 @@ func (i *Item) duplicates(readDir readDir) []Item {
continue
}
path := filepath.Join(parent, fi.Name())
- item, err := newItem(path, i.ModTime, i.itemParser)
+ item, err := newItem(path, i.ModTime, i.localDir)
if err != nil {
item.reject(err.Error())
} else {
@@ -72,12 +60,16 @@ func (i *Item) duplicates(readDir readDir) []Item {
return items
}
-func newItem(remotePath string, modTime time.Time, itemParser itemParser) (Item, error) {
- item := Item{RemotePath: remotePath, ModTime: modTime, Reason: "no match", itemParser: itemParser}
- if err := item.setMedia(filepath.Base(remotePath)); err != nil {
- return item, err
+func newItem(remotePath string, modTime time.Time, localDir LocalDir) (Item, error) {
+ item := Item{RemotePath: remotePath, ModTime: modTime, Reason: "no match", localDir: localDir}
+ media, err := localDir.Media(remotePath)
+ if err != nil {
+ return Item{}, err
+ }
+ item.Media = media
+ item.LocalPath, err = media.PathIn(localDir.Template)
+ if err != nil {
+ return Item{}, err
}
- var err error
- item.LocalPath, err = item.Media.PathIn(itemParser.template)
- return item, err
+ return item, nil
}
diff --git a/queue/item_test.go b/queue/item_test.go
index 8e6375b..b7b2d03 100644
--- a/queue/item_test.go
+++ b/queue/item_test.go
@@ -10,41 +10,41 @@ import (
"github.com/mpolden/lftpq/parser"
)
-func newTestItem(remotePath string, itemParser itemParser) Item {
- item, _ := newItem(remotePath, time.Time{}, itemParser)
+func newTestItem(remotePath string, localDir LocalDir) Item {
+ item, _ := newItem(remotePath, time.Time{}, localDir)
return item
}
-func showItemParser() itemParser {
- return itemParser{
+func showDir() LocalDir {
+ return LocalDir{
parser: parser.Show,
- template: template.Must(template.New("t").Parse("/tmp/{{ .Name }}/S{{ .Season }}/")),
+ Template: template.Must(template.New("t").Parse("/tmp/{{ .Name }}/S{{ .Season }}/")),
}
}
-func movieItemParser() itemParser {
- return itemParser{
+func movieDir() LocalDir {
+ return LocalDir{
parser: parser.Movie,
- template: template.Must(template.New("t").Parse("/tmp/{{ .Year }}/{{ .Name }}/")),
+ Template: template.Must(template.New("t").Parse("/tmp/{{ .Year }}/{{ .Name }}/")),
}
}
func TestNewItemShow(t *testing.T) {
- item := newTestItem("/foo/The.Wire.S03E01", showItemParser())
+ item := newTestItem("/foo/The.Wire.S03E01", showDir())
if expected := "/tmp/The.Wire/S3/The.Wire.S03E01"; item.LocalPath != expected {
t.Fatalf("Expected %q, got %q", expected, item.LocalPath)
}
}
func TestNewItemMovie(t *testing.T) {
- item := newTestItem("/foo/Apocalypse.Now.1979", movieItemParser())
+ item := newTestItem("/foo/Apocalypse.Now.1979", movieDir())
if expected := "/tmp/1979/Apocalypse.Now/Apocalypse.Now.1979"; item.LocalPath != expected {
t.Fatalf("Expected %q, got %q", expected, item.LocalPath)
}
}
func TestNewItemDefaultParser(t *testing.T) {
- tmpl := itemParser{parser: parser.Default, template: template.Must(template.New("t").Parse("/tmp/"))}
+ tmpl := LocalDir{parser: parser.Default, Template: template.Must(template.New("t").Parse("/tmp/"))}
item := newTestItem("/foo/The.Wire.S03E01", tmpl)
if expected := "/tmp/The.Wire.S03E01"; item.LocalPath != expected {
t.Fatalf("Expected %s, got %s", expected, item.LocalPath)
@@ -52,15 +52,15 @@ func TestNewItemDefaultParser(t *testing.T) {
}
func TestNewItemUnparsable(t *testing.T) {
- _, err := newItem("/foo/bar", time.Time{}, showItemParser())
+ _, err := newItem("/foo/bar", time.Time{}, showDir())
if err == nil {
t.Fatal("Expected error")
}
}
func TestNewItemWithReplacements(t *testing.T) {
- tmpl := showItemParser()
- tmpl.replacements = []Replacement{
+ tmpl := showDir()
+ tmpl.Replacements = []Replacement{
{pattern: regexp.MustCompile("_"), Replacement: "."},
{pattern: regexp.MustCompile(`\.Of\.`), Replacement: ".of."},
{pattern: regexp.MustCompile(`\.the\.`), Replacement: ".The."},
@@ -92,11 +92,11 @@ func TestLocalPath(t *testing.T) {
{"/remote/bar", "/local/bar", "/local/bar"},
}
for _, tt := range tests {
- itemParser := itemParser{
+ localDir := LocalDir{
parser: parser.Default,
- template: template.Must(template.New("t").Parse(tt.template)),
+ Template: template.Must(template.New("t").Parse(tt.template)),
}
- item := newTestItem(tt.remotePath, itemParser)
+ item := newTestItem(tt.remotePath, localDir)
if item.LocalPath != tt.out {
t.Errorf("Expected %q, got %q", tt.out, item.LocalPath)
}
diff --git a/queue/queue.go b/queue/queue.go
index daa3b71..d2340d2 100644
--- a/queue/queue.go
+++ b/queue/queue.go
@@ -63,7 +63,7 @@ func Read(sites []Site, r io.Reader) ([]Queue, error) {
indices[site.Name] = i
}
q := &qs[i]
- item, err := newItem(fields[1], time.Time{}, q.itemParser)
+ item, err := newItem(fields[1], time.Time{}, q.localDir)
if err != nil {
item.reject(err.Error())
} else {
@@ -216,7 +216,7 @@ func newQueue(site Site, files []os.FileInfo, readDir readDir) Queue {
// Initial filtering
now := time.Now().Round(time.Second)
for _, f := range files {
- item, err := newItem(f.Name(), f.ModTime(), q.itemParser)
+ item, err := newItem(f.Name(), f.ModTime(), q.localDir)
if err != nil {
item.reject(err.Error())
} else if isSymlink := f.Mode()&os.ModeSymlink != 0; q.SkipSymlinks && isSymlink {
diff --git a/queue/queue_test.go b/queue/queue_test.go
index e40222d..b98237c 100644
--- a/queue/queue_test.go
+++ b/queue/queue_test.go
@@ -14,14 +14,14 @@ import (
)
func newTestSite() Site {
- localDir := template.Must(template.New("t").Parse("/local/{{ .Name }}/S{{ .Season }}/"))
+ tmpl := template.Must(template.New("t").Parse("/local/{{ .Name }}/S{{ .Season }}/"))
patterns := []*regexp.Regexp{regexp.MustCompile(".*")}
return Site{
GetCmd: "mirror",
Name: "test",
patterns: patterns,
- itemParser: itemParser{
- template: localDir,
+ localDir: LocalDir{
+ Template: tmpl,
parser: parser.Show,
},
}
@@ -55,8 +55,8 @@ func TestNewQueue(t *testing.T) {
SkipSymlinks: true,
SkipExisting: true,
SkipFiles: true,
- itemParser: itemParser{
- template: template.Must(template.New("localDir").Parse("/local/")),
+ localDir: LocalDir{
+ Template: template.Must(template.New("localDir").Parse("/local/")),
parser: parser.Default,
},
}
@@ -117,14 +117,14 @@ func TestNewQueue(t *testing.T) {
}
q := newQueue(s, files, readDir)
expected := []Item{
- {itemParser: q.itemParser, RemotePath: files[0].Name(), Transfer: false, Reason: "IsSymlink=true SkipSymlinks=true"},
- {itemParser: q.itemParser, RemotePath: files[1].Name(), Transfer: false, Reason: "Age=48h0m0s MaxAge=24h0m0s"},
- {itemParser: q.itemParser, RemotePath: files[2].Name(), Transfer: true, Reason: "Match=dir\\d"},
- {itemParser: q.itemParser, RemotePath: files[3].Name(), Transfer: true, Reason: "Match=dir\\d"},
- {itemParser: q.itemParser, RemotePath: files[4].Name(), Transfer: false, Reason: "IsDstDirEmpty=false"},
- {itemParser: q.itemParser, RemotePath: files[5].Name(), Transfer: false, Reason: "no match"},
- {itemParser: q.itemParser, RemotePath: files[6].Name(), Transfer: false, Reason: "Filter=^incomplete-"},
- {itemParser: q.itemParser, RemotePath: files[7].Name(), Transfer: false, Reason: "IsFile=true SkipFiles=true"},
+ {localDir: q.localDir, RemotePath: files[0].Name(), Transfer: false, Reason: "IsSymlink=true SkipSymlinks=true"},
+ {localDir: q.localDir, RemotePath: files[1].Name(), Transfer: false, Reason: "Age=48h0m0s MaxAge=24h0m0s"},
+ {localDir: q.localDir, RemotePath: files[2].Name(), Transfer: true, Reason: "Match=dir\\d"},
+ {localDir: q.localDir, RemotePath: files[3].Name(), Transfer: true, Reason: "Match=dir\\d"},
+ {localDir: q.localDir, RemotePath: files[4].Name(), Transfer: false, Reason: "IsDstDirEmpty=false"},
+ {localDir: q.localDir, RemotePath: files[5].Name(), Transfer: false, Reason: "no match"},
+ {localDir: q.localDir, RemotePath: files[6].Name(), Transfer: false, Reason: "Filter=^incomplete-"},
+ {localDir: q.localDir, RemotePath: files[7].Name(), Transfer: false, Reason: "IsFile=true SkipFiles=true"},
}
actual := q.Items
if len(expected) != len(actual) {