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
This commit is contained in:
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Assembles build/iPod Contacts and Calendar Sync.app from the SwiftPM executable.
|
||||
#
|
||||
# SwiftPM builds a bare binary; macOS needs it inside a bundle with an
|
||||
# Info.plist before Contacts and EventKit will even prompt for access, so the
|
||||
# bundle is put together here rather than by an Xcode target.
|
||||
#
|
||||
# ./Scripts/bundle.sh # native architecture
|
||||
# ./Scripts/bundle.sh --universal # arm64 + x86_64
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
APP_NAME="iPod Contacts and Calendar Sync"
|
||||
BUNDLE_ID="ca.unsupervised.ipodsync"
|
||||
VERSION="2.0"
|
||||
MIN_MACOS="14.0"
|
||||
|
||||
BUILD_DIR="$ROOT/build"
|
||||
APP="$BUILD_DIR/$APP_NAME.app"
|
||||
|
||||
# Bash 3.2 ships with macOS and treats an empty array as unbound under
|
||||
# `set -u`, hence the ${ARCH_FLAGS[@]+…} guard at each use site.
|
||||
ARCH_FLAGS=()
|
||||
if [ "${1:-}" = "--universal" ]; then
|
||||
ARCH_FLAGS=(--arch arm64 --arch x86_64)
|
||||
fi
|
||||
|
||||
echo "Building…"
|
||||
swift build --package-path "$ROOT" -c release ${ARCH_FLAGS[@]+"${ARCH_FLAGS[@]}"}
|
||||
BIN_PATH="$(swift build --package-path "$ROOT" -c release ${ARCH_FLAGS[@]+"${ARCH_FLAGS[@]}"} --show-bin-path)"
|
||||
|
||||
echo "Assembling $APP_NAME.app…"
|
||||
rm -rf "$APP"
|
||||
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
|
||||
|
||||
cp "$BIN_PATH/iPodSyncApp" "$APP/Contents/MacOS/$APP_NAME"
|
||||
|
||||
cat > "$APP/Contents/Info.plist" <<PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>$APP_NAME</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>$APP_NAME</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$BUNDLE_ID</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$VERSION</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$VERSION</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$APP_NAME</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$MIN_MACOS</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSSupportsAutomaticTermination</key>
|
||||
<true/>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>iPod Contacts and Calendar Sync reads your contacts so it can write one iPod-compatible vCard file per person.</string>
|
||||
<key>NSCalendarsFullAccessUsageDescription</key>
|
||||
<string>iPod Contacts and Calendar Sync reads your calendars so it can write iPod-compatible calendar files.</string>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>iPod Contacts and Calendar Sync reads your calendars so it can write iPod-compatible calendar files.</string>
|
||||
</dict>
|
||||
PLIST
|
||||
echo "</plist>" >> "$APP/Contents/Info.plist"
|
||||
|
||||
# Ad-hoc signature. Contacts and Calendars identify an app by its code
|
||||
# signature, so an unsigned bundle either re-prompts constantly or is denied
|
||||
# outright. This is not a Developer ID signature — Gatekeeper still requires
|
||||
# right-click → Open the first time.
|
||||
echo "Signing (ad-hoc)…"
|
||||
codesign --force --sign - --timestamp=none "$APP" >/dev/null 2>&1
|
||||
|
||||
echo "Built $APP"
|
||||
Reference in New Issue
Block a user