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