Fix ADIF import silently dropping every QSO from a headerless log - #672
Fix ADIF import silently dropping every QSO from a headerless log#672patrickrb wants to merge 1 commit into
Conversation
The two ADIF importers (LogFileImport, used by the web-logger HTTP upload, and ImportSharedLogs, used by the Android share/VIEW intent) both derived the record body by splitting the file on <eoh> and returning "" when no marker was found. Per the ADI file format a Header is optional: it is present only when the file does NOT begin with '<'. A file that begins with '<' is headerless and consists entirely of data records. So a valid headerless ADIF log — a common export shape, and what a WSJT-X wsjtx_log.adi whose header was written with the v2.2.2 <eh> bug looks like — imported as zero QSOs, with no error surfaced to the user (getErrorCount() == 0). Silent data loss on import. Root cause fix: a shared, spec-correct AdifFormat.stripHeader() returns the whole content for a headerless file (first non-blank char, after an optional UTF-8 BOM, is '<'), the text after the first <eoh> for a headed file, and "" only when a header is present but unterminated or the input is null. Both getLogBody() methods now delegate to it. Tests: new AdifFormat.stripHeader cases (headed/headerless/BOM/whitespace/ case-insensitive <eoh>/first-<eoh>-only/unterminated/null) and an end-to-end LogFileImport headerless-import test (replacing the stale test that pinned the old drop-everything behaviour). All pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #672 +/- ##
============================================
+ Coverage 36.73% 36.80% +0.06%
- Complexity 197 211 +14
============================================
Files 216 219 +3
Lines 26882 27054 +172
Branches 3284 3321 +37
============================================
+ Hits 9876 9957 +81
- Misses 16781 16863 +82
- Partials 225 234 +9
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR fixes a silent ADIF import failure where headerless .adi logs (files that begin with <field...>, with no <eoh> header terminator) previously imported zero QSOs without surfacing an error. It centralizes the header/body split in AdifFormat.stripHeader(String) and updates both import paths to use it.
Changes:
- Added
AdifFormat.stripHeader(String)to correctly handle optional ADIF headers (headerless vs headed-with-<eoh>). - Updated both
LogFileImport.getLogBody()(web upload path) andImportSharedLogs.getLogBody()(share/VIEW intent path) to usestripHeader. - Added/updated unit tests covering headerless logs and unterminated headers.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| ft8af/app/src/main/java/com/k1af/ft8af/log/AdifFormat.java | Adds stripHeader helper to correctly derive record body for headed and headerless ADIF files. |
| ft8af/app/src/main/java/com/k1af/ft8af/log/LogFileImport.java | Switches record-body extraction to AdifFormat.stripHeader for the web upload import path. |
| ft8af/app/src/main/java/com/k1af/ft8af/log/ImportSharedLogs.java | Switches record-body extraction to AdifFormat.stripHeader for the share/VIEW-intent import path. |
| ft8af/app/src/test/java/com/k1af/ft8af/log/AdifFormatTest.java | Adds focused unit tests for stripHeader behavior (headed/headerless/case/BOM/unterminated). |
| ft8af/app/src/test/java/com/k1af/ft8af/log/LogFileImportTest.java | Updates end-to-end import tests to cover headerless ADIF logs and malformed unterminated headers. |
Comments suppressed due to low confidence (1)
ft8af/app/src/test/java/com/k1af/ft8af/log/AdifFormatTest.java:247
- Test method name says "headless" but this test is about "headerless" ADIF logs (no header). Renaming makes the suite easier to scan and avoids conflating with unrelated "headless" terminology.
public void stripHeader_headlessFile_skipsLeadingWhitespaceAndBom() {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| @Test | ||
| public void stripHeader_headlessFile_returnsWholeContent() { |
| } | ||
|
|
||
| @Test | ||
| public void stripHeader_headPresentButNoEoh_returnsEmpty() { |
Summary
Importing a headerless ADIF log (a file that begins directly with a
<field...>record, with no header) resulted in zero QSOs imported and no error shown to the user — silent data loss on import.Per the ADI file format the Header is optional: it is present only when the file does not begin with
<. A file that begins with<is headerless and consists entirely of data records.Both ADIF importers derived the record body by splitting the file on
<eoh>and returning""whenever no marker was found:LogFileImport.getLogBody()— the built-in web-logger HTTP upload path (LogHttpServer→new LogFileImport)ImportSharedLogs.getLogBody()— the Android share / VIEW-intent path (another app hands a.adito FT8AF)For a valid headerless file the split found no
<eoh>, returned"", andgetLogRecords()produced an empty list — whilegetErrorCount()stayed0, so the import UI reported success. Every QSO was dropped.This shape is common: many loggers and hand exports omit the header, and a WSJT-X
wsjtx_log.adiwhose header was written with the known v2.2.2<eh>bug also has no parseable<eoh>.Root cause & fix
Added a single spec-correct, unit-tested helper
AdifFormat.stripHeader(String)and pointed bothgetLogBody()methods at it (same root cause → one PR). It returns:<);<eoh>when a header is present and terminated;""only when a header is present but unterminated (no<eoh>), or the input is null — genuinely no record body.No protocol/DSP behavior changes; this is purely the ADIF reader's header/record split. Headed files (including the existing WSJT-X test fixtures) are handled exactly as before.
Testing performed
./gradlew :app:testDebugUnitTestfor the three affected classes — all pass (33 + 30 + 16 tests, 0 failures):AdifFormatTest: newstripHeadercases — headed body-after-<eoh>, headerless whole-content, leading BOM/whitespace, case-insensitive<eoh>, first-<eoh>-only, header-present-but-unterminated → empty, null/empty.LogFileImportTest: new end-to-endheaderlessAdif_importsRecords(drives the real constructor →getLogRecords, now 2 records) andheaderPresentButUnterminated_yieldsNoRecords. These replace the stalegetLogBody_returnsEmptyWhenNoEohtest, which pinned the old drop-everything behavior.The end-to-end test was confirmed to fail against the pre-fix code (0 records) before the fix was applied.
Risk assessment
Low. The change only affects files that previously imported as zero records; headed files are unchanged (verified by the untouched, passing WSJT-X fixture tests). Nothing downstream keys off an empty body to signal an invalid file —
doImportADIsimply iterates the record list.