i18n work in progress... desperately needs some housecleaning.
[barry.git] / src / r_contact.cc
blob9a0de068f7d259c12b7f8aa1bf2fa48fd742433d
1 ///
2 /// \file r_contact.cc
3 /// Blackberry database record parser class for contact records.
4 ///
6 /*
7 Copyright (C) 2005-2008, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include "r_contact.h"
23 #include "record-internal.h"
24 #include "protocol.h"
25 #include "protostructs.h"
26 #include "data.h"
27 #include "time.h"
28 #include "error.h"
29 #include "endian.h"
30 #include <ostream>
31 #include <iomanip>
32 #include <time.h>
33 #include <stdexcept>
35 #define __DEBUG_MODE__
36 #include "debug.h"
38 using namespace std;
39 using namespace Barry::Protocol;
41 namespace Barry {
45 ///////////////////////////////////////////////////////////////////////////////
46 // Contact class
48 // Contact field codes
49 #define CFC_EMAIL 1
50 #define CFC_PHONE 2
51 #define CFC_FAX 3
52 #define CFC_WORK_PHONE 6
53 #define CFC_HOME_PHONE 7
54 #define CFC_MOBILE_PHONE 8
55 #define CFC_PAGER 9
56 #define CFC_PIN 10
57 #define CFC_RADIO 14 // 0x0e
58 #define CFC_WORK_PHONE_2 16 // 0x10
59 #define CFC_HOME_PHONE_2 17 // 0x11
60 #define CFC_OTHER_PHONE 18 // 0x12
61 #define CFC_NAME 32 // 0x20 used twice, in first/last name order
62 #define CFC_COMPANY 33
63 #define CFC_DEFAULT_COMM_METHOD 34
64 #define CFC_ADDRESS1 35
65 #define CFC_ADDRESS2 36
66 #define CFC_ADDRESS3 37
67 #define CFC_CITY 38
68 #define CFC_PROVINCE 39
69 #define CFC_POSTAL_CODE 40
70 #define CFC_COUNTRY 41
71 #define CFC_TITLE 42 // 0x2a
72 #define CFC_PUBLIC_KEY 43
73 #define CFC_GROUP_FLAG 44
74 #define CFC_GROUP_LINK 52
75 #define CFC_URL 54 // 0x36
76 #define CFC_PREFIX 55 // 0x37
77 #define CFC_CATEGORY 59 // 0x3B
78 #define CFC_HOME_ADDRESS1 61 // 0x3D
79 #define CFC_HOME_ADDRESS2 62 // 0x3E
80 // If the address 3 isn't mapped then it appears
81 // in the same field as address2 with a space
82 #define CFC_HOME_ADDRESS3 63 // 0x3F
83 #define CFC_NOTES 64 // 0x40
84 #define CFC_USER_DEFINED_1 65 // 0x41
85 #define CFC_USER_DEFINED_2 66 // 0x42
86 #define CFC_USER_DEFINED_3 67 // 0x43
87 #define CFC_USER_DEFINED_4 68 // 0x44
88 #define CFC_HOME_CITY 69 // 0x45
89 #define CFC_HOME_PROVINCE 70 // 0x46
90 #define CFC_HOME_POSTAL_CODE 71 // 0x47
91 #define CFC_HOME_COUNTRY 72 // 0x48
92 #define CFC_IMAGE 77 // 0x4D
93 #define CFC_INVALID_FIELD 255
95 // Contact code to field table
96 Utf8FieldLink<Contact> ContactFieldLinks[] = {
97 { CFC_EMAIL, "Email", "mail",0, &Contact::Email, 0, 0, true },
98 { CFC_PHONE, "Phone", 0,0, &Contact::Phone, 0, 0, true },
99 { CFC_FAX, "Fax", "facsimileTelephoneNumber",0, &Contact::Fax, 0, 0, true },
100 { CFC_WORK_PHONE, "WorkPhone", "telephoneNumber",0, &Contact::WorkPhone, 0, 0, true },
101 { CFC_HOME_PHONE, "HomePhone", "homePhone",0, &Contact::HomePhone, 0, 0, true },
102 { CFC_MOBILE_PHONE, "MobilePhone","mobile",0, &Contact::MobilePhone, 0, 0, true },
103 { CFC_PAGER, "Pager", "pager",0, &Contact::Pager, 0, 0, true },
104 { CFC_PIN, "PIN", 0,0, &Contact::PIN, 0, 0, true },
105 { CFC_RADIO, "Radio", 0,0, &Contact::Radio, 0, 0, true },
106 { CFC_WORK_PHONE_2, "WorkPhone2", 0,0, &Contact::WorkPhone2, 0, 0, true },
107 { CFC_HOME_PHONE_2, "HomePhone2", 0,0, &Contact::HomePhone2, 0, 0, true },
108 { CFC_OTHER_PHONE, "OtherPhone", 0,0, &Contact::OtherPhone, 0, 0, true },
109 { CFC_COMPANY, "Company", "o",0, &Contact::Company, 0, 0, true },
110 { CFC_DEFAULT_COMM_METHOD,"DefaultCommMethod",0,0, &Contact::DefaultCommunicationsMethod, 0, 0, true },
111 { CFC_ADDRESS1, "WorkAddress1", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address1, true },
112 { CFC_ADDRESS2, "WorkAddress2", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address2, true },
113 { CFC_ADDRESS3, "WorkAddress3", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address3, true },
114 { CFC_CITY, "WorkCity", "l",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::City, true },
115 { CFC_PROVINCE, "WorkProvince", "st",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Province, true },
116 { CFC_POSTAL_CODE, "WorkPostalCode", "postalCode",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::PostalCode, true },
117 { CFC_COUNTRY, "WorkCountry", "c", "country", 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Country, true },
118 { CFC_TITLE, "JobTitle", "title",0, &Contact::JobTitle, 0, 0, true },
119 { CFC_PUBLIC_KEY, "PublicKey", 0,0, &Contact::PublicKey, 0, 0, false },
120 { CFC_URL, "URL", 0,0, &Contact::URL, 0, 0, true },
121 { CFC_PREFIX, "Prefix", 0,0, &Contact::Prefix, 0, 0, true },
122 { CFC_HOME_ADDRESS1,"HomeAddress1", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address1, true },
123 { CFC_HOME_ADDRESS2,"HomeAddress2", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address2, true },
124 { CFC_HOME_ADDRESS3,"HomeAddress3", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address3, true },
125 { CFC_NOTES, "Notes", 0,0, &Contact::Notes, 0, 0, true },
126 { CFC_USER_DEFINED_1, "UserDefined1", 0,0, &Contact::UserDefined1, 0, 0, true },
127 { CFC_USER_DEFINED_2, "UserDefined2", 0,0, &Contact::UserDefined2, 0, 0, true },
128 { CFC_USER_DEFINED_3, "UserDefined3", 0,0, &Contact::UserDefined3, 0, 0, true },
129 { CFC_USER_DEFINED_4, "UserDefined4", 0,0, &Contact::UserDefined4, 0, 0, true },
130 { CFC_HOME_CITY, "HomeCity", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::City, true },
131 { CFC_HOME_PROVINCE,"HomeProvince", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Province, true },
132 { CFC_HOME_POSTAL_CODE, "HomePostalCode", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::PostalCode, true },
133 { CFC_HOME_COUNTRY, "HomeCountry",0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Country, true },
134 { CFC_IMAGE, "Image", 0,0, &Contact::Image, 0, 0, false },
135 { CFC_INVALID_FIELD,"EndOfList", 0, 0, 0 }
138 Contact::Contact()
139 : RecType(Contact::GetDefaultRecType()),
140 RecordId(0),
141 m_FirstNameSeen(false)
145 Contact::~Contact()
149 const unsigned char* Contact::ParseField(const unsigned char *begin,
150 const unsigned char *end)
152 const CommonField *field = (const CommonField *) begin;
154 // advance and check size
155 begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
156 if( begin > end ) // if begin==end, we are ok
157 return begin;
159 if( !btohs(field->size) ) // if field has no size, something's up
160 return begin;
162 // cycle through the type table
163 for( Utf8FieldLink<Contact> *b = ContactFieldLinks;
164 b->type != CFC_INVALID_FIELD;
165 b++ )
167 if( b->type == field->type ) {
168 if( b->strMember ) {
169 std::string &s = this->*(b->strMember);
170 s = ParseFieldString(field);
171 if( b->utf8Needed )
172 ToUtf8(s);
173 return begin; // done!
175 else if( b->postMember && b->postField ) {
176 std::string &s = (this->*(b->postMember)).*(b->postField);
177 s = ParseFieldString(field);
178 if( b->utf8Needed )
179 ToUtf8(s);
180 return begin;
182 else {
183 break; // fall through to special handling
188 // if not found in the type table, check for special handling
189 switch( field->type )
191 case CFC_NAME: {
192 // can be used multiple times, for first/last names
193 std::string *name;
194 if( FirstName.size() || m_FirstNameSeen ) {
195 // first name already filled, use last name
196 name = &LastName;
197 m_FirstNameSeen = false;
199 else {
200 name = &FirstName;
201 m_FirstNameSeen = true;
204 *name = ParseFieldString(field);
205 ToUtf8(*name);
207 return begin;
209 case CFC_GROUP_LINK:
210 // just add the unique ID to the list
211 GroupLinks.push_back(
212 GroupLink(field->u.link.uniqueId,
213 field->u.link.unknown));
214 return begin;
216 case CFC_GROUP_FLAG:
217 // ignore the group flag... the presense of group link items
218 // behaves as the flag in this class
219 return begin;
221 case CFC_CATEGORY: {
222 std::string catstring = ParseFieldString(field);
223 ToUtf8(catstring);
224 CategoryStr2List(catstring, Categories);
226 return begin;
229 // if still not handled, add to the Unknowns list
230 UnknownField uf;
231 uf.type = field->type;
232 uf.data.assign((const char*)field->u.raw, btohs(field->size));
233 Unknowns.push_back(uf);
235 // return new pointer for next field
236 return begin;
239 void Contact::ParseHeader(const Data &data, size_t &offset)
241 // no header to parse in Contact records
244 // this is called by the RecordParser<> class, which checks size for us
245 void Contact::ParseFields(const Data &data, size_t &offset)
247 const unsigned char *finish = ParseCommonFields(*this,
248 data.GetData() + offset, data.GetData() + data.GetSize());
249 offset += finish - (data.GetData() + offset);
252 void Contact::BuildHeader(Data &data, size_t &offset) const
254 // no header in Contact records
258 // BuildFields
260 /// Build fields part of record
262 void Contact::BuildFields(Data &data, size_t &offset) const
264 data.Zap();
266 // check if this is a group link record, and if so, output
267 // the group flag
268 if( GroupLinks.size() )
269 BuildField(data, offset, CFC_GROUP_FLAG, 'G');
271 // special fields not in type table
272 if( FirstName.size() ) {
273 std::string s = FromUtf8(FirstName);
274 BuildField(data, offset, CFC_NAME, s);
276 if( LastName.size() ) {
277 if( !FirstName.size() ) {
278 // order matters with first/last name, and if
279 // last name exists, and first name doesn't,
280 // insert blank first name ahead of it
281 BuildField(data, offset, CFC_NAME, "");
283 BuildField(data, offset, CFC_NAME, FromUtf8(LastName));
286 // cycle through the type table
287 for( Utf8FieldLink<Contact> *b = ContactFieldLinks;
288 b->type != CFC_INVALID_FIELD;
289 b++ )
291 // print only fields with data
292 if( b->strMember ) {
293 const std::string &field = this->*(b->strMember);
294 if( field.size() ) {
295 std::string s = b->utf8Needed ? FromUtf8(field) : field;
296 BuildField(data, offset, b->type, s);
299 else if( b->postMember && b->postField ) {
300 const std::string &field = (this->*(b->postMember)).*(b->postField);
301 if( field.size() ) {
302 std::string s = b->utf8Needed ? FromUtf8(field) : field;
303 BuildField(data, offset, b->type, s);
308 // save any group links
309 GroupLinksType::const_iterator
310 gb = GroupLinks.begin(), ge = GroupLinks.end();
311 for( ; gb != ge; gb++ ) {
312 Barry::Protocol::GroupLink link;
313 link.uniqueId = htobl(gb->Link);
314 link.unknown = htobs(gb->Unknown);
315 BuildField(data, offset, CFC_GROUP_LINK, link);
318 if( Categories.size() ) {
319 string store;
320 CategoryList2Str(Categories, store);
321 BuildField(data, offset, CFC_CATEGORY, FromUtf8(store));
324 // and finally save unknowns
325 UnknownsType::const_iterator
326 ub = Unknowns.begin(), ue = Unknowns.end();
327 for( ; ub != ue; ub++ ) {
328 BuildField(data, offset, *ub);
331 data.ReleaseBuffer(offset);
334 void Contact::Clear()
336 RecType = Contact::GetDefaultRecType();
338 Email.clear();
339 Phone.clear();
340 Fax.clear();
341 WorkPhone.clear();
342 HomePhone.clear();
343 MobilePhone.clear();
344 Pager.clear();
345 PIN.clear();
346 Radio.clear();
347 WorkPhone2.clear();
348 HomePhone2.clear();
349 OtherPhone.clear();
350 FirstName.clear();
351 LastName.clear();
352 Company.clear();
353 DefaultCommunicationsMethod.clear();
354 JobTitle.clear();
355 PublicKey.clear();
356 URL.clear();
357 Prefix.clear();
358 Notes.clear();
359 UserDefined1.clear();
360 UserDefined2.clear();
361 UserDefined3.clear();
362 UserDefined4.clear();
363 Image.clear();
365 WorkAddress.Clear();
366 HomeAddress.Clear();
368 Categories.clear();
370 GroupLinks.clear();
371 Unknowns.clear();
373 m_FirstNameSeen = false;
377 // GetFullName
379 /// Helper function that returns a formatted full name
381 std::string Contact::GetFullName() const
383 std::string Full = FirstName;
384 if( Full.size() && LastName.size() )
385 Full += " ";
386 Full += LastName;
387 return Full;
390 void Contact::Dump(std::ostream &os) const
392 ios::fmtflags oldflags = os.setf(ios::left);
393 char fill = os.fill(' ');
395 os << "Contact: 0x" << setbase(16) << GetID()
396 << " (" << (unsigned int)RecType << ")\n";
398 // special fields not in type table
399 os << " " << setw(20) << "FirstName";
400 os << ": " << FirstName << "\n";
401 os << " " << setw(20) << "LastName";
402 os << ": " << LastName << "\n";
404 // cycle through the type table
405 for( Utf8FieldLink<Contact> *b = ContactFieldLinks;
406 b->type != CFC_INVALID_FIELD;
407 b++ )
409 const std::string *pField = 0;
410 if( b->strMember ) {
411 pField = &(this->*(b->strMember));
413 else if( b->postMember && b->postField ) {
414 pField = &((this->*(b->postMember)).*(b->postField));
417 // print only fields with data
418 if( pField && pField->size() ) {
419 os << " " << setw(20) << b->name;
420 os << ": " << *pField << "\n";
424 if( Categories.size() ) {
425 string display;
426 CategoryList2Str(Categories, display);
427 os << " Categories : " << display << "\n";
430 // print any group links
431 GroupLinksType::const_iterator
432 gb = GroupLinks.begin(), ge = GroupLinks.end();
433 if( gb != ge )
434 os << " GroupLinks:\n";
435 for( ; gb != ge; gb++ ) {
436 os << " ID: 0x" << setbase(16) << gb->Link << "\n";
439 // and finally print unknowns
440 os << Unknowns;
442 // cleanup the stream
443 os.flags(oldflags);
444 os.fill(fill);
447 void Contact::SplitName(const std::string &full, std::string &first, std::string &last)
449 first.clear();
450 last.clear();
452 string::size_type pos = full.find_last_of(' ');
453 if( pos != string::npos ) {
454 // has space, assume last word is last name
455 last = full.c_str() + pos + 1;
456 first = full.substr(0, pos);
458 else {
459 // no space, assume only first name
460 first = full.substr(0);
464 void Contact::CategoryStr2List(const std::string &str,
465 Barry::CategoryList &list)
467 // start fresh
468 list.clear();
470 if( !str.size() )
471 return;
473 // parse the comma-delimited string to a list, stripping away
474 // any white space around each category name
475 string::size_type start = 0, end = 0, delim = str.find(',', start);
476 while( start != string::npos ) {
477 if( delim == string::npos )
478 end = str.size() - 1;
479 else
480 end = delim - 1;
482 // strip surrounding whitespace
483 while( str[start] == ' ' )
484 start++;
485 while( end && str[end] == ' ' )
486 end--;
488 if( start <= end ) {
489 string token = str.substr(start, end-start+1);
490 list.push_back(token);
493 // next
494 start = delim;
495 if( start != string::npos )
496 start++;
497 delim = str.find(',', start);
501 void Contact::CategoryList2Str(const Barry::CategoryList &list,
502 std::string &str)
504 str.clear();
506 Barry::CategoryList::const_iterator i = list.begin();
507 for( ; i != list.end(); ++i ) {
508 if( str.size() )
509 str += ", ";
510 str += *i;
514 } // namespace Barry