Skip to content

chore: validate PR 717 review-fix application#751

Closed
therobbiedavis wants to merge 140 commits into
canaryfrom
automation/pr717-review-fixes
Closed

chore: validate PR 717 review-fix application#751
therobbiedavis wants to merge 140 commits into
canaryfrom
automation/pr717-review-fixes

Conversation

@therobbiedavis

Copy link
Copy Markdown
Collaborator

Temporary same-repository validation branch used to apply and test the four PR #717 review findings. The workflow removes its own helper files before committing the validated production changes. This PR will be closed after the validated commit is transferred to bugfix/unix-folder-name-space.

therobbiedavis and others added 30 commits July 1, 2026 14:13
Preserve download-client reported path whitespace for torrent source/content paths while keeping destination validation separate. Add user-provided library destination validation for add, move, and root-folder workflows, allowing filesystem roots for root folders while rejecting parent traversal for concrete destinations.
Allow root-folder configuration to accept filesystem roots, including Windows current-drive roots, while keeping concrete destination paths strict against root-only and parent-traversal targets. Add cross-platform root-folder validation coverage and standardize the new torrent path mapper test header.
Cherry-picked PR #727 from 3103996 and adapted the current branch by removing the now-redundant MoveJobs runtime schema repair.

Co-authored-by: Kevin Heneveld <1192102+kevinheneveld@users.noreply.github.com>

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Make root-folder operations semantics-aware by threading FileSystemPathSemantics through root path checks, repository migrations, deletion safeguards, and unmatched-file filtering, and block new roots that overlap active relocation boundaries.

Improve relocation/move reliability with atomic-rename recovery markers, stricter cleanup reconciliation (including ambiguous quarantine detection), resilient heartbeat/broadcast error handling, and retry/reconcile support for superseded move jobs. Remove the temporary frontend debug test placeholder.
Adds lease-generation fencing for move jobs so only the active claimant can heartbeat, update status/phase, and persist move-manifest cleanup state. Move processing now cancels when a lease is lost, and persistence now increments/stores LeaseGeneration with a migration.

Path handling was also made semantics-driven across backend and frontend: remote/client path mapping, scan/planning, metadata parsing, and destination validation now honor explicit syntax/case-sensitivity instead of host defaults or path-shape guesses. Tests were updated to cover lease loss behavior, cross-platform path rules, and new architecture boundaries.
Refactor UpdateJobStatusAsync to isolate persistence, relocation, and broadcast into separate try/catch blocks so persistence failures propagate without broadcasting. Add specific catch blocks in MoveBackgroundService and MoveJobProcessor for MoveLeaseLostException and PersistenceException to prevent lease-lost scenarios from marking jobs as Failed. Update root folder change toast messages for clarity. Add tests covering persistence failure propagation, post-commit relocation failures, and lease-loss handling.
Diagnosis: LibraryMoveWorkflow compares user-library source and destination paths, not internal system paths. The relevant semantics are the destination root/output path boundary semantics already resolved from configured root folders or output path. This changes no public API surface; LibraryMoveWorkflow is DI-constructed and callers continue through LibraryController.
Diagnosis: DownloadImportService plans user-library destination paths under the audiobook destination volume. The correct semantics already flow in through resolved destination semantics. This changes no public API surface; ImportDestinationPlanner now separates planning from committing so a failed file action cannot reserve a batch destination.
Diagnosis: these are user-library path boundaries. Manual import source validation should use source/root semantics, preview/scan containment should use the selected root semantics, and durable content moves already receive resolved semantics from MoveJobProcessor. AudiobookContentMoveRequest is internal-only, so making semantics required does not change public API surface.
Diagnosis: this is enforcement, not a migration of one runtime path. Permanent allow-list entries are internal/runtime helpers; temporary entries are user-library sites that still need explicit semantics. The guard converts future drift into a CI failure while the temporary list is reduced subsystem by subsystem.
Diagnosis: MetadataRescanProcessor compares user-library audiobook and file paths when pruning non-audio file rows. The correct semantics come from the configured root containing the audiobook path, falling back to probing the path boundary. No public API changes are required because the processor already resolves scoped services from DI.
Diagnosis: organize preview and execution compare user-library audiobook paths within a single audiobook boundary. The correct semantics are resolved once from the audiobook current path/root and threaded through ordering, equality, containment, allowed roots, and path summary updates. The public IRenameService API is unchanged; only the service constructor and internal helpers changed.
Diagnosis: AudiobookFileService compares user-library paths while deciding whether a scanned or imported audio file is safe to associate with an audiobook. These are not internal system paths. The correct semantics come from the configured root containing the existing audiobook directory or base path, with boundary probing as fallback. IAudiobookFileService remains unchanged because semantics are resolved inside the service.
Diagnosis: LibraryPathPlanner and FileNamingService compare user-library roots and scan directories, not internal system paths. Scan base-path calculation now receives explicit scan-root semantics, and output-root comparison uses resolved configured-root semantics. IFileNamingService remains source-compatible because semantics resolution is injected optionally and used internally.
Diagnosis: scan queue dedupe compares user-library paths. These are not internal paths, but failures only duplicate or miss queue dedupe rather than mutating files. Active scan and unmatched-scan dedupe now resolve the queued path semantics before comparing. The unmatched last-job cache keeps exact original keys because the controller normally queries the same root-folder path, avoiding a synchronous resolver API change.
Comment on lines +106 to +112
foreach (var root in candidateRoots)
{
if (TryValidateResolvedComponents(normalizedTarget, root, out reason))
{
return true;
}
}
Comment on lines +356 to +370
foreach (var directoryEntry in manifest
.Where(entry => entry.EntryType == MoveJobEntryType.Directory)
.OrderByDescending(entry => entry.RelativePath.Length))
{
if (FileSystemPathIdentity.TryResolveRelativePathWithinBase(
quarantineRoot,
directoryEntry.RelativePath,
sourceSemantics,
out var quarantineDirectory)
&& Directory.Exists(quarantineDirectory)
&& !Directory.EnumerateFileSystemEntries(quarantineDirectory).Any())
{
Directory.Delete(quarantineDirectory, false);
}
}
Comment on lines +59 to +76
foreach (var boundary in boundaries)
{
if (await ActiveBoundaryConflictsWithTargetAsync(
path,
semantics,
boundary.SourcePath,
boundary.SourceCaseSensitivityMode,
cancellationToken)
|| await ActiveBoundaryConflictsWithTargetAsync(
path,
semantics,
boundary.TargetPath,
boundary.TargetCaseSensitivityMode,
cancellationToken))
{
return true;
}
}
return null;
}

var anchor = Path.Combine(volumeRoot, firstSegment);
Comment on lines +82 to +94
if (!string.IsNullOrWhiteSpace(expectedSourcePath))
{
if (string.IsNullOrWhiteSpace(sourceBasePath)
|| !StoredSourcePathMatchesExpected(
expectedSourcePath,
sourceBasePath,
sourceSemantics))
{
throw new ApplicationConflictException(
"source_path_changed",
"The audiobook source path changed. Refresh and try again.");
}
}
Comment on lines +68 to +79
if (sourceBoundary != null)
{
sourceSemantics = sourceBoundary.Semantics;
}
else
{
// Metadata-only updates must not require source filesystem access.
// If the source is not inside a configured boundary, reuse the validated
// target boundary semantics only for stale-source comparison and best-effort
// reference rewriting. Invalid source references are preserved by the rewriter.
sourceSemantics = destination.TargetBoundary.Semantics;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants