Skip to content

fix(sd-download): keep the partial log when the device reports a read error - #51

Draft
tylerkron wants to merge 1 commit into
mainfrom
fix/sd-download-keep-partial-on-transfer-error
Draft

tylerkron wants to merge 1 commit into
mainfrom
fix/sd-download-keep-partial-on-transfer-error

Conversation

@tylerkron

@tylerkron tylerkron commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Why this is still a draft

daqifi/daqifi-core#721 is merged (commit a305432, 2026-09-08). The remaining blocker is narrower: no Core release contains it yet.

The latest release and the newest package on nuget.org are both v1.7.0 (2026-08-24), which predates the merge, and this repo pins Daqifi.Core 1.4.0 — so CI here will still fail with:

Program.cs(1046,24): error CS0246: The type or namespace name 'SdCardTransferErrorException' could not be found

Once Core cuts a release off main, bump the pin in Daqifi.Core.Cli.csproj to it and this goes green. That bump is the only outstanding work; the pin is deliberately left alone rather than pointed at a version that does not exist.

The problem

Firmware v3.7.3+ ends a failed SD read with the __TRANSFER_ERROR__ marker. Core #721 surfaces that as SdCardTransferErrorException and deliberately leaves the bytes received before the marker in the caller's destination stream — they are genuine file content, and the exception carries BytesReceived. The PR's rationale: "a caller that can use a partial log may keep them."

The CLI could not. Every failure went through one bare catch that deleted the sibling temp file and rethrew, so a download that died partway through a log left the user with nothing at all.

On the bench (Nq1, FW 3.8.0, 2026-09-08) log_20260814_114608.json threw with BytesReceived=49152, and a direct Core harness confirmed the destination stream held exactly those 49,152 bytes of clean, valid JSON. Through the CLI: nothing.

The change

Catch SdCardTransferErrorException ahead of the bare catch and publish the temp file as <destination>.partial instead of deleting it, printing how many bytes were recovered, that the file is incomplete, and where it was written:

  Received 49,152 bytes...
Recovered 49,152 bytes received before the device reported the error.
The file is incomplete, so it was not parsed.
Partial file: /path/to/log_20260814_114608.json.partial
Error: SdCardTransferErrorException: The device reported a read error while sending SD card file ...

Rethrowing keeps the exit code and Core's own diagnostic, and skips the post-download parse — parsing an incomplete log would fail or mislead.

Notes on the details:

  • .partial is overwritten unconditionally. It is this tool's own record of a failed attempt; on a retry the newest one is the one that matters. The user's actual destination is never touched.
  • A failed rename does not cost the bytes. If the move to .partial fails, the temp file is left where it is and reported, rather than deleted.
  • The stream is already flushed when the catch runs. The exception propagates out of the await using block, so disposal happens before the handler — the temp file is complete and closed, which also matters for the rename on Windows.

What is unchanged

Every other exception keeps the delete-and-rethrow it had. The two neighbouring SD failures are siblings under SdCardOperationException, not subclasses, so the new catch cannot swallow them:

  • SdCardTruncatedTransferException — its bytes are a short reply standing in for the file, explicitly not file content, and must be discarded.
  • SdCardTransferStalledException.

Verification

Re-verified against merged Core main (a305432) after #721 landed, not just the PR branch. The shipped public surface is exactly what this code was written against, with no drift during review:

Daqifi.Core.Device.SdCard.SdCardTransferErrorException.BytesReceived.get -> long
Daqifi.Core.Device.SdCard.SdCardTransferErrorException.FileName.get -> string!
Daqifi.Core.Device.SdCard.SdCardTransferErrorException.SdCardTransferErrorException(string! fileName, long bytesReceived) -> void

Build against Core main via -p:DaqifiCoreProjectPath=...: clean, 0 warnings, 0 errors.

A harness drove all three SD exceptions through this exact block:

Case .partial Destination Leftover .part-* Exit
SdCardTransferErrorException 49,152 bytes, starts {"samples":[, no marker bytes absent 0 1
SdCardTruncatedTransferException none absent 0 1
SdCardTransferStalledException none absent 0 1

The transfer-error case also replaced a stale .partial left from an earlier attempt.

Not exercised against the bench device: no DAQiFi device was attached over serial during this work, and the fault needs the specific damaged file. The bench evidence above is from the session that motivated the change.

🤖 Generated with Claude Code

… error

Firmware v3.7.3+ ends a failed SD read with the __TRANSFER_ERROR__ marker, and
daqifi-core#721 surfaces that as SdCardTransferErrorException while deliberately
leaving the bytes received before the marker in the caller's destination stream.
They are genuine file content, so a caller that can use a partial log may keep
them.

The CLI could not: every failure went through one bare catch that deleted the
sibling temp file and rethrew, so a download that died 49,152 valid bytes into a
log left the user with nothing.

Catch that one exception ahead of the bare catch and publish the temp file as
<destination>.partial instead of deleting it, reporting how many bytes were
recovered, that the file is incomplete, and where it went. Rethrowing keeps the
exit code and Core's own diagnostic, and skips the post-download parse, which on
an incomplete log would fail or mislead.

Every other exception keeps the delete-and-rethrow it had. That includes the two
neighbouring SD failures, which are siblings under SdCardOperationException
rather than subclasses, so the new catch cannot swallow them:
SdCardTruncatedTransferException, whose bytes are a short reply standing in for
the file and must be discarded, and SdCardTransferStalledException.

Verified against daqifi-core#721's branch with a harness driving all three
exceptions through this block: the transfer-error case leaves 49,152 bytes of
clean content in .partial (replacing a stale one from an earlier attempt, with
no marker bytes leaked, the destination untouched and no temp file left behind),
while the truncated and stalled cases still leave the directory empty.

Depends on daqifi-core#721 landing and a Core release containing it; the pinned
Daqifi.Core 1.4.0 has no SdCardTransferErrorException, so this cannot build until
the pin is bumped to that release.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant