Files
iPod-export/README.md
T
thatguygriffandClaude Opus 5 7b9d16e442 Add Swift app for iPod contacts and calendar export
Replaces the bash/AppleScript/Python app bundle with a SwiftUI app backed
by a testable library target.

The app now reads Contacts and EventKit directly rather than sanitizing
files someone exported by hand, so the properties that break click-wheel
firmware are never written instead of being stripped afterwards: no PHOTO,
no X-, no VALARM, no VTIMEZONE, no TZID. Recurring events arrive from
EventKit already expanded into occurrences, so no RRULE is emitted either
- the open question from the previous version no longer applies.

Sanitizing existing .vcf/.ics files is kept as a second path, and gains
handling the Python lacked: vCard 2.1 quoted-printable soft line breaks,
Apple's item1. property groups, nested STANDARD/DAYLIGHT components inside
VTIMEZONE, and UTC times converted to local floating times.

Output is laid out as Contacts/ and Calendars/ subfolders mirroring the
device, one file per contact and one per calendar.

Built via SwiftPM plus Scripts/bundle.sh, which assembles and ad-hoc signs
the bundle. Ad-hoc signing is required rather than cosmetic: Contacts and
Calendars key permissions off the code signature.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01C5X1tYo9oxAfvoiFMh1QQr
2026-08-08 20:21:09 -03:00

135 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.