diff --git a/internal/database/filter.go b/internal/database/filter.go index 6e17173..0ed7099 100644 --- a/internal/database/filter.go +++ b/internal/database/filter.go @@ -221,6 +221,8 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter "except_language", "tags", "except_tags", + "tags_match_logic", + "except_tags_match_logic", "origins", "except_origins", "external_script_enabled", @@ -248,11 +250,11 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter } var f domain.Filter - var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, extScriptCmd, extScriptArgs, extWebhookHost, extWebhookData sql.NullString + var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, tagsMatchLogic, exceptTagsMatchLogic, extScriptCmd, extScriptArgs, extWebhookHost, extWebhookData sql.NullString var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac, extScriptEnabled, extWebhookEnabled sql.NullBool var delay, maxDownloads, logScore, extWebhookStatus, extScriptStatus sql.NullInt32 - if err := row.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &matchReleaseTags, &exceptReleaseTags, &f.UseRegexReleaseTags, &scene, &freeleech, &freeleechPercent, &f.SmartEpisode, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, pq.Array(&f.MatchLanguage), pq.Array(&f.ExceptLanguage), &tags, &exceptTags, pq.Array(&f.Origins), pq.Array(&f.ExceptOrigins), &extScriptEnabled, &extScriptCmd, &extScriptArgs, &extScriptStatus, &extWebhookEnabled, &extWebhookHost, &extWebhookData, &extWebhookStatus, &f.CreatedAt, &f.UpdatedAt); err != nil { + if err := row.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &matchReleaseTags, &exceptReleaseTags, &f.UseRegexReleaseTags, &scene, &freeleech, &freeleechPercent, &f.SmartEpisode, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, pq.Array(&f.MatchLanguage), pq.Array(&f.ExceptLanguage), &tags, &exceptTags, &tagsMatchLogic, &exceptTagsMatchLogic, pq.Array(&f.Origins), pq.Array(&f.ExceptOrigins), &extScriptEnabled, &extScriptCmd, &extScriptArgs, &extScriptStatus, &extWebhookEnabled, &extWebhookHost, &extWebhookData, &extWebhookStatus, &f.CreatedAt, &f.UpdatedAt); err != nil { return nil, errors.Wrap(err, "error scanning row") } @@ -284,6 +286,8 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter f.ExceptUploaders = exceptUploaders.String f.Tags = tags.String f.ExceptTags = exceptTags.String + f.TagsMatchLogic = tagsMatchLogic.String + f.ExceptTagsMatchLogic = exceptTagsMatchLogic.String f.UseRegex = useRegex.Bool f.Scene = scene.Bool f.Freeleech = freeleech.Bool @@ -384,6 +388,8 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe "f.except_language", "f.tags", "f.except_tags", + "f.tags_match_logic", + "f.except_tags_match_logic", "f.origins", "f.except_origins", "f.external_script_enabled", @@ -421,11 +427,11 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe for rows.Next() { var f domain.Filter - var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, extScriptCmd, extScriptArgs, extWebhookHost, extWebhookData sql.NullString + var minSize, maxSize, maxDownloadsUnit, matchReleases, exceptReleases, matchReleaseGroups, exceptReleaseGroups, matchReleaseTags, exceptReleaseTags, freeleechPercent, shows, seasons, episodes, years, artists, albums, matchCategories, exceptCategories, matchUploaders, exceptUploaders, tags, exceptTags, tagsMatchLogic, exceptTagsMatchLogic, extScriptCmd, extScriptArgs, extWebhookHost, extWebhookData sql.NullString var useRegex, scene, freeleech, hasLog, hasCue, perfectFlac, extScriptEnabled, extWebhookEnabled sql.NullBool var delay, maxDownloads, logScore, extWebhookStatus, extScriptStatus sql.NullInt32 - if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &matchReleaseTags, &exceptReleaseTags, &f.UseRegexReleaseTags, &scene, &freeleech, &freeleechPercent, &f.SmartEpisode, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, pq.Array(&f.MatchLanguage), pq.Array(&f.ExceptLanguage), &tags, &exceptTags, pq.Array(&f.Origins), pq.Array(&f.ExceptOrigins), &extScriptEnabled, &extScriptCmd, &extScriptArgs, &extScriptStatus, &extWebhookEnabled, &extWebhookHost, &extWebhookData, &extWebhookStatus, &f.CreatedAt, &f.UpdatedAt); err != nil { + if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &matchReleaseTags, &exceptReleaseTags, &f.UseRegexReleaseTags, &scene, &freeleech, &freeleechPercent, &f.SmartEpisode, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, pq.Array(&f.MatchLanguage), pq.Array(&f.ExceptLanguage), &tags, &exceptTags, &tagsMatchLogic, &exceptTagsMatchLogic, pq.Array(&f.Origins), pq.Array(&f.ExceptOrigins), &extScriptEnabled, &extScriptCmd, &extScriptArgs, &extScriptStatus, &extWebhookEnabled, &extWebhookHost, &extWebhookData, &extWebhookStatus, &f.CreatedAt, &f.UpdatedAt); err != nil { return nil, errors.Wrap(err, "error scanning row") } @@ -457,6 +463,8 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe f.ExceptUploaders = exceptUploaders.String f.Tags = tags.String f.ExceptTags = exceptTags.String + f.TagsMatchLogic = tagsMatchLogic.String + f.ExceptTagsMatchLogic = exceptTagsMatchLogic.String f.UseRegex = useRegex.Bool f.Scene = scene.Bool f.Freeleech = freeleech.Bool @@ -521,6 +529,8 @@ func (r *FilterRepo) Store(ctx context.Context, filter domain.Filter) (*domain.F "except_language", "tags", "except_tags", + "tags_match_logic", + "except_tags_match_logic", "artists", "albums", "release_types_match", @@ -583,6 +593,8 @@ func (r *FilterRepo) Store(ctx context.Context, filter domain.Filter) (*domain.F pq.Array(filter.ExceptLanguage), filter.Tags, filter.ExceptTags, + filter.TagsMatchLogic, + filter.ExceptTagsMatchLogic, filter.Artists, filter.Albums, pq.Array(filter.MatchReleaseTypes), @@ -664,6 +676,8 @@ func (r *FilterRepo) Update(ctx context.Context, filter domain.Filter) (*domain. Set("except_language", pq.Array(filter.ExceptLanguage)). Set("tags", filter.Tags). Set("except_tags", filter.ExceptTags). + Set("tags_match_logic", filter.TagsMatchLogic). + Set("except_tags_match_logic", filter.ExceptTagsMatchLogic). Set("artists", filter.Artists). Set("albums", filter.Albums). Set("release_types_match", pq.Array(filter.MatchReleaseTypes)). @@ -825,6 +839,12 @@ func (r *FilterRepo) UpdatePartial(ctx context.Context, filter domain.FilterUpda if filter.ExceptTags != nil { q = q.Set("except_tags", filter.ExceptTags) } + if filter.TagsMatchLogic != nil { + q = q.Set("tags_match_logic", filter.TagsMatchLogic) + } + if filter.ExceptTagsMatchLogic != nil { + q = q.Set("except_tags_match_logic", filter.ExceptTagsMatchLogic) + } if filter.Artists != nil { q = q.Set("artists", filter.Artists) } diff --git a/internal/database/postgres_migrate.go b/internal/database/postgres_migrate.go index 43e0773..cd8815c 100644 --- a/internal/database/postgres_migrate.go +++ b/internal/database/postgres_migrate.go @@ -115,6 +115,8 @@ CREATE TABLE filter except_language TEXT [] DEFAULT '{}', tags TEXT, except_tags TEXT, + tags_match_logic TEXT, + except_tags_match_logic TEXT, origins TEXT [] DEFAULT '{}', except_origins TEXT [] DEFAULT '{}', external_script_enabled BOOLEAN DEFAULT FALSE, @@ -655,4 +657,18 @@ ADD COLUMN info_url TEXT; ALTER TABLE "release" ADD COLUMN download_url TEXT; `, + `ALTER TABLE filter + ADD COLUMN tags_match_logic TEXT; + + ALTER TABLE filter + ADD COLUMN except_tags_match_logic TEXT; + + UPDATE filter + SET tags_match_logic = 'ANY' + WHERE tags IS NOT NULL; + + UPDATE filter + SET except_tags_match_logic = 'ANY' + WHERE except_tags IS NOT NULL; + `, } diff --git a/internal/database/sqlite_migrate.go b/internal/database/sqlite_migrate.go index 9c12694..b937daf 100644 --- a/internal/database/sqlite_migrate.go +++ b/internal/database/sqlite_migrate.go @@ -115,6 +115,8 @@ CREATE TABLE filter except_language TEXT [] DEFAULT '{}', tags TEXT, except_tags TEXT, + tags_match_logic TEXT, + except_tags_match_logic TEXT, origins TEXT [] DEFAULT '{}', except_origins TEXT [] DEFAULT '{}', external_script_enabled BOOLEAN DEFAULT FALSE, @@ -1048,4 +1050,18 @@ ADD COLUMN info_url TEXT; ALTER TABLE "release" ADD COLUMN download_url TEXT; `, + `ALTER TABLE filter + ADD COLUMN tags_match_logic TEXT; + + ALTER TABLE filter + ADD COLUMN except_tags_match_logic TEXT; + + UPDATE filter + SET tags_match_logic = 'ANY' + WHERE tags IS NOT NULL; + + UPDATE filter + SET except_tags_match_logic = 'ANY' + WHERE except_tags IS NOT NULL; + `, } diff --git a/internal/domain/filter.go b/internal/domain/filter.go index 67bf7b7..7f9d496 100644 --- a/internal/domain/filter.go +++ b/internal/domain/filter.go @@ -115,6 +115,8 @@ type Filter struct { ExceptTags string `json:"except_tags,omitempty"` TagsAny string `json:"tags_any,omitempty"` ExceptTagsAny string `json:"except_tags_any,omitempty"` + TagsMatchLogic string `json:"tags_match_logic,omitempty"` + ExceptTagsMatchLogic string `json:"except_tags_match_logic,omitempty"` MatchReleaseTags string `json:"match_release_tags,omitempty"` ExceptReleaseTags string `json:"except_release_tags,omitempty"` UseRegexReleaseTags bool `json:"use_regex_release_tags,omitempty"` @@ -190,6 +192,8 @@ type FilterUpdate struct { ExceptTags *string `json:"except_tags,omitempty"` TagsAny *string `json:"tags_any,omitempty"` ExceptTagsAny *string `json:"except_tags_any,omitempty"` + TagsMatchLogic *string `json:"tags_match_logic,omitempty"` + ExceptTagsMatchLogic *string `json:"except_tags_match_logic,omitempty"` ExternalScriptEnabled *bool `json:"external_script_enabled,omitempty"` ExternalScriptCmd *string `json:"external_script_cmd,omitempty"` ExternalScriptArgs *string `json:"external_script_args,omitempty"` @@ -379,12 +383,20 @@ func (f Filter) CheckFilter(r *Release) ([]string, bool) { r.addRejectionF("size not matching. got: %v want min: %v max: %v", r.Size, f.MinSize, f.MaxSize) } - if f.Tags != "" && !containsAny(r.Tags, f.Tags) { - r.addRejectionF("tags not matching. got: %v want: %v", r.Tags, f.Tags) + if f.Tags != "" { + if f.TagsMatchLogic == "ANY" && !containsAny(r.Tags, f.Tags) { + r.addRejectionF("tags not matching. got: %v want: %v", r.Tags, f.Tags) + } else if f.TagsMatchLogic == "ALL" && !containsAll(r.Tags, f.Tags) { + r.addRejectionF("tags not matching. got: %v want(all): %v", r.Tags, f.Tags) + } } - if f.ExceptTags != "" && containsAny(r.Tags, f.ExceptTags) { - r.addRejectionF("tags unwanted. got: %v want: %v", r.Tags, f.ExceptTags) + if f.ExceptTags != "" { + if f.ExceptTagsMatchLogic == "ANY" && containsAny(r.Tags, f.ExceptTags) { + r.addRejectionF("tags unwanted. got: %v want: %v", r.Tags, f.ExceptTags) + } else if f.ExceptTagsMatchLogic == "ALL" && containsAll(r.Tags, f.ExceptTags) { + r.addRejectionF("tags unwanted. got: %v want(all): %v", r.Tags, f.ExceptTags) + } } if len(f.Artists) > 0 && !contains(r.Artists, f.Artists) { @@ -620,6 +632,10 @@ func containsAny(tags []string, filter string) bool { return containsMatch(tags, strings.Split(filter, ",")) } +func containsAll(tags []string, filter string) bool { + return containsAllMatch(tags, strings.Split(filter, ",")) +} + func containsAnyOther(filter string, tags ...string) bool { return containsMatch(tags, strings.Split(filter, ",")) } @@ -686,6 +702,39 @@ func containsMatch(tags []string, filters []string) bool { return false } +func containsAllMatch(tags []string, filters []string) bool { + for _, filter := range filters { + if filter == "" { + continue + } + filter = strings.ToLower(filter) + filter = strings.Trim(filter, " ") + found := false + + for _, tag := range tags { + if tag == "" { + continue + } + tag = strings.ToLower(tag) + + if tag == filter { + found = true + break + } else if strings.ContainsAny(filter, "?|*") { + if wildcard.Match(filter, tag) { + found = true + break + } + } + } + if !found { + return false + } + } + + return true +} + func containsMatchBasic(tags []string, filters []string) bool { for _, tag := range tags { if tag == "" { diff --git a/internal/domain/filter_test.go b/internal/domain/filter_test.go index 4b7706f..0316802 100644 --- a/internal/domain/filter_test.go +++ b/internal/domain/filter_test.go @@ -520,7 +520,7 @@ func TestFilter_CheckFilter(t *testing.T) { want: true, }, { - name: "match_tags", + name: "match_tags_any", fields: &Release{ TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2", Category: "TV", @@ -535,12 +535,34 @@ func TestFilter_CheckFilter(t *testing.T) { ExceptUploaders: "Anonymous", Shows: "Good show", Tags: "tv", + TagsMatchLogic: "ANY", }, }, want: true, }, { - name: "match_tags_bad", + name: "match_tags_all", + fields: &Release{ + TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2", + Category: "TV", + Uploader: "Uploader1", + Tags: []string{"tv", "foreign"}, + }, + args: args{ + filter: Filter{ + Enabled: true, + MatchCategories: "*tv*", + MatchUploaders: "Uploader1,Uploader2", + ExceptUploaders: "Anonymous", + Shows: "Good show", + Tags: "tv,foreign", + TagsMatchLogic: "ALL", + }, + }, + want: true, + }, + { + name: "match_tags_any_bad", fields: &Release{ TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2", Category: "TV", @@ -555,13 +577,36 @@ func TestFilter_CheckFilter(t *testing.T) { ExceptUploaders: "Anonymous", Shows: "Good show", Tags: "tv", + TagsMatchLogic: "ANY", }, rejections: []string{"tags not matching. got: [foreign] want: tv"}, }, want: false, }, { - name: "match_except_tags", + name: "match_tags_all_bad", + fields: &Release{ + TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2", + Category: "TV", + Uploader: "Uploader1", + Tags: []string{"foreign"}, + }, + args: args{ + filter: Filter{ + Enabled: true, + MatchCategories: "*tv*", + MatchUploaders: "Uploader1,Uploader2", + ExceptUploaders: "Anonymous", + Shows: "Good show", + Tags: "tv,foreign", + TagsMatchLogic: "ALL", + }, + rejections: []string{"tags not matching. got: [foreign] want(all): tv,foreign"}, + }, + want: false, + }, + { + name: "match_except_tags_any", fields: &Release{ TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2", Category: "TV", @@ -576,12 +621,13 @@ func TestFilter_CheckFilter(t *testing.T) { ExceptUploaders: "Anonymous", Shows: "Good show", ExceptTags: "tv", + TagsMatchLogic: "ANY", }, }, want: true, }, { - name: "match_except_tags_2", + name: "match_except_tags_all", fields: &Release{ TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2", Category: "TV", @@ -595,12 +641,56 @@ func TestFilter_CheckFilter(t *testing.T) { MatchUploaders: "Uploader1,Uploader2", ExceptUploaders: "Anonymous", Shows: "Good show", - ExceptTags: "foreign", + ExceptTags: "tv,foreign", + TagsMatchLogic: "ALL", + }, + }, + want: true, + }, + { + name: "match_except_tags_any_2", + fields: &Release{ + TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2", + Category: "TV", + Uploader: "Uploader1", + Tags: []string{"foreign"}, + }, + args: args{ + filter: Filter{ + Enabled: true, + MatchCategories: "*tv*", + MatchUploaders: "Uploader1,Uploader2", + ExceptUploaders: "Anonymous", + Shows: "Good show", + ExceptTags: "foreign", + ExceptTagsMatchLogic: "ANY", }, rejections: []string{"tags unwanted. got: [foreign] want: foreign"}, }, want: false, }, + { + name: "match_except_tags_all_2", + fields: &Release{ + TorrentName: "Good show S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP2", + Category: "TV", + Uploader: "Uploader1", + Tags: []string{"tv", "foreign"}, + }, + args: args{ + filter: Filter{ + Enabled: true, + MatchCategories: "*tv*", + MatchUploaders: "Uploader1,Uploader2", + ExceptUploaders: "Anonymous", + Shows: "Good show", + ExceptTags: "foreign,tv", + ExceptTagsMatchLogic: "ALL", + }, + rejections: []string{"tags unwanted. got: [tv foreign] want(all): foreign,tv"}, + }, + want: false, + }, { name: "match_group_1", fields: &Release{ @@ -1717,64 +1807,64 @@ func TestFilter_CheckFilter1(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f := Filter{ - ID: tt.fields.ID, - Name: tt.fields.Name, - Enabled: tt.fields.Enabled, - CreatedAt: tt.fields.CreatedAt, - UpdatedAt: tt.fields.UpdatedAt, - MinSize: tt.fields.MinSize, - MaxSize: tt.fields.MaxSize, - Delay: tt.fields.Delay, - Priority: tt.fields.Priority, - MaxDownloads: tt.fields.MaxDownloads, - MaxDownloadsUnit: tt.fields.MaxDownloadsUnit, - MatchReleases: tt.fields.MatchReleases, - ExceptReleases: tt.fields.ExceptReleases, - UseRegex: tt.fields.UseRegex, - MatchReleaseGroups: tt.fields.MatchReleaseGroups, - ExceptReleaseGroups: tt.fields.ExceptReleaseGroups, - MatchReleaseTags: tt.fields.MatchReleaseTags, - ExceptReleaseTags: tt.fields.ExceptReleaseTags, - UseRegexReleaseTags: tt.fields.UseRegexReleaseTags, - Scene: tt.fields.Scene, - Origins: tt.fields.Origins, - ExceptOrigins: tt.fields.ExceptOrigins, - Freeleech: tt.fields.Freeleech, - FreeleechPercent: tt.fields.FreeleechPercent, - Shows: tt.fields.Shows, - Seasons: tt.fields.Seasons, - Episodes: tt.fields.Episodes, - Resolutions: tt.fields.Resolutions, - Codecs: tt.fields.Codecs, - Sources: tt.fields.Sources, - Containers: tt.fields.Containers, - MatchHDR: tt.fields.MatchHDR, - ExceptHDR: tt.fields.ExceptHDR, - Years: tt.fields.Years, - Artists: tt.fields.Artists, - Albums: tt.fields.Albums, - MatchReleaseTypes: tt.fields.MatchReleaseTypes, - ExceptReleaseTypes: tt.fields.ExceptReleaseTypes, - Formats: tt.fields.Formats, - Quality: tt.fields.Quality, - Media: tt.fields.Media, - PerfectFlac: tt.fields.PerfectFlac, - Cue: tt.fields.Cue, - Log: tt.fields.Log, - LogScore: tt.fields.LogScore, - MatchOther: tt.fields.MatchOther, - ExceptOther: tt.fields.ExceptOther, - MatchCategories: tt.fields.MatchCategories, - ExceptCategories: tt.fields.ExceptCategories, - MatchUploaders: tt.fields.MatchUploaders, - ExceptUploaders: tt.fields.ExceptUploaders, - Tags: tt.fields.Tags, - ExceptTags: tt.fields.ExceptTags, - TagsAny: tt.fields.TagsAny, - ExceptTagsAny: tt.fields.ExceptTagsAny, - Actions: tt.fields.Actions, - Indexers: tt.fields.Indexers, - Downloads: tt.fields.Downloads, + ID: tt.fields.ID, + Name: tt.fields.Name, + Enabled: tt.fields.Enabled, + CreatedAt: tt.fields.CreatedAt, + UpdatedAt: tt.fields.UpdatedAt, + MinSize: tt.fields.MinSize, + MaxSize: tt.fields.MaxSize, + Delay: tt.fields.Delay, + Priority: tt.fields.Priority, + MaxDownloads: tt.fields.MaxDownloads, + MaxDownloadsUnit: tt.fields.MaxDownloadsUnit, + MatchReleases: tt.fields.MatchReleases, + ExceptReleases: tt.fields.ExceptReleases, + UseRegex: tt.fields.UseRegex, + MatchReleaseGroups: tt.fields.MatchReleaseGroups, + ExceptReleaseGroups: tt.fields.ExceptReleaseGroups, + MatchReleaseTags: tt.fields.MatchReleaseTags, + ExceptReleaseTags: tt.fields.ExceptReleaseTags, + UseRegexReleaseTags: tt.fields.UseRegexReleaseTags, + Scene: tt.fields.Scene, + Origins: tt.fields.Origins, + ExceptOrigins: tt.fields.ExceptOrigins, + Freeleech: tt.fields.Freeleech, + FreeleechPercent: tt.fields.FreeleechPercent, + Shows: tt.fields.Shows, + Seasons: tt.fields.Seasons, + Episodes: tt.fields.Episodes, + Resolutions: tt.fields.Resolutions, + Codecs: tt.fields.Codecs, + Sources: tt.fields.Sources, + Containers: tt.fields.Containers, + MatchHDR: tt.fields.MatchHDR, + ExceptHDR: tt.fields.ExceptHDR, + Years: tt.fields.Years, + Artists: tt.fields.Artists, + Albums: tt.fields.Albums, + MatchReleaseTypes: tt.fields.MatchReleaseTypes, + ExceptReleaseTypes: tt.fields.ExceptReleaseTypes, + Formats: tt.fields.Formats, + Quality: tt.fields.Quality, + Media: tt.fields.Media, + PerfectFlac: tt.fields.PerfectFlac, + Cue: tt.fields.Cue, + Log: tt.fields.Log, + LogScore: tt.fields.LogScore, + MatchOther: tt.fields.MatchOther, + ExceptOther: tt.fields.ExceptOther, + MatchCategories: tt.fields.MatchCategories, + ExceptCategories: tt.fields.ExceptCategories, + MatchUploaders: tt.fields.MatchUploaders, + ExceptUploaders: tt.fields.ExceptUploaders, + Tags: tt.fields.Tags, + ExceptTags: tt.fields.ExceptTags, + TagsMatchLogic: tt.fields.TagsMatchLogic, + ExceptTagsMatchLogic: tt.fields.ExceptTagsMatchLogic, + Actions: tt.fields.Actions, + Indexers: tt.fields.Indexers, + Downloads: tt.fields.Downloads, } tt.args.r.ParseString(tt.args.r.TorrentName) rejections, match := f.CheckFilter(tt.args.r) @@ -1784,6 +1874,54 @@ func TestFilter_CheckFilter1(t *testing.T) { } } +func Test_containsMatch(t *testing.T) { + type args struct { + tags []string + filters []string + } + tests := []struct { + name string + args args + want bool + }{ + {name: "test_1", args: args{tags: []string{"HDR", "DV"}, filters: []string{"DV"}}, want: true}, + {name: "test_2", args: args{tags: []string{"HDR", "DV"}, filters: []string{"HD*", "D*"}}, want: true}, + {name: "test_3", args: args{tags: []string{"HDR"}, filters: []string{"DV"}}, want: false}, + {name: "test_4", args: args{tags: []string{"HDR"}, filters: []string{"TEST*"}}, want: false}, + {name: "test_5", args: args{tags: []string{""}, filters: []string{"test,"}}, want: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, containsMatch(tt.args.tags, tt.args.filters), "containsMatch(%v, %v)", tt.args.tags, tt.args.filters) + }) + } +} + +func Test_containsAllMatch(t *testing.T) { + type args struct { + tags []string + filters []string + } + tests := []struct { + name string + args args + want bool + }{ + {name: "test_1", args: args{tags: []string{"HDR", "DV"}, filters: []string{"DV"}}, want: true}, + {name: "test_2", args: args{tags: []string{"HDR", "DV"}, filters: []string{"DV", "DoVI"}}, want: false}, + {name: "test_3", args: args{tags: []string{"HDR", "DV", "DoVI"}, filters: []string{"DV", "DoVI"}}, want: true}, + {name: "test_4", args: args{tags: []string{"HDR", "DV"}, filters: []string{"HD*", "D*"}}, want: true}, + {name: "test_5", args: args{tags: []string{"HDR", "DV"}, filters: []string{"HD*", "TEST*"}}, want: false}, + {name: "test_6", args: args{tags: []string{"HDR"}, filters: []string{"DV"}}, want: false}, + {name: "test_7", args: args{tags: []string{""}, filters: []string{"test,"}}, want: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, containsAllMatch(tt.args.tags, tt.args.filters), "containsAllMatch(%v, %v)", tt.args.tags, tt.args.filters) + }) + } +} + func Test_contains(t *testing.T) { type args struct { tag string @@ -1854,6 +1992,31 @@ func Test_containsAny(t *testing.T) { } } +func Test_containsAll(t *testing.T) { + type args struct { + tags []string + filter string + } + tests := []struct { + name string + args args + want bool + }{ + {name: "test_1", args: args{tags: []string{"HDR", "DV"}, filter: "DV"}, want: true}, + {name: "test_2", args: args{tags: []string{"HDR", "DV"}, filter: "HDR,DV"}, want: true}, + {name: "test_2", args: args{tags: []string{"HDR", "DV"}, filter: "HD*,D*"}, want: true}, + {name: "test_3", args: args{tags: []string{"HDR", "DoVI"}, filter: "HDR,DV"}, want: false}, + {name: "test_4", args: args{tags: []string{"HDR", "DV", "HDR10+"}, filter: "HDR,DV"}, want: true}, + {name: "test_5", args: args{tags: []string{"HDR"}, filter: "DV"}, want: false}, + {name: "test_6", args: args{tags: []string{""}, filter: "test,"}, want: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, containsAll(tt.args.tags, tt.args.filter), "containsAll(%v, %v)", tt.args.tags, tt.args.filter) + }) + } +} + func Test_sliceContainsSlice(t *testing.T) { type args struct { tags []string diff --git a/web/src/components/inputs/select.tsx b/web/src/components/inputs/select.tsx index 33f24b3..6fe8dfa 100644 --- a/web/src/components/inputs/select.tsx +++ b/web/src/components/inputs/select.tsx @@ -251,6 +251,7 @@ export interface SelectFieldProps { label: string; optionDefaultText: string; options: SelectFieldOption[]; + columns?: COL_WIDTHS; tooltip?: JSX.Element; } @@ -259,10 +260,15 @@ export const Select = ({ label, tooltip, optionDefaultText, - options + options, + columns }: SelectFieldProps) => { return ( -
Comma separated list of categories to match.
https://autobrr.com/filters/categoriesComma separated list of categories to ignore (takes priority over Match releases).
https://autobrr.com/filters/categoriesComma separated list of tags to match.
https://autobrr.com/filters#advanced} /> -Comma separated list of tags to ignore (takes priority over Match releases).
hhttps://autobrr.com/filters#advanced} /> +Comma separated list of tags to match.
https://autobrr.com/filters#advanced} /> +