From 440db5fb41a0daac20d286f9769d5995f75513fa Mon Sep 17 00:00:00 2001 From: Iori Mizutani Date: Wed, 28 Jun 2023 07:59:39 +0200 Subject: [PATCH] feat: make the rule criteria conjuct with each other --- asset.go | 4 + asset_test.go | 2 +- cmd/radicron/main.go | 2 +- download.go | 6 +- region_test.go | 2 +- rule.go | 147 +++++++++++++++--------- rule_test.go | 259 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 362 insertions(+), 60 deletions(-) diff --git a/asset.go b/asset.go index 6e47610..09853ef 100644 --- a/asset.go +++ b/asset.go @@ -20,12 +20,16 @@ import ( ) var ( + // Base64FullKey holds the /assets/flutter_assets/assets/key/android.jpg in the v8 APK //go:embed assets/base64-full.key Base64FullKey embed.FS + // CoordinatesJSON is a JSON contains the base GPS locations //go:embed assets/coordinates.json CoordinatesJSON embed.FS + // RegionsJSON is a JSON contains the region mapping //go:embed assets/regions.json RegionsJSON embed.FS + // VersionsJSON is a JSON contains the valid SDK versions //go:embed assets/versions.json VersionsJSON embed.FS ) diff --git a/asset_test.go b/asset_test.go index 381d761..05e79bc 100644 --- a/asset_test.go +++ b/asset_test.go @@ -33,7 +33,7 @@ func TestNewAsset(t *testing.T) { areaCount := 0 for region := range asset.Regions { for range asset.Regions[region] { - areaCount += 1 + areaCount++ } } if areaCount != nAreas { diff --git a/cmd/radicron/main.go b/cmd/radicron/main.go index a8cc991..20a3ac3 100644 --- a/cmd/radicron/main.go +++ b/cmd/radicron/main.go @@ -145,7 +145,7 @@ func run(wg *sync.WaitGroup, configFileName string) { // check each program for _, p := range weeklyPrograms { if rules.HasMatch(stationID, p) { - err = radicron.Download(wg, ctx, p) + err = radicron.Download(ctx, wg, p) if err != nil { log.Printf("downlod faild: %s", err) } diff --git a/download.go b/download.go index 99aa653..0980e98 100644 --- a/download.go +++ b/download.go @@ -21,8 +21,8 @@ import ( var sem = make(chan struct{}, MaxConcurrency) func Download( - wg *sync.WaitGroup, ctx context.Context, + wg *sync.WaitGroup, prog *Prog, ) (err error) { asset := GetAsset(ctx) @@ -91,7 +91,7 @@ func Download( log.Printf("start downloading [%s]%s (%s): %s", prog.StationID, title, start, uri) prog.M3U8 = uri wg.Add(1) - go downloadProgram(wg, ctx, prog, output) + go downloadProgram(ctx, wg, prog, output) return nil } @@ -171,8 +171,8 @@ func downloadLink(link, output string) error { // downloadProgram manages the download for the given program // in a go routine and notify the wg when finished func downloadProgram( - wg *sync.WaitGroup, // the wg to notify ctx context.Context, // the context for the request + wg *sync.WaitGroup, // the wg to notify prog *Prog, // the program metadata output *radigo.OutputConfig, // the file configuration ) { diff --git a/region_test.go b/region_test.go index 6c1a84e..36219fd 100644 --- a/region_test.go +++ b/region_test.go @@ -19,7 +19,7 @@ func TestFetchXMLRegion(t *testing.T) { for _, stations := range region.Region { for _, station := range stations.Stations { t.Logf("%v (%v) %v\n", stations.RegionID, station.AreaID, station.Name) - stationCount += 1 + stationCount++ } } if stationCount < nStations { diff --git a/rule.go b/rule.go index e9251a8..3f4e81f 100644 --- a/rule.go +++ b/rule.go @@ -48,9 +48,9 @@ type Rule struct { // Match returns true if the rule matches the program // 1. check the Window filter // 2. check the DoW filter -// 3. check the StationID filter -// TODO: reduce the complexity -func (r *Rule) Match(stationID string, p *Prog) bool { //nolint:gocyclo +// 3. check the StationID +// 4. match the criteria +func (r *Rule) Match(stationID string, p *Prog) bool { // 1. check Window if r.HasWindow() { startTime, err := time.ParseInLocation(DatetimeLayout, p.Ft, Location) @@ -67,61 +67,19 @@ func (r *Rule) Match(stationID string, p *Prog) bool { //nolint:gocyclo return false // skip before the fetch window } } - // 2. check DoW - if r.HasDoW() { - dow := map[string]time.Weekday{ - "sun": time.Sunday, - "mon": time.Monday, - "tue": time.Tuesday, - "wed": time.Wednesday, - "thu": time.Thursday, - "fri": time.Friday, - "sat": time.Saturday, - } - st, _ := time.ParseInLocation(DatetimeLayout, p.Ft, Location) - dowMatch := false - for _, d := range r.DoW { - if st.Weekday() == dow[strings.ToLower(d)] { - dowMatch = true - } - } - if !dowMatch { - return false - } + // 2. check dow + if !r.MatchDoW(p.Ft) { + return false } - // 3. check StationID - if r.HasStationID() && r.StationID != stationID { - return false // skip mismatching rules for stationID + // 3. check station-id + if !r.MatchStationID(stationID) { + return false } - // 4. Match - if r.HasTitle() && strings.Contains(p.Title, r.Title) { - log.Printf("rule[%s] matched with title: '%s'", r.Name, p.Title) - return true - } else if r.HasPfm() && strings.Contains(p.Pfm, r.Pfm) { - log.Printf("rule[%s] matched with pfm: '%s'", r.Name, p.Pfm) + + // 4. match + if r.MatchPfm(p.Pfm) && r.MatchTitle(p.Title) && r.MatchKeyword(p) { return true - } else if r.HasKeyword() { - if strings.Contains(p.Title, r.Keyword) { - log.Printf("rule[%s] matched with title: '%s'", r.Name, p.Title) - return true - } else if strings.Contains(p.Pfm, r.Keyword) { - log.Printf("rule[%s] matched with pfm: '%s'", r.Name, p.Pfm) - return true - } else if strings.Contains(p.Info, r.Keyword) { - log.Printf("rule[%s] matched with info: \n%s", r.Name, strings.ReplaceAll(p.Info, "\n", "")) - return true - } else if strings.Contains(p.Desc, r.Keyword) { - log.Printf("rule[%s] matched with desc: '%s'", r.Name, strings.ReplaceAll(p.Desc, "\n", "")) - return true - } - for _, tag := range p.Tags { - if strings.Contains(tag, r.Keyword) { - log.Printf("rule[%s] matched with tag: '%s'", r.Name, tag) - return true - } - } } - // both title and keyword are empty or not found return false } @@ -153,6 +111,87 @@ func (r *Rule) HasWindow() bool { return r.Window != "" } +func (r *Rule) MatchDoW(ft string) bool { + if !r.HasDoW() { + return true + } + dow := map[string]time.Weekday{ + "sun": time.Sunday, + "mon": time.Monday, + "tue": time.Tuesday, + "wed": time.Wednesday, + "thu": time.Thursday, + "fri": time.Friday, + "sat": time.Saturday, + } + st, _ := time.ParseInLocation(DatetimeLayout, ft, Location) + for _, d := range r.DoW { + if st.Weekday() == dow[strings.ToLower(d)] { + return true + } + } + return false +} + +func (r *Rule) MatchKeyword(p *Prog) bool { + if !r.HasKeyword() { + return true // if no keyward, match all + } + + if strings.Contains(p.Title, r.Keyword) { + log.Printf("rule[%s] matched with title: '%s'", r.Name, p.Title) + return true + } else if strings.Contains(p.Pfm, r.Keyword) { + log.Printf("rule[%s] matched with pfm: '%s'", r.Name, p.Pfm) + return true + } else if strings.Contains(p.Info, r.Keyword) { + log.Printf("rule[%s] matched with info: \n%s", r.Name, strings.ReplaceAll(p.Info, "\n", "")) + return true + } else if strings.Contains(p.Desc, r.Keyword) { + log.Printf("rule[%s] matched with desc: '%s'", r.Name, strings.ReplaceAll(p.Desc, "\n", "")) + return true + } + for _, tag := range p.Tags { + if strings.Contains(tag, r.Keyword) { + log.Printf("rule[%s] matched with tag: '%s'", r.Name, tag) + return true + } + } + return false +} + +func (r *Rule) MatchPfm(pfm string) bool { + if !r.HasPfm() { + return true // if no pfm, match all + } + if strings.Contains(pfm, r.Pfm) { + log.Printf("rule[%s] matched with pfm: '%s'", r.Name, pfm) + return true + } + return false +} + +func (r *Rule) MatchStationID(stationID string) bool { + if !r.HasStationID() { + return true // if no station-id, match all + } + if r.StationID == stationID { + return true + } + return false +} + +func (r *Rule) MatchTitle(title string) bool { + if !r.HasTitle() { + return true // if not title, match all + } + if strings.Contains(title, r.Title) { + log.Printf("rule[%s] matched with title: '%s'", r.Name, title) + return true + } + return false +} + func (r *Rule) SetName(name string) { r.Name = name } diff --git a/rule_test.go b/rule_test.go index 19f0ac9..81d2686 100644 --- a/rule_test.go +++ b/rule_test.go @@ -2,6 +2,265 @@ package radicron import "testing" +var dowtests = []struct { + in *Rule + ft string + out bool +}{ + { + &Rule{"Name", "Title", []string{}, "Keyword", "Pfm", "StationID", "Window"}, + "20230625050000", // sun + true, + }, + { + &Rule{"Name", "Title", []string{"sun"}, "Keyword", "Pfm", "StationID", "Window"}, + "20230625050000", // sun + true, + }, + { + &Rule{"Name", "Title", []string{"mon", "tue"}, "Keyword", "Pfm", "StationID", "Window"}, + "20230625050000", // sun + false, + }, +} + +func TestMatchDoW(t *testing.T) { + for _, tt := range dowtests { + got := tt.in.MatchDoW(tt.ft) + if got != tt.out { + t.Errorf("(%v).MatchDoW => %v, want %v", tt.in, got, tt.out) + } + } +} + +var keywordtests = []struct { + in *Rule + prog *Prog + out bool +}{ + { + &Rule{"Name", "Title", []string{}, "", "Pfm", "StationID", "Window"}, + &Prog{ + "ID", + "StationID", + "Ft", + "To", + "Title", + "Desc", + "Info", + "Pfm", + []string{}, + ProgGenre{}, + "", + }, + true, + }, + { + &Rule{"Name", "Title", []string{}, "Keyword", "Pfm", "StationID", "Window"}, + &Prog{ + "ID", + "StationID", + "Ft", + "To", + "Keyword", // match + "Desc", + "Info", + "Pfm", + []string{}, + ProgGenre{}, + "", + }, + true, + }, + { + &Rule{"Name", "Title", []string{}, "Keyword", "Pfm", "StationID", "Window"}, + &Prog{ + "ID", + "StationID", + "Ft", + "To", + "Title", + "Keyword", // match + "Info", + "Pfm", + []string{}, + ProgGenre{}, + "", + }, + true, + }, + { + &Rule{"Name", "Title", []string{}, "Keyword", "Pfm", "StationID", "Window"}, + &Prog{ + "ID", + "StationID", + "Ft", + "To", + "Title", + "Desc", + "Keyword", // match + "Pfm", + []string{}, + ProgGenre{}, + "", + }, + true, + }, + { + &Rule{"Name", "Title", []string{}, "Keyword", "Pfm", "StationID", "Window"}, + &Prog{ + "test", + "test", + "test", + "test", + "test", + "test", + "test", + "Keyword", // match + []string{}, + ProgGenre{}, + "", + }, + true, + }, + { + &Rule{"Name", "Title", []string{}, "Keyword", "Pfm", "StationID", "Window"}, + &Prog{ + "test", + "test", + "test", + "test", + "test", + "test", + "test", + "test", + []string{"Keyword"}, // match + ProgGenre{}, + "test", + }, + true, + }, + { + &Rule{"Name", "Title", []string{}, "Keyword", "Pfm", "StationID", "Window"}, + &Prog{ + "ID", + "StationID", + "Ft", + "To", + "Title", + "Desc", + "Info", + "Pfm", + []string{}, + ProgGenre{}, + "", + }, + false, + }, +} + +func TestMatchKeyword(t *testing.T) { + for _, tt := range keywordtests { + got := tt.in.MatchKeyword(tt.prog) + if got != tt.out { + t.Errorf("(%v).MatchKeyword => %v, want %v", tt.in, got, tt.out) + } + } +} + +var pfmtests = []struct { + in *Rule + pfm string + out bool +}{ + { + &Rule{"Name", "Title", []string{"sun"}, "Keyword", "", "StationID", "Window"}, + "Pfm", + true, + }, + { + &Rule{"", "", []string{}, "", "Pfm", "", ""}, + "Pfm", + true, + }, + { + &Rule{"", "", []string{}, "", "Pfm", "", ""}, + "Someone", + false, + }, +} + +func TestMatchPfm(t *testing.T) { + for _, tt := range pfmtests { + got := tt.in.MatchPfm(tt.pfm) + if got != tt.out { + t.Errorf("(%v).MatchPfm => %v, want %v", tt.in, got, tt.out) + } + } +} + +var stationtests = []struct { + in *Rule + stationID string + out bool +}{ + { + &Rule{"Name", "Title", []string{"sun"}, "Keyword", "Pfm", "FMT", "Window"}, + "FMT", + true, + }, + { + &Rule{"", "", []string{}, "", "", "", ""}, + "FMT", + true, + }, + { + &Rule{"", "", []string{}, "", "", "FMT", ""}, + "TBS", + false, + }, +} + +func TestMatchStationID(t *testing.T) { + for _, tt := range stationtests { + got := tt.in.MatchStationID(tt.stationID) + if got != tt.out { + t.Errorf("(%v).MatchStationID => %v, want %v", tt.in, got, tt.out) + } + } +} + +var titletests = []struct { + in *Rule + title string + out bool +}{ + { + &Rule{"Name", "Title", []string{"sun"}, "Keyword", "Pfm", "FMT", "Window"}, + "Title", + true, + }, + { + &Rule{"", "", []string{}, "", "", "", ""}, + "Title", + true, + }, + { + &Rule{"", "Title", []string{}, "", "", "FMT", ""}, + "Radio", + false, + }, +} + +func TestMatchTitle(t *testing.T) { + for _, tt := range titletests { + got := tt.in.MatchTitle(tt.title) + if got != tt.out { + t.Errorf("(%v).MatchTitle => %v, want %v", tt.in, got, tt.out) + } + } +} + var ruletests = []struct { in *Rule out bool