/* Copyright (c) 2017, Computer History Museum All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the disclaimer below) provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "osxabsync.h" #define FILE_NUM 148 /* Copyright Qualcomm, Inc. 2003 */ #include "MachOWrapper.h" #include "osxabsyncinc.h" ///////////////////////////////////////////////////////////////////////// // // osxabsync.c // // This file provides services to slurp in all the contacts from the // Mac OS X address book into a Eudora nicknames file. One way synching // is supported, with two way not too far away. // // An interesting mix of C and C++. C++ was used in the original // OSXAddressBookSync EMSAPI pluging. Rather than rewriting all // of that code, I've chosen to live in a muddled world. // ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// // // OS X Address Book Type Declarations. // ///////////////////////////////////////////////////////////////////////// /* * ABSyncRecordField */ typedef struct _ABSyncRecordField { long m_EudoraABFieldName; const char *m_pOSXABProperty; const char *m_pOSXABKey; const char *m_pOSXABLabel; } ABSyncRecordField; /* * ABSyncRecord */ // ABSyncRecord class class ABSyncRecord { private: // are we a group? Boolean fIsGroup; // nickname to use for this record Str255 fEudoraNickName; // a table of field mapping data const ABSyncRecordField *fFieldMappingTable; // an array of the address book field values CFStringRef fOSXABFieldValues[ABReservedTagsLimit]; // the nickname pulled from the AddressBook CFStringRef fOSXABNickName; // an array of the address book field multi values ABMultiValueRef fOSXABFieldMultiValues[ABReservedTagsLimit]; // other email addresses CFMutableStringRef fOtherEmailAddresses; // initialize internals void ABSyncRecord_Initialize(void); // recursive group import void ABSyncRecord_ImportGroupMembers(ABGroupRef abRecord); public: // constructor ABSyncRecord(); // destructor ~ABSyncRecord(); // release internals void ABSyncRecord_Reset(void); // import a contact from the OS X Address Book void ABSyncRecord_ImportContact(ABPersonRef abRecord); // import a group from the OS X Address Book void ABSyncRecord_ImportGroup(ABGroupRef abRecord); // generate a Nickname from a record unsigned char *ABSyncRecord_GenerateNickName(void); // Generate a Eudora Address Book compatible alias line Handle ABSyncRecord_GenerateAddressLine(void); // Generate a Eudora Address Book compatible notes line Handle ABSyncRecord_GenerateNotesLine(void); }; ///////////////////////////////////////////////////////////////////////// // // Globals // ///////////////////////////////////////////////////////////////////////// // MachOWrappage. Global so we only make 'em once. // AddressBook.framework MachOWrapper ABGetSharedAddressBook ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABGetSharedAddressBook" )); MachOWrapper ABCopyArrayOfAllPeople ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABCopyArrayOfAllPeople" )); MachOWrapper ABCopyArrayOfAllGroups ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABCopyArrayOfAllGroups" )); MachOWrapper ABGroupCopyArrayOfAllMembers ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABGroupCopyArrayOfAllMembers" )); MachOWrapper ABGroupCopyArrayOfAllSubgroups ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABGroupCopyArrayOfAllSubgroups" )); MachOWrapper ABTypeOfProperty ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABTypeOfProperty" )); MachOWrapper ABMultiValueCount ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueCount" )); MachOWrapper ABRecordCopyValue ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABRecordCopyValue" )); MachOWrapper ABMultiValueCreateCopy ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueCreateCopy" )); MachOWrapper ABMultiValueCopyValueAtIndex ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueCopyValueAtIndex" )); MachOWrapper ABMultiValueIndexForIdentifier ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueIndexForIdentifier" )); MachOWrapper ABMultiValueCopyLabelAtIndex ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueCopyLabelAtIndex" )); MachOWrapper ABMultiValueCopyPrimaryIdentifier ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueCopyPrimaryIdentifier" )); MachOWrapper ABGroupCopyDistributionIdentifier ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABGroupCopyDistributionIdentifier" )); // Notifications MachOWrapper CFNotificationCenterGetDistributedCenter ( CFSTR ( "CoreFoundation.framework" ), CFSTR ( "CFNotificationCenterGetDistributedCenter" )); MachOWrapper CFNotificationCenterAddObserver ( CFSTR ( "CoreFoundation.framework" ), CFSTR ( "CFNotificationCenterAddObserver" )); MachOWrapper CFNotificationCenterRemoveObserver ( CFSTR ( "CoreFoundation.framework" ), CFSTR ( "CFNotificationCenterRemoveObserver" )); // nickname file command strings and such #define kEndOfLine 0x0D #define kSpace 0x20 #define kNoEmailAddressChar ',' #define pDoubleQuoteChar "\p\"" #define pUnderbarChar "\p_" // Table to map OSX AB contact fields to Eudora AB contact fields. static const ABSyncRecordField gFieldMappingTable[ABReservedTagsLimit] = { { abTagName, NULL, NULL, NULL }, // full name { abTagFirst, kABFirstNameProperty, NULL, NULL }, // first { abTagLast, kABLastNameProperty, NULL, NULL }, // last { abTagEmail, kABEmailProperty, NULL, NULL }, // email address { abTagAddress, kABAddressProperty, kABAddressStreetKey, kABAddressHomeLabel }, // address { abTagCity, kABAddressProperty, kABAddressCityKey, kABAddressHomeLabel }, // city { abTagState, kABAddressProperty, kABAddressStateKey, kABAddressHomeLabel }, // state { abTagCountry, kABAddressProperty, kABAddressCountryKey, kABAddressHomeLabel }, // country { abTagZip, kABAddressProperty, kABAddressZIPKey, kABAddressHomeLabel }, // zip { abTagPhone, kABPhoneProperty, kABPhoneProperty, kABPhoneHomeLabel }, // phone { abTagFax, kABPhoneProperty, kABPhoneProperty, kABPhoneHomeFAXLabel }, // fax { abTagMobile, kABPhoneProperty, kABPhoneProperty, kABPhoneMobileLabel }, // mobile { abTagPrimary, kABPhoneProperty, kABPhoneProperty, kABPhoneMainLabel }, // primary { abTagAIM, kABAIMInstantProperty, kABAIMInstantProperty, kABAIMHomeLabel }, // home aim { abTagWeb, kABHomePageProperty, NULL, NULL }, // web { abTagTitle, kABJobTitleProperty, NULL, NULL }, // title { abTagCompany, kABOrganizationProperty, NULL, NULL }, // company { abTagAddress2, kABAddressProperty, kABAddressStreetKey, kABAddressWorkLabel }, // address { abTagCity2, kABAddressProperty, kABAddressCityKey, kABAddressWorkLabel }, // city { abTagState2, kABAddressProperty, kABAddressStateKey, kABAddressWorkLabel }, // state { abTagCountry2, kABAddressProperty, kABAddressCountryKey, kABAddressWorkLabel }, // country { abTagZip2, kABAddressProperty, kABAddressZIPKey, kABAddressWorkLabel }, // zip { abTagPhone2, kABPhoneProperty, kABPhoneProperty, kABPhoneWorkLabel }, // phone { abTagFax2, kABPhoneProperty, kABPhoneProperty, kABPhoneWorkFAXLabel }, // fax { abTagMobile2, kABPhoneProperty, kABPhoneProperty, kABPhonePagerLabel }, // mobile { abTagAIM2, kABAIMInstantProperty, kABAIMInstantProperty, kABAIMWorkLabel }, // work aim { abTagWeb2, kABHomePageProperty, NULL, NULL }, // web { abTagOtherEmail, kABEmailProperty, NULL, NULL }, // otheremail { abTagOtherPhone, kABPhoneProperty, NULL, kABPhoneMainLabel }, // otherphone { abTagOtherWeb, NULL, NULL, NULL }, // otherweb { abTagNote, kABNoteProperty, NULL, NULL }, // notes, { abTagPicture, NULL, NULL, NULL }, // picture { abNickname, NULL, NULL, NULL }, // nickname { ABReservedTagsLimit, NULL, NULL, NULL } // EudoraABFieldNamesLimit }; // has address book syncing been initialized? Boolean gOSXABSyncInit = false; // notification callback CFNotificationCallback gABCallback = NULL; // schedule sync Boolean bNeedsSync = false; ///////////////////////////////////////////////////////////////////////// // // Prototypes // ///////////////////////////////////////////////////////////////////////// // AddressBookSync short GetOSXAddressBookFile(void); OSStatus DoOSXAddressBookSync(short which); void RemoveOSXAddressBookFile(void); // notifications void OSXAddressBookNotificationReg(void); void OSXABSyncNotificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo); void OSXAddressBookNotificationDeReg(void); // Utilities short SafeStrLen(const char *s); void ConvertLineBreaks(Str255 in); #define HaveOS102() (GetOSVersion() >= 0x1020) #define HaveOS103() (GetOSVersion() >= 0x1030) void *MachOFunctionPointerForCFMFunctionPointer( void *cfmfp ); OSErr MakeEudoraABEntry(short which, Str255 nickname, Handle addresses, Handle notes); OSErr ConvertFieldValueToPString(CFStringRef fieldValue, Str255 pValue); // externs extern "C" { void GenAliasList(void); } ///////////////////////////////////////////////////////////////////////// // // AddressBookSync // ///////////////////////////////////////////////////////////////////////// /************************************************************************ * OSXAddressBookSync - sync with the OS X AddressBook, if we ought. ************************************************************************/ OSStatus OSXAddressBookSync(void) { OSStatus err = noErr; short which; // we must have OS 10.2 or great to do any of this ... if (!HaveOS102()) return (noErr); // Does the user wish to sync with the OS X Address Book? if (!PrefIsSet(PREF_SYNC_OSXAB)) return (noErr); // are we in Free mode? Then forget it. if (IsFreeMode()) return (noErr); // register for notifications OSXAddressBookNotificationReg(); // we're set up and ready to go ... gOSXABSyncInit = true; // make sure the aliases are around ... RegenerateAllAliases(false); // create the address book file that will hold the new addresses which = GetOSXAddressBookFile(); if (which != -1) { // sync the address book with the OS X Address Book. err = DoOSXAddressBookSync(which); if (err == noErr) { // save the file SetAliasDirty(which); SaveIndNickFile(which, false); } // lock the resulting file until we implement a full, two way sync FSpSetFLock(&((*Aliases)[which].spec)); // tell Eudora about the new address book file GenAliasList(); RegenerateAllAliases(true); ABTickleHardEnoughToMakeYouPuke(); } else err = fnfErr; // couldn't create Eudora nickname file to sync to return (err); } /************************************************************************ * OSXAddressBookSyncCleanup - Cleanup after OSX AB Sync ************************************************************************/ void OSXAddressBookSyncCleanup(Boolean bRemoveNickFile) { if (gOSXABSyncInit) { OSXAddressBookNotificationDeReg(); if (bRemoveNickFile) RemoveOSXAddressBookFile(); gOSXABSyncInit = false; } } /************************************************************************ * OSXAddressBookScheduledSync - perform scheduled sync ************************************************************************/ void OSXAddressBookScheduledSync(void) { if (bNeedsSync) { bNeedsSync = false; OSXAddressBookSync(); } } /************************************************************************ * RemoveOSXAddressBookFile - remove the nickname file we created ************************************************************************/ void RemoveOSXAddressBookFile(void) { Str255 name; short which; GetRString(name, OSXAB_FILE); for (which = 0; which < NAliases; which++) if (StringSame((*Aliases)[which].spec.name, name)) break; if (which < NAliases) { // is this nickname file locked? if ((*Aliases)[which].ro) { FSpRstFLock(&((*Aliases)[which].spec)); FSpDelete(&((*Aliases)[which].spec)); GenAliasList(); RegenerateAllAliases(true); ABTickleHardEnoughToMakeYouPuke(); } } } /************************************************************************ * GetOSXAddressBookFile - figure out where to put OS X AddressBook * contacts. Create a new nickname file if needed. ************************************************************************/ short GetOSXAddressBookFile(void) { short which = -1; AliasDesc ad; FSSpec folderSpec; Str255 scratch; long dirID; OSErr err = noErr; Zero(ad); // get the name of the address book file ... GetRString(ad.spec.name, OSXAB_FILE); // does an address book with this name already exist? for (which = 0; which < NAliases; which++) if (StringSame((*Aliases)[which].spec.name, ad.spec.name)) break; if (which < NAliases) { // it already exists. Make sure it's unlocked and delete it. FSpRstFLock(&((*Aliases)[which].spec)); FSpDelete(&((*Aliases)[which].spec)); RegenerateAllAliases(true); } // create the address book file. // find the nicknames folder ... if (!SubFolderSpec(NICK_FOLDER,&folderSpec)) SimpleMakeFSSpec(folderSpec.vRefNum,folderSpec.parID,ad.spec.name,&ad.spec); else { SimpleMakeFSSpec(Root.vRef,Root.dirId,GetRString(scratch,NICK_FOLDER),&folderSpec); FSpDirCreate(&folderSpec,smSystemScript,&dirID); SubFolderSpec(NICK_FOLDER,&folderSpec); SimpleMakeFSSpec(folderSpec.vRefNum,folderSpec.parID,ad.spec.name,&ad.spec); } // make a new nickname file. err = MakeResFile(ad.spec.name,ad.spec.vRefNum,ad.spec.parID,CREATOR,'TEXT'); if (err == noErr) { if (PtrPlusHand((void *)(&ad),(char **)(Aliases),sizeof(ad))) err = MemError(); which = HandleCount (Aliases) - 1; } return (which); } /************************************************************************ * DoOSXAddressBookSync - do the deed ************************************************************************/ OSStatus DoOSXAddressBookSync(short which) { OSStatus err = noErr; ABPersonRef curPerson; ABGroupRef curGroup; Handle addressH, notesH; ABSyncRecord record; CFIndex count; ABAddressBookRef addressBook = ABGetSharedAddressBook(); // get the OS X Address Book CFArrayRef people = addressBook? ABCopyArrayOfAllPeople(addressBook) : NULL; // get a list of all the people CFArrayRef groups = addressBook? ABCopyArrayOfAllGroups(addressBook) : NULL; // get a list of all the groups if (people) { // count the number of records count = CFArrayGetCount(people); if (count > 0) { // iterate over each record for (CFIndex curRecord = 0; curRecord < count; curRecord++) { // locate the current person in the OS X address book curPerson = (ABPersonRef)CFArrayGetValueAtIndex(people, curRecord); // import contact information record.ABSyncRecord_ImportContact(curPerson); // convert the OS X AddressBook record into something Eudora understands addressH = record.ABSyncRecord_GenerateAddressLine(); notesH = record.ABSyncRecord_GenerateNotesLine(); // add the OS X address book entry to the Eudora Address Book file we're creating if (addressH && notesH) MakeEudoraABEntry(which, record.ABSyncRecord_GenerateNickName(), addressH, notesH); // cleanup record.ABSyncRecord_Reset(); if (addressH) DisposeHandle(addressH); if (notesH) DisposeHandle(notesH); } } // else // nothing to do. CFRelease(people); } // count the number of groups if (groups) { count = CFArrayGetCount(groups); if (count > 0) { // iterate over each record for (CFIndex curRecord = 0; curRecord < count; curRecord++) { // locate the current person in the OS X address book curGroup = (ABGroupRef)CFArrayGetValueAtIndex(groups, curRecord); // import group information record.ABSyncRecord_ImportGroup(curGroup); // convert the OS X AddressBook record into something Eudora understands addressH = record.ABSyncRecord_GenerateAddressLine(); notesH = record.ABSyncRecord_GenerateNotesLine(); // add the OS X address book entry to the Eudora Address Book file we're creating if (addressH && notesH) MakeEudoraABEntry(which, record.ABSyncRecord_GenerateNickName(), addressH, notesH); // cleanup record.ABSyncRecord_Reset(); if (addressH) DisposeHandle(addressH); if (notesH) DisposeHandle(notesH); } } // else // nothing to do. CFRelease(groups); } return (noErr); } ///////////////////////////////////////////////////////////////////////// // // Notifications // ///////////////////////////////////////////////////////////////////////// /************************************************************************ * OSXAddressBookNotificationReg - register for OS X AB notifications * requires 10.3 or later. ************************************************************************/ void OSXAddressBookNotificationReg(void) { if (!gOSXABSyncInit && HaveOS103()) { gABCallback = (CFNotificationCallback)MachOFunctionPointerForCFMFunctionPointer(OSXABSyncNotificationCallback); CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), NULL, gABCallback, CFSTR("ABDatabaseChangedNotification"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately); } } /************************************************************************ * OSXABSyncNotificationCallback - notification callback ************************************************************************/ void OSXABSyncNotificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { // schedule a sync to be done next time it's convenient. bNeedsSync = true; } /************************************************************************ * OSXAddressBookNotificationDeReg - deregister for OS X AB notifications ************************************************************************/ void OSXAddressBookNotificationDeReg(void) { if (gOSXABSyncInit && HaveOS103()) { CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), NULL, CFSTR("ABDatabaseChangedNotification"), NULL); DisposePtr((Ptr)gABCallback); } } ///////////////////////////////////////////////////////////////////////// // // ABSyncRecord // ///////////////////////////////////////////////////////////////////////// /************************************************************************************************* * ABSyncRecord - creator *************************************************************************************************/ ABSyncRecord::ABSyncRecord(void) { this->ABSyncRecord_Initialize(); } /************************************************************************************************* * ~ABSyncRecord - destructor. Clean up after a sync record. *************************************************************************************************/ ABSyncRecord::~ABSyncRecord(void) { this->ABSyncRecord_Reset(); } /************************************************************************************************* * ABSyncRecord_Initialize - initialize a sync record *************************************************************************************************/ void ABSyncRecord::ABSyncRecord_Initialize(void) { fOtherEmailAddresses = NULL; long count; fFieldMappingTable = gFieldMappingTable; for (count = 0; count < ABReservedTagsLimit; count++) { fOSXABFieldValues[count] = NULL; fOSXABFieldMultiValues[count] = NULL; fOSXABNickName = NULL; } } /************************************************************************************************* * ABSyncRecord_ReleaseABValues - release memory allocated during record conversion *************************************************************************************************/ void ABSyncRecord::ABSyncRecord_Reset(void) { for (long count = 0; count < ABReservedTagsLimit; count++) { if (fOSXABFieldValues[count]) { CFRelease(fOSXABFieldValues[count]); fOSXABFieldValues[count] = NULL; } if (fOSXABFieldMultiValues[count]) { CFRelease(fOSXABFieldMultiValues[count]); fOSXABFieldMultiValues[count] = NULL; } fOSXABNickName = NULL; fEudoraNickName[0] = 0; } if (fOtherEmailAddresses) { CFRelease(fOtherEmailAddresses); fOtherEmailAddresses = NULL; } } /************************************************************************************************* * ABSyncRecord_ImportContact - Read in contact data from the OS X Address Book *************************************************************************************************/ void ABSyncRecord::ABSyncRecord_ImportContact(ABPersonRef abRecord) { ABPropertyType curPropertyType; int index, count; int primaryEmailIndex; CFStringRef recordType; CFStringRef property; CFStringRef label; CFStringRef key; CFStringRef valueCopy; ABAddressBookRef addressBook = ABGetSharedAddressBook(); // initialize and set up this->fIsGroup = false; // import the values from each of the address book record fields const ABSyncRecordField *curField = fFieldMappingTable; while (curField->m_EudoraABFieldName != ABReservedTagsLimit) { recordType = CFStringCreateWithCString(NULL, kABPersonRecordType, NULL); property = curField->m_pOSXABProperty ? CFStringCreateWithCString(NULL, curField->m_pOSXABProperty, NULL) : NULL; label = curField->m_pOSXABLabel ? CFStringCreateWithCString(NULL, curField->m_pOSXABLabel, NULL) : NULL; key = curField->m_pOSXABKey ? CFStringCreateWithCString(NULL, curField->m_pOSXABKey, NULL) : NULL; // does this Eudora Address Book field have a corresponding OS X Address Book property? if (curField->m_pOSXABProperty != NULL) { // figure out what type of property this is ... curPropertyType = ABTypeOfProperty(addressBook, recordType, property); // Is this a multi value property? if ((curPropertyType & kABMultiValueMask) != NULL) { // find the value of this property ... valueCopy = (CFStringRef)ABRecordCopyValue(abRecord, property); if (valueCopy) { fOSXABFieldMultiValues[curField->m_EudoraABFieldName] = ABMultiValueCreateCopy((ABMultiValueRef)valueCopy); if (fOSXABFieldMultiValues[curField->m_EudoraABFieldName] != NULL) { // // special cases // // Is this the email field? if (curField->m_pOSXABProperty == kABEmailProperty) { if (curField->m_EudoraABFieldName == abTagEmail) { // place the primary email address into the Email field CFStringRef primaryEmailKey = ABMultiValueCopyPrimaryIdentifier(fOSXABFieldMultiValues[curField->m_EudoraABFieldName]); if (primaryEmailKey != NULL) { primaryEmailIndex = ABMultiValueIndexForIdentifier(fOSXABFieldMultiValues[curField->m_EudoraABFieldName], primaryEmailKey); if (primaryEmailIndex >= 0) fOSXABFieldValues[abTagEmail] = (CFStringRef)ABMultiValueCopyValueAtIndex(fOSXABFieldMultiValues[curField->m_EudoraABFieldName], primaryEmailIndex); CFRelease(primaryEmailKey); } } else { // place all other email addresses into the OtherEmail accumulator index = ABMultiValueCount(fOSXABFieldMultiValues[curField->m_EudoraABFieldName]); while (--index) { if (index != primaryEmailIndex) { CFStringRef fieldValue = (CFStringRef)ABMultiValueCopyValueAtIndex(fOSXABFieldMultiValues[curField->m_EudoraABFieldName], index); if (fieldValue != NULL) { if (fOtherEmailAddresses == NULL) { fOtherEmailAddresses = CFStringCreateMutableCopy(NULL, 1000, fieldValue); } else { CFStringAppendPascalString(fOtherEmailAddresses, "\p, ", NULL);; CFStringAppend(fOtherEmailAddresses, fieldValue); } CFRelease(fieldValue); } } } } } else { // find the proper label within this property count = ABMultiValueCount(fOSXABFieldMultiValues[curField->m_EudoraABFieldName]); for (index = 0; (count > 0) && (index < count); index++) { CFStringRef curLabel = ABMultiValueCopyLabelAtIndex(fOSXABFieldMultiValues[curField->m_EudoraABFieldName], index); if (curLabel != NULL) { if (CFStringCompare(curLabel, label, NULL) == kCFCompareEqualTo) { // do the proper thing to store the C-string representation of this value ... if ((curPropertyType == kABDictionaryProperty) || (curPropertyType == kABMultiDictionaryProperty)) { // this multi-value property is a Dictionary CFDictionaryRef fieldDictionary = CFDictionaryCreateCopy(NULL, (CFDictionaryRef)ABMultiValueCopyValueAtIndex(fOSXABFieldMultiValues[curField->m_EudoraABFieldName], index)); CFStringRef dictFieldValue = (CFStringRef) CFDictionaryGetValue(fieldDictionary, key); if (dictFieldValue) { fOSXABFieldValues[curField->m_EudoraABFieldName] = CFStringCreateCopy(NULL, dictFieldValue); CFRelease(dictFieldValue); } } else if ((curPropertyType == kABMultiStringProperty) != 0) { // this multi-value property is a multi-string fOSXABFieldValues[curField->m_EudoraABFieldName] = (CFStringRef)ABMultiValueCopyValueAtIndex(fOSXABFieldMultiValues[curField->m_EudoraABFieldName],index); } // else // this is some other unhandled type. Forgettaboutit. CFRelease(curLabel); break; } CFRelease(curLabel); } } } } CFRelease(valueCopy); } } else { // find the value of this propetry ... fOSXABFieldValues[curField->m_EudoraABFieldName] = (CFStringRef)ABRecordCopyValue(abRecord, property); } } // else // ignore this address book field. // cleanup if (recordType) CFRelease(recordType); if (property) CFRelease(property); if (label) CFRelease(label); if (key) CFRelease(key); // next field curField++; } // we've read all the fields that map to Eudora's address book. // Grab the OS X nickname as well. property = CFStringCreateWithCString(NULL, kABNicknameProperty, NULL); fOSXABNickName = (CFStringRef)ABRecordCopyValue(abRecord, property); CFRelease(property); } /************************************************************************************************* * ABSyncRecord_ImportGroup - Read in group data from the OS X Address Book *************************************************************************************************/ void ABSyncRecord::ABSyncRecord_ImportGroup(ABGroupRef abRecord) { // initialize and set up this->fIsGroup = true; // read the name of this group CFStringRef property = CFStringCreateWithCString(NULL,kABGroupNameProperty, NULL); fOSXABFieldValues[abTagName] = (CFStringRef)ABRecordCopyValue(abRecord, property); CFRelease(property); // read in all members, including subgroups ABSyncRecord_ImportGroupMembers(abRecord); } /************************************************************************************************* * ABSyncRecord_ImportGroupMembers - Import a group. *************************************************************************************************/ void ABSyncRecord::ABSyncRecord_ImportGroupMembers(ABGroupRef abRecord) { int count, emailIndex; CFStringRef property; ABPersonRef curMember; CFStringRef addressFieldValues; ABMultiValueRef addressFieldMultiValues; CFStringRef emailKey; CFStringRef address; // // Import group members // // next, grab the list of all members of this group CFArrayRef members = ABGroupCopyArrayOfAllMembers(abRecord); if (members) { count = CFArrayGetCount(members); if (count > 0) { // iterate over each member property = CFStringCreateWithCString(NULL,kABEmailProperty, NULL); for (CFIndex curRecord = 0; curRecord < count; curRecord++) { curMember = (ABPersonRef)CFArrayGetValueAtIndex(members, curRecord); // read their email address for this group addressFieldValues = (CFStringRef)ABRecordCopyValue(curMember, property); if (addressFieldValues) { addressFieldMultiValues = ABMultiValueCreateCopy((ABMultiValueRef)addressFieldValues); if (addressFieldMultiValues) { emailKey = ABGroupCopyDistributionIdentifier(abRecord, curMember, property); if (emailKey) { emailIndex = ABMultiValueIndexForIdentifier(addressFieldMultiValues, emailKey); address = (CFStringRef)ABMultiValueCopyValueAtIndex(addressFieldMultiValues, emailIndex); if (address) { // add this address to the group's list of addresses if (fOtherEmailAddresses == NULL) { fOtherEmailAddresses = CFStringCreateMutableCopy(NULL, 1000, address); } else { CFStringAppendPascalString(fOtherEmailAddresses, "\p, ", NULL);; CFStringAppend(fOtherEmailAddresses, address); } CFRelease(address); } CFRelease(emailKey); } CFRelease(addressFieldMultiValues); } CFRelease(addressFieldValues); } } CFRelease(property); } CFRelease(members); } // // Import sub groups // CFArrayRef subGroups = ABGroupCopyArrayOfAllSubgroups(abRecord); if (subGroups) { count = CFArrayGetCount(subGroups); if (count > 0) { for (CFIndex curRecord = 0; curRecord < count; curRecord++) ABSyncRecord_ImportGroupMembers((ABGroupRef)CFArrayGetValueAtIndex(subGroups, curRecord)); } CFRelease(subGroups); } } /************************************************************************************************* * ABSyncRecord_GenerateAddressLine - generat a list of address to create a new Eudora nick from *************************************************************************************************/ Handle ABSyncRecord::ABSyncRecord_GenerateAddressLine(void) { Handle aliasH = NULL; OSErr err = noErr; Accumulator line; Boolean needsQuote = false; const char *pAddresses; Str255 emailAddress; err = AccuInit(&line); if (err == noErr) { // are we a group? if (fIsGroup) { // // add the list of email addresses ... // if (fOtherEmailAddresses) { pAddresses = CFStringGetCStringPtr(fOtherEmailAddresses, NULL); if (pAddresses && *pAddresses) AccuAddPtr(&line, (unsigned char *)pAddresses, SafeStrLen(pAddresses)); } } else { // // add the primary email address ... // if (fOSXABFieldValues[abTagEmail] != NULL) { ConvertFieldValueToPString(fOSXABFieldValues[abTagEmail], emailAddress); AccuAddPtr(&line, emailAddress+1, emailAddress[0]); } else AccuAddChar(&line, kNoEmailAddressChar); AccuAddChar(&line, kEndOfLine); } // // return the alias line // AccuTrim(&line); aliasH = line.data; } return (aliasH); } /************************************************************************************************* * ABSyncRecord_GenerateNotesLine - generate a notes line to create a new Eudora nickname with *************************************************************************************************/ Handle ABSyncRecord::ABSyncRecord_GenerateNotesLine(void) { Handle notesH = NULL; OSErr err = noErr; Boolean needsQuote = false; Str255 value; Str255 tag; notesH = (char **)NuHandle(0); err = MemError (); if (err == noErr) { // // add all the fields that belong in the notes ... // const ABSyncRecordField *curField = fFieldMappingTable; while ((err == noErr) && (curField->m_EudoraABFieldName < ABReservedTagsLimit)) { // // special cases // if (curField->m_EudoraABFieldName == abTagOtherEmail) { if (!this->fIsGroup) { // add the list of other email addresses. Don't do this for groups. if (fOtherEmailAddresses) { const char *pOtherEmailAddresses = CFStringGetCStringPtr(fOtherEmailAddresses, NULL); int len = pOtherEmailAddresses ? strlen(pOtherEmailAddresses) : 0; if (len) err = AddAttributeValuePair((char **)notesH, GetRString(tag, ABReservedTagsStrn+abTagOtherEmail), (char *)pOtherEmailAddresses, len); } } } // // one to one mapped fields // else if (fOSXABFieldValues[curField->m_EudoraABFieldName]) { ConvertFieldValueToPString(fOSXABFieldValues[curField->m_EudoraABFieldName], value); ConvertLineBreaks(value); err = AddAttributeValuePair((char **)notesH, GetRString (tag, ABReservedTagsStrn + curField->m_EudoraABFieldName), (char *)(value+1), (long)value[0]); } curField++; } } return (notesH); } /************************************************************************ * ABSyncRecord_GenerateNickName - build a nickname for a record in a way * not quite unlike how Eudora does it. ************************************************************************/ unsigned char * ABSyncRecord::ABSyncRecord_GenerateNickName(void) { OSErr err = noErr; Boolean needsQuote = false; Str255 firstName, lastName; // Init Zero(fEudoraNickName); // use the OSX AddressBook nickname if it exists if (fOSXABNickName) { ConvertFieldValueToPString(fOSXABNickName, fEudoraNickName); } else { // otherwise generate our own // get first and last name from the record ConvertFieldValueToPString(fOSXABFieldValues[abTagFirst], firstName); ConvertFieldValueToPString(fOSXABFieldValues[abTagLast], lastName); // Derive the nickname by first looking joining the first and last names if (*firstName || *lastName) JoinFirstLast (fEudoraNickName, firstName, lastName); // If that didn't work, try the full name field if (!*(fEudoraNickName)) ConvertFieldValueToPString(fOSXABFieldValues[abTagName], fEudoraNickName); // If that didn't work, try the company name field if (!*(fEudoraNickName)) ConvertFieldValueToPString(fOSXABFieldValues[abTagCompany], fEudoraNickName); } // clean up the nickname BeautifyFrom (fEudoraNickName); SanitizeFN (fEudoraNickName, fEudoraNickName, NICK_BAD_CHAR, NICK_REP_CHAR, false); *fEudoraNickName = MIN (*fEudoraNickName, sizeof (Str31) - 1); return (fEudoraNickName); } ///////////////////////////////////////////////////////////////////////// // // Utilities // ///////////////////////////////////////////////////////////////////////// /************************************************************************ * SafeStrLen - strlen that handles strlen(NULL) ************************************************************************/ short SafeStrLen(const char *s) { short i = 0; if (s) { while (*s) { i++; s++; } } return(i); } /********************************************************************** * ConvertLineBreaks - convert line breaks. The Eudora Address Book * gets all choked up on what the OS X Address Book gives it. **********************************************************************/ void ConvertLineBreaks(Str255 in) { PReplace(in, "\p\n","\p\003"); PReplace(in, "\p\r","\p\003"); } /************************************************************************ * MachOFunctionPointerForCFMFunctionPointer - This function allocates a * block of CFM glue code which contains the instructions to call CFM * routines. Thanks, Marshall! :) ************************************************************************/ UInt32 templ[6] = {0x3D800000, 0x618C0000, 0x800C0000, 0x804C0004, 0x7C0903A6, 0x4E800420}; void *MachOFunctionPointerForCFMFunctionPointer( void *cfmfp ) { UInt32 *mfp = (UInt32*) NewPtr( sizeof(templ) ); // Must later dispose of allocated memory if (mfp) { mfp[0] = templ[0] | ((UInt32)cfmfp >> 16); mfp[1] = templ[1] | ((UInt32)cfmfp & 0xFFFF); mfp[2] = templ[2]; mfp[3] = templ[3]; mfp[4] = templ[4]; mfp[5] = templ[5]; MakeDataExecutable( mfp, sizeof(templ) ); } return( mfp ); } /************************************************************************ * MakeEudoraABEntry - make an address book entry in Eudora ************************************************************************/ OSErr MakeEudoraABEntry(short which, Str255 nickname, Handle addresses, Handle notes) { OSErr err = noErr; Handle mungedAddresses = nil; if (addresses) err=SuckAddresses((unsigned char ***)&mungedAddresses, (unsigned char **)addresses,true,false,false,nil); // Add this nickname. err = NewNickLow(mungedAddresses, notes, which, nickname, false, nrReplace, false); if (mungedAddresses) DisposeHandle(mungedAddresses); return (err); } /************************************************************************ * ConvertFieldValueToPString - turn a field value into a PString, * decoding Unicode while we're at it. ************************************************************************/ OSErr ConvertFieldValueToPString(CFStringRef fieldValue, Str255 pValue) { static UnicodeToTextInfo info; static TextEncoding encoding; static Boolean bInit = false; OSErr err = noErr; const UniChar *pucv; const char *puv; // Init pValue[0] = 0; if (!bInit) { encoding = DefaultEncoding(kTextEncodingMacRoman); err = CreateUnicodeToTextInfoByEncoding (encoding, &info); if (err == noErr) bInit = true; } // convert if (err == noErr) { if (fieldValue) { puv = CFStringGetCStringPtr(fieldValue, NULL); if (puv) { pValue[0] = SafeStrLen(puv); BMD(puv, pValue+1, pValue[0]); } else { pucv = CFStringGetCharactersPtr(fieldValue); if (pucv) err = ConvertFromUnicodeToPString(info, CFStringGetLength(fieldValue) * 2, pucv, pValue); } } } return (err); }