Files
iPod-export/README.md
T
thatguygriffandClaude Opus 5 e515277015
Tests / test (push) Failing after 41s
Run tests in CI on Linux
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
2026-08-08 20:26:29 -03:00

6.5 KiB
Raw Blame History

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.
  • RRULEs 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.

./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.