Near perfect...
[barry/progweb.git] / opensync-plugin-0.4x / src / vcard.cc
blob604137d48cc626f1eca52f0ac22d5e6f26efc3b4
1 ///
2 /// \file vcard.cc
3 /// Conversion routines for vcards
4 ///
6 /*
7 Copyright (C) 2006-2009, 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 "vcard.h"
23 #include "environment.h"
24 #include "trace.h"
25 #include "vformat.h" // comes from opensync, but not a public header yet
26 #include <stdint.h>
27 #include <glib.h>
28 #include <string.h>
29 #include <sstream>
30 #include <ctype.h>
33 //////////////////////////////////////////////////////////////////////////////
34 // Utility functions
36 void ToLower(std::string &str)
38 size_t i = 0;
39 while( i < str.size() ) {
40 str[i] = tolower(str[i]);
41 i++;
45 //////////////////////////////////////////////////////////////////////////////
46 // vCard
48 vCard::vCard()
49 : m_gCardData(0)
53 vCard::~vCard()
55 if( m_gCardData ) {
56 g_free(m_gCardData);
60 void vCard::AddAddress(const char *rfc_type, const Barry::PostalAddress &address)
62 // add label first
63 vAttrPtr label = NewAttr("LABEL");
64 AddParam(label, "TYPE", rfc_type);
65 AddValue(label, address.GetLabel().c_str());
66 AddAttr(label);
68 // add breakout address form
69 vAttrPtr adr = NewAttr("ADR"); // RFC 2426, 3.2.1
70 AddParam(adr, "TYPE", rfc_type);
71 AddValue(adr, address.Address3.c_str()); // PO Box
72 AddValue(adr, address.Address2.c_str()); // Extended address
73 AddValue(adr, address.Address1.c_str()); // Street address
74 AddValue(adr, address.City.c_str()); // Locality (city)
75 AddValue(adr, address.Province.c_str()); // Region (province)
76 AddValue(adr, address.PostalCode.c_str()); // Postal code
77 AddValue(adr, address.Country.c_str()); // Country name
78 AddAttr(adr);
82 void vCard::AddCategories(const Barry::CategoryList &categories)
84 if( !categories.size() )
85 return;
87 vAttrPtr cat = NewAttr("CATEGORIES"); // RFC 2426, 3.6.1
88 Barry::CategoryList::const_iterator i = categories.begin();
89 for( ; i < categories.end(); ++i ) {
90 AddValue(cat, i->c_str());
92 AddAttr(cat);
95 /// Add phone conditionally, only if phone has data in it. This version
96 /// does not add a TYPE parameter to the item.
97 void vCard::AddPhoneCond(const std::string &phone)
99 if( phone.size() ) {
100 vAttrPtr tel = NewAttr("TEL", phone.c_str());
101 AddAttr(tel);
105 /// Add phone conditionally, only if phone has data in it
106 void vCard::AddPhoneCond(const char *rfc_type, const std::string &phone)
108 if( phone.size() ) {
109 vAttrPtr tel = NewAttr("TEL", phone.c_str());
110 AddParam(tel, "TYPE", rfc_type);
111 AddAttr(tel);
115 void vCard::ParseAddress(vAttr &adr, Barry::PostalAddress &address)
117 // RFC 2426, 3.2.1
118 address.Address3 = adr.GetValue(0); // PO Box
119 address.Address2 = adr.GetValue(1); // Extended address
120 address.Address1 = adr.GetValue(2); // Street address
121 address.City = adr.GetValue(3); // Locality (city)
122 address.Province = adr.GetValue(4); // Region (province)
123 address.PostalCode = adr.GetValue(5); // Postal code
124 address.Country = adr.GetValue(6); // Country name
127 void vCard::ParseCategories(vAttr &cat, Barry::CategoryList &cats)
129 int i = 0;
130 std::string value = cat.GetValue(i);
131 while( value.size() ) {
132 cats.push_back(value);
133 i++;
134 value = cat.GetValue(i);
140 // Main conversion routine for converting from Barry::Contact to
141 // a vCard string of data.
142 const std::string& vCard::ToVCard(const Barry::Contact &con)
144 Trace trace("vCard::ToVCard");
145 std::ostringstream oss;
146 con.Dump(oss);
147 trace.logf("ToVCard, initial Barry record: %s", oss.str().c_str());
149 // start fresh
150 Clear();
151 SetFormat( b_vformat_new() );
152 if( !Format() )
153 throw ConvertError("resource error allocating vformat");
155 // store the Barry object we're working with
156 m_BarryContact = con;
159 // begin building vCard data
162 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Contact Record//EN"));
164 std::string fullname = con.GetFullName();
165 if( fullname.size() ) {
166 AddAttr(NewAttr("FN", fullname.c_str()));
168 else {
170 // RFC 2426, 3.1.1 states that FN MUST be present in the
171 // vcard object. Unfortunately, the Blackberry doesn't
172 // require a name, only a name or company name.
174 // In this case we do nothing, and generate an invalid
175 // vcard, since if we try to fix our output here, we'll
176 // likely end up with duplicated company names in the
177 // Blackberry record after a few syncs.
181 if( con.FirstName.size() || con.LastName.size() ) {
182 vAttrPtr name = NewAttr("N"); // RFC 2426, 3.1.2
183 AddValue(name, con.LastName.c_str()); // Family Name
184 AddValue(name, con.FirstName.c_str()); // Given Name
185 AddValue(name, ""); // Additional Names
186 AddValue(name, con.Prefix.c_str()); // Honorific Prefixes
187 AddValue(name, ""); // Honorific Suffixes
188 AddAttr(name);
191 if( con.WorkAddress.HasData() )
192 AddAddress("work", con.WorkAddress);
193 if( con.HomeAddress.HasData() )
194 AddAddress("home", con.HomeAddress);
196 // add all applicable phone numbers... there can be multiple
197 // TEL fields, even with the same TYPE value... therefore, the
198 // second TEL field with a TYPE=work, will be stored in WorkPhone2
199 AddPhoneCond("pref", con.Phone);
200 AddPhoneCond("fax", con.Fax);
201 AddPhoneCond("work", con.WorkPhone);
202 AddPhoneCond("work", con.WorkPhone2);
203 AddPhoneCond("home", con.HomePhone);
204 AddPhoneCond("home", con.HomePhone2);
205 AddPhoneCond("cell", con.MobilePhone);
206 AddPhoneCond("pager", con.Pager);
207 AddPhoneCond(con.OtherPhone);
209 // add all email addresses, marking first one as "pref"
210 Barry::Contact::EmailList::const_iterator eai = con.EmailAddresses.begin();
211 for( unsigned int i = 0; eai != con.EmailAddresses.end(); ++eai, ++i ) {
212 const std::string& e = con.GetEmail(i);
213 if( e.size() ) {
214 vAttrPtr email = NewAttr("EMAIL", e.c_str());
215 if( i == 0 ) {
216 AddParam(email, "TYPE", "internet,pref");
218 else {
219 AddParam(email, "TYPE", "internet");
221 AddAttr(email);
225 if( con.JobTitle.size() ) {
226 AddAttr(NewAttr("TITLE", con.JobTitle.c_str()));
227 AddAttr(NewAttr("ROLE", con.JobTitle.c_str()));
230 // Image not supported, since vformat routines probably don't
231 // support binary VCARD fields....
233 if( con.Company.size() ) {
234 // RFC 2426, 3.5.5
235 vAttrPtr org = NewAttr("ORG", con.Company.c_str()); // Organization name
236 AddValue(org, ""); // Division name
237 AddAttr(org);
240 if( con.Birthday.HasData() )
241 AddAttr(NewAttr("BDAY", con.Birthday.ToYYYYMMDD().c_str()));
243 if( con.Notes.size() )
244 AddAttr(NewAttr("NOTE", con.Notes.c_str()));
245 if( con.URL.size() )
246 AddAttr(NewAttr("URL", con.URL.c_str()));
247 if( con.Categories.size() )
248 AddCategories(con.Categories);
250 // Image / Photo
251 if (con.Image.size()) {
252 vAttrPtr photo = NewAttr("PHOTO");
253 AddEncodedValue(photo, VF_ENCODING_BASE64, con.Image.c_str(), con.Image.size());
254 AddParam(photo, "ENCODING", "BASE64");
255 AddAttr(photo);
258 // generate the raw VCARD data
259 m_gCardData = b_vformat_to_string(Format(), VFORMAT_CARD_30);
260 m_vCardData = m_gCardData;
262 trace.logf("ToVCard, resulting vcard data: %s", m_vCardData.c_str());
263 return m_vCardData;
266 // Main conversion routine for converting from vCard data string
267 // to a Barry::Contact object.
268 const Barry::Contact& vCard::ToBarry(const char *vcard, uint32_t RecordId)
270 using namespace std;
272 Trace trace("vCard::ToBarry");
273 trace.logf("ToBarry, working on vcard data: %s", vcard);
275 // start fresh
276 Clear();
278 // store the vCard raw data
279 m_vCardData = vcard;
281 // create format parser structures
282 SetFormat( b_vformat_new_from_string(vcard) );
283 if( !Format() )
284 throw ConvertError("resource error allocating vformat");
288 // Parse the vcard data
291 Barry::Contact &con = m_BarryContact;
292 con.SetIds(Barry::Contact::GetDefaultRecType(), RecordId);
294 vAttr name = GetAttrObj("N");
295 if( name.Get() ) {
296 // RFC 2426, 3.1.2
297 con.LastName = name.GetValue(0); // Family Name
298 con.FirstName = name.GetValue(1); // Given Name
299 con.Prefix = name.GetValue(3); // Honorific Prefixes
302 vAttr adr = GetAttrObj("ADR");
303 for( int i = 0; adr.Get(); adr = GetAttrObj("ADR", ++i) )
305 std::string type = adr.GetAllParams("TYPE");
306 ToLower(type);
308 // do not use "else" here, since TYPE can have multiple keys
309 if( strstr(type.c_str(), "work") )
310 ParseAddress(adr, con.WorkAddress);
311 if( strstr(type.c_str(), "home") )
312 ParseAddress(adr, con.HomeAddress);
316 // add all applicable phone numbers... there can be multiple
317 // TEL fields, even with the same TYPE value... therefore, the
318 // second TEL field with a TYPE=work, will be stored in WorkPhone2
319 vAttr tel = GetAttrObj("TEL");
320 for( int i = 0; tel.Get(); tel = GetAttrObj("TEL", ++i) )
322 // grab all parameter values for this param name
323 std::string stype = tel.GetAllParams("TYPE");
325 // turn to lower case for comparison
326 // FIXME - is this i18n safe?
327 ToLower(stype);
329 // state
330 const char *type = stype.c_str();
331 bool used = false;
333 // Do not use "else" here, since TYPE can have multiple keys
334 // Instead, only fill in a field if it is empty
335 if( strstr(type, "pref") && con.Phone.size() == 0 ) {
336 con.Phone = tel.GetValue();
337 used = true;
340 if( strstr(type, "fax") && con.Fax.size() == 0 ) {
341 con.Fax = tel.GetValue();
342 used = true;
345 if( strstr(type, "work") ) {
346 if( con.WorkPhone.size() == 0 ) {
347 con.WorkPhone = tel.GetValue();
348 used = true;
350 else if( con.WorkPhone2.size() == 0 ) {
351 con.WorkPhone2 = tel.GetValue();
352 used = true;
356 if( strstr(type, "home") ) {
357 if( con.HomePhone.size() == 0 ) {
358 con.HomePhone = tel.GetValue();
359 used = true;
361 else if( con.HomePhone2.size() == 0 ) {
362 con.HomePhone2 = tel.GetValue();
363 used = true;
367 if( strstr(type, "cell") && con.MobilePhone.size() == 0 ) {
368 con.MobilePhone = tel.GetValue();
369 used = true;
372 if( (strstr(type, "pager") || strstr(type, "msg")) && con.Pager.size() == 0 ) {
373 con.Pager = tel.GetValue();
374 used = true;
377 // if this value has not been claimed by any of the
378 // cases above, claim it now as "OtherPhone"
379 if( !used && con.OtherPhone.size() == 0 ) {
380 con.OtherPhone = tel.GetValue();
383 // Final check: If Phone is blank, and OtherPhone has a value, swap.
384 if( con.OtherPhone.size() && !con.Phone.size() ) {
385 con.Phone.swap(con.OtherPhone);
388 // scan for all email addresses... append addresses to the
389 // list by default, but prepend if its type is set to "pref"
390 // i.e. we want the preferred email address first
391 vAttr email = GetAttrObj("EMAIL");
392 for( int i = 0; email.Get(); email = GetAttrObj("EMAIL", ++i) )
394 std::string type = email.GetAllParams("TYPE");
395 ToLower(type);
397 bool of_interest = (i == 0 || strstr(type.c_str(), "pref"));
398 bool x400 = strstr(type.c_str(), "x400");
400 if( of_interest && !x400 ) {
401 con.EmailAddresses.insert(con.EmailAddresses.begin(), email.GetValue());
403 else {
404 con.EmailAddresses.push_back( email.GetValue() );
408 // figure out which company title we want to keep...
409 // favour the TITLE field, but if it's empty, use ROLE
410 con.JobTitle = GetAttr("TITLE");
411 if( !con.JobTitle.size() )
412 con.JobTitle = GetAttr("ROLE");
414 con.Company = GetAttr("ORG");
415 con.Notes = GetAttr("NOTE");
416 con.URL = GetAttr("URL");
417 if( GetAttr("BDAY").size() && !con.Birthday.FromYYYYMMDD( GetAttr("BDAY") ) )
418 throw ConvertError("Unable to parse BDAY field");
420 // Photo vCard ?
421 vAttr photo = GetAttrObj("PHOTO");
422 if (photo.Get()) {
423 std::string sencoding = photo.GetAllParams("ENCODING");
425 ToLower(sencoding);
427 const char *encoding = sencoding.c_str();
429 if (strstr(encoding, "quoted-printable")) {
430 photo.Get()->encoding = VF_ENCODING_QP;
432 con.Image = photo.GetDecodedValue();
434 else if (strstr(encoding, "b")) {
435 photo.Get()->encoding = VF_ENCODING_BASE64;
437 con.Image = photo.GetDecodedValue();
439 // Else
440 // We ignore the photo, I don't know decoded !
443 vAttr cat = GetAttrObj("CATEGORIES");
444 if( cat.Get() )
445 ParseCategories(cat, con.Categories);
447 // Last sanity check: Blackberry requires that at least
448 // name or Company has data.
449 if( !con.GetFullName().size() && !con.Company.size() )
450 throw ConvertError("FN and ORG fields both blank in VCARD data");
452 return m_BarryContact;
455 // Transfers ownership of m_gCardData to the caller.
456 char* vCard::ExtractVCard()
458 char *ret = m_gCardData;
459 m_gCardData = 0;
460 return ret;
463 void vCard::Clear()
465 vBase::Clear();
466 m_vCardData.clear();
467 m_BarryContact.Clear();
469 if( m_gCardData ) {
470 g_free(m_gCardData);
471 m_gCardData = 0;
477 //////////////////////////////////////////////////////////////////////////////
480 VCardConverter::VCardConverter()
481 : m_Data(0)
485 VCardConverter::VCardConverter(uint32_t newRecordId)
486 : m_Data(0),
487 m_RecordId(newRecordId)
491 VCardConverter::~VCardConverter()
493 if( m_Data )
494 g_free(m_Data);
497 // Transfers ownership of m_Data to the caller
498 char* VCardConverter::ExtractData()
500 Trace trace("VCardConverter::ExtractData");
501 char *ret = m_Data;
502 m_Data = 0;
503 return ret;
506 bool VCardConverter::ParseData(const char *data)
508 Trace trace("VCardConverter::ParseData");
510 try {
512 vCard vcard;
513 m_Contact = vcard.ToBarry(data, m_RecordId);
516 catch( vCard::ConvertError &ce ) {
517 trace.logf("ERROR: vCard::ConvertError exception: %s", ce.what());
518 return false;
521 return true;
524 // Barry storage operator
525 void VCardConverter::operator()(const Barry::Contact &rec)
527 Trace trace("VCardConverter::operator()");
529 // Delete data if some already exists
530 if( m_Data ) {
531 g_free(m_Data);
532 m_Data = 0;
535 try {
537 vCard vcard;
538 vcard.ToVCard(rec);
539 m_Data = vcard.ExtractVCard();
542 catch( vCard::ConvertError &ce ) {
543 trace.logf("ERROR: vCard::ConvertError exception: %s", ce.what());
547 // Barry builder operator
548 bool VCardConverter::operator()(Barry::Contact &rec, unsigned int dbId)
550 Trace trace("VCardConverter::builder operator()");
552 rec = m_Contact;
553 return true;
556 // Handles calling of the Barry::Controller to fetch a specific
557 // record, indicated by index (into the RecordStateTable).
558 // Returns a g_malloc'd string of data containing the vcard30
559 // data. It is the responsibility of the caller to free it.
560 // This is intended to be passed into the GetChanges() function.
561 char* VCardConverter::GetRecordData(BarryEnvironment *env, unsigned int dbId,
562 Barry::RecordStateTable::IndexType index)
564 Trace trace("VCardConverter::GetRecordData()");
566 using namespace Barry;
568 VCardConverter contact2vcard;
569 RecordParser<Contact, VCardConverter> parser(contact2vcard);
570 env->m_pDesktop->GetRecord(dbId, index, parser);
571 return contact2vcard.ExtractData();
574 bool VCardConverter::CommitRecordData(BarryEnvironment *env, unsigned int dbId,
575 Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId,
576 const char *data, bool add, std::string &errmsg)
578 Trace trace("VCardConverter::CommitRecordData()");
580 uint32_t newRecordId;
581 if( add ) {
582 // use given id if possible
583 if( recordId && !env->m_ContactsSync.m_Table.GetIndex(recordId) ) {
584 // recordId is unique and non-zero
585 newRecordId = recordId;
587 else {
588 trace.log("Can't use recommended recordId, generating new one.");
589 newRecordId = env->m_ContactsSync.m_Table.MakeNewRecordId();
592 else {
593 newRecordId = env->m_ContactsSync.m_Table.StateMap[StateIndex].RecordId;
595 trace.logf("newRecordId: %lu", newRecordId);
597 VCardConverter convert(newRecordId);
598 if( !convert.ParseData(data) ) {
599 std::ostringstream oss;
600 oss << "unable to parse change data for new RecordId: "
601 << newRecordId << " data: " << data;
602 errmsg = oss.str();
603 trace.log(errmsg.c_str());
604 return false;
607 Barry::RecordBuilder<Barry::Contact, VCardConverter> builder(convert);
609 try {
610 if( add ) {
611 trace.log("adding record");
612 env->m_pDesktop->AddRecord(dbId, builder);
614 else {
615 trace.log("setting record");
616 env->m_pDesktop->SetRecord(dbId, StateIndex, builder);
617 trace.log("clearing dirty flag");
618 env->m_pDesktop->ClearDirty(dbId, StateIndex);
621 catch (Barry::Error &e ) {
622 trace.logf("ERROR: VCardConverter::CommitRecordData - Format error");
623 return false;
626 return true;