Skip to content

feat(search): configurable automatic-search sweep interval, per-book cooldown and book delay - #714

Merged
therobbiedavis merged 1 commit into
Listenarrs:canaryfrom
kevinheneveld:feat/automatic-search-tuning
Oct 25, 2026
Merged

therobbiedavis merged 1 commit into
Listenarrs:canaryfrom
kevinheneveld:feat/automatic-search-tuning

Conversation

@kevinheneveld

@kevinheneveld kevinheneveld commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The automatic-search sweep ran on a hardcoded 6-hour cadence that also served as the "not searched since" cutoff, and it searched every eligible book back-to-back. On a large wanted list that hammers the indexers (and a shared Prowlarr). This makes the timing configurable and, per review, keeps the sweep cadence and the per-book eligibility separate: the worker can check hourly while each book is only searched once per cooldown.

Scope is timing only. The title-only fallback from the earlier revision is gone; that lane belongs to #1072 and the identity-matching follow-ups.

Changes

Added

  • ApplicationSettings.AutomaticSearchIntervalHours (default 1, clamped [1, 168]) — how often the worker sweeps for eligible books. Only the cadence of the check.
  • ApplicationSettings.AutomaticSearchCooldownHours (default 24, clamped [1, 720]) — how long a book stays ineligible after its last search. One shared cooldown for every book.
  • ApplicationSettings.AutomaticSearchBookDelaySeconds (default 5, clamped [0, 300]) — pause between consecutive books inside one sweep. 0 disables it. Manual "Search now" is unaffected.
  • AutomaticSearchTiming (HostedServices/Search/) — resolves and clamps the three from settings so AutomaticSearchService and AutomaticSearchProcessor share one definition; EligibilityCutoff(now) is now − cooldown.
  • Migration 20261021212418_AddAutomaticSearchTiming (+ Designer, snapshot). Column defaults match the property initializers so the existing singleton settings row behaves exactly like a fresh install.

Changed

  • AutomaticSearchService: the periodic runner's intervalProvider reads the sweep interval from settings each cycle (falls back to the default on a read error), so a change takes effect without a restart.
  • AutomaticSearchProcessor: the eligibility cutoff comes from the cooldown instead of the sweep interval; the per-book delay is applied between books (not after the last one).

Removed

  • The title-only fallback (AutomaticSearchTitleOnlyFallback, BuildTitleOnlySearchQuery, the retry in ProcessAudiobookAsync).

Testing

  • AutomaticSearchTimingTests (new, 17 cases): defaults, each clamp at both bounds, and that the eligibility cutoff follows the cooldown rather than the sweep interval.
  • MigrationMetadataTests.AddAutomaticSearchTiming_IsDiscoverableAndAddsSettingsColumnsWithDefaults: migration id, the three AddColumn operations with their defaults, the three drops, and the target model.
  • SqliteMigrationSchemaTests (migrated-schema column parity, migration history, released-schema upgrades) and HostedServicesRegistrationTests pass with the new migration; ActiveProductionSourceFiles_RemainFocused still passes.
  • Release build 0 errors; dotnet format --verify-no-changes clean on the touched files.

Notes

  • Defaults follow the review suggestion (hourly sweep, 24h cooldown). Upstream's previous behaviour was 6h/6h; existing installs will sweep more often but search each book less often than before.
  • No UI in this PR — the three settings are plain ApplicationSettings fields reachable through the settings API like the other untuned knobs. Happy to add a small Settings block in a follow-up if you'd like them surfaced.
  • Rebased onto canary v1.3.17 (thanks for the rebase); the earlier July-dated migration is replaced by a fresh one so it sorts after 20261002210137_AddOperationEvidencePublications.

🤖 Generated with Claude Code

@therobbiedavis therobbiedavis left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Removed, meant to post with changes requested]

@therobbiedavis therobbiedavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevinheneveld Could we keep this PR focused on configurable search timing and leave the fallback to #1072 and the identity-matching follow-ups already underway?

I’d separate the sweep interval from the per-book cooldown: the worker could check hourly while each book remains eligible only once every 24 hours. The delay between books would remain a separate throttle to protect indexers. Currently, AutomaticSearchIntervalHours controls both the sweep cadence and eligibility cutoff; could we give the cutoff its own setting? One shared cooldown for all books should be enough for now.

@kevinheneveld
kevinheneveld force-pushed the feat/automatic-search-tuning branch from b4fbe84 to 853be7e Compare October 21, 2026 21:31
@kevinheneveld kevinheneveld changed the title feat(search): configurable automatic-search interval, per-book delay, title-only fallback feat(search): configurable automatic-search sweep interval, per-book cooldown and book delay Oct 21, 2026
@kevinheneveld

Copy link
Copy Markdown
Contributor Author

@therobbiedavis reworked as suggested, thanks for the rebase:

  • Title-only fallback removed — left to Retry automatic search without the author when the full query returns nothing (Fixes #1068) #1072.
  • Sweep cadence and eligibility are now separate settings: AutomaticSearchIntervalHours (default 1 — how often the worker checks) and AutomaticSearchCooldownHours (default 24 — one shared per-book cooldown that drives the LastSearchTime cutoff). AutomaticSearchBookDelaySeconds stays as the between-books throttle.
  • A small AutomaticSearchTiming resolver holds the clamps so the service and processor read the same definition; 17 unit cases cover defaults/clamps and that the cutoff follows the cooldown, not the interval.
  • Fresh migration (20261021212418_AddAutomaticSearchTiming) replacing the July-dated one so it sorts after AddOperationEvidencePublications; the migration-history and schema-parity tests are updated and green.

Description updated to match. No UI in this PR — say the word if you'd like a Settings block for the three and I'll add it as a follow-up.

@therobbiedavis therobbiedavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevinheneveld reviewed and tested the updated head. The fallback is removed and the sweep interval, per-book cooldown, and delay are separate now. 57 tests passed with 2 platform skips, along with 19 live container checks. No remaining blockers from me.

Copy link
Copy Markdown
Collaborator

@kevinheneveld Thanks, this matches what we discussed. API-only is fine for this PR. Yes, let’s do a follow-up to add the Settings block.

@therobbiedavis
therobbiedavis force-pushed the feat/automatic-search-tuning branch from 853be7e to 5bdf118 Compare October 22, 2026 13:05
@therobbiedavis therobbiedavis added the minor minor version bump - add functionality in a backward compatible manner label Oct 22, 2026
@therobbiedavis

Copy link
Copy Markdown
Collaborator

@kevinheneveld Please fix the failing tests. Thank you!

@kevinheneveld
kevinheneveld force-pushed the feat/automatic-search-tuning branch from 5bdf118 to 950bb94 Compare October 22, 2026 19:21
@kevinheneveld

Copy link
Copy Markdown
Contributor Author

Fixed in 950bb94 (amended, same single commit on your rebase): AutomaticSearchTimingTests now inherits BaseTests (the TestClasses_FollowRepositoryConventions violation on Windows) and the two usings that format:backend:check flagged as IDE0005 are gone (they were already in GlobalUsings.cs). Locally: BackendArchitectureTests 45/45, the timing tests 17/17, dotnet format listenarr.slnx --verify-no-changes clean. Nothing else changed.

@therobbiedavis
therobbiedavis force-pushed the feat/automatic-search-tuning branch 2 times, most recently from 50d65af to 461460f Compare October 25, 2026 03:12
…cooldown and book delay

The automatic-search sweep ran on a hardcoded 6-hour cadence that doubled as
the "not searched since" cutoff, and it searched every eligible book
back-to-back. Three settings replace that, read fresh each cycle:

- AutomaticSearchIntervalHours (default 1, clamped [1,168]): how often the
  worker looks for eligible books. Only the cadence of the check.
- AutomaticSearchCooldownHours (default 24, clamped [1,720]): how long a book
  stays ineligible after its last search. One shared cooldown for all books.
- AutomaticSearchBookDelaySeconds (default 5, clamped [0,300]): pause between
  consecutive books inside one sweep. Manual "Search now" is unaffected.

AutomaticSearchTiming resolves and clamps the three from ApplicationSettings
so the service and the processor share one definition. The migration's column
defaults match the property initializers so the existing settings row behaves
like a fresh install.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@therobbiedavis
therobbiedavis force-pushed the feat/automatic-search-tuning branch from 461460f to b5b7c76 Compare October 25, 2026 03:37
@therobbiedavis
therobbiedavis merged commit 5c4e801 into Listenarrs:canary Oct 25, 2026
7 checks passed
kevinheneveld added a commit to kevinheneveld/Listenarr that referenced this pull request Oct 25, 2026
…v1.5.2)

Upstream v1.3.24-v1.5.2: Listenarrs#757 indexer-timeout containment, Listenarrs#605 quality-profile
picker, Listenarrs#714 automatic-search timing (sweep interval / per-book cooldown / book
delay), Listenarrs#1072 author-free retry with title constraint, Listenarrs#990 post-nominal author
folders.

Resolutions:
- ApplicationSettings / AutomaticSearchService / AutomaticSearchResultClassifier:
  upstream's three timing columns and author-free fallback replace the fork's
  AutomaticSearchIntervalHours-as-cooldown, trailing book delay and opt-out
  title-only fallback. Fork's SearchProgressReporter broadcasts, blocked-release
  and filter-pipeline hooks kept; SearchNow.cs no longer passes the fallback flag.
- Migrations: fork-only 20261021212417_RetireForkAutomaticSearchTuning renames the
  July fork columns to Retired_* (native transactional ALTER, no rebuild) so
  upstream's 20261021212418_AddAutomaticSearchTiming applies unchanged. Snapshot =
  upstream's. SqliteMigrationSchemaTests expected history updated.
- IndexerSearchWorkflow: fork's inner HttpClient-timeout catch removed in favor of
  upstream's per-indexer OperationCanceledException containment (Listenarrs#757 supersedes
  Listenarrs#967); the contradicted cooperative-cancellation test removed. Torznab provider
  catch and HttpClientTimeoutClassifier remain.
- WantedView.vue: fork's server-side wanted-search strip/queue kept alongside
  Listenarrs#605's profile-recovery banner, modal and pending-refresh set; upstream's
  browser-loop remnants (sleep/abortDelay/showConfirmSearchAll) not reintroduced;
  duplicated useToast import collapsed.
- AutomaticSearchProcessorFallbackTests: scorer mock adapted to the fork's
  three-parameter ScoreSearchResults.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor minor version bump - add functionality in a backward compatible manner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants