- added Category support to the opensync plugin
[barry.git] / opensync-plugin / src / vcard.cc
blobb6cc7192b499f03a77da2173e67c6597e130e5fa
1 ///
2 /// \file vcard.cc
3 /// Conversion routines for vcards
4 ///
6 /*
7 Copyright (C) 2006-2007, 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 <sstream>
29 #include <ctype.h>
32 //////////////////////////////////////////////////////////////////////////////
33 // Utility functions
35 void ToLower(std::string &str)
37 size_t i = 0;
38 while( i < str.size() ) {
39 str[i] = tolower(str[i]);
40 i++;
44 //////////////////////////////////////////////////////////////////////////////
45 // vCard
47 vCard::vCard()
48 : m_gCardData(0)
52 vCard::~vCard()
54 if( m_gCardData ) {
55 g_free(m_gCardData);
59 void vCard::AddAddress(const char *rfc_type, const Barry::PostalAddress &address)
61 // add label first
62 vAttrPtr label = NewAttr("LABEL");
63 AddParam(label, "TYPE", rfc_type);
64 AddValue(label, address.GetLabel().c_str());
65 AddAttr(label);
67 // add breakout address form
68 vAttrPtr adr = NewAttr("ADR"); // RFC 2426, 3.2.1
69 AddParam(adr, "TYPE", rfc_type);
70 AddValue(adr, address.Address3.c_str()); // PO Box
71 AddValue(adr, address.Address2.c_str()); // Extended address
72 AddValue(adr, address.Address1.c_str()); // Street address
73 AddValue(adr, address.City.c_str()); // Locality (city)
74 AddValue(adr, address.Province.c_str()); // Region (province)
75 AddValue(adr, address.PostalCode.c_str()); // Postal code
76 AddValue(adr, address.Country.c_str()); // Country name
77 AddAttr(adr);
80 void vCard::AddCategories(const Barry::CategoryList &categories)
82 if( !categories.size() )
83 return;
85 vAttrPtr cat = NewAttr("CATEGORIES"); // RFC 2426, 3.6.1
86 Barry::CategoryList::const_iterator i = categories.begin();
87 for( ; i < categories.end(); ++i ) {
88 AddValue(cat, i->c_str());
90 AddAttr(cat);
93 /// Add phone conditionally, only if phone has data in it
94 void vCard::AddPhoneCond(const char *rfc_type, const std::string &phone)
96 if( phone.size() ) {
97 vAttrPtr tel = NewAttr("TEL", phone.c_str());
98 AddParam(tel, "TYPE", rfc_type);
99 AddAttr(tel);
103 void vCard::ParseAddress(vAttr &adr, Barry::PostalAddress &address)
105 // RFC 2426, 3.2.1
106 address.Address3 = adr.GetValue(0); // PO Box
107 address.Address2 = adr.GetValue(1); // Extended address
108 address.Address1 = adr.GetValue(2); // Street address
109 address.City = adr.GetValue(3); // Locality (city)
110 address.Province = adr.GetValue(4); // Region (province)
111 address.PostalCode = adr.GetValue(5); // Postal code
112 address.Country = adr.GetValue(6); // Country name
115 void vCard::ParseCategories(vAttr &cat, Barry::CategoryList &cats)
117 int i = 0;
118 std::string value = cat.GetValue(i);
119 while( value.size() ) {
120 cats.push_back(value);
121 i++;
122 value = cat.GetValue(i);
126 // Main conversion routine for converting from Barry::Contact to
127 // a vCard string of data.
128 const std::string& vCard::ToVCard(const Barry::Contact &con)
130 Trace trace("vCard::ToVCard");
131 std::ostringstream oss;
132 con.Dump(oss);
133 trace.logf("ToVCard, initial Barry record: %s", oss.str().c_str());
135 // start fresh
136 Clear();
137 SetFormat( vformat_new() );
138 if( !Format() )
139 throw ConvertError("resource error allocating vformat");
141 // store the Barry object we're working with
142 m_BarryContact = con;
145 // begin building vCard data
148 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Contact Record//EN"));
150 std::string fullname = con.GetFullName();
151 if( fullname.size() )
152 AddAttr(NewAttr("FN", fullname.c_str()));
154 if( con.FirstName.size() || con.LastName.size() ) {
155 vAttrPtr name = NewAttr("N"); // RFC 2426, 3.1.2
156 AddValue(name, con.LastName.c_str()); // Family Name
157 AddValue(name, con.FirstName.c_str()); // Given Name
158 AddValue(name, ""); // Additional Names
159 AddValue(name, con.Prefix.c_str()); // Honorific Prefixes
160 AddValue(name, ""); // Honorific Suffixes
161 AddAttr(name);
164 if( con.WorkAddress.HasData() )
165 AddAddress("work", con.WorkAddress);
166 if( con.HomeAddress.HasData() )
167 AddAddress("home", con.HomeAddress);
169 // add all applicable phone numbers... can't add the WorkPhone2
170 // since VCARD30 TEL fields only take one number, as far as I know
171 AddPhoneCond("pref", con.Phone);
172 AddPhoneCond("fax", con.Fax);
173 AddPhoneCond("work", con.WorkPhone);
174 AddPhoneCond("home", con.HomePhone);
175 AddPhoneCond("cell", con.MobilePhone);
176 AddPhoneCond("msg", con.Pager);
178 if( con.Email.size() ) {
179 vAttrPtr email = NewAttr("EMAIL", con.Email.c_str());
180 AddParam(email, "TYPE", "internet");
181 AddAttr(email);
184 if( con.JobTitle.size() ) {
185 AddAttr(NewAttr("TITLE", con.JobTitle.c_str()));
186 AddAttr(NewAttr("ROLE", con.JobTitle.c_str()));
189 // Image not supported, since vformat routines probably don't
190 // support binary VCARD fields....
192 if( con.Company.size() ) {
193 // RFC 2426, 3.5.5
194 vAttrPtr org = NewAttr("ORG", con.Company.c_str()); // Organization name
195 AddValue(org, ""); // Division name
196 AddAttr(org);
199 if( con.Notes.size() )
200 AddAttr(NewAttr("NOTE", con.Notes.c_str()));
201 if( con.URL.size() )
202 AddAttr(NewAttr("URL", con.URL.c_str()));
203 if( con.Categories.size() )
204 AddCategories(con.Categories);
206 // generate the raw VCARD data
207 m_gCardData = vformat_to_string(Format(), VFORMAT_CARD_30);
208 m_vCardData = m_gCardData;
210 trace.logf("ToVCard, resulting vcard data: %s", m_vCardData.c_str());
211 return m_vCardData;
214 // Main conversion routine for converting from vCard data string
215 // to a Barry::Contact object.
216 const Barry::Contact& vCard::ToBarry(const char *vcard, uint32_t RecordId)
218 using namespace std;
220 Trace trace("vCard::ToBarry");
221 trace.logf("ToBarry, working on vcard data: %s", vcard);
223 // start fresh
224 Clear();
226 // store the vCard raw data
227 m_vCardData = vcard;
229 // create format parser structures
230 SetFormat( vformat_new_from_string(vcard) );
231 if( !Format() )
232 throw ConvertError("resource error allocating vformat");
236 // Parse the vcard data
239 Barry::Contact &con = m_BarryContact;
240 con.SetIds(Barry::Contact::GetDefaultRecType(), RecordId);
242 vAttr name = GetAttrObj("N");
243 if( !name.Get() )
244 throw ConvertError("no FN field in VCARD data");
245 // RFC 2426, 3.1.2
246 con.LastName = name.GetValue(0); // Family Name
247 con.FirstName = name.GetValue(1); // Given Name
248 con.Prefix = name.GetValue(3); // Honorific Prefixes
250 vAttr adr = GetAttrObj("ADR");
251 for( int i = 0; adr.Get(); adr = GetAttrObj("ADR", ++i) )
253 std::string type = adr.GetParam("TYPE");
254 ToLower(type);
256 // do not use "else" here, since TYPE can have multiple keys
257 if( strstr(type.c_str(), "work") )
258 ParseAddress(adr, con.WorkAddress);
259 if( strstr(type.c_str(), "home") )
260 ParseAddress(adr, con.HomeAddress);
264 // add all applicable phone numbers... can't add the WorkPhone2
265 // since VCARD30 TEL fields only take one number, as far as I know
266 vAttr tel = GetAttrObj("TEL");
267 for( int i = 0; tel.Get(); tel = GetAttrObj("TEL", ++i) )
269 std::string stype = tel.GetParam("TYPE");
270 ToLower(stype);
271 const char *type = stype.c_str();
273 // do not use "else" here, since TYPE can have multiple keys
274 if( strstr(type, "pref") )
275 con.Phone = tel.GetValue();
276 if( strstr(type, "fax") )
277 con.Fax = tel.GetValue();
278 if( strstr(type, "work") )
279 con.WorkPhone = tel.GetValue();
280 if( strstr(type, "home") )
281 con.HomePhone = tel.GetValue();
282 if( strstr(type, "cell") )
283 con.MobilePhone = tel.GetValue();
284 if( strstr(type, "msg") )
285 con.Pager = tel.GetValue();
288 // scan for all email addresses... save the first one found
289 // by default, then overwrite it with any following email
290 // address if its type is set to "pref"... i.e. we want
291 // the preferred email address here.
292 vAttr email = GetAttrObj("EMAIL");
293 for( int i = 0; email.Get(); email = GetAttrObj("EMAIL", ++i) )
295 std::string type = email.GetParam("TYPE");
296 ToLower(type);
298 bool of_interest = (i == 0 || strstr(type.c_str(), "pref"));
299 bool x400 = strstr(type.c_str(), "x400");
301 if( of_interest && !x400 ) {
302 con.Email = GetAttr("EMAIL");
306 // figure out which company title we want to keep...
307 // favour the TITLE field, but if it's empty, use ROLE
308 con.JobTitle = GetAttr("TITLE");
309 if( !con.JobTitle.size() )
310 con.JobTitle = GetAttr("ROLE");
312 con.Company = GetAttr("ORG");
313 con.Notes = GetAttr("NOTE");
314 con.URL = GetAttr("URL");
316 vAttr cat = GetAttrObj("CATEGORIES");
317 if( cat.Get() )
318 ParseCategories(cat, con.Categories);
320 return m_BarryContact;
323 // Transfers ownership of m_gCardData to the caller.
324 char* vCard::ExtractVCard()
326 char *ret = m_gCardData;
327 m_gCardData = 0;
328 return ret;
331 void vCard::Clear()
333 vBase::Clear();
334 m_vCardData.clear();
335 m_BarryContact.Clear();
337 if( m_gCardData ) {
338 g_free(m_gCardData);
339 m_gCardData = 0;
345 //////////////////////////////////////////////////////////////////////////////
348 VCardConverter::VCardConverter()
349 : m_Data(0)
353 VCardConverter::VCardConverter(uint32_t newRecordId)
354 : m_Data(0),
355 m_RecordId(newRecordId)
359 VCardConverter::~VCardConverter()
361 if( m_Data )
362 g_free(m_Data);
365 // Transfers ownership of m_Data to the caller
366 char* VCardConverter::ExtractData()
368 Trace trace("VCardConverter::ExtractData");
369 char *ret = m_Data;
370 m_Data = 0;
371 return ret;
374 bool VCardConverter::ParseData(const char *data)
376 Trace trace("VCardConverter::ParseData");
378 try {
380 vCard vcard;
381 m_Contact = vcard.ToBarry(data, m_RecordId);
384 catch( vCard::ConvertError &ce ) {
385 trace.logf("ERROR: vCard::ConvertError exception: %s", ce.what());
386 return false;
389 return true;
392 // Barry storage operator
393 void VCardConverter::operator()(const Barry::Contact &rec)
395 Trace trace("VCardConverter::operator()");
397 // Delete data if some already exists
398 if( m_Data ) {
399 g_free(m_Data);
400 m_Data = 0;
403 try {
405 vCard vcard;
406 vcard.ToVCard(rec);
407 m_Data = vcard.ExtractVCard();
410 catch( vCard::ConvertError &ce ) {
411 trace.logf("ERROR: vCard::ConvertError exception: %s", ce.what());
415 // Barry builder operator
416 bool VCardConverter::operator()(Barry::Contact &rec, unsigned int dbId)
418 Trace trace("VCardConverter::builder operator()");
420 rec = m_Contact;
421 return true;
424 // Handles calling of the Barry::Controller to fetch a specific
425 // record, indicated by index (into the RecordStateTable).
426 // Returns a g_malloc'd string of data containing the vcard30
427 // data. It is the responsibility of the caller to free it.
428 // This is intended to be passed into the GetChanges() function.
429 char* VCardConverter::GetRecordData(BarryEnvironment *env, unsigned int dbId,
430 Barry::RecordStateTable::IndexType index)
432 Trace trace("VCardConverter::GetRecordData()");
434 using namespace Barry;
436 VCardConverter contact2vcard;
437 RecordParser<Contact, VCardConverter> parser(contact2vcard);
438 env->m_pCon->GetRecord(dbId, index, parser);
439 return contact2vcard.ExtractData();
442 bool VCardConverter::CommitRecordData(BarryEnvironment *env, unsigned int dbId,
443 Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId,
444 const char *data, bool add, std::string &errmsg)
446 Trace trace("VCardConverter::CommitRecordData()");
448 uint32_t newRecordId;
449 if( add ) {
450 // use given id if possible
451 if( recordId && !env->m_ContactsSync.m_Table.GetIndex(recordId) ) {
452 // recordId is unique and non-zero
453 newRecordId = recordId;
455 else {
456 trace.log("Can't use recommended recordId, generating new one.");
457 newRecordId = env->m_ContactsSync.m_Table.MakeNewRecordId();
460 else {
461 newRecordId = env->m_ContactsSync.m_Table.StateMap[StateIndex].RecordId;
463 trace.logf("newRecordId: %lu", newRecordId);
465 VCardConverter convert(newRecordId);
466 if( !convert.ParseData(data) ) {
467 std::ostringstream oss;
468 oss << "unable to parse change data for new RecordId: "
469 << newRecordId << " data: " << data;
470 errmsg = oss.str();
471 trace.logf(errmsg.c_str());
472 return false;
475 Barry::RecordBuilder<Barry::Contact, VCardConverter> builder(convert);
477 if( add ) {
478 trace.log("adding record");
479 env->m_pCon->AddRecord(dbId, builder);
481 else {
482 trace.log("setting record");
483 env->m_pCon->SetRecord(dbId, StateIndex, builder);
484 trace.log("clearing dirty flag");
485 env->m_pCon->ClearDirty(dbId, StateIndex);
488 return true;