Don't move ebook files when no colocated destination applies - #127
Open
jbob06 wants to merge 1 commit into
Open
Conversation
EbookColocateOnAudiobookImportHandler exists solely to place ebook files alongside a just-imported audiobook of the same work. It moved a file whenever GetOrganizeDestination returned CanOrganize, but that is true whether or not the colocation planner actually applied: when the planner declines, the plan still carries the plain naming destination. The handler's gate reads the audiobook's root folder while the planner's gate reads the ebook's root folder, and Author.GetRootFolderForQuality returns different roots per media type. An author with a Mixed audiobook root and a separate ebook-only root therefore passes the handler's gate and fails the planner's, so every audiobook import re-organized that author's ebook files to their plain naming path even though nothing was being colocated. In that state MoveBookFile also took its non-colocation branch, whose unconditional TransferFile throws SameFilenameException for a file that is already at its canonical path. The handler's try/catch sits outside the per-file loop, so one already-correct ebook aborted the whole batch. Surface whether colocation was applied on BookFileMovePlan and skip the move when it was not. The flag is now the single source of truth for "this plan is a colocation" and selects the colocating branch in MoveBookFile, replacing the previous implicit ReplicaPaths != null check that encoded the same fact. Fixes Chaptarr#126 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MfnN7ewDrDq99e4LvzCzaW
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.
Fixes #126.
EbookColocateOnAudiobookImportHandlerexists solely to place ebook files alongside a just-imported audiobook of the same work. It moved a file wheneverGetOrganizeDestinationreturnedCanOrganize, but that is true whether or not the colocation planner actually applied: when the planner declines, the plan still carries the plain naming destination, and the handler relocated the ebook to it. That is a full re-organize of a file that was never part of the triggering import.The two gates disagree by construction. The handler resolves its root from the audiobook's quality, the planner resolves its root from the ebook's quality, and
Author.GetRootFolderForQualityreturns a different root per media type.AuthorService.IsCompatibleRootFolderallows an author to have a Mixed audiobook root and a separate ebook only root, andRootFolderResourceforcesPlaceEbooksWithAudiobooks = falseon any non Mixed root, so that configuration is guaranteed to pass the handler's gate and fail the planner's.NoAudiobookFoldersis reachable the same way.In that state
MoveBookFilealso took its non colocation branch, whose unconditionalTransferFilethrowsSameFilenameExceptionfor a file already at its canonical path. The handler'stry/catchsits outside the per file loop and nothing on this path catches that exception, so one already correct ebook aborted the whole batch under the generic "Failed to colocate ebooks after audiobook import" warning.Change
Surface whether colocation was applied on
BookFileMovePlanand skip the move when it was not.ColocationAppliedis also now the single source of truth for "this plan is a colocation" and selects the colocating branch inMoveBookFile, replacing the previousplan.ReplicaPaths != nullcheck that encoded the same fact implicitly. Those two were provably equivalent for every plan reachingMoveBookFilein production (two construction sites, two callers, and the planner's singleApplies = truesite always assigns a non null list), so this is behavior preserving and removes an unsynchronized second encoding rather than adding one. The colocating branch is null tolerant either way, sinceReconcileReplicaFilesnull coalesces its argument.RenameBookFileServiceis unaffected: it reads onlyCanOrganize,DestinationPath,SkipReason, the author folder paths andShouldUpdateStoredAuthorPath.Tests
should_not_move_ebook_when_no_colocated_destination_applieshands the handler a plan where colocation did not apply and asserts no move, noUpdate, and no events. It fails on current develop and passes with the change.should_not_flag_colocation_when_planner_declinespins the flag on the realBookFileMovingServicewith a declining planner, assertingCanOrganizestays true whileColocationAppliedis false, soRenameBookFileService's behavior is explicitly unchanged. The existing colocation test gained the positive assertion.Full
Chaptarr.Core.Testsuite: 3010 passed, 0 failed.Known deferral
Because the handler now returns before
MoveBookFile, it no longer triggers theShouldCleanupReplicaspath in the declined case, so an ebook carrying staleReplicaPathsfrom an earlier colocation is no longer pruned opportunistically during an unrelated audiobook import. That cleanup still happens viaRenameBookFileServiceand viaGetImportDestinationPathon the next import of that file. This seemed like correct scoping for a handler whose mandate is colocation rather than general organizing, but happy to restore an explicit cleanup only path here if you would rather keep it.🤖 Generated with Claude Code
https://claude.ai/code/session_01MfnN7ewDrDq99e4LvzCzaW