Record a failed grab in history when the download client rejects it - #882
Open
m4bard wants to merge 1 commit into
Open
Record a failed grab in history when the download client rejects it#882m4bard wants to merge 1 commit into
m4bard wants to merge 1 commit into
Conversation
A submission the client refuses currently leaves no trace anywhere. The provisional Download row is deleted, which is right in itself since no client item backs it, and RecordGrabbedAsync only runs after the client has accepted, so nothing reaches history either. The next automatic search repeats the same grab and the user has nothing to look at. Record a DownloadFailed event before removing the provisional row. RecordDownloadFailedAsync already exists on IDownloadHistoryService and was only wired to client-side failures in DownloadMonitorService. Cancellation is left as it was: a shutdown is not a release failure. RemoveProvisionalDownloadAsync moves out of DownloadService into DownloadSubmissionFailureHandler next to the new call, matching DownloadRecordFactory and DownloadDuplicateGuard in the same namespace. DownloadService.cs goes from 498 to 487 lines, back under the 500 line budget.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Record a failed grab in history when the download client rejects it
What this fixes
When a download client refuses a submission, the attempt disappears completely. No
Downloadrow, no history event, nothing on the book to say it was tried. The next automatic search runs the same grab and fails the same way, and from the UI the book looks like it was never searched.Two separate steps cause it, and each one looks reasonable on its own:
DownloadService.cs:350removes the provisionalDownloadrow when the client throws. Right on its own: no client item backs the row, and leaving it would produce the stuckImportPendingrecords described in ImportPending downloads whose client item vanished block re-grabs with 409 forever, and orphan cleanup can't reach them #758.RecordGrabbedAsyncis only reached atDownloadService.cs:374, after the client has accepted. The comment above it is deliberate, and history should not claim a grab that never happened.Between the two, nothing is written.
IDownloadHistoryService.RecordDownloadFailedAsyncalready exists and is currently only wired to client-side failures fromDownloadMonitorService.cs:381, which is the path for a download that failed after the client accepted it. A submission the client never accepted never gets there.Where this diverges from Sonarr, deliberately
I should be straight about this rather than claim precedent I do not have.
Sonarr does not record failed grabs either. There is no
GrabFailedinEpisodeHistoryEventTypeorDownloadHistoryEventType, andSonarr.Core/Download/DownloadService.cshas noIHistoryServicedependency at all. Every catch arm logs and rethrows, andProcessDownloadDecisionsturns most of them intoSkippedwith nothing persisted. Sonarr reaches the same end state by never creating a record, where Listenarr creates one and deletes it.So this is a proposal, not a port. My reasoning for it: Listenarr already has a
DownloadFailedhistory event and an Activity view that surfaces it, this is the only failure path that writes nothing to either, and #838's blocklist would need a record like this to exist before it had anything to key on. If you would rather match Sonarr and stay silent here, that is a coherent answer and I will drop it.What changed
Record a
DownloadFailedevent before removing the provisional row.Cancellation is left as it was. A shutdown is not a release failure and should not appear in a user's history as one, so the
OperationCanceledExceptionpath still only removes the row.RemoveProvisionalDownloadAsyncmoves out ofDownloadServiceinto a newDownloadSubmissionFailureHandlernext to the new call, matchingDownloadRecordFactoryandDownloadDuplicateGuard, which are already internal static helpers in the same namespace. That keepsDownloadService.csunder the 500 line architecture budget: it was 498, now 487.The history write is wrapped so that a history failure cannot raise a second exception on a path already handling one, matching how the success-path history call at
DownloadService.cs:381is guarded.Scope, and one open question
This makes the failure visible. It does not stop it repeating. Nothing consults history to suppress a re-grab.
The open question: a qBittorrent 5.2 duplicate rejection comes through this same path, and Sonarr/Sonarr#8854 decided a duplicate should be a quiet skip rather than a failure. If Listenarr follows that, a 409 probably should not write a
DownloadFailedrow, and this would want a carve-out. I have filed the 409 behaviour separately as #881, since it needs a decision first.Tests
SendToDownloadClientAsync_WhenClientRejectsSubmission_RecordsFailedAttemptInHistory. Confirmed it fails with the production change reverted (Expected invocation on the mock once, but was 0 times) and passes with it.historyMock.VerifyNoOtherCalls(). Their names sayDoesNotRecordGraband that is still what they check, now stated directly asRecordGrabbedAsyncwithTimes.Neverrather than as no calls at all.