Tests / test (push) Failing after 41s
Adds a Gitea Actions workflow running swift test in a swift:6.2 container. The kit did not build without Apple frameworks, so the two files that need them are wrapped in #if canImport, and the SwiftUI app target is added to Package.swift only on macOS. Neither can exist in a Linux container. This costs no coverage: all 43 tests are about file format and none touch the Contacts or EventKit readers. The integration those readers represent remains verifiable only on a Mac. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01C5X1tYo9oxAfvoiFMh1QQr
147 lines
6.5 KiB
Markdown
147 lines
6.5 KiB
Markdown
# iPod Contacts and Calendar Sync
|
||
|
||
A macOS app that writes contacts and calendars in a form a click-wheel iPod
|
||
can actually read.
|
||
|
||
## Why this exists
|
||
|
||
Since Catalina, Finder has had no contacts/calendar sync for a click-wheel iPod
|
||
— that lived in iTunes' "Info" tab, which was removed in 2019. The device still
|
||
mounts in Finder with `Contacts` and `Calendars` folders that accept dropped
|
||
`.vcf` and `.ics` files, but exporting from Contacts.app and Calendar.app and
|
||
dragging the results across mostly doesn't work:
|
||
|
||
- A single `.vcf` holding hundreds of contacts shows up as **one** contact, or
|
||
a few. The firmware reads the first card and stops, or stops at the first
|
||
card it can't parse.
|
||
- A `.ics` from Calendar.app often shows up as **nothing at all**. The exports
|
||
carry `VTIMEZONE` blocks, `TZID`-qualified date-times and `VALARM` blocks,
|
||
none of which the device implements — and a file it can't parse is a file it
|
||
discards entirely, not one it partially reads.
|
||
|
||
This app reads Contacts and Calendars directly and writes output shaped around
|
||
those limits. There's no export-then-clean step: the problem properties are
|
||
never written in the first place.
|
||
|
||
## What it produces
|
||
|
||
Pick an output folder and you get:
|
||
|
||
```
|
||
YourFolder/
|
||
Contacts/ one .vcf per person → drag into the iPod's Contacts folder
|
||
Calendars/ one .ics per calendar → drag into the iPod's Calendars folder
|
||
```
|
||
|
||
The subfolders mirror the device's own layout, so the drag across in Finder is
|
||
unambiguous.
|
||
|
||
### Contacts
|
||
|
||
- **One file per contact.** The single biggest fix — it sidesteps the
|
||
multi-card parsing failure entirely.
|
||
- vCard **3.0**, not 4.0. The click-wheel address book was written against
|
||
2.1/3.0 and doesn't recognise 4.0's property forms.
|
||
- No `PHOTO`, `LOGO`, `SOUND` or `X-` properties.
|
||
- Contacts labels map to the `TYPE` tokens vCard 3.0 actually defines; labels
|
||
with no standard equivalent ("Other", custom ones) fall back to a bare type
|
||
rather than an invented one.
|
||
- Notes are not exported. `CNContactNoteKey` has required a restricted Apple
|
||
entitlement since macOS 11, and requesting it unentitled makes the whole
|
||
fetch fail.
|
||
- Birthdays without a year are dropped rather than given an invented one.
|
||
|
||
### Calendars
|
||
|
||
- **One file per calendar**, so one bad calendar can't take the others down
|
||
with it on the device.
|
||
- **Repeating events are expanded into individual events.** Nothing emits an
|
||
`RRULE`, so the device never has to interpret one — this was the open
|
||
question in the previous version and it's now moot.
|
||
- Times are **floating**: local wall-clock, no `TZID`, no `VTIMEZONE`, no `Z`.
|
||
- No `VALARM`, no `X-` properties.
|
||
- You choose the date range; the default is one month back to a year ahead.
|
||
|
||
### Existing `.vcf` / `.ics` files
|
||
|
||
The app can also clean files you already have, which is the path the old Python
|
||
script covered. Files are split and filtered rather than regenerated:
|
||
|
||
- Multi-card `.vcf` files split into one file per card.
|
||
- `PHOTO`/`LOGO`/`SOUND`/`KEY` and any base64 payload removed; `X-` properties
|
||
removed; Apple's `item1.` property groups unwrapped.
|
||
- `ENCODING=QUOTED-PRINTABLE` values decoded to UTF-8, including vCard 2.1 soft
|
||
line breaks. Filenames come from the *decoded* name, so you get
|
||
`Renée Fleming.vcf`, not `Ren=C3=A9e Fleming.vcf`.
|
||
- `VALARM` and `VTIMEZONE` blocks removed (including their nested
|
||
`STANDARD`/`DAYLIGHT` components); `TZID` parameters dropped; UTC `Z` times
|
||
converted to local floating times.
|
||
- `RRULE`s in these files are **kept and flagged**, not expanded — there's no
|
||
recurrence engine on this path. Export from Calendars instead if you want
|
||
them expanded.
|
||
|
||
## Building
|
||
|
||
Requires Xcode (for the macOS SDK). No third-party dependencies.
|
||
|
||
```sh
|
||
./Scripts/bundle.sh # build/iPod Contacts and Calendar Sync.app, native arch
|
||
./Scripts/bundle.sh --universal # arm64 + x86_64
|
||
swift test # run the test suite
|
||
```
|
||
|
||
`Package.swift` opens directly in Xcode if you'd rather work there.
|
||
|
||
### CI
|
||
|
||
`.gitea/workflows/test.yml` runs `swift test` in a `swift:6.2` container on
|
||
every push and pull request.
|
||
|
||
It covers `IPodSyncKit` only. The Contacts and EventKit readers are compiled
|
||
out on Linux via `#if canImport(…)`, and the SwiftUI app target is excluded
|
||
from `Package.swift` on non-Apple platforms — neither can exist in a Linux
|
||
container. No test coverage is lost by this: every test is about file format,
|
||
and none of them touch the readers. What CI cannot tell you is whether reading
|
||
from the real Contacts and Calendars databases still works; that needs a Mac.
|
||
|
||
The bundle is **ad-hoc signed**, which is not the same as signed for
|
||
distribution. Ad-hoc signing is required — Contacts and Calendars identify an
|
||
app by its code signature, and an unsigned bundle gets denied or re-prompts
|
||
forever. Gatekeeper will still block a plain double-click the first time:
|
||
right-click → **Open**, then confirm. That's once per build.
|
||
|
||
If you rebuild and the permission prompts come back, that's expected: the
|
||
signature changed, so macOS treats it as a different app. Clearing the old
|
||
entry in System Settings › Privacy & Security is sometimes needed.
|
||
|
||
## Project layout
|
||
|
||
```
|
||
Sources/IPodSyncKit/ all logic, no UI — what the tests cover
|
||
ContentLine.swift folding, escaping, unfolding (both formats)
|
||
PropertyLine.swift NAME;PARAM=value:VALUE parsing
|
||
Models.swift IPodContact, IPodEvent
|
||
VCardWriter.swift IPodContact → vCard 3.0
|
||
ICalendarWriter.swift [IPodEvent] → VCALENDAR
|
||
ContactsReader.swift Contacts.framework → IPodContact
|
||
CalendarReader.swift EventKit → IPodEvent, recurrences expanded
|
||
VCardFileSanitizer.swift existing .vcf → split + filtered
|
||
ICalendarFileSanitizer.swift existing .ics → filtered
|
||
FileNaming.swift ASCII-safe, collision-free filenames
|
||
ExportSession.swift writes the Contacts/ and Calendars/ layout
|
||
QuotedPrintable.swift =XX decoding
|
||
TextFile.swift encoding-tolerant reading
|
||
|
||
Sources/iPodSyncApp/ SwiftUI window app
|
||
Tests/IPodSyncKitTests/ 43 tests
|
||
Scripts/bundle.sh assembles and signs the .app
|
||
```
|
||
|
||
## Status
|
||
|
||
Everything is verified at the file level by the test suite. **None of it has
|
||
been checked against a physical iPod Classic yet** — the format decisions come
|
||
from the documented firmware limitations, not from a device that has been
|
||
confirmed to read the output. If something still doesn't appear on the device,
|
||
the calendar path is the more likely suspect than the contacts path.
|