eudora-mac/osxabsync.cp

1 line
40 KiB
C++
Executable File

/* 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<ABAddressBookRef (*)(void)>
ABGetSharedAddressBook ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABGetSharedAddressBook" ));
MachOWrapper<CFArrayRef (*)(ABAddressBookRef)>
ABCopyArrayOfAllPeople ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABCopyArrayOfAllPeople" ));
MachOWrapper<CFArrayRef (*)(ABAddressBookRef)>
ABCopyArrayOfAllGroups ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABCopyArrayOfAllGroups" ));
MachOWrapper<CFArrayRef (*)(ABGroupRef)>
ABGroupCopyArrayOfAllMembers ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABGroupCopyArrayOfAllMembers" ));
MachOWrapper<CFArrayRef (*)(ABGroupRef)>
ABGroupCopyArrayOfAllSubgroups ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABGroupCopyArrayOfAllSubgroups" ));
MachOWrapper<ABPropertyType (*)(ABAddressBookRef, CFStringRef, CFStringRef)>
ABTypeOfProperty ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABTypeOfProperty" ));
MachOWrapper<unsigned (*)(ABMultiValueRef)>
ABMultiValueCount ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueCount" ));
MachOWrapper<CFTypeRef (*)(ABRecordRef, CFStringRef)>
ABRecordCopyValue ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABRecordCopyValue" ));
MachOWrapper<ABMultiValueRef (*)(ABMultiValueRef)>
ABMultiValueCreateCopy ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueCreateCopy" ));
MachOWrapper<CFTypeRef (*)(ABMultiValueRef, int)>
ABMultiValueCopyValueAtIndex ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueCopyValueAtIndex" ));
MachOWrapper<int (*)(ABMultiValueRef, CFStringRef)>
ABMultiValueIndexForIdentifier ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueIndexForIdentifier" ));
MachOWrapper<CFStringRef (*)(ABMultiValueRef, int)>
ABMultiValueCopyLabelAtIndex ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueCopyLabelAtIndex" ));
MachOWrapper<CFStringRef (*)(ABMultiValueRef)>
ABMultiValueCopyPrimaryIdentifier ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABMultiValueCopyPrimaryIdentifier" ));
MachOWrapper<CFStringRef (*)(ABGroupRef, ABPersonRef, CFStringRef)>
ABGroupCopyDistributionIdentifier ( CFSTR ( "AddressBook.framework" ), CFSTR ( "ABGroupCopyDistributionIdentifier" ));
// Notifications
MachOWrapper<CFNotificationCenterRef (*)(void)>
CFNotificationCenterGetDistributedCenter ( CFSTR ( "CoreFoundation.framework" ), CFSTR ( "CFNotificationCenterGetDistributedCenter" ));
MachOWrapper<void (*)(CFNotificationCenterRef, const void *, CFNotificationCallback, CFStringRef, const void *, CFNotificationSuspensionBehavior)>
CFNotificationCenterAddObserver ( CFSTR ( "CoreFoundation.framework" ), CFSTR ( "CFNotificationCenterAddObserver" ));
MachOWrapper<void (*)(CFNotificationCenterRef center, const void *observer, CFStringRef name, const void *object)>
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);
}