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-2011, 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 <ostream>
00032 #include <iomanip>
00033 #include <time.h>
00034 #include <stdexcept>
00035 
00036 #define __DEBUG_MODE__
00037 #include "debug.h"
00038 
00039 using namespace std;
00040 using namespace Barry::Protocol;
00041 
00042 namespace Barry {
00043 
00044 
00045 
00046 ///////////////////////////////////////////////////////////////////////////////
00047 // Contact class
00048 
00049 // Contact field codes
00050 #define CFC_EMAIL               1
00051 #define CFC_PHONE               2
00052 #define CFC_FAX                 3
00053 #define CFC_WORK_PHONE          6
00054 #define CFC_HOME_PHONE          7
00055 #define CFC_MOBILE_PHONE        8
00056 #define CFC_PAGER               9
00057 #define CFC_PIN                 10
00058 #define CFC_RADIO               14      // 0x0e
00059 #define CFC_WORK_PHONE_2        16      // 0x10
00060 #define CFC_HOME_PHONE_2        17      // 0x11
00061 #define CFC_OTHER_PHONE         18      // 0x12
00062 #define CFC_MOBILE_PHONE_2      19      // 0x13
00063 #define CFC_HOME_FAX            20      // 0x14
00064 #define CFC_NAME                32      // 0x20 used twice, in first/last name order
00065 #define CFC_COMPANY             33
00066 #define CFC_DEFAULT_COMM_METHOD 34
00067 #define CFC_ADDRESS1            35
00068 #define CFC_ADDRESS2            36
00069 #define CFC_ADDRESS3            37
00070 #define CFC_CITY                38
00071 #define CFC_PROVINCE            39
00072 #define CFC_POSTAL_CODE         40
00073 #define CFC_COUNTRY             41
00074 #define CFC_TITLE               42      // 0x2a
00075 #define CFC_PUBLIC_KEY          43
00076 #define CFC_GROUP_FLAG          44
00077 #define CFC_GROUP_LINK          52
00078 #define CFC_URL                 54      // 0x36
00079 #define CFC_PREFIX              55      // 0x37
00080 #define CFC_CATEGORY            59      // 0x3B
00081 #define CFC_HOME_ADDRESS1       61      // 0x3D
00082 #define CFC_HOME_ADDRESS2       62      // 0x3E
00083   // If the address 3 isn't mapped then it appears
00084   // in the same field as address2 with a space
00085 #define CFC_HOME_ADDRESS3       63      // 0x3F
00086 #define CFC_NOTES               64      // 0x40
00087 #define CFC_USER_DEFINED_1      65      // 0x41
00088 #define CFC_USER_DEFINED_2      66      // 0x42
00089 #define CFC_USER_DEFINED_3      67      // 0x43
00090 #define CFC_USER_DEFINED_4      68      // 0x44
00091 #define CFC_HOME_CITY           69      // 0x45
00092 #define CFC_HOME_PROVINCE       70      // 0x46
00093 #define CFC_HOME_POSTAL_CODE    71      // 0x47
00094 #define CFC_HOME_COUNTRY        72      // 0x48
00095 #define CFC_IMAGE               77      // 0x4D
00096 #define CFC_BIRTHDAY            82      // 0x52
00097 #define CFC_ANNIVERSARY         83      // 0x53
00098 #define CFC_MAYBE_CATEGORYID    84      // 0x54
00099 #define CFC_UNIQUEID            85      // 0x55
00100 #define CFC_NICKNAME            86      // 0x56
00101 #define CFC_INVALID_FIELD       255
00102 
00103 // Contact code to field table
00104 static FieldLink<Contact> ContactFieldLinks[] = {
00105    { CFC_NICKNAME,     "Nickname",   0,0,                 &Contact::Nickname, 0, 0, 0, 0, true },
00106    { CFC_PHONE,        "Phone",      0,0,                 &Contact::Phone, 0, 0, 0, 0, true },
00107    { CFC_FAX,          "Fax",        "facsimileTelephoneNumber",0, &Contact::Fax, 0, 0, 0, 0, true },
00108    { CFC_HOME_FAX,     "HomeFax",    0,0,                 &Contact::HomeFax, 0, 0, 0, 0, true },
00109    { CFC_WORK_PHONE,   "WorkPhone",  "telephoneNumber",0, &Contact::WorkPhone, 0, 0, 0, 0, true },
00110    { CFC_HOME_PHONE,   "HomePhone",  "homePhone",0,       &Contact::HomePhone, 0, 0, 0, 0, true },
00111    { CFC_MOBILE_PHONE, "MobilePhone","mobile",0,          &Contact::MobilePhone, 0, 0, 0, 0, true },
00112    { CFC_MOBILE_PHONE_2,"MobilePhone2",0,0,               &Contact::MobilePhone2, 0, 0, 0, 0, true },
00113    { CFC_PAGER,        "Pager",      "pager",0,           &Contact::Pager, 0, 0, 0, 0, true },
00114    { CFC_PIN,          "PIN",        0,0,                 &Contact::PIN, 0, 0, 0, 0, true },
00115    { CFC_RADIO,        "Radio",      0,0,                 &Contact::Radio, 0, 0, 0, 0, true },
00116    { CFC_WORK_PHONE_2, "WorkPhone2", 0,0,                 &Contact::WorkPhone2, 0, 0, 0, 0, true },
00117    { CFC_HOME_PHONE_2, "HomePhone2", 0,0,                 &Contact::HomePhone2, 0, 0, 0, 0, true },
00118    { CFC_OTHER_PHONE,  "OtherPhone", 0,0,                 &Contact::OtherPhone, 0, 0, 0, 0, true },
00119    { CFC_COMPANY,      "Company",    "o",0,               &Contact::Company, 0, 0, 0, 0, true },
00120    { CFC_DEFAULT_COMM_METHOD,"DefaultCommMethod",0,0,     &Contact::DefaultCommunicationsMethod, 0, 0, 0, 0, true },
00121    { CFC_ADDRESS1,     "WorkAddress1",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address1, true },
00122    { CFC_ADDRESS2,     "WorkAddress2",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address2, true },
00123    { CFC_ADDRESS3,     "WorkAddress3",   0,0,             0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address3, true },
00124    { CFC_CITY,         "WorkCity",       "l",0,           0, 0, 0, &Contact::WorkAddress, &PostalAddress::City, true },
00125    { CFC_PROVINCE,     "WorkProvince",   "st",0,          0, 0, 0, &Contact::WorkAddress, &PostalAddress::Province, true },
00126    { CFC_POSTAL_CODE,  "WorkPostalCode", "postalCode",0,  0, 0, 0, &Contact::WorkAddress, &PostalAddress::PostalCode, true },
00127    { CFC_COUNTRY,      "WorkCountry",    "c", "country",  0, 0, 0, &Contact::WorkAddress, &PostalAddress::Country, true },
00128    { CFC_TITLE,        "JobTitle",   "title",0,           &Contact::JobTitle, 0, 0, 0, 0, true },
00129    { CFC_PUBLIC_KEY,   "PublicKey",  0,0,                 &Contact::PublicKey, 0, 0, 0, 0, false },
00130    { CFC_URL,          "URL",        0,0,                 &Contact::URL, 0, 0, 0, 0, true },
00131    { CFC_PREFIX,       "Prefix",     0,0,                 &Contact::Prefix, 0, 0, 0, 0, true },
00132    { CFC_HOME_ADDRESS1,"HomeAddress1", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address1, true },
00133    { CFC_HOME_ADDRESS2,"HomeAddress2", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address2, true },
00134    { CFC_HOME_ADDRESS3,"HomeAddress3", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address3, true },
00135    { CFC_NOTES,        "Notes",      0,0,                 &Contact::Notes, 0, 0, 0, 0, true },
00136    { CFC_USER_DEFINED_1, "UserDefined1", 0,0,             &Contact::UserDefined1, 0, 0, 0, 0, true },
00137    { CFC_USER_DEFINED_2, "UserDefined2", 0,0,             &Contact::UserDefined2, 0, 0, 0, 0, true },
00138    { CFC_USER_DEFINED_3, "UserDefined3", 0,0,             &Contact::UserDefined3, 0, 0, 0, 0, true },
00139    { CFC_USER_DEFINED_4, "UserDefined4", 0,0,             &Contact::UserDefined4, 0, 0, 0, 0, true },
00140    { CFC_HOME_CITY,    "HomeCity",   0,0,                 0, 0, 0, &Contact::HomeAddress, &PostalAddress::City, true },
00141    { CFC_HOME_PROVINCE,"HomeProvince", 0,0,               0, 0, 0, &Contact::HomeAddress, &PostalAddress::Province, true },
00142    { CFC_HOME_POSTAL_CODE, "HomePostalCode", 0,0,         0, 0, 0, &Contact::HomeAddress, &PostalAddress::PostalCode, true },
00143    { CFC_HOME_COUNTRY, "HomeCountry",0,0,                 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Country, true },
00144    { CFC_IMAGE,        "Image",      0,0,                 &Contact::Image, 0, 0, 0, 0, false },
00145    { CFC_INVALID_FIELD,"EndOfList",  0, 0, 0, 0, 0, 0, 0, false }
00146 };
00147 
00148 Contact::Contact()
00149         : RecType(Contact::GetDefaultRecType()),
00150         RecordId(0),
00151         m_FirstNameSeen(false)
00152 {
00153 }
00154 
00155 Contact::~Contact()
00156 {
00157 }
00158 
00159 const unsigned char* Contact::ParseField(const unsigned char *begin,
00160                                          const unsigned char *end,
00161                                          const IConverter *ic)
00162 {
00163         const CommonField *field = (const CommonField *) begin;
00164 
00165         // advance and check size
00166         begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
00167         if( begin > end )               // if begin==end, we are ok
00168                 return begin;
00169 
00170         if( !btohs(field->size) )       // if field has no size, something's up
00171                 return begin;
00172 
00173         // cycle through the type table
00174         for(    FieldLink<Contact> *b = ContactFieldLinks;
00175                 b->type != CFC_INVALID_FIELD;
00176                 b++ )
00177         {
00178                 if( b->type == field->type ) {
00179                         if( b->strMember ) {
00180                                 std::string &s = this->*(b->strMember);
00181                                 s = ParseFieldString(field);
00182                                 if( b->iconvNeeded && ic )
00183                                         s = ic->FromBB(s);
00184                                 return begin;   // done!
00185                         }
00186                         else if( b->postMember && b->postField ) {
00187                                 std::string &s = (this->*(b->postMember)).*(b->postField);
00188                                 s = ParseFieldString(field);
00189                                 if( b->iconvNeeded && ic )
00190                                         s = ic->FromBB(s);
00191                                 return begin;
00192                         }
00193                         else {
00194                                 break;  // fall through to special handling
00195                         }
00196                 }
00197         }
00198 
00199         // if not found in the type table, check for special handling
00200         switch( field->type )
00201         {
00202         case CFC_EMAIL: {
00203                 std::string s = ParseFieldString(field);
00204                 if( ic )
00205                         s = ic->FromBB(s);
00206                 EmailAddresses.push_back( s );
00207                 }
00208                 return begin;
00209 
00210         case CFC_NAME: {
00211                 // can be used multiple times, for first/last names
00212                 std::string *name;
00213                 if( FirstName.size() || m_FirstNameSeen ) {
00214                         // first name already filled, use last name
00215                         name = &LastName;
00216                         m_FirstNameSeen = false;
00217                 }
00218                 else {
00219                         name = &FirstName;
00220                         m_FirstNameSeen = true;
00221                 }
00222 
00223                 *name = ParseFieldString(field);
00224                 if( ic )
00225                         *name = ic->FromBB(*name);
00226                 }
00227                 return begin;
00228 
00229         case CFC_GROUP_LINK:
00230                 // just add the unique ID to the list
00231                 GroupLinks.push_back(
00232                         GroupLink(field->u.link.uniqueId,
00233                                 field->u.link.unknown));
00234                 return begin;
00235 
00236         case CFC_GROUP_FLAG:
00237                 // ignore the group flag... the presense of group link items
00238                 // behaves as the flag in this class
00239                 return begin;
00240 
00241         case CFC_CATEGORY: {
00242                 std::string catstring = ParseFieldString(field);
00243                 if( ic )
00244                         catstring = ic->FromBB(catstring);
00245                 Categories.CategoryStr2List(catstring);
00246                 }
00247                 return begin;
00248 
00249         case CFC_BIRTHDAY: {
00250                 std::string bstring = ParseFieldString(field);
00251                 Birthday.FromBBString(bstring);
00252                 }
00253                 return begin;
00254 
00255         case CFC_ANNIVERSARY: {
00256                 std::string astring = ParseFieldString(field);
00257                 Anniversary.FromBBString(astring);
00258                 }
00259                 return begin;
00260 
00261         case CFC_UNIQUEID:
00262                 // this is a duplicate of the UniqueID that comes from
00263                 // the envelope part of the protocol... just throw this
00264                 // away, since when we upload it, we need to use a
00265                 // consisten UniqueID / RecordID from the API
00266                 return begin;
00267         }
00268 
00269         // if still not handled, add to the Unknowns list
00270         UnknownField uf;
00271         uf.type = field->type;
00272         uf.data.assign((const char*)field->u.raw, btohs(field->size));
00273         Unknowns.push_back(uf);
00274 
00275         // return new pointer for next field
00276         return begin;
00277 }
00278 
00279 void Contact::ParseHeader(const Data &data, size_t &offset)
00280 {
00281         // no header to parse in Contact records
00282 }
00283 
00284 void Contact::ParseFields(const Data &data, size_t &offset, const IConverter *ic)
00285 {
00286         const unsigned char *finish = ParseCommonFields(*this,
00287                 data.GetData() + offset, data.GetData() + data.GetSize(), ic);
00288         offset += finish - (data.GetData() + offset);
00289 }
00290 
00291 void Contact::BuildHeader(Data &data, size_t &offset) const
00292 {
00293         // no header in Contact records
00294 }
00295 
00296 //
00297 // BuildFields
00298 //
00299 /// Build fields part of record
00300 ///
00301 void Contact::BuildFields(Data &data, size_t &offset, const IConverter *ic) const
00302 {
00303         data.Zap();
00304 
00305         // Sanity check: the Blackberry requires at least a name or
00306         // a company name for each address record.
00307         if( !GetFullName().size() && !Company.size() )
00308                 throw BadData("Contact must have name or company name.");
00309 
00310         // check if this is a group link record, and if so, output
00311         // the group flag
00312         if( GroupLinks.size() )
00313                 BuildField(data, offset, CFC_GROUP_FLAG, 'G');
00314 
00315         // special fields not in type table
00316         if( FirstName.size() ) {
00317                 std::string s = ic ? ic->ToBB(FirstName) : FirstName;
00318                 BuildField(data, offset, CFC_NAME, s);
00319         }
00320         if( LastName.size() ) {
00321                 if( !FirstName.size() ) {
00322                         // order matters with first/last name, and if
00323                         // last name exists, and first name doesn't,
00324                         // insert blank first name ahead of it
00325                         BuildField(data, offset, CFC_NAME, "");
00326                 }
00327                 BuildField(data, offset, CFC_NAME, ic ? ic->ToBB(LastName) : LastName);
00328         }
00329 
00330 //      FIXME
00331 //      // add unknown data
00332 //      char buffer[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
00333 //      BuildField(data, offset, 0x54, buffer, 8);
00334 
00335         // With the BlackBerry Storm, I have to add this entry.
00336         // Otherwise the uniqueId of this contact is reseted !
00337         // The device seems accept the multiple contact with the same uniqueId,
00338         // but the synchronization process uses this uniqueId to identify the contact.
00339         // add uniqueId
00340         BuildField(data, offset, CFC_UNIQUEID, RecordId);
00341 
00342         // add all email addresses
00343         EmailList::const_iterator eai = EmailAddresses.begin();
00344         for( ; eai != EmailAddresses.end(); ++eai ) {
00345                 if( eai->size() ) {
00346                         BuildField(data, offset, CFC_EMAIL, ic ? ic->ToBB(*eai) : *eai);
00347                 }
00348         }
00349 
00350         // cycle through the type table
00351         for(    FieldLink<Contact> *b = ContactFieldLinks;
00352                 b->type != CFC_INVALID_FIELD;
00353                 b++ )
00354         {
00355                 // print only fields with data
00356                 if( b->strMember ) {
00357                         const std::string &field = this->*(b->strMember);
00358                         if( field.size() ) {
00359                                 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
00360                                 BuildField(data, offset, b->type, s);
00361                         }
00362                 }
00363                 else if( b->postMember && b->postField ) {
00364                         const std::string &field = (this->*(b->postMember)).*(b->postField);
00365                         if( field.size() ) {
00366                                 std::string s = (b->iconvNeeded && ic) ? ic->ToBB(field) : field;
00367                                 BuildField(data, offset, b->type, s);
00368                         }
00369                 }
00370         }
00371 
00372         // save any group links
00373         GroupLinksType::const_iterator
00374                 gb = GroupLinks.begin(), ge = GroupLinks.end();
00375         for( ; gb != ge; gb++ ) {
00376                 Barry::Protocol::GroupLink link;
00377                 link.uniqueId = htobl(gb->Link);
00378                 link.unknown = htobs(gb->Unknown);
00379                 BuildField(data, offset, CFC_GROUP_LINK, link);
00380         }
00381 
00382         // save categories
00383         if( Categories.size() ) {
00384                 string store;
00385                 Categories.CategoryList2Str(store);
00386                 BuildField(data, offset, CFC_CATEGORY, ic ? ic->ToBB(store) : store);
00387         }
00388 
00389         // save Birthday and Anniversary
00390         if( Birthday.HasData() )
00391                 BuildField(data, offset, CFC_BIRTHDAY, Birthday.ToBBString());
00392         if( Anniversary.HasData() )
00393                 BuildField(data, offset, CFC_ANNIVERSARY, Anniversary.ToBBString());
00394 
00395         // and finally save unknowns
00396         UnknownsType::const_iterator
00397                 ub = Unknowns.begin(), ue = Unknowns.end();
00398         for( ; ub != ue; ub++ ) {
00399                 BuildField(data, offset, *ub);
00400         }
00401 
00402         data.ReleaseBuffer(offset);
00403 }
00404 
00405 void Contact::Clear()
00406 {
00407         RecType = GetDefaultRecType();
00408         RecordId = 0;
00409 
00410         EmailAddresses.clear();
00411         Phone.clear();
00412 
00413         Fax.clear();
00414         HomeFax.clear();
00415         WorkPhone.clear();
00416         HomePhone.clear();
00417         MobilePhone.clear();
00418         MobilePhone2.clear();
00419         Pager.clear();
00420         PIN.clear();
00421         Radio.clear();
00422         WorkPhone2.clear();
00423         HomePhone2.clear();
00424         OtherPhone.clear();
00425         FirstName.clear();
00426         LastName.clear();
00427         Company.clear();
00428         DefaultCommunicationsMethod.clear();
00429         JobTitle.clear();
00430         PublicKey.clear();
00431         URL.clear();
00432         Prefix.clear();
00433         Notes.clear();
00434         UserDefined1.clear();
00435         UserDefined2.clear();
00436         UserDefined3.clear();
00437         UserDefined4.clear();
00438         Image.clear();
00439         Nickname.clear();
00440 
00441         Birthday.Clear();
00442         Anniversary.Clear();
00443 
00444         WorkAddress.Clear();
00445         HomeAddress.Clear();
00446 
00447         Categories.clear();
00448 
00449         GroupLinks.clear();
00450         Unknowns.clear();
00451 
00452         m_FirstNameSeen = false;
00453 }
00454 
00455 std::string Contact::GetDescription() const
00456 {
00457         return GetFullName();
00458 }
00459 
00460 //
00461 // GetFullName
00462 //
00463 /// Helper function that returns a formatted full name
00464 ///
00465 std::string Contact::GetFullName() const
00466 {
00467         std::string Full = FirstName;
00468         if( Full.size() && LastName.size() )
00469                 Full += " ";
00470         Full += LastName;
00471         return Full;
00472 }
00473 
00474 //
00475 // GetEmail
00476 //
00477 /// Helper function that always returns a valid string.  The string
00478 /// may be empty if there is no address at the specified index.
00479 ///
00480 const std::string& Contact::GetEmail(unsigned int index) const
00481 {
00482         static const std::string blank;
00483         if( index < EmailAddresses.size() )
00484                 return EmailAddresses[index];
00485         return blank;
00486 }
00487 
00488 void Contact::Dump(std::ostream &os) const
00489 {
00490         ios::fmtflags oldflags = os.setf(ios::left);
00491         char fill = os.fill(' ');
00492 
00493         os << "Contact: 0x" << setbase(16) << GetID()
00494                 << " (" << (unsigned int)RecType << ")\n";
00495 
00496         // special fields not in type table
00497         os << "    " << setw(20) << "FirstName";
00498         os << ": " << FirstName << "\n";
00499         os << "    " << setw(20) << "LastName";
00500         os << ": " << LastName << "\n";
00501 
00502         // cycle through email addresses
00503         EmailList::const_iterator eai = EmailAddresses.begin();
00504         for( ; eai != EmailAddresses.end(); ++eai ) {
00505                 if( eai->size() ) {
00506                         os << "    Email               : " << *eai << "\n";
00507                 }
00508         }
00509 
00510         // cycle through the type table
00511         for(    FieldLink<Contact> *b = ContactFieldLinks;
00512                 b->type != CFC_INVALID_FIELD;
00513                 b++ )
00514         {
00515                 // special case: don't dump the raw image data, but
00516                 // leave that for a special hex dump
00517                 if( b->type == CFC_IMAGE )
00518                         continue;
00519 
00520                 const std::string *pField = 0;
00521                 if( b->strMember ) {
00522                         pField = &(this->*(b->strMember));
00523                 }
00524                 else if( b->postMember && b->postField ) {
00525                         pField = &((this->*(b->postMember)).*(b->postField));
00526                 }
00527 
00528                 // print only fields with data
00529                 if( pField && pField->size() ) {
00530                         os << "    " << setw(20) << b->name;
00531                         os << ": " << Cr2LfWrapper(*pField) << "\n";
00532                 }
00533         }
00534 
00535         if( Categories.size() ) {
00536                 string display;
00537                 Categories.CategoryList2Str(display);
00538                 os << "    Categories          : " << display << "\n";
00539         }
00540 
00541         // print Birthday and Anniversary
00542         if( Birthday.HasData() ) {
00543                 os << "    Birthday            : " << Birthday << "\n";
00544         }
00545         if( Anniversary.HasData() ) {
00546                 os << "    Anniversary         : " << Anniversary << "\n";
00547         }
00548 
00549         // print any group links
00550         GroupLinksType::const_iterator
00551                 gb = GroupLinks.begin(), ge = GroupLinks.end();
00552         if( gb != ge )
00553                 os << "    GroupLinks:\n";
00554         for( ; gb != ge; gb++ ) {
00555                 os << "        ID: 0x" << setbase(16) << gb->Link << "\n";
00556         }
00557 
00558         // print Image in hex dump format, if available
00559         if( Image.size() ) {
00560                 Data image(Image.data(), Image.size());
00561                 os << "    Photo image:\n";
00562                 os << image << "\n";
00563         }
00564 
00565         // and finally print unknowns
00566         os << Unknowns;
00567 
00568         // cleanup the stream
00569         os.flags(oldflags);
00570         os.fill(fill);
00571 }
00572 
00573 bool Contact::operator<(const Contact &other) const
00574 {
00575         // old sorting mechanism, to put group links at the bottom
00576         //return GroupLinks.size() == 0 && other.GroupLinks.size() > 0;
00577         // testing - put group links at the top
00578         //return GroupLinks.size() > 0 && other.GroupLinks.size() == 0;
00579 
00580         // usually one of these fields is filled in, so compare
00581         // them all in a ( LastName + FirstName + Company ) key style
00582         int cmp = LastName.compare(other.LastName);
00583         if( cmp == 0 )
00584                 cmp = FirstName.compare(other.FirstName);
00585         if( cmp == 0 )
00586                 cmp = Company.compare(other.Company);
00587         return cmp < 0;
00588 }
00589 
00590 void Contact::SplitName(const std::string &full, std::string &first, std::string &last)
00591 {
00592         first.clear();
00593         last.clear();
00594 
00595         string::size_type pos = full.find_last_of(' ');
00596         if( pos != string::npos ) {
00597                 // has space, assume last word is last name
00598                 last = full.c_str() + pos + 1;
00599                 first = full.substr(0, pos);
00600         }
00601         else {
00602                 // no space, assume only first name
00603                 first = full.substr(0);
00604         }
00605 }
00606 
00607 } // namespace Barry
00608 

Generated on Tue Mar 1 17:50:16 2011 for Barry by  doxygen 1.5.6