r_contact.cc

Go to the documentation of this file.
00001 ///
00002 /// \file       r_contact.cc
00003 ///             Blackberry database record parser class for contact records.
00004 ///
00005 
00006 /*
00007     Copyright (C) 2005-2012, Net Direct Inc. (http://www.netdirect.ca/)
00008 
00009     This program is free software; you can redistribute it and/or modify
00010     it under the terms of the GNU General Public License as published by
00011     the Free Software Foundation; either version 2 of the License, or
00012     (at your option) any later version.
00013 
00014     This program is distributed in the hope that it will be useful,
00015     but WITHOUT ANY WARRANTY; without even the implied warranty of
00016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018     See the GNU General Public License in the COPYING file at the
00019     root directory of this project for more details.
00020 */
00021 
00022 #include "r_contact.h"
00023 #include "record-internal.h"
00024 #include "protocol.h"
00025 #include "protostructs.h"
00026 #include "data.h"
00027 #include "time.h"
00028 #include "error.h"
00029 #include "endian.h"
00030 #include "iconv.h"
00031 #include "trim.h"
00032 #include <iostream>
00033 #include <iomanip>
00034 #include <sstream>
00035 #include <time.h>
00036 #include <stdexcept>
00037 #include "ios_state.h"
00038 
00039 #define __DEBUG_MODE__
00040 #include "debug.h"
00041 
00042 using namespace std;
00043 using namespace Barry::Protocol;
00044 
00045 namespace Barry {
00046 
00047 
00048 
00049 ///////////////////////////////////////////////////////////////////////////////
00050 // Contact class
00051 
00052 // Contact field codes
00053 #define CFC_EMAIL               1
00054 #define CFC_PHONE               2
00055 #define CFC_FAX                 3
00056 #define CFC_WORK_PHONE          6
00057 #define CFC_HOME_PHONE          7
00058 #define CFC_MOBILE_PHONE        8
00059 #define CFC_PAGER               9
00060 #define CFC_PIN                 10
00061 #define CFC_RADIO               14      // 0x0e
00062 #define CFC_WORK_PHONE_2        16      // 0x10
00063 #define CFC_HOME_PHONE_2        17      // 0x11
00064 #define CFC_OTHER_PHONE         18      // 0x12
00065 #define CFC_MOBILE_PHONE_2      19      // 0x13
00066 #define CFC_HOME_FAX            20      // 0x14
00067 #define CFC_NAME                32      // 0x20 used twice, in first/last name order
00068 #define CFC_COMPANY             33
00069 #define CFC_DEFAULT_COMM_METHOD 34
00070 #define CFC_ADDRESS1            35
00071 #define CFC_ADDRESS2            36
00072 #define CFC_ADDRESS3            37
00073 #define CFC_CITY                38
00074 #define CFC_PROVINCE            39
00075 #define CFC_POSTAL_CODE         40
00076 #define CFC_COUNTRY             41
00077 #define CFC_TITLE               42      // 0x2a
00078 #define CFC_PUBLIC_KEY          43
00079 #define CFC_GROUP_FLAG          44
00080 #define CFC_GROUP_LINK          52
00081 #define CFC_URL                 54      // 0x36
00082 #define CFC_PREFIX              55      // 0x37
00083 #define CFC_CATEGORY            59      // 0x3B
00084 #define CFC_HOME_ADDRESS1       61      // 0x3D
00085 #define CFC_HOME_ADDRESS2       62      // 0x3E
00086   // If the address 3 isn't mapped then it appears
00087   // in the same field as address2 with a space
00088 #define CFC_HOME_ADDRESS3       63      // 0x3F
00089 #define CFC_NOTES               64      // 0x40
00090 #define CFC_USER_DEFINED_1      65      // 0x41
00091 #define CFC_USER_DEFINED_2      66      // 0x42
00092 #define CFC_USER_DEFINED_3      67      // 0x43
00093 #define CFC_USER_DEFINED_4      68      // 0x44
00094 #define CFC_HOME_CITY           69      // 0x45
00095 #define CFC_HOME_PROVINCE       70      // 0x46
00096 #define CFC_HOME_POSTAL_CODE    71      // 0x47
00097 #define CFC_HOME_COUNTRY        72      // 0x48
00098 #define CFC_IMAGE               77      // 0x4D
00099 #define CFC_BIRTHDAY            82      // 0x52
00100 #define CFC_ANNIVERSARY         83      // 0x53
00101 #define CFC_MAYBE_CATEGORYID    84      // 0x54
00102 #define CFC_UNIQUEID            85      // 0x55
00103 #define CFC_NICKNAME            86      // 0x56
00104 #define CFC_INVALID_FIELD       255
00105 
00106 // Contact code to field table
00107 static FieldLink<Contact> ContactFieldLinks[] = {
00108    { CFC_NICKNAME,     "Nickname",   0,0,                 &Contact::Nickname, 0, 0, 0, 0, true },
00109    { CFC_PHONE,        "Phone",      0,0,                 &Contact::Phone, 0, 0, 0, 0, true },
00110    { CFC_FAX,          "Fax",        "facsimileTelephoneNumber",0, &Contact::Fax, 0, 0, 0, 0, true },
00111    { CFC_HOME_FAX,     "HomeFax",    0,0,                 &Contact::HomeFax, 0, 0, 0, 0, true },
00112    { CFC_WORK_PHONE,   "WorkPhone",  "telephoneNumber",0, &Contact::WorkPhone, 0, 0, 0, 0, true },
00113    { CFC_HOME_PHONE,   "HomePhone",  "homePhone",0,       &Contact::HomePhone, 0, 0, 0, 0, true },
00114    { CFC_MOBILE_PHONE, "MobilePhone","mobile",0,          &Contact::MobilePhone, 0, 0, 0, 0, true },
00115    { CFC_MOBILE_PHONE_2,"MobilePhone2",0,0,               &Contact::MobilePhone2, 0, 0, 0, 0, true },
00116    { CFC_PAGER,        "Pager",      "pager",0,           &Contact::Pager, 0, 0, 0, 0, true },
00117    { CFC_PIN,          "PIN",        0,0,                 &Contact::PIN, 0, 0, 0, 0, true },
00118    { CFC_RADIO,        "Radio",      0,0,                 &Contact::Radio, 0, 0, 0, 0, true },
00119    { CFC_WORK_PHONE_2, "WorkPhone2", 0,0,                 &Contact::WorkPhone2, 0, 0, 0, 0, true },
00120    { CFC_HOME_PHONE_2, "HomePhone2", 0,0,                 &Contact::HomePhone2, 0, 0, 0, 0, true },
00121    { CFC_OTHER_PHONE,  "OtherPhone", 0,0,                 &Contact::OtherPhone, 0, 0, 0, 0, true },
00122    { CFC_COMPANY,      "Company",    "o",0,               &Contact::Company, 0, 0, 0, 0, true },
00123    { CFC_DEFAULT_COMM_METHOD,"DefaultCommMethod",0,0,     &Contact::DefaultCommunicationsMethod, 0, 0, 0, 0, true },
00124    { CFC_ADDRESS1,     "WorkAddress1",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address1, true },
00125    { CFC_ADDRESS2,     "WorkAddress2",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address2, true },
00126    { CFC_ADDRESS3,     "WorkAddress3",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address3, true },
00127    { CFC_CITY,         "WorkCity",       "l",0,           0, 0, 0, &Contact::WorkAddress, &PostalAddress::City, true },
00128    { CFC_PROVINCE,     "WorkProvince",   "st",0,          0, 0, 0, &Contact::WorkAddress, &PostalAddress::Province, true },
00129    { CFC_POSTAL_CODE,  "WorkPostalCode", "postalCode",0,  0, 0, 0, &Contact::WorkAddress, &PostalAddress::PostalCode, true },
00130    { CFC_COUNTRY,      "WorkCountry",    "c", "country",  0, 0, 0, &Contact::WorkAddress, &PostalAddress::Country, true },
00131    { CFC_TITLE,        "JobTitle",   "title",0,           &Contact::JobTitle, 0, 0, 0, 0, true },
00132    { CFC_PUBLIC_KEY,   "PublicKey",  0,0,                 &Contact::PublicKey, 0, 0, 0, 0, false },
00133    { CFC_URL,          "URL",        0,0,                 &Contact::URL, 0, 0, 0, 0, true },
00134    { CFC_PREFIX,       "Prefix",     0,0,                 &Contact::Prefix, 0, 0, 0, 0, true },
00135    { CFC_HOME_ADDRESS1,"HomeAddress1", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address1, true },
00136    { CFC_HOME_ADDRESS2,"HomeAddress2", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address2, true },
00137    { CFC_HOME_ADDRESS3,"HomeAddress3", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address3, true },
00138    { CFC_NOTES,        "Notes",      0,0,                 &Contact::Notes, 0, 0, 0, 0, true },
00139    { CFC_USER_DEFINED_1, "UserDefined1", 0,0,             &Contact::UserDefined1, 0, 0, 0, 0, true },
00140    { CFC_USER_DEFINED_2, "UserDefined2", 0,0,             &Contact::UserDefined2, 0, 0, 0, 0, true },
00141    { CFC_USER_DEFINED_3, "UserDefined3", 0,0,             &Contact::UserDefined3, 0, 0, 0, 0, true },
00142    { CFC_USER_DEFINED_4, "UserDefined4", 0,0,             &Contact::UserDefined4, 0, 0, 0, 0, true },
00143    { CFC_HOME_CITY,    "HomeCity",   0,0,                 0, 0, 0, &Contact::HomeAddress, &PostalAddress::City, true },
00144    { CFC_HOME_PROVINCE,"HomeProvince", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Province, true },
00145    { CFC_HOME_POSTAL_CODE, "HomePostalCode", 0,0,         0, 0, 0, &Contact::HomeAddress, &PostalAddress::PostalCode, true },
00146    { CFC_HOME_COUNTRY, "HomeCountry",0,0,                 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Country, true },
00147    { CFC_IMAGE,        "Image",      0,0,                 &Contact::Image, 0, 0, 0, 0, false },
00148    { CFC_INVALID_FIELD,"EndOfList",  0, 0, 0, 0, 0, 0, 0, false }
00149 };
00150 
00151 Contact::Contact()
00152         : RecType(Contact::GetDefaultRecType()),
00153         RecordId(0),
00154         m_FirstNameSeen(false)
00155 {
00156 }
00157 
00158 Contact::~Contact()
00159 {
00160 }
00161 
00162 const unsigned char* Contact::ParseField(const unsigned char *begin,
00163                                          const unsigned char *end,
00164                                          const IConverter *ic)
00165 {
00166         const CommonField *field = (const CommonField *) begin;
00167 
00168         // advance and check size
00169         begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
00170         if( begin > end )               // if begin==end, we are ok
00171                 return begin;
00172 
00173         if( !btohs(field->size) )       // if field has no size, something's up
00174                 return begin;
00175 
00176         // cycle through the type table
00177         for(    FieldLink<Contact> *b = ContactFieldLinks;
00178                 b->type != CFC_INVALID_FIELD;
00179                 b++ )
00180         {
00181                 if( b->type == field->type ) {
00182                         if( b->strMember ) {
00183                                 std::string &s = this->*(b->strMember);
00184                                 s = ParseFieldString(field);
00185                                 if( b->iconvNeeded && ic )
00186                                         s = ic->FromBB(s);
00187                                 return begin;   // done!
00188                         }
00189                         else if( b->postMember && b->postField ) {
00190                                 std::string &s = (this->*(b->postMember)).*(b->postField);
00191                                 s = ParseFieldString(field);
00192                                 if( b->iconvNeeded && ic )
00193                                         s = ic->FromBB(s);
00194                                 return begin;
00195                         }
00196                         else {
00197                                 break;  // fall through to special handling
00198                         }
00199                 }
00200         }
00201 
00202         // if not found in the type table, check for special handling
00203         switch( field->type )
00204         {
00205         case CFC_EMAIL: {
00206                 std::string s = ParseFieldString(field);
00207                 if( ic )
00208                         s = ic->FromBB(s);
00209                 EmailAddresses.push_back( s );
00210                 }
00211                 return begin;
00212 
00213         case CFC_NAME: {
00214                 // can be used multiple times, for first/last names
00215                 std::string *name;
00216                 if( FirstName.size() || m_FirstNameSeen ) {
00217                         // first name already filled, use last name
00218                         name = &LastName;
00219                         m_FirstNameSeen = false;
00220                 }
00221                 else {
00222                         name = &FirstName;
00223                         m_FirstNameSeen = true;
00224                 }
00225 
00226                 *name = ParseFieldString(field);
00227                 if( ic )
00228                         *name = ic->FromBB(*name);
00229                 }
00230                 return begin;
00231 
00232         case CFC_GROUP_LINK:
00233                 // just add the unique ID to the list
00234                 GroupLinks.push_back(
00235                         GroupLink(field->u.link.uniqueId,
00236                                 field->u.link.unknown));
00237                 return begin;
00238 
00239         case CFC_GROUP_FLAG:
00240                 // ignore the group flag... the presense of group link items
00241                 // behaves as the flag in this class
00242                 return begin;
00243 
00244         case CFC_CATEGORY: {
00245                 std::string catstring = ParseFieldString(field);
00246                 if( ic )
00247                         catstring = ic->FromBB(catstring);
00248                 Categories.CategoryStr2List(catstring);
00249                 }
00250                 return begin;
00251 
00252         case CFC_BIRTHDAY: {
00253                 std::string bstring = ParseFieldString(field);
00254                 Birthday.FromBBString(bstring);
00255                 }
00256                 return begin;
00257 
00258         case CFC_ANNIVERSARY: {
00259                 std::string astring = ParseFieldString(field);
00260                 Anniversary.FromBBString(astring);
00261                 }
00262                 return begin;
00263 
00264         case CFC_UNIQUEID:
00265                 // this is a duplicate of the UniqueID that comes from
00266                 // the envelope part of the protocol... just throw this
00267                 // away, since when we upload it, we need to use a
00268                 // consistent UniqueID / RecordID from the API
00269                 return begin;
00270         }
00271 
00272         // if still not handled, add to the Unknowns list
00273         UnknownField uf;
00274         uf.type = field->type;
00275         uf.data.assign((const char*)field->u.raw, btohs(field->size));
00276         Unknowns.push_back(uf);
00277 
00278         // return new pointer for next field
00279         return begin;
00280 }
00281 
00282 void Contact::ParseHeader(const Data &data, size_t &offset)
00283 {
00284         // no header to parse in Contact records
00285 }
00286 
00287 void Contact::ParseFields(const Data &data, size_t &offset, const IConverter *ic)
00288 {
00289         const unsigned char *finish = ParseCommonFields(*this,
00290                 data.GetData() + offset, data.GetData() + data.GetSize(), ic);
00291         offset += finish - (data.GetData() + offset);
00292 }
00293 
00294 void Contact::Validate() const
00295 {
00296         if( !GetFullName().size() && !Company.size() ) {
00297                 throw Barry::ValidationError("A contact record must contain either a First/Last name, or a Company name.");
00298         }
00299 }
00300 
00301 void Contact::BuildHeader(Data &data, size_t &offset) const
00302 {
00303         // no header in Contact records
00304 }
00305 
00306 //
00307 // BuildFields
00308 //
00309 /// Build fields part of record
00310 ///
00311 void Contact::BuildFields(Data &data, size_t &offset, const IConverter *ic) const
00312 {
00313         data.Zap();
00314 
00315         // Sanity check: the Blackberry requires at least a name or
00316         // a company name for each address record.
00317         if( !GetFullName().size() && !Company.size() )
00318                 throw BadData("Contact must have name or company name.");
00319 
00320         // check if this is a group link record, and if so, output
00321         // the group flag
00322         if( GroupLinks.size() )
00323                 BuildField(data, offset, CFC_GROUP_FLAG, 'G');
00324 
00325         // special fields not in type table
00326         if( FirstName.size() ) {
00327                 std::string s = ic ? ic->ToBB(FirstName) : FirstName;
00328                 BuildField(data, offset, CFC_NAME, s);
00329         }
00330         if( LastName.size() ) {
00331                 if( !FirstName.size() ) {
00332                         // order matters with first/last name, and if
00333                         // last name exists, and first name doesn't,
00334                         // insert blank first name ahead of it
00335                         BuildField(data, offset, CFC_NAME, "");
00336                 }
00337                 BuildField(data, offset, CFC_NAME, ic ? ic->ToBB(LastName) : LastName);
00338         }
00339 
00340 //      FIXME
00341 //      // add unknown data
00342 //      char buffer[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
00343 //      BuildField(data, offset, 0x54, buffer, 8);
00344 
00345         // With the BlackBerry Storm, I have to add this entry.
00346         // Otherwise the uniqueId of this contact is reseted !
00347         // The device seems accept the multiple contact with the same uniqueId,
00348         // but the synchronization process uses this uniqueId to identify the contact.
00349         // add uniqueId
00350         BuildField(data, offset, CFC_UNIQUEID, RecordId);
00351 
00352         // add all email addresses
00353         EmailList::const_iterator eai = EmailAddresses.begin();
00354         for( ; eai != EmailAddresses.end(); ++eai ) {
00355                 if( eai->size() ) {
00356                         BuildField(data, offset, CFC_EMAIL, ic ? ic->ToBB(*eai) : *eai);
00357                 }
00358         }
00359 
00360         // cycle through the type table
00361         for(    FieldLink<Contact> *b = ContactFieldLinks;
00362                 b->type != CFC_INVALID_FIELD;
00363                 b++ )
00364         {
00365                 // print only fields with data
00366                 if( b->strMember ) {
00367                         const std::string &field = this->*(b->strMember);
00368                         if( field.size() ) {
00369                                 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
00370                                 BuildField(data, offset, b->type, s);
00371                         }
00372                 }
00373                 else if( b->postMember && b->postField ) {
00374                         const std::string &field = (this->*(b->postMember)).*(b->postField);
00375                         if( field.size() ) {
00376                                 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
00377                                 BuildField(data, offset, b->type, s);
00378                         }
00379                 }
00380         }
00381 
00382         // save any group links
00383         GroupLinksType::const_iterator
00384                 gb = GroupLinks.begin(), ge = GroupLinks.end();
00385         for( ; gb != ge; gb++ ) {
00386                 Barry::Protocol::GroupLink link;
00387                 link.uniqueId = htobl(gb->Link);
00388                 link.unknown = htobs(gb->Unknown);
00389                 BuildField(data, offset, CFC_GROUP_LINK, link);
00390         }
00391 
00392         // save categories
00393         if( Categories.size() ) {
00394                 string store;
00395                 Categories.CategoryList2Str(store);
00396                 BuildField(data, offset, CFC_CATEGORY, ic ? ic->ToBB(store) : store);
00397         }
00398 
00399         // save Birthday and Anniversary
00400         if( Birthday.HasData() )
00401                 BuildField(data, offset, CFC_BIRTHDAY, Birthday.ToBBString());
00402         if( Anniversary.HasData() )
00403                 BuildField(data, offset, CFC_ANNIVERSARY, Anniversary.ToBBString());
00404 
00405         // and finally save unknowns
00406         UnknownsType::const_iterator
00407                 ub = Unknowns.begin(), ue = Unknowns.end();
00408         for( ; ub != ue; ub++ ) {
00409                 BuildField(data, offset, *ub);
00410         }
00411 
00412         data.ReleaseBuffer(offset);
00413 }
00414 
00415 void Contact::Clear()
00416 {
00417         RecType = GetDefaultRecType();
00418         RecordId = 0;
00419 
00420         EmailAddresses.clear();
00421         Phone.clear();
00422 
00423         Fax.clear();
00424         HomeFax.clear();
00425         WorkPhone.clear();
00426         HomePhone.clear();
00427         MobilePhone.clear();
00428         MobilePhone2.clear();
00429         Pager.clear();
00430         PIN.clear();
00431         Radio.clear();
00432         WorkPhone2.clear();
00433         HomePhone2.clear();
00434         OtherPhone.clear();
00435         FirstName.clear();
00436         LastName.clear();
00437         Company.clear();
00438         DefaultCommunicationsMethod.clear();
00439         JobTitle.clear();
00440         PublicKey.clear();
00441         URL.clear();
00442         Prefix.clear();
00443         Notes.clear();
00444         UserDefined1.clear();
00445         UserDefined2.clear();
00446         UserDefined3.clear();
00447         UserDefined4.clear();
00448         Image.clear();
00449         Nickname.clear();
00450 
00451         Birthday.Clear();
00452         Anniversary.Clear();
00453 
00454         WorkAddress.Clear();
00455         HomeAddress.Clear();
00456 
00457         Categories.clear();
00458 
00459         GroupLinks.clear();
00460         Unknowns.clear();
00461 
00462         m_FirstNameSeen = false;
00463 }
00464 
00465 const FieldHandle<Contact>::ListT& Contact::GetFieldHandles()
00466 {
00467         static FieldHandle<Contact>::ListT fhv;
00468 
00469         if( fhv.size() )
00470                 return fhv;
00471 
00472 #undef CONTAINER_OBJECT_NAME
00473 #define CONTAINER_OBJECT_NAME fhv
00474 
00475 #undef RECORD_CLASS_NAME
00476 #define RECORD_CLASS_NAME Contact
00477 
00478         // first number is priority of fields... 0 being most critical fields
00479         FHP(RecType, "Record Type Code");
00480         FHP(RecordId, "Unique ID");
00481         FHP(EmailAddresses, "Email Addresses");
00482 
00483         FHP(FirstName, "First Name");
00484         FHP(LastName, "Last Name");
00485         FHL(Company, "Company", CFC_COMPANY, true, "o", 0);
00486         FHL(JobTitle, "Job Title", CFC_TITLE, true, "title", 0);
00487         FHD(Prefix, "Prefix", CFC_PREFIX, true);
00488 
00489         FHD(Nickname, "Nickname", CFC_NICKNAME, true);
00490         FHD(Phone, "Phone (deprecated)", CFC_PHONE, true);
00491         FHL(Fax, "Work Fax", CFC_FAX, true, "facsimileTelephoneNumber", 0);
00492         FHD(HomeFax, "Home Fax", CFC_HOME_FAX, true);
00493         FHL(WorkPhone, "Work Phone", CFC_WORK_PHONE, true,
00494                 "telephoneNumber", 0);
00495         FHD(WorkPhone2, "Work Phone 2", CFC_WORK_PHONE_2, true);
00496         FHL(HomePhone, "Home Phone", CFC_HOME_PHONE, true, "homePhone", 0);
00497         FHD(HomePhone2, "Home Phone 2", CFC_HOME_PHONE_2, true);
00498         FHL(MobilePhone, "Mobile Phone", CFC_MOBILE_PHONE, true, "mobile", 0);
00499         FHD(MobilePhone2, "Mobile Phone 2", CFC_MOBILE_PHONE_2, true);
00500         FHD(OtherPhone, "Other Phone", CFC_OTHER_PHONE, true);
00501         FHL(Pager, "Pager", CFC_PAGER, true, "pager", 0);
00502         FHD(PIN, "PIN", CFC_PIN, true);
00503         FHD(Radio, "Radio", CFC_RADIO, true);
00504         FHD(DefaultCommunicationsMethod, "Default Communications Method",
00505                 CFC_DEFAULT_COMM_METHOD, true);
00506         FHD(PublicKey, "Public Key", CFC_PUBLIC_KEY, false);
00507         FHD(URL, "URL", CFC_URL, true);
00508         FHD(Notes, "Notes", CFC_NOTES, true);
00509         FHD(UserDefined1, "User Defined Field 1", CFC_USER_DEFINED_1, true);
00510         FHD(UserDefined2, "User Defined Field 2", CFC_USER_DEFINED_2, true);
00511         FHD(UserDefined3, "User Defined Field 3", CFC_USER_DEFINED_3, true);
00512         FHD(UserDefined4, "User Defined Field 4", CFC_USER_DEFINED_4, true);
00513         FHD(Image, "Image", CFC_IMAGE, false);
00514 
00515         FHD(Birthday, "Birthday", CFC_BIRTHDAY, true);
00516         FHD(Anniversary, "Anniversary", CFC_ANNIVERSARY, true);
00517 
00518         FHC(WorkAddress, "Work Address");
00519         FHS(WorkAddress, Address1, "Work Address 1",
00520                                         CFC_ADDRESS1, true, 0, 0);
00521         FHS(WorkAddress, Address2, "Work Address 2",
00522                                         CFC_ADDRESS2, true, 0, 0);
00523         FHS(WorkAddress, Address3, "Work Address 3",
00524                                         CFC_ADDRESS3, true, 0, 0);
00525         FHS(WorkAddress, City, "Work City",
00526                                         CFC_CITY, true, "l", 0);
00527         FHS(WorkAddress, Province, "Work Province",
00528                                         CFC_PROVINCE, true, "st", 0);
00529         FHS(WorkAddress, PostalCode, "Work Postal Code",
00530                                         CFC_POSTAL_CODE, true, "postalCode", 0);
00531         FHS(WorkAddress, Country, "Work Country",
00532                                         CFC_COUNTRY, true, "c", "country");
00533 
00534         FHC(HomeAddress, "Home Address");
00535         FHS(HomeAddress, Address1, "Home Address 1",
00536                                         CFC_HOME_ADDRESS1, true, 0, 0);
00537         FHS(HomeAddress, Address2, "Home Address 2",
00538                                         CFC_HOME_ADDRESS2, true, 0, 0);
00539         FHS(HomeAddress, Address3, "Home Address 3",
00540                                         CFC_HOME_ADDRESS3, true, 0, 0);
00541         FHS(HomeAddress, City, "Home City",
00542                                         CFC_HOME_CITY, true, 0, 0);
00543         FHS(HomeAddress, Province, "Home Province",
00544                                         CFC_HOME_PROVINCE, true, 0, 0);
00545         FHS(HomeAddress, PostalCode, "Home Postal Code",
00546                                         CFC_HOME_POSTAL_CODE, true, 0, 0);
00547         FHS(HomeAddress, Country, "Home Country",
00548                                         CFC_HOME_COUNTRY, true, 0, 0);
00549 
00550         FHP(Categories, "Categories");
00551 //      FHP(GroupLinks, "Group Links");
00552         FHP(Unknowns, "Unknown Fields");
00553 
00554         return fhv;
00555 }
00556 
00557 std::string Contact::GetDescription() const
00558 {
00559         string desc = GetFullName();
00560         if( desc.size() == 0 && Company.size() )
00561                 return Company;
00562         return desc;
00563 }
00564 
00565 //
00566 // GetFullName
00567 //
00568 /// Helper function that returns a formatted full name
00569 ///
00570 std::string Contact::GetFullName() const
00571 {
00572         std::string Full = FirstName;
00573         if( Full.size() && LastName.size() )
00574                 Full += " ";
00575         Full += LastName;
00576         return Full;
00577 }
00578 
00579 //
00580 // GetEmail
00581 //
00582 /// Helper function that always returns a valid string.  The string
00583 /// may be empty if there is no address at the specified index.
00584 ///
00585 const std::string& Contact::GetEmail(unsigned int index) const
00586 {
00587         static const std::string blank;
00588         if( index < EmailAddresses.size() )
00589                 return EmailAddresses[index];
00590         return blank;
00591 }
00592 
00593 void Contact::Dump(std::ostream &os) const
00594 {
00595         ios_format_state state(os);
00596 
00597         os.setf(ios::left);
00598         os.fill(' ');
00599 
00600         os << "Contact: 0x" << setbase(16) << GetID()
00601                 << " (" << (unsigned int)RecType << ")\n";
00602 
00603         // special fields not in type table
00604         os << "    " << setw(20) << "FirstName";
00605         os << ": " << FirstName << "\n";
00606         os << "    " << setw(20) << "LastName";
00607         os << ": " << LastName << "\n";
00608 
00609         // cycle through email addresses
00610         EmailList::const_iterator eai = EmailAddresses.begin();
00611         for( ; eai != EmailAddresses.end(); ++eai ) {
00612                 if( eai->size() ) {
00613                         os << "    Email               : " << *eai << "\n";
00614                 }
00615         }
00616 
00617         // cycle through the type table
00618         for(    FieldLink<Contact> *b = ContactFieldLinks;
00619                 b->type != CFC_INVALID_FIELD;
00620                 b++ )
00621         {
00622                 // special case: don't dump the raw image data, but
00623                 // leave that for a special hex dump
00624                 if( b->type == CFC_IMAGE )
00625                         continue;
00626 
00627                 const std::string *pField = 0;
00628                 if( b->strMember ) {
00629                         pField = &(this->*(b->strMember));
00630                 }
00631                 else if( b->postMember && b->postField ) {
00632                         pField = &((this->*(b->postMember)).*(b->postField));
00633                 }
00634 
00635                 // print only fields with data
00636                 if( pField && pField->size() ) {
00637                         os << "    " << setw(20) << b->name;
00638                         os << ": " << Cr2LfWrapper(*pField) << "\n";
00639                 }
00640         }
00641 
00642         if( Categories.size() ) {
00643                 string display;
00644                 Categories.CategoryList2Str(display);
00645                 os << "    Categories          : " << display << "\n";
00646         }
00647 
00648         // print Birthday and Anniversary
00649         if( Birthday.HasData() ) {
00650                 os << "    Birthday            : " << Birthday << "\n";
00651         }
00652         if( Anniversary.HasData() ) {
00653                 os << "    Anniversary         : " << Anniversary << "\n";
00654         }
00655 
00656         // print any group links
00657         GroupLinksType::const_iterator
00658                 gb = GroupLinks.begin(), ge = GroupLinks.end();
00659         if( gb != ge )
00660                 os << "    GroupLinks:\n";
00661         for( ; gb != ge; gb++ ) {
00662                 os << "        ID: 0x" << setbase(16) << gb->Link << "\n";
00663         }
00664 
00665         // print Image in hex dump format, if available
00666         if( Image.size() ) {
00667                 Data image(Image.data(), Image.size());
00668                 os << "    Photo image:\n";
00669                 os << image << "\n";
00670         }
00671 
00672         // and finally print unknowns
00673         os << Unknowns;
00674 }
00675 
00676 bool Contact::operator<(const Contact &other) const
00677 {
00678         // old sorting mechanism, to put group links at the bottom
00679         //return GroupLinks.size() == 0 && other.GroupLinks.size() > 0;
00680         // testing - put group links at the top
00681         //return GroupLinks.size() > 0 && other.GroupLinks.size() == 0;
00682 
00683         // usually one of these fields is filled in, so compare
00684         // them all in a ( LastName + FirstName + Company ) key style
00685         int cmp = LastName.compare(other.LastName);
00686         if( cmp == 0 )
00687                 cmp = FirstName.compare(other.FirstName);
00688         if( cmp == 0 )
00689                 cmp = Company.compare(other.Company);
00690         return cmp < 0;
00691 }
00692 
00693 void Contact::SplitName(const std::string &full, std::string &first, std::string &last)
00694 {
00695         first.clear();
00696         last.clear();
00697 
00698         string::size_type pos = full.find_last_of(' ');
00699         if( pos != string::npos ) {
00700                 // has space, assume last word is last name
00701                 last = full.c_str() + pos + 1;
00702                 first = full.substr(0, pos);
00703         }
00704         else {
00705                 // no space, assume only first name
00706                 first = full.substr(0);
00707         }
00708 }
00709 
00710 std::string Contact::Email2CommaString(const EmailList &list)
00711 {
00712         ostringstream oss;
00713         for( EmailList::const_iterator i = list.begin(); i!=list.end(); ++i ) {
00714                 if( i != list.begin() )
00715                         oss << ", ";
00716                 oss << *i;
00717         }
00718         return oss.str();
00719 }
00720 
00721 /// Replaces the EmailAddresses list with the parsed results of
00722 /// list.  If list is empty, then EmailAddresses will also be empty.
00723 /// Note that incoming addresses need to be in simple format, not
00724 /// complex formats like "Name <user@example.com>" but just
00725 /// "user@example.com".  This is a device limitation.
00726 ///
00727 /// Any complex email addresses found in the list will be dropped,
00728 /// with a message sent to the debug output stream.
00729 void Contact::CommaString2Email(const std::string &list, EmailList &result)
00730 {
00731         // start fresh
00732         result.clear();
00733 
00734         // parse the comma separated list
00735         istringstream iss(list);
00736         string address;
00737 
00738         while( iss >> ws && getline(iss, address, ',') ) {
00739                 // trim any trailing whitespace in the address
00740                 Inplace::rtrim(address);
00741 
00742                 // is this a complex address?  like:
00743                 // Chris Frey <cdfrey@foursquare.net>
00744                 // The device only accepts the plain
00745                 // "cdfrey@foursquare.net" part here
00746                 if( address.rfind('>') != string::npos ) {
00747                         dout("Error: Cannot convert complex name+address to a simple contact email address, skipping: " << address);
00748                         continue;
00749                 }
00750 
00751                 // add to list if anything left
00752                 if( address.size() ) {
00753                         result.push_back(address);
00754                 }
00755         }
00756 }
00757 
00758 } // namespace Barry
00759