5fa63c04a6
The source was always meant to be GPL v2 or later, but wasn't marked consistently as such. Copyright belongs to Patrick Ohly, with all code up to 0.7 also owned by Funambol due to a copyright transfer at that time. git-svn-id: https://zeitsenke.de/svn/SyncEvolution/trunk@739 15ad00c4-1369-45f4-8270-35d70d36bdcd
1470 lines
52 KiB
C++
1470 lines
52 KiB
C++
/*
|
|
* Copyright (C) 2007-2008 Patrick Ohly
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <memory>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <list>
|
|
using namespace std;
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef ENABLE_ADDRESSBOOK
|
|
|
|
#ifdef IPHONE
|
|
# define ABAddRecord ABCAddRecord
|
|
# define ABCopyArrayOfAllPeople ABCCopyArrayOfAllPeople
|
|
# define ABGetSharedAddressBook ABCGetSharedAddressBook
|
|
# define ABMultiValueAdd ABCMultiValueAdd
|
|
# define ABMultiValueCopyLabelAtIndex ABCMultiValueCopyLabelAtIndex
|
|
# define ABMultiValueCopyValueAtIndex ABCMultiValueCopyValueAtIndex
|
|
# define ABMultiValueCount ABCMultiValueGetCount
|
|
# define ABMultiValueCreateMutable ABCMultiValueCreateMutable
|
|
// # define ABPersonCopyImageData ABCPersonCopyImageData
|
|
# define PersonCreateWrapper(_addressbook) ABCPersonCreateNewPerson(_addressbook)
|
|
/**
|
|
* The iPhone stores photos in three (?) different sizes.
|
|
* Storing just one copy is okay, albeit a bit inefficient:
|
|
* it needs to be scaled down each time it is accessed.
|
|
*
|
|
* @todo When importing photos into the address book, create
|
|
* all three different sizes.
|
|
*/
|
|
enum {
|
|
IPHONE_PHOTO_SIZE_THUMBNAIL,
|
|
IPHONE_PHOTO_SIZE_MEDIUM,
|
|
IPHONE_PHOTO_SIZE_ORIGINAL
|
|
};
|
|
# define PersonSetImageDataWrapper(_person, _dataref) ABCPersonSetImageDataAndCropRect(_person, IPHONE_PHOTO_SIZE_THUMBNAIL, _dataref, 0,0,0,0)
|
|
# define ABRecordCopyValue ABCRecordCopyValue
|
|
# define ABRecordRemoveValue ABCRecordRemoveValue
|
|
# define ABRecordSetValue ABCRecordSetValue
|
|
# define ABRemoveRecord ABCRemoveRecord
|
|
# define ABSave ABCSave
|
|
# define kABAIMInstantProperty kABCAIMInstantProperty
|
|
# define kABAddressCityKey kABCAddressCityKey
|
|
# define kABAddressCountryKey kABCAddressCountryKey
|
|
# define kABAddressHomeLabel kABCAddressHomeLabel
|
|
# define kABAddressProperty kABCAddressProperty
|
|
# define kABAddressStateKey kABCAddressStateKey
|
|
# define kABAddressStreetKey kABCAddressStreetKey
|
|
# define kABAddressWorkLabel kABCAddressWorkLabel
|
|
# define kABAddressZIPKey kABCAddressZIPKey
|
|
# define kABAssistantLabel kABCAssistantLabel
|
|
# define kABBirthdayProperty kABCBirthdayProperty
|
|
# define kABCreationDateProperty kABCCreationDateProperty
|
|
# define kABDepartmentProperty kABCDepartmentProperty
|
|
# define kABEmailHomeLabel kABCEmailHomeLabel
|
|
# define kABEmailProperty kABCEmailProperty
|
|
# define kABEmailWorkLabel kABCEmailWorkLabel
|
|
# define kABFirstNameProperty kABCFirstNameProperty
|
|
# define kABHomePageLabel kABCHomePageLabel
|
|
/* # define kABHomePageProperty kABCHomePageProperty */
|
|
# define kABICQInstantProperty kABCICQInstantProperty
|
|
# define kABJabberHomeLabel kABCJabberHomeLabel
|
|
# define kABJabberInstantProperty kABCJabberInstantProperty
|
|
# define kABJabberWorkLabel kABCJabberWorkLabel
|
|
# define kABJobTitleProperty kABCJobTitleProperty
|
|
# define kABLastNameProperty kABCLastNameProperty
|
|
# define kABMSNInstantProperty kABCMSNInstantProperty
|
|
# define kABManagerLabel kABCManagerLabel
|
|
# define kABMiddleNameProperty kABCMiddleNameProperty
|
|
# define kABModificationDateProperty kABCModificationDateProperty
|
|
# define kABNicknameProperty kABCNicknameProperty
|
|
# define kABNoteProperty kABCNoteProperty
|
|
# define kABOrganizationProperty kABCOrganizationProperty
|
|
# define kABOtherDatesProperty kABCOtherDatesProperty
|
|
# define kABPhoneHomeFAXLabel kABCPhoneHomeFAXLabel
|
|
# define kABPhoneHomeLabel kABCPhoneHomeLabel
|
|
# define kABPhoneMainLabel kABCPhoneMainLabel
|
|
# define kABPhoneMobileLabel kABCPhoneMobileLabel
|
|
# define kABPhonePagerLabel kABCPhonePagerLabel
|
|
# define kABPhoneProperty kABCPhoneProperty
|
|
# define kABPhoneWorkFAXLabel kABCPhoneWorkFAXLabel
|
|
# define kABPhoneWorkLabel kABCPhoneWorkLabel
|
|
# define kABRelatedNamesProperty kABCRelatedNamesProperty
|
|
# define kABSpouseLabel kABCSpouseLabel
|
|
# define kABSuffixProperty kABCSuffixProperty
|
|
// # define kABTitleProperty kABCTitleProperty
|
|
// # define kABURLsProperty kABCURLsProperty
|
|
# define kABYahooInstantProperty kABCYahooInstantProperty
|
|
#else
|
|
# define PersonCreateWrapper(_addressbook) ABPersonCreate()
|
|
# define PersonSetImageDataWrapper(_person, _dataref) ABPersonSetImageData(_person, _dataref)
|
|
#endif
|
|
#include "EvolutionSyncClient.h"
|
|
#include "AddressBookSource.h"
|
|
|
|
#include <common/base/Log.h>
|
|
#include <common/base/util/StringBuffer.h>
|
|
#include "vocl/VConverter.h"
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
|
|
using namespace vocl;
|
|
|
|
/** converts a CFString to std::string in UTF-8 - does not free input, throws exception if conversion impossible */
|
|
static string CFString2Std(CFStringRef cfstring)
|
|
{
|
|
const char *str = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8);
|
|
if (str) {
|
|
return string(str);
|
|
}
|
|
|
|
CFIndex len = CFStringGetLength(cfstring) * 2 + 1;
|
|
for (int tries = 0; tries < 3; tries++) {
|
|
arrayptr<char> buf(new char[len], "buffer");
|
|
if (CFStringGetCString(cfstring, buf, len, kCFStringEncodingUTF8)) {
|
|
return string((char *)buf);
|
|
}
|
|
len *= 2;
|
|
}
|
|
EvolutionSyncClient::throwError("converting CF string failed");
|
|
}
|
|
|
|
/** converts a string in UTF-8 into a CFString - throws an exception if no valid reference can be generated */
|
|
static CFStringRef Std2CFString(const string &str)
|
|
{
|
|
ref<CFStringRef> cfstring(CFStringCreateWithCString(NULL, str.c_str(), kCFStringEncodingUTF8), "conversion from CFString");
|
|
return cfstring.release();
|
|
}
|
|
|
|
/** generic label for 'other' items in a multi-value list */
|
|
static const CFStringRef otherLabel(CFSTR("_$!<Other>!$_"));
|
|
/** generic label for 'work' items in a multi-value list */
|
|
static const CFStringRef workLabel(CFSTR("_$!<Work>!$_"));
|
|
/** custom label used for "TEL;PREF;WORK" */
|
|
static const CFStringRef mainWorkLabel(CFSTR("main work"));
|
|
|
|
#ifdef IPHONE
|
|
|
|
/** declarations and functions which are missing in iPhone framework */
|
|
extern "C" {
|
|
extern const CFStringRef kABCHomePageProperty;
|
|
extern const CFStringRef kABCURLProperty;
|
|
|
|
ABPersonRef ABCPersonCreateNewPerson(ABAddressBookRef addressbook);
|
|
|
|
ABRecordRef ABCPersonGetRecordForUniqueID(ABAddressBookRef addressBook, SInt32 uid);
|
|
ABRecordRef ABCopyRecordForUniqueId(ABAddressBookRef addressBook, CFStringRef uniqueId) {
|
|
SInt32 uid = CFStringGetIntValue(uniqueId);
|
|
return ABCPersonGetRecordForUniqueID(addressBook, uid);
|
|
}
|
|
|
|
SInt32 ABCRecordGetUniqueId(ABRecordRef record);
|
|
CFStringRef ABRecordCopyUniqueId(ABRecordRef record) {
|
|
SInt32 uid = ABCRecordGetUniqueId(record);
|
|
return CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), uid);
|
|
}
|
|
|
|
CFDataRef ABCPersonCopyImageData(ABPersonRef person, int format);
|
|
bool ABCPersonSetImageData(ABPersonRef person, int format, CFDataRef data);
|
|
bool ABCPersonSetImageDataAndCropRect(ABPersonRef person, int format, CFDataRef data, int crop_x, int crop_y, int crop_width, int crop_height);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
/**
|
|
* a strtok_r() which does no skip delimiters at the start and end and does
|
|
* not merge consecutive delimiters, i.e. returned string may be empty
|
|
*
|
|
* @return NULL if no further tokens
|
|
*/
|
|
static char *my_strtok_r(char *buffer, char delim, char **ptr, char **endptr)
|
|
{
|
|
char *res;
|
|
|
|
if (buffer) {
|
|
*ptr = buffer;
|
|
*endptr = buffer + strlen(buffer);
|
|
}
|
|
res = *ptr;
|
|
if (res == *endptr) {
|
|
return NULL;
|
|
}
|
|
|
|
while (**ptr) {
|
|
if (**ptr == delim) {
|
|
**ptr = 0;
|
|
(*ptr)++;
|
|
break;
|
|
}
|
|
(*ptr)++;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/** converts between vCard and ABPerson and back */
|
|
class vCard2ABPerson {
|
|
public:
|
|
vCard2ABPerson(string &vcard, ABPersonRef person) :
|
|
m_vcard(vcard),
|
|
m_person(person) {
|
|
}
|
|
|
|
/** parses vcard and stores result in person */
|
|
void toPerson() {
|
|
std::auto_ptr<VObject> vobj(VConverter::parse((char *)m_vcard.c_str()));
|
|
if (vobj.get() == 0) {
|
|
throwError("parsing contact");
|
|
}
|
|
vobj->toNativeEncoding();
|
|
|
|
// Remove all properties from person that we might set:
|
|
// those still found in the vCard will be recreated.
|
|
// Properties that we do not support are left untouched.
|
|
for (int mapindex = 0;
|
|
m_mapping[mapindex].m_vCardProp;
|
|
mapindex++) {
|
|
const mapping &map = m_mapping[mapindex];
|
|
if (map.m_abPersonProp) {
|
|
if (!ABRecordRemoveValue(m_person, *map.m_abPersonProp)) {
|
|
throwError("removing old value "
|
|
#ifndef IPHONE
|
|
+ CFString2Std(*map.m_abPersonProp) + " " +
|
|
#endif
|
|
"failed");
|
|
}
|
|
}
|
|
}
|
|
for (int multi = 0; multi < MAX_MULTIVALUE; multi++) {
|
|
if (!ABRecordRemoveValue(m_person, *m_multiProp[multi])) {
|
|
throwError(string("removing old value ")
|
|
#ifndef IPHONE
|
|
+ CFString2Std(*m_multiProp[multi]) + " "
|
|
#endif
|
|
+ "failed");
|
|
}
|
|
}
|
|
|
|
// walk through all properties and handle them
|
|
int propindex = 0;
|
|
VProperty *vprop;
|
|
while ((vprop = vobj->getProperty(propindex)) != NULL) {
|
|
for (int mapindex = 0;
|
|
m_mapping[mapindex].m_vCardProp;
|
|
mapindex++) {
|
|
const mapping &map = m_mapping[mapindex];
|
|
if (!strcmp(map.m_vCardProp, vprop->getName())) {
|
|
toPerson_t handler = map.m_toPerson;
|
|
if (!handler) {
|
|
handler = &vCard2ABPerson::toPersonString;
|
|
}
|
|
(this->*handler)(map, *vprop);
|
|
break;
|
|
}
|
|
}
|
|
propindex++;
|
|
}
|
|
|
|
// now copy all those values to the person which did not map directly
|
|
for (int multi = 0; multi < MAX_MULTIVALUE; multi++) {
|
|
if (m_multi[multi]) {
|
|
setPersonProp(*m_multiProp[multi], m_multi[multi], false);
|
|
}
|
|
}
|
|
|
|
VProperty *photo = vobj->getProperty("PHOTO");
|
|
if (photo) {
|
|
int len;
|
|
arrayptr<char> decoded((char *)b64_decode(len, photo->getValue()), "photo");
|
|
ref<CFDataRef> data(CFDataCreate(NULL, (UInt8 *)(char *)decoded, len));
|
|
if (!PersonSetImageDataWrapper(m_person, data)) {
|
|
EvolutionSyncClient::throwError("cannot set photo data");
|
|
}
|
|
}
|
|
}
|
|
|
|
/** convert person into vCard 2.1 or 3.0 and store it in string */
|
|
void fromPerson(bool asVCard30) {
|
|
string tmp;
|
|
const unsigned char *text;
|
|
|
|
// VObject is so broken that it neither as a reset nor
|
|
// an assignment operator - no, I didn't write it :-/
|
|
//
|
|
// Reseting m_vobj was supposed to allow repeated calls
|
|
// to fromPerson, but this is not really necessary.
|
|
// m_vobj = VObject();
|
|
|
|
m_vobj.addProperty("BEGIN", "VCARD");
|
|
m_vobj.addProperty("VERSION", asVCard30 ? "3.0" : "2.1");
|
|
m_vobj.setVersion(asVCard30 ? "3.0" : "2.1");
|
|
|
|
// iterate over all person properties and handle them
|
|
for (int mapindex = 0;
|
|
m_mapping[mapindex].m_vCardProp;
|
|
mapindex++ ) {
|
|
const mapping &map = m_mapping[mapindex];
|
|
if (map.m_abPersonProp) {
|
|
#ifdef IPHONE
|
|
// some of the properties returned on the iPhone can neither
|
|
// be printed nor released: trying it leads to crashes, so
|
|
// avoid it
|
|
CFTypeRef value = ABRecordCopyValue(m_person, *map.m_abPersonProp);
|
|
#else
|
|
ref<CFTypeRef> value(ABRecordCopyValue(m_person, *map.m_abPersonProp));
|
|
#endif
|
|
if (value) {
|
|
fromPerson_t handler = map.m_fromPerson;
|
|
if (!handler) {
|
|
handler = &vCard2ABPerson::fromPersonString;
|
|
}
|
|
(this->*handler)(map, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add properties which did not map directly
|
|
string n;
|
|
n += m_strings[LAST_NAME];
|
|
n += VObject::SEMICOLON_REPLACEMENT;
|
|
n += m_strings[FIRST_NAME];
|
|
n += VObject::SEMICOLON_REPLACEMENT;
|
|
n += m_strings[MIDDLE_NAME];
|
|
n += VObject::SEMICOLON_REPLACEMENT;
|
|
n += m_strings[TITLE];
|
|
n += VObject::SEMICOLON_REPLACEMENT;
|
|
n += m_strings[SUFFIX];
|
|
m_vobj.addProperty("N", n.c_str());
|
|
|
|
if (m_strings[ORGANIZATION].size() ||
|
|
m_strings[DEPARTMENT].size() ) {
|
|
string org;
|
|
org += m_strings[ORGANIZATION];
|
|
org += VObject::SEMICOLON_REPLACEMENT;
|
|
org += m_strings[DEPARTMENT];
|
|
m_vobj.addProperty("ORG", org.c_str());
|
|
}
|
|
|
|
ref<CFDataRef> photo;
|
|
#ifdef IPHONE
|
|
// ask for largets size first
|
|
for(int format = IPHONE_PHOTO_SIZE_ORIGINAL; format >= 0; format--) {
|
|
photo.set(ABCPersonCopyImageData(m_person, format));
|
|
if (photo) {
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
photo.set(ABPersonCopyImageData(m_person));
|
|
#endif
|
|
if (photo) {
|
|
StringBuffer encoded;
|
|
b64_encode(encoded, (void *)CFDataGetBytePtr(photo), CFDataGetLength(photo));
|
|
VProperty vprop("PHOTO");
|
|
vprop.addParameter("ENCODING", asVCard30 ? "B" : "BASE64");
|
|
vprop.setValue(encoded.c_str());
|
|
m_vobj.addProperty(&vprop);
|
|
}
|
|
|
|
m_vobj.addProperty("END", "VCARD");
|
|
m_vobj.fromNativeEncoding();
|
|
arrayptr<char> finalstr(m_vobj.toString(), "VOCL string");
|
|
m_vcard = (char *)finalstr;
|
|
}
|
|
|
|
private:
|
|
string &m_vcard;
|
|
ABPersonRef m_person;
|
|
VObject m_vobj;
|
|
|
|
void throwError(const string &error) {
|
|
EvolutionSyncClient::throwError(string("vCard<->Addressbook conversion: ") + error);
|
|
}
|
|
|
|
/** intermediate storage for strings gathered from either vcard or person */
|
|
enum {
|
|
FIRST_NAME,
|
|
MIDDLE_NAME,
|
|
LAST_NAME,
|
|
TITLE,
|
|
SUFFIX,
|
|
ORGANIZATION,
|
|
DEPARTMENT,
|
|
MAX_STRINGS
|
|
};
|
|
string m_strings[MAX_STRINGS];
|
|
|
|
/** intermediate storage for multi-value data later passed to ABPerson - keep in sync with m_multiProp */
|
|
enum {
|
|
URLS,
|
|
EMAILS,
|
|
PHONES,
|
|
#ifndef IPHONE
|
|
DATES,
|
|
AIM,
|
|
JABBER,
|
|
MSN,
|
|
YAHOO,
|
|
ICQ,
|
|
#endif
|
|
NAMES,
|
|
ADDRESSES,
|
|
MAX_MULTIVALUE
|
|
};
|
|
ref<ABMutableMultiValueRef, IPHONE_RELEASE> m_multi[MAX_MULTIVALUE];
|
|
/**
|
|
* the ABPerson property which corresponds to the m_multi array:
|
|
* a pointer because the tool chain for the iPhone did not properly
|
|
* handle the constants when referenced in data initialization directly
|
|
*/
|
|
static const CFStringRef *m_multiProp[MAX_MULTIVALUE];
|
|
|
|
struct mapping;
|
|
/** member function which handles one specific vCard property */
|
|
typedef void (vCard2ABPerson::*toPerson_t)(const mapping &map, VProperty &vprop);
|
|
/** member function which handles one specific ABPerson property */
|
|
typedef void (vCard2ABPerson::*fromPerson_t)(const mapping &map, CFTypeRef cftype);
|
|
|
|
/** store a string in the ABPerson */
|
|
void setPersonProp(CFStringRef property, const string &str) {
|
|
ref<CFStringRef> cfstring(Std2CFString(str));
|
|
setPersonProp(property, cfstring);
|
|
}
|
|
/** store a string in the ABPerson */
|
|
void setPersonProp(CFStringRef property, const char *str) {
|
|
ref<CFStringRef> cfstring(Std2CFString(str));
|
|
setPersonProp(property, cfstring);
|
|
}
|
|
/**
|
|
* store a generic property in the ABPerson
|
|
* @param dump avoid CFCopyDescription() for some properties (iPhone bug)
|
|
*/
|
|
void setPersonProp(CFStringRef property, CFTypeRef cftype, bool dump = true) {
|
|
ref<CFStringRef> descr;
|
|
if (dump) {
|
|
descr.set(CFCopyDescription(cftype));
|
|
}
|
|
if (!ABRecordSetValue(m_person, property, cftype)) {
|
|
if (dump) {
|
|
throwError(string("setting ") +
|
|
#ifndef IPHONE
|
|
CFString2Std(property) +
|
|
#else
|
|
"property " +
|
|
#endif
|
|
+ " to " + CFString2Std(descr) + "'");
|
|
} else {
|
|
throwError(string("setting ") +
|
|
#ifndef IPHONE
|
|
CFString2Std(property)
|
|
#else
|
|
"property"
|
|
#endif
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** add another label/value pair to a multi-value list */
|
|
void toPersonMultiVal(const mapping &map, CFStringRef label, CFTypeRef value) {
|
|
if (!m_multi[map.m_customInt]) {
|
|
m_multi[map.m_customInt].set(ABMultiValueCreateMutable(), "multivalue");
|
|
}
|
|
CFStringRef res;
|
|
if (!ABMultiValueAdd(m_multi[map.m_customInt],
|
|
value,
|
|
label,
|
|
&res)) {
|
|
throwError(string("adding multi value for ") + map.m_vCardProp);
|
|
} else {
|
|
#ifndef IPHONE
|
|
CFRelease(res);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
* mapping between vCard and ABPerson properties
|
|
*/
|
|
static const struct mapping {
|
|
/** the name of the vCard property, e.g. "ADDR", NULL terminates array */
|
|
const char *m_vCardProp;
|
|
/** address of ABPerson property, NULL pointer if none matches directly */
|
|
const CFStringRef *m_abPersonProp;
|
|
/** called when the property is found in the VObject: default is to copy string */
|
|
toPerson_t m_toPerson;
|
|
/** called when the property is found in the ABPerson: default is to copy string */
|
|
fromPerson_t m_fromPerson;
|
|
/** custom value available to callbacks */
|
|
int m_customInt;
|
|
/** custom value available to callbacks */
|
|
CFStringRef m_customString;
|
|
} m_mapping[];
|
|
|
|
/** copy normal string directly */
|
|
void fromPersonString(const mapping &map, CFTypeRef cftype) {
|
|
string value(CFString2Std((CFStringRef)cftype));
|
|
m_vobj.addProperty(map.m_vCardProp, value.c_str());
|
|
}
|
|
|
|
/** copy normal string directly */
|
|
void toPersonString(const mapping &map, VProperty &vprop) {
|
|
const char *value = vprop.getValue();
|
|
/*
|
|
* Empty strings are not properly ignored by the iPhone GUI,
|
|
* better not add empty string properties. Empty vcard
|
|
* properties as an indication that the property is to be
|
|
* cleared are still handled because all known properties
|
|
* were removed in toPerson().
|
|
*/
|
|
if (value && *value) {
|
|
setPersonProp(*map.m_abPersonProp, value);
|
|
}
|
|
}
|
|
|
|
/** remember string to compose a more complex vCard property later (e.g. "N") */
|
|
void fromPersonStoreString(const mapping &map, CFTypeRef cftype) {
|
|
m_strings[map.m_customInt] = CFString2Std((CFStringRef)cftype);
|
|
}
|
|
|
|
/**
|
|
* add a generic string with a predefined label
|
|
* (map.m_customString) or a work/home label to multi-value
|
|
*/
|
|
void toPersonStore(const mapping &map, VProperty &vprop) {
|
|
const char *value = vprop.getValue();
|
|
if (!value || !value[0]) {
|
|
return;
|
|
}
|
|
ref<CFStringRef> cfstring(Std2CFString(value));
|
|
CFStringRef label = map.m_customString;
|
|
if (!label) {
|
|
// IM property: label depends on type;
|
|
// same simplification as in fromPersonChat
|
|
if (map.m_customString) {
|
|
label = map.m_customString;
|
|
} else if (vprop.isType("HOME")) {
|
|
label = kABJabberHomeLabel;
|
|
} else if (vprop.isType("WORK")) {
|
|
label = kABJabberHomeLabel;
|
|
} else {
|
|
label = otherLabel;
|
|
}
|
|
}
|
|
|
|
toPersonMultiVal(map, label, cfstring);
|
|
}
|
|
|
|
/** copy date */
|
|
void fromPersonDate(const mapping &map, CFTypeRef cftype) {
|
|
ref<CFTimeZoneRef> tz(CFTimeZoneCopyDefault());
|
|
CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(CFDateGetAbsoluteTime((CFDateRef)cftype), tz);
|
|
char buffer[40];
|
|
sprintf(buffer, "%04d-%02d-%02d", date.year, date.month, date.day);
|
|
m_vobj.addProperty(map.m_vCardProp, buffer);
|
|
}
|
|
|
|
/** copy date */
|
|
void toPersonDate(const mapping &map, VProperty &vprop) {
|
|
int year, month, day;
|
|
const char *value = vprop.getValue();
|
|
if (!value || !value[0]) {
|
|
return;
|
|
}
|
|
if (sscanf(value, "%d-%d-%d", &year, &month, &day) == 3) {
|
|
CFGregorianDate date;
|
|
memset(&date, 0, sizeof(date));
|
|
date.year = year;
|
|
date.month = month;
|
|
date.day = day;
|
|
|
|
/*
|
|
* The iPhone stores absolute times for dates, but
|
|
* interprets them according to the current time zone.
|
|
* The effect is that a birthday changes as the system
|
|
* timezone is changed.
|
|
*
|
|
* To mitigate this problem dates are created with
|
|
* an absolute time in the current time zone, just like
|
|
* the iPhone GUI does.
|
|
*/
|
|
ref<CFTimeZoneRef> tz(CFTimeZoneCopyDefault());
|
|
ref<CFDateRef> cfdate(CFDateCreate(NULL, CFGregorianDateGetAbsoluteTime(date, tz)));
|
|
if (cfdate) {
|
|
// assert(map.m_abPersonProp);
|
|
setPersonProp(*map.m_abPersonProp, cfdate);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** map URL multi-value to vCard URL with different TYPEs */
|
|
void fromPersonURLs(const mapping &map, CFTypeRef cftype) {
|
|
int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1;
|
|
while (index >= 0) {
|
|
ref<CFStringRef> label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label");
|
|
ref<CFStringRef> value((CFStringRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value");
|
|
|
|
VProperty vprop("URL");
|
|
string url = CFString2Std(value);
|
|
vprop.setValue(url.c_str());
|
|
if (CFStringCompare(label, (CFStringRef)kABHomePageLabel, 0) == kCFCompareEqualTo) {
|
|
// leave type blank
|
|
} else if (CFStringCompare(label, (CFStringRef)workLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "WORK");
|
|
} else if (CFStringCompare(label, otherLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "OTHER");
|
|
} else {
|
|
string labelstr = CFString2Std(label);
|
|
vprop.addParameter("TYPE", labelstr.c_str());
|
|
}
|
|
m_vobj.addProperty(&vprop);
|
|
|
|
index--;
|
|
}
|
|
}
|
|
|
|
/** iPhone: add another URL to multi-value (Mac OS X only has one string property) */
|
|
void toPersonURLs(const mapping &map, VProperty &vprop) {
|
|
const char *value = vprop.getValue();
|
|
if (!value || !value[0]) {
|
|
return;
|
|
}
|
|
arrayptr<char> buffer(wstrdup(value));
|
|
char *saveptr, *endptr;
|
|
|
|
ref<CFStringRef> cfvalue(Std2CFString(value));
|
|
CFStringRef label;
|
|
ref<CFStringRef> custom;
|
|
const char *type = vprop.getParameterValue("TYPE");
|
|
if (vprop.isType("WORK")) {
|
|
label = workLabel;
|
|
} else if(vprop.isType("HOME")) {
|
|
label = (CFStringRef)kABHomePageLabel;
|
|
} else if(vprop.isType("OTHER")) {
|
|
label = otherLabel;
|
|
} else if (type) {
|
|
custom.set(Std2CFString(type));
|
|
label = custom;
|
|
} else {
|
|
label = (CFStringRef)kABHomePageLabel;
|
|
}
|
|
toPersonMultiVal(map, label, cfvalue);
|
|
}
|
|
|
|
/** map email multi-value to vCard EMAIL with different TYPEs */
|
|
void fromPersonEMail(const mapping &map, CFTypeRef cftype) {
|
|
int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1;
|
|
while (index >= 0) {
|
|
ref<CFStringRef> label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label");
|
|
ref<CFStringRef> value((CFStringRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value");
|
|
VProperty vprop("EMAIL");
|
|
|
|
if (CFStringCompare(label, kABEmailWorkLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "WORK");
|
|
} else if (CFStringCompare(label, kABEmailHomeLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "HOME");
|
|
} else {
|
|
string labelstr = CFString2Std(label);
|
|
vprop.addParameter("TYPE", labelstr.c_str());
|
|
}
|
|
|
|
string email = CFString2Std(value);
|
|
vprop.setValue(email.c_str());
|
|
m_vobj.addProperty(&vprop);
|
|
|
|
index--;
|
|
}
|
|
}
|
|
|
|
/** add another EMAIL to the email multi-value */
|
|
void toPersonEMail(const mapping &map, VProperty &vprop) {
|
|
const char *value = vprop.getValue();
|
|
if (!value || !value[0]) {
|
|
return;
|
|
}
|
|
arrayptr<char> buffer(wstrdup(value));
|
|
char *saveptr, *endptr;
|
|
|
|
ref<CFStringRef> cfvalue(Std2CFString(value));
|
|
CFStringRef label;
|
|
ref<CFStringRef> custom;
|
|
const char *type = vprop.getParameterValue("TYPE");
|
|
if (vprop.isType("WORK")) {
|
|
label = kABEmailWorkLabel;
|
|
} else if(vprop.isType("HOME")) {
|
|
label = kABEmailHomeLabel;
|
|
} else if (type) {
|
|
custom.set(Std2CFString(type));
|
|
label = custom;
|
|
} else {
|
|
label = otherLabel;
|
|
}
|
|
|
|
toPersonMultiVal(map, label, cfvalue);
|
|
}
|
|
|
|
/** map address multi-value to vCard ADR with different TYPEs */
|
|
void fromPersonAddr(const mapping &map, CFTypeRef cftype) {
|
|
int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1;
|
|
while (index >= 0) {
|
|
ref<CFStringRef> label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label");
|
|
ref<CFDictionaryRef> value((CFDictionaryRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value");
|
|
CFStringRef part;
|
|
VProperty vprop((char *)map.m_vCardProp);
|
|
|
|
string adr;
|
|
// no PO box
|
|
adr += VObject::SEMICOLON_REPLACEMENT;
|
|
// no extended address
|
|
adr += VObject::SEMICOLON_REPLACEMENT;
|
|
// street
|
|
part = (CFStringRef)CFDictionaryGetValue(value, kABAddressStreetKey);
|
|
if (part) {
|
|
adr += CFString2Std(part);
|
|
}
|
|
adr += VObject::SEMICOLON_REPLACEMENT;
|
|
// city
|
|
part = (CFStringRef)CFDictionaryGetValue(value, kABAddressCityKey);
|
|
if (part) {
|
|
adr += CFString2Std(part);
|
|
}
|
|
adr += VObject::SEMICOLON_REPLACEMENT;
|
|
// region
|
|
part = (CFStringRef)CFDictionaryGetValue(value, kABAddressStateKey);
|
|
if (part) {
|
|
adr += CFString2Std(part);
|
|
}
|
|
adr += VObject::SEMICOLON_REPLACEMENT;
|
|
// ZIP code
|
|
part = (CFStringRef)CFDictionaryGetValue(value, kABAddressZIPKey);
|
|
if (part) {
|
|
adr += CFString2Std(part);
|
|
}
|
|
adr += VObject::SEMICOLON_REPLACEMENT;
|
|
// country
|
|
part = (CFStringRef)CFDictionaryGetValue(value, kABAddressCountryKey);
|
|
if (part) {
|
|
adr += CFString2Std(part);
|
|
}
|
|
adr += VObject::SEMICOLON_REPLACEMENT;
|
|
|
|
// not supported: kABAddressCountryCodeKey
|
|
|
|
if (CFStringCompare(label, kABAddressWorkLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "WORK");
|
|
} else if (CFStringCompare(label, kABAddressHomeLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "HOME");
|
|
}
|
|
|
|
vprop.setValue(adr.c_str());
|
|
m_vobj.addProperty(&vprop);
|
|
|
|
index--;
|
|
}
|
|
}
|
|
|
|
/** add another ADR to address multi-value */
|
|
void toPersonAddr(const mapping &map, VProperty &vprop) {
|
|
const char *value = vprop.getValue();
|
|
if (!value || !value[0]) {
|
|
return;
|
|
}
|
|
arrayptr<char> buffer(wstrdup(value));
|
|
char *saveptr, *endptr;
|
|
|
|
ref<CFMutableDictionaryRef> dict(CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
|
|
|
|
// cannot store PO box and extended address
|
|
char *pobox = my_strtok_r(buffer, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
char *extadr = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
|
|
char *street = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
if (street && *street) {
|
|
ref<CFStringRef> cfstring(Std2CFString(street));
|
|
CFDictionarySetValue(dict, kABAddressStreetKey, cfstring);
|
|
}
|
|
char *city = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
if (city && *city) {
|
|
ref<CFStringRef> cfstring(Std2CFString(city));
|
|
CFDictionarySetValue(dict, kABAddressCityKey, cfstring);
|
|
}
|
|
char *region = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
if (region && *region) {
|
|
ref<CFStringRef> cfstring(Std2CFString(region));
|
|
CFDictionarySetValue(dict, kABAddressStateKey, cfstring);
|
|
}
|
|
char *zip = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
if (zip && *zip) {
|
|
ref<CFStringRef> cfstring(Std2CFString(zip));
|
|
CFDictionarySetValue(dict, kABAddressZIPKey, cfstring);
|
|
}
|
|
char *country = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
if (country && *country) {
|
|
ref<CFStringRef> cfstring(Std2CFString(country));
|
|
CFDictionarySetValue(dict, kABAddressCountryKey, cfstring);
|
|
}
|
|
|
|
CFStringRef label;
|
|
if (vprop.isType("WORK")) {
|
|
label = kABAddressWorkLabel;
|
|
} else if(vprop.isType("HOME")) {
|
|
label = kABAddressHomeLabel;
|
|
} else {
|
|
label = otherLabel;
|
|
}
|
|
|
|
toPersonMultiVal(map, label, dict);
|
|
}
|
|
|
|
/** map phone multi-value to vCard TEL with different TYPEs */
|
|
void fromPersonPhone(const mapping &map, CFTypeRef cftype) {
|
|
int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1;
|
|
while (index >= 0) {
|
|
ref<CFStringRef> label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label");
|
|
ref<CFStringRef> value((CFStringRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value");
|
|
VProperty vprop("TEL");
|
|
|
|
if (CFStringCompare(label, kABPhoneWorkLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "WORK");
|
|
vprop.addParameter("TYPE", "VOICE");
|
|
} else if (CFStringCompare(label, mainWorkLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "WORK");
|
|
vprop.addParameter("TYPE", "PREF");
|
|
} else if (CFStringCompare(label, kABPhoneHomeLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "HOME");
|
|
vprop.addParameter("TYPE", "VOICE");
|
|
} else if (CFStringCompare(label, kABPhoneMobileLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "CELL");
|
|
} else if (CFStringCompare(label, kABPhoneMainLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "PREF");
|
|
vprop.addParameter("TYPE", "VOICE");
|
|
} else if (CFStringCompare(label, kABPhoneHomeFAXLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "HOME");
|
|
vprop.addParameter("TYPE", "FAX");
|
|
} else if (CFStringCompare(label, kABPhoneWorkFAXLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "WORK");
|
|
vprop.addParameter("TYPE", "FAX");
|
|
} else if (CFStringCompare(label,kABPhonePagerLabel , 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "PAGER");
|
|
} else {
|
|
// custom phone types not supported
|
|
vprop.addParameter("TYPE", "VOICE");
|
|
}
|
|
|
|
string phone = CFString2Std(value);
|
|
vprop.setValue(phone.c_str());
|
|
m_vobj.addProperty(&vprop);
|
|
|
|
index--;
|
|
}
|
|
}
|
|
|
|
/** add another phone to the multi-value */
|
|
void toPersonPhone(const mapping &map, VProperty &vprop) {
|
|
const char *value = vprop.getValue();
|
|
if (!value || !value[0]) {
|
|
return;
|
|
}
|
|
arrayptr<char> buffer(wstrdup(value));
|
|
char *saveptr, *endptr;
|
|
|
|
ref<CFStringRef> cfvalue(Std2CFString(value));
|
|
CFStringRef label;
|
|
if (vprop.isType("WORK")) {
|
|
if (vprop.isType("FAX")) {
|
|
label = kABPhoneWorkFAXLabel;
|
|
} else if (vprop.isType("PREF")) {
|
|
label = mainWorkLabel;
|
|
} else {
|
|
label = kABPhoneWorkLabel;
|
|
}
|
|
} else if(vprop.isType("HOME")) {
|
|
if (vprop.isType("FAX")) {
|
|
label = kABPhoneHomeFAXLabel;
|
|
} else {
|
|
label = kABPhoneHomeLabel;
|
|
}
|
|
} else if(vprop.isType("PREF") || vprop.isType("VOICE")) {
|
|
label = kABPhoneMainLabel;
|
|
} else if(vprop.isType("PAGER")) {
|
|
label = kABPhonePagerLabel;
|
|
} else if(vprop.isType("CELL")) {
|
|
label = kABPhoneMobileLabel;
|
|
} else {
|
|
label = otherLabel;
|
|
}
|
|
|
|
toPersonMultiVal(map, label, cfvalue);
|
|
}
|
|
|
|
/**
|
|
* map chat contact multi-value to respective vCard X- properties
|
|
*
|
|
* complementary operation is toPersonStore()
|
|
*/
|
|
void fromPersonChat(const mapping &map, CFTypeRef cftype) {
|
|
int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1;
|
|
while (index >= 0) {
|
|
ref<CFStringRef> label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label");
|
|
ref<CFStringRef> value((CFStringRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value");
|
|
VProperty vprop((char *)map.m_vCardProp);
|
|
|
|
// this is a slight over-simplification:
|
|
// the assumption is that the labels for all IM properties are interchangeable
|
|
// although the header file has different constants for them
|
|
if (CFStringCompare(label, kABJabberWorkLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "WORK");
|
|
} else if (CFStringCompare(label, kABJabberHomeLabel, 0) == kCFCompareEqualTo) {
|
|
vprop.addParameter("TYPE", "HOME");
|
|
} else {
|
|
// custom IM types not supported
|
|
}
|
|
|
|
string im = CFString2Std(value);
|
|
vprop.setValue(im.c_str());
|
|
m_vobj.addProperty(&vprop);
|
|
|
|
index--;
|
|
}
|
|
}
|
|
|
|
/** map related names multi-value to some vCard extension properties */
|
|
void fromPersonNames(const mapping &map, CFTypeRef cftype) {
|
|
int index = ABMultiValueCount((ABMultiValueRef)cftype) - 1;
|
|
while (index >= 0) {
|
|
ref<CFStringRef> label((CFStringRef)ABMultiValueCopyLabelAtIndex((ABMultiValueRef)cftype, index), "label");
|
|
ref<CFStringRef> value((CFStringRef)ABMultiValueCopyValueAtIndex((ABMultiValueRef)cftype, index), "value");
|
|
string name = CFString2Std(value);
|
|
|
|
// there are no standard fields for all these related names:
|
|
// use the ones from Evolution because some SyncML servers have
|
|
// been extended to support them
|
|
if (CFStringCompare(label, kABManagerLabel, 0) == kCFCompareEqualTo) {
|
|
m_vobj.addProperty("X-EVOLUTION-MANAGER", name.c_str());
|
|
} else if (CFStringCompare(label, kABAssistantLabel, 0) == kCFCompareEqualTo) {
|
|
m_vobj.addProperty("X-EVOLUTION-ASSISTANT", name.c_str());
|
|
} else if (CFStringCompare(label, kABSpouseLabel, 0) == kCFCompareEqualTo) {
|
|
m_vobj.addProperty("X-EVOLUTION-SPOUSE", name.c_str());
|
|
} else {
|
|
// many related names not supported
|
|
}
|
|
|
|
index--;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* decode vCard N and store in person properties
|
|
*
|
|
* complementary operation is fromPersonStoreString()
|
|
*/
|
|
void toPersonName(const mapping &map, VProperty &vprop) {
|
|
const char *value = vprop.getValue();
|
|
if (!value || !value[0]) {
|
|
return;
|
|
}
|
|
arrayptr<char> buffer(wstrdup(value));
|
|
char *saveptr, *endptr;
|
|
|
|
char *last = my_strtok_r(buffer, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
if (last && *last) {
|
|
setPersonProp(kABLastNameProperty, last);
|
|
}
|
|
|
|
char *first = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
if (first && *first) {
|
|
setPersonProp(kABFirstNameProperty, first);
|
|
}
|
|
|
|
char *middle = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
if (middle && *middle) {
|
|
setPersonProp(kABMiddleNameProperty, middle);
|
|
}
|
|
|
|
char *prefix = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
#ifndef IPHONE
|
|
if (prefix && *prefix) {
|
|
setPersonProp(kABTitleProperty, prefix);
|
|
}
|
|
#endif
|
|
|
|
char *suffix = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
if (suffix && *suffix) {
|
|
setPersonProp(kABSuffixProperty, suffix);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* decode ORG and store in person properties
|
|
*
|
|
* complementary operation is fromPersonStoreString()
|
|
*/
|
|
void toPersonOrg(const mapping &map, VProperty &vprop) {
|
|
const char *value = vprop.getValue();
|
|
if (!value || !value[0]) {
|
|
return;
|
|
}
|
|
arrayptr<char> buffer(wstrdup(value));
|
|
char *saveptr, *endptr;
|
|
|
|
char *company = my_strtok_r(buffer, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
if (company && *company) {
|
|
setPersonProp(kABOrganizationProperty, company);
|
|
}
|
|
|
|
char *department = my_strtok_r(NULL, VObject::SEMICOLON_REPLACEMENT, &saveptr, &endptr);
|
|
if (department && *department) {
|
|
setPersonProp(kABDepartmentProperty, department);
|
|
}
|
|
}
|
|
};
|
|
|
|
const CFStringRef *vCard2ABPerson::m_multiProp[MAX_MULTIVALUE] = {
|
|
#ifdef IPHONE
|
|
&kABCURLProperty,
|
|
#else
|
|
(CFStringRef*)&kABURLsProperty,
|
|
#endif
|
|
&kABEmailProperty,
|
|
&kABPhoneProperty,
|
|
#ifndef IPHONE
|
|
&kABOtherDatesProperty,
|
|
&kABAIMInstantProperty,
|
|
&kABJabberInstantProperty,
|
|
&kABMSNInstantProperty,
|
|
&kABYahooInstantProperty,
|
|
&kABICQInstantProperty,
|
|
#endif
|
|
&kABRelatedNamesProperty,
|
|
&kABAddressProperty
|
|
};
|
|
|
|
const vCard2ABPerson::mapping vCard2ABPerson::m_mapping[] = {
|
|
{ "", &kABFirstNameProperty, NULL, &vCard2ABPerson::fromPersonStoreString, FIRST_NAME },
|
|
{ "", &kABLastNameProperty, NULL, &vCard2ABPerson::fromPersonStoreString, LAST_NAME },
|
|
{ "", &kABMiddleNameProperty, NULL, &vCard2ABPerson::fromPersonStoreString, MIDDLE_NAME },
|
|
#ifndef IPHONE
|
|
{ "", &kABTitleProperty, NULL, &vCard2ABPerson::fromPersonStoreString, TITLE },
|
|
#endif
|
|
{ "", &kABSuffixProperty, NULL, &vCard2ABPerson::fromPersonStoreString, SUFFIX },
|
|
{ "N", 0, &vCard2ABPerson::toPersonName },
|
|
/* "FN" */
|
|
/* kABFirstNamePhoneticProperty */
|
|
/* kABLastNamePhoneticProperty */
|
|
/* kABMiddleNamePhoneticProperty */
|
|
{ "BDAY", &kABBirthdayProperty, &vCard2ABPerson::toPersonDate, &vCard2ABPerson::fromPersonDate },
|
|
|
|
{ "", &kABOrganizationProperty, NULL, &vCard2ABPerson::fromPersonStoreString, ORGANIZATION },
|
|
{ "", &kABDepartmentProperty, NULL, &vCard2ABPerson::fromPersonStoreString, DEPARTMENT },
|
|
{ "ORG", 0, &vCard2ABPerson::toPersonOrg },
|
|
|
|
{ "TITLE", &kABJobTitleProperty },
|
|
/* "ROLE" */
|
|
|
|
#ifdef IPHONE
|
|
{ "URL", &kABCURLProperty, &vCard2ABPerson::toPersonURLs, &vCard2ABPerson::fromPersonURLs, URLS },
|
|
#else
|
|
/**
|
|
* bug in the header files for kABHomePageProperty and kABURLsProperty,
|
|
* typecast required
|
|
*/
|
|
{ "URL", (CFStringRef *)&kABHomePageProperty },
|
|
{ "", (CFStringRef *)&kABURLsProperty, NULL, &vCard2ABPerson::fromPersonURLs },
|
|
#endif
|
|
#if 0
|
|
kABHomePageLabel
|
|
#endif
|
|
|
|
{ "EMAIL", &kABEmailProperty, &vCard2ABPerson::toPersonEMail, &vCard2ABPerson::fromPersonEMail, EMAILS },
|
|
#if 0
|
|
kABEmailWorkLabel
|
|
kABEmailHomeLabel
|
|
#endif
|
|
|
|
{ "ADR", &kABAddressProperty, &vCard2ABPerson::toPersonAddr, &vCard2ABPerson::fromPersonAddr, ADDRESSES },
|
|
#if 0
|
|
kABAddressWorkLabel
|
|
kABAddressHomeLabel
|
|
|
|
kABAddressStreetKey
|
|
kABAddressCityKey
|
|
kABAddressStateKey
|
|
kABAddressZIPKey
|
|
kABAddressCountryKey
|
|
kABAddressCountryCodeKey
|
|
#endif
|
|
/* LABEL */
|
|
|
|
{ "TEL", &kABPhoneProperty, &vCard2ABPerson::toPersonPhone, &vCard2ABPerson::fromPersonPhone, PHONES },
|
|
|
|
#if 0
|
|
kABPhoneWorkLabel
|
|
kABPhoneHomeLabel
|
|
kABPhoneMobileLabel
|
|
kABPhoneMainLabel
|
|
kABPhoneHomeFAXLabel
|
|
kABPhoneWorkFAXLabel
|
|
kABPhonePagerLabel
|
|
#endif
|
|
#ifndef IPHONE
|
|
{ "X-AIM", &kABAIMInstantProperty, &vCard2ABPerson::toPersonStore, &vCard2ABPerson::fromPersonChat, AIM },
|
|
{ "X-JABBER", &kABJabberInstantProperty, &vCard2ABPerson::toPersonStore, &vCard2ABPerson::fromPersonChat, JABBER },
|
|
{ "X-MSN", &kABMSNInstantProperty, &vCard2ABPerson::toPersonStore, &vCard2ABPerson::fromPersonChat, MSN },
|
|
{ "X-YAHOO", &kABYahooInstantProperty, &vCard2ABPerson::toPersonStore, &vCard2ABPerson::fromPersonChat, YAHOO },
|
|
{ "X-ICQ", &kABICQInstantProperty, &vCard2ABPerson::toPersonStore, &vCard2ABPerson::fromPersonChat, ICQ },
|
|
#endif
|
|
/* "X-GROUPWISE */
|
|
{ "NOTE", &kABNoteProperty },
|
|
{ "NICKNAME", &kABNicknameProperty },
|
|
|
|
/* kABMaidenNameProperty */
|
|
/* kABOtherDatesProperty */
|
|
#ifndef IPHONE
|
|
{ "", &kABRelatedNamesProperty, NULL, &vCard2ABPerson::fromPersonNames },
|
|
#endif
|
|
#if 0
|
|
kABMotherLabel
|
|
kABFatherLabel
|
|
kABParentLabel
|
|
kABSisterLabel
|
|
kABBrotherFAXLabel
|
|
kABChildLabel
|
|
kABFriendLabel
|
|
kABSpouseLabel
|
|
kABPartnerLabel
|
|
kABAssistantLabel
|
|
kABManagerLabel
|
|
#endif
|
|
{ "X-EVOLUTION-MANAGER", 0, &vCard2ABPerson::toPersonStore, NULL, NAMES, kABManagerLabel },
|
|
{ "X-EVOLUTION-ASSISTANT", 0, &vCard2ABPerson::toPersonStore, NULL, NAMES, kABAssistantLabel },
|
|
{ "X-EVOLUTION-SPOUSE", 0, &vCard2ABPerson::toPersonStore, NULL, NAMES, kABSpouseLabel },
|
|
|
|
/* kABPersonFlags */
|
|
/* X-EVOLUTION-FILE-AS */
|
|
/* CATEGORIES */
|
|
/* CALURI */
|
|
/* FBURL */
|
|
/* X-EVOLUTION-VIDEO-URL */
|
|
/* X-MOZILLA-HTML */
|
|
/* X-EVOLUTION-ANNIVERSARY */
|
|
|
|
{ NULL }
|
|
};
|
|
|
|
|
|
string AddressBookSource::getModTime(ABRecordRef record)
|
|
{
|
|
double absolute;
|
|
#ifdef IPHONE
|
|
absolute = (double)(int)ABRecordCopyValue(record,
|
|
kABModificationDateProperty);
|
|
#else
|
|
ref<CFDateRef> itemModTime((CFDateRef)ABRecordCopyValue(record,
|
|
kABModificationDateProperty));
|
|
if (!itemModTime) {
|
|
itemModTime.set((CFDateRef)ABRecordCopyValue(record,
|
|
kABCreationDateProperty));
|
|
}
|
|
if (!itemModTime) {
|
|
throwError("extracting time stamp");
|
|
}
|
|
absolute = CFDateGetAbsoluteTime(itemModTime);
|
|
#endif
|
|
|
|
// round up to next full second:
|
|
// together with a sleep of 1 second in endSyncThrow() this ensures
|
|
// that our time stamps are always >= the stored time stamp even if
|
|
// the time stamp is rounded in the database
|
|
char buffer[128];
|
|
sprintf(buffer, "%.0f", ceil(absolute));
|
|
return buffer;
|
|
}
|
|
|
|
|
|
AddressBookSource::AddressBookSource(const EvolutionSyncSourceParams ¶ms, bool asVCard30) :
|
|
TrackingSyncSource(params),
|
|
m_asVCard30(asVCard30),
|
|
m_addressbook(0)
|
|
{
|
|
}
|
|
|
|
EvolutionSyncSource::Databases AddressBookSource::getDatabases()
|
|
{
|
|
Databases result;
|
|
|
|
result.push_back(Database("<<system>>", ""));
|
|
return result;
|
|
}
|
|
|
|
void AddressBookSource::open()
|
|
{
|
|
m_addressbook = ABGetSharedAddressBook();
|
|
if (!m_addressbook) {
|
|
throwError("opening address book");
|
|
}
|
|
}
|
|
|
|
void AddressBookSource::listAllItems(RevisionMap_t &revisions)
|
|
{
|
|
ref<CFArrayRef> allPersons(ABCopyArrayOfAllPeople(m_addressbook), "list of all people");
|
|
|
|
for (CFIndex i = 0; i < CFArrayGetCount(allPersons); i++) {
|
|
ref<CFStringRef> cfuid(ABRecordCopyUniqueId((ABRecordRef)CFArrayGetValueAtIndex(allPersons, i)), "reading UID");
|
|
string uid(CFString2Std(cfuid));
|
|
|
|
revisions[uid] = getModTime((ABRecordRef)CFArrayGetValueAtIndex(allPersons, i));
|
|
}
|
|
}
|
|
|
|
void AddressBookSource::close()
|
|
{
|
|
if (m_addressbook && !hasFailed()) {
|
|
LOG.debug("flushing address book");
|
|
// store changes persistently
|
|
if (!ABSave(m_addressbook)) {
|
|
throwError("saving address book");
|
|
}
|
|
|
|
// time stamps are rounded to next second,
|
|
// so to prevent changes in that range of inaccurracy
|
|
// sleep a bit before returning control
|
|
sleep(2);
|
|
|
|
LOG.debug("done with address book");
|
|
}
|
|
|
|
m_addressbook = NULL;
|
|
}
|
|
|
|
void AddressBookSource::exportData(ostream &out)
|
|
{
|
|
ref<CFArrayRef> allPersons(ABCopyArrayOfAllPeople(m_addressbook), "list of all people");
|
|
|
|
for (CFIndex i = 0; i < CFArrayGetCount(allPersons); i++) {
|
|
ABRecordRef person = (ABRecordRef)CFArrayGetValueAtIndex(allPersons, i);
|
|
CFStringRef descr = CFCopyDescription(person);
|
|
ref<CFStringRef> cfuid(ABRecordCopyUniqueId(person), "reading UID");
|
|
string uid(CFString2Std(cfuid));
|
|
cxxptr<SyncItem> item(createItem(uid, true), "sync item");
|
|
|
|
out << (char *)item->getData() << "\n";
|
|
}
|
|
}
|
|
|
|
SyncItem *AddressBookSource::createItem(const string &uid, bool asVCard30)
|
|
{
|
|
logItem(uid, "extracting from address book", true);
|
|
|
|
ref<CFStringRef> cfuid(Std2CFString(uid));
|
|
ref<ABPersonRef> person((ABPersonRef)ABCopyRecordForUniqueId(m_addressbook, cfuid), "contact");
|
|
auto_ptr<SyncItem> item(new SyncItem(uid.c_str()));
|
|
|
|
#ifdef USE_ADDRESS_BOOK_VCARD
|
|
ref<CFDataRef> vcard(ABPersonCopyVCardRepresentation(person), "vcard");
|
|
LOG.debug("%*s", (int)CFDataGetLength(vcard), (const char *)CFDataGetBytePtr(vcard));
|
|
item->setData(CFDataGetBytePtr(vcard), CFDataGetLength(vcard));
|
|
#else
|
|
string vcard;
|
|
try {
|
|
vCard2ABPerson conv(vcard, person);
|
|
conv.fromPerson(asVCard30);
|
|
} catch (const std::exception &ex) {
|
|
throwError("creating vCard for " + uid + " failed: " + ex.what());
|
|
}
|
|
item->setData(vcard.c_str(), vcard.size());
|
|
#endif
|
|
|
|
item->setDataType(getMimeType());
|
|
item->setModificationTime(0);
|
|
|
|
return item.release();
|
|
}
|
|
|
|
AddressBookSource::InsertItemResult AddressBookSource::insertItem(const string &luid, const SyncItem &item)
|
|
{
|
|
bool update = !luid.empty();
|
|
string newluid = luid;
|
|
string data = (const char *)item.getData();
|
|
ref<ABPersonRef> person;
|
|
|
|
#ifdef USE_ADDRESS_BOOK_VCARD
|
|
if (uid) {
|
|
// overwriting the UID of a new contact failed - resort to deleting the old contact and inserting a new one
|
|
deleteItem(uid);
|
|
}
|
|
|
|
ref<CFDataRef> vcard(CFDataCreate(NULL, (const UInt8 *)data.c_str(), data.size()), "vcard");
|
|
person.set((ABPersonRef)ABPersonCreateWithVCardRepresentation(vcard));
|
|
if (!person) {
|
|
throwError(string("parsing vcard ") + data);
|
|
}
|
|
#else
|
|
if (update) {
|
|
// overwrite existing contact
|
|
ref<CFStringRef> cfuid(Std2CFString(luid));
|
|
person.set((ABPersonRef)ABCopyRecordForUniqueId(m_addressbook, cfuid), "contact");
|
|
} else {
|
|
// new contact
|
|
person.set(PersonCreateWrapper(m_addressbook), "contact");
|
|
}
|
|
try {
|
|
LOG.debug("storing vCard for %s:\n%s",
|
|
update ? luid.c_str() : "new contact",
|
|
data.c_str());
|
|
vCard2ABPerson converter(data, person);
|
|
converter.toPerson();
|
|
} catch (const std::exception &ex) {
|
|
throwError(string("storing vCard for ") + (update ? luid : "new contact") + " failed: " + ex.what());
|
|
}
|
|
#endif
|
|
|
|
|
|
// make sure we have a modification time stamp, otherwise the address book
|
|
// sets one at random times
|
|
CFAbsoluteTime nowabs = CFAbsoluteTimeGetCurrent();
|
|
#ifdef IPHONE
|
|
void *now = (void *)(int)round(nowabs);
|
|
#else
|
|
ref<CFDateRef> now(CFDateCreate(NULL, nowabs), "current time");
|
|
#endif
|
|
if (!ABRecordSetValue(person, kABModificationDateProperty, now)) {
|
|
throwError("setting mod time");
|
|
}
|
|
|
|
// existing contacts do not have to (and cannot) be added (?)
|
|
if (update || ABAddRecord(m_addressbook, person)) {
|
|
// need to save to get UID (iPhone) and final modification time (Mac OS X)?
|
|
ABSave(m_addressbook);
|
|
|
|
ref<CFStringRef> cfuid(ABRecordCopyUniqueId(person), "uid");
|
|
newluid = CFString2Std(cfuid);
|
|
} else {
|
|
throwError("storing new contact");
|
|
}
|
|
string modtime = getModTime(person);
|
|
|
|
return InsertItemResult(newluid, modtime, false);
|
|
}
|
|
|
|
void AddressBookSource::deleteItem(const string &uid)
|
|
{
|
|
ref<CFStringRef> cfuid(Std2CFString(uid.c_str()));
|
|
ref<ABPersonRef> person((ABPersonRef)ABCopyRecordForUniqueId(m_addressbook, cfuid));
|
|
|
|
if (person) {
|
|
if (!ABRemoveRecord(m_addressbook, person)) {
|
|
throwError(string("deleting contact ") + uid);
|
|
}
|
|
} else {
|
|
LOG.debug("%s: %s: request to delete non-existant contact ignored",
|
|
getName(), uid.c_str());
|
|
}
|
|
}
|
|
|
|
void AddressBookSource::logItem(const string &uid, const string &info, bool debug)
|
|
{
|
|
if (LOG.getLevel() >= (debug ? LOG_LEVEL_DEBUG : LOG_LEVEL_INFO)) {
|
|
string line;
|
|
|
|
#if 0
|
|
// TODO
|
|
|
|
if (e_book_get_contact( m_addressbook,
|
|
uid.c_str(),
|
|
&contact,
|
|
&gerror )) {
|
|
const char *fileas = (const char *)e_contact_get_const( contact, E_CONTACT_FILE_AS );
|
|
if (fileas) {
|
|
line += fileas;
|
|
} else {
|
|
const char *name = (const char *)e_contact_get_const( contact, E_CONTACT_FULL_NAME );
|
|
if (name) {
|
|
line += name;
|
|
} else {
|
|
line += "<unnamed contact>";
|
|
}
|
|
}
|
|
} else {
|
|
line += "<name unavailable>";
|
|
}
|
|
#endif
|
|
|
|
line += " (";
|
|
line += uid;
|
|
line += "): ";
|
|
line += info;
|
|
|
|
(LOG.*(debug ? &Log::debug : &Log::info))( "%s: %s", getName(), line.c_str() );
|
|
}
|
|
}
|
|
|
|
void AddressBookSource::logItem(const SyncItem &item, const string &info, bool debug)
|
|
{
|
|
if (LOG.getLevel() >= (debug ? LOG_LEVEL_DEBUG : LOG_LEVEL_INFO)) {
|
|
string line;
|
|
const char *data = (const char *)item.getData();
|
|
int datasize = item.getDataSize();
|
|
if (datasize <= 0) {
|
|
data = "";
|
|
datasize = 0;
|
|
}
|
|
string vcard( data, datasize );
|
|
|
|
size_t offset = vcard.find( "FN:");
|
|
if (offset != vcard.npos) {
|
|
int len = vcard.find( "\r", offset ) - offset - 3;
|
|
line += vcard.substr( offset + 3, len );
|
|
} else {
|
|
line += "<unnamed contact>";
|
|
}
|
|
|
|
if (!item.getKey() ) {
|
|
line += ", NULL UID (?!)";
|
|
} else if (!strlen( item.getKey() )) {
|
|
line += ", empty UID";
|
|
} else {
|
|
line += ", ";
|
|
line += item.getKey();
|
|
|
|
#if 0
|
|
// TODO
|
|
EContact *contact;
|
|
GError *gerror = NULL;
|
|
if (e_book_get_contact( m_addressbook,
|
|
item.getKey(),
|
|
&contact,
|
|
&gerror )) {
|
|
line += ", EV ";
|
|
const char *fileas = (const char *)e_contact_get_const( contact, E_CONTACT_FILE_AS );
|
|
if (fileas) {
|
|
line += fileas;
|
|
} else {
|
|
const char *name = (const char *)e_contact_get_const( contact, E_CONTACT_FULL_NAME );
|
|
if (name) {
|
|
line += name;
|
|
} else {
|
|
line += "<unnamed contact>";
|
|
}
|
|
}
|
|
} else {
|
|
line += ", not in Evolution";
|
|
}
|
|
#endif
|
|
}
|
|
line += ": ";
|
|
line += info;
|
|
|
|
(LOG.*(debug ? &Log::debug : &Log::info))( "%s: %s", getName(), line.c_str() );
|
|
}
|
|
}
|
|
|
|
#endif /* ENABLE_ADDRESSBOOK */
|