fix: harden Blossom sync concurrency and cancellation

Audit fixes for the Blossom client:

- BlossomBlobManagerViewModel: use StateFlow.update{} for the presence
  matrix so the Main-thread sync collector and IO-thread delete/mirror
  actions can't lose each other's writes; add refreshJob de-dup so two
  quick refreshes can't interleave; rethrow CancellationException; bound
  the /list HEAD-probe backfill with a Semaphore(8).
- BlossomClient.has(): rethrow CancellationException instead of
  swallowing it as 'not found'.
- BlossomSyncForegroundService: drop the stale 'running' de-dup guard so
  a fresh sweep always gets foreground protection.
- CLI mirror: strip query/fragment before extracting the sha256.
- DisplayBlossomSyncProgress: retain the last state so the slide-out exit
  animation still has content to draw after the state clears.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
This commit is contained in:
Claude
2026-07-18 13:46:06 +00:00
parent 84640a5890
commit a25c5f9518
5 changed files with 70 additions and 37 deletions
@@ -116,7 +116,12 @@ object BlossomCommands {
val args = Args(rest)
val server = args.flag("server") ?: return Output.error("bad_args", "blossom mirror requires --server URL")
val sourceUrl = args.positional(0, "source-url")
val hash = sourceUrl.substringAfterLast('/').substringBefore('.')
val hash =
sourceUrl
.substringBefore('?')
.substringBefore('#')
.substringAfterLast('/')
.substringBefore('.')
if (hash.length != 64 || hash.any { it !in "0123456789abcdef" }) {
return Output.error("bad_args", "could not extract a sha256 from the source url '$sourceUrl'")
}