(Pardon my dust if I'm not following patch protocol exactly)
Attached is a patch file, and the associated files that were changed, to
support birthday in the Barry::Contact and for syncing in the opensync
plugin.
Regarding OpenSync... it seems that adding support for a new field does
not automatically cause that new data to be sync'd... makes sense
because it doesn't appear as though the record changed.
To see the birthdays I had in evo-2 show up, I had to clear my BB
contacts and the opensync group and start over to get the birthdays
transferred.
-Lee
On Wed, 2008-08-06 at 17:30 -0400, Chris Frey wrote:
> On Wed, Aug 06, 2008 at 03:30:14PM -0400, Lee Dixon wrote:
> > Hi all!
> > First of all, BIG HUGE THANKS for Barry. I use it to sync with evo2 and
> > things work pretty dang well. I am using Barry 0.13 and OpenSync 0.22
> > on Gutsy with a Blackberry Pearl 8130.
> >
> > Of course, as a programmer, I want to improve and fix stuff. I found a
> > couple issues and have some resolutions:
>
> Welcome to Barry! Glad to hear Barry is working well for you.
>
>
> > 1) Endless loop in vCard::ParseCategories()
> > I'm surprised that no one else has seen this: if I have an evo contact
> > with a category set, then the call to vAttr.GetValue(index) will keep
> > returning the same value as the index increases unbounded. This causes
> > an endless loop and unbounded memory consumption, causing the OS to
> > gobble up the swap and the machine slows heavily. I put a simple check
> > in to make sure the value returned doesn't match the previous one, but I
> > don't think that is the long-term answer. vAttr should be fixed.
> > Anybody else see this?
>
> Well, a huge thanks right back at you! :-) People have been reporting this
> memory consumption bug for a while, and I haven't been able to find it.
> With this report, I've added the one-liner fix to vbase.cc, and it is
> now in the latest CVS (and git).
>
>
> > 2) No support for Birthday field. I added this quite easily thanks to
> > debug data from btool. Unfortunately, birthdays don't show up in the
> > Blackberry calendar like they do when you *author* the birthday entry on
> > the device.
> >
> > 3) No anniversary date support. I think I can add Anniversary support
> > too like Birthday.
>
> Please send a patch with what you have, so we can see and discuss it better.
> I've found that sometimes calendar records tend to use special flags,
> for recurring, "all day event", etc. Birthday and Anniversary might just
> be flags similar to this.
>
>
> > 4) I did a calendar sync and recurring events didn't seem to work right.
> > In vevent.cc I see:
> > void vCalendar::RecurToBarryCal()
> > {
> > // FIXME - needs to be implemented
> >
> > // GetWeekDayIndex()
> > }
> >
> > But I don't see this in the TODO list from CVS. Anybody else working
> > this? Can I help out?
>
> I've added this to the TODO list. In fact, it is very safe to assume that
> anything in the code marked "FIXME" is a todo list item by default. :-)
>
> And yes you can definitely help out! No one else is working on the
> calendar part of the plugin right now.
>
> You can send patches to the mailing list, or you can use the Barry
> git repository if you prefer working that way, and then let me know
> where to pull from. You can find more info here:
>
> http://www.netdirect.ca/software/packages/barry/patches.php
>
> - Chris
>
>
> -------------------------------------------------------------------------
> This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
> Build the coolest Linux based applications with Moblin SDK & win great prizes
> Grand prize is a trip for two to an Open Source event anywhere in the world
> http://moblin-contest.org/redirect.php?banner_id=100&url=/
> _______________________________________________
> Barry-devel mailing list
> Barry-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/barry-devel
diff -Naur barry-0.13.old/opensync-plugin/src/vcard.cc barry-0.13.new/opensync-plugin/src/vcard.cc
--- barry-0.13.old/opensync-plugin/src/vcard.cc 2008-07-24 18:20:32.000000000 -0400
+++ barry-0.13.new/opensync-plugin/src/vcard.cc 2008-08-08 17:29:56.000000000 -0400
@@ -124,6 +124,43 @@
}
}
+
+// Parse a Barry date in the form of DD/MM/YY into the format
+// for VCard YYYYMMDD
+std::string vCard::ParseBarryDate( const std::string& s )
+{
+ std::string dateStr;
+ int m, d, y;
+ if( 3==sscanf( s.c_str(), "%d/%d/%d", &d, &m, &y ) )
+ {
+ char buf[32];
+ sprintf( buf, "%4d%02d%02d", y, m, d );
+ dateStr.assign( buf );
+ }
+ return dateStr;
+}
+
+
+// Parse a VCard date in the form of YYYYMMDD into the format
+// for Barry DD/MM/YY
+std::string vCard::ParseVCardDate( const std::string& s )
+{
+ std::string dateStr;
+ if( s.size() )
+ {
+ int m, d, y;
+ if( 3==sscanf( s.c_str(), "%4d%2d%2d", &y, &m, &d ) )
+ {
+ char buf[32];
+ sprintf( buf, "%02d/%02d/%04d", d, m, y );
+ dateStr.assign( buf );
+ }
+ }
+ return dateStr;
+}
+
+
+
// Main conversion routine for converting from Barry::Contact to
// a vCard string of data.
const std::string& vCard::ToVCard(const Barry::Contact &con)
@@ -207,6 +244,9 @@
AddAttr(org);
}
+ if( con.Birthday.size() )
+ AddAttr(NewAttr("BDAY", ParseBarryDate( con.Birthday ).c_str() ));
+
if( con.Notes.size() )
AddAttr(NewAttr("NOTE", con.Notes.c_str()));
if( con.URL.size() )
@@ -325,6 +365,7 @@
con.Company = GetAttr("ORG");
con.Notes = GetAttr("NOTE");
con.URL = GetAttr("URL");
+ con.Birthday = ParseVCardDate( GetAttr("BDAY") );
vAttr cat = GetAttrObj("CATEGORIES");
if( cat.Get() )
diff -Naur barry-0.13.old/opensync-plugin/src/vcard.h barry-0.13.new/opensync-plugin/src/vcard.h
--- barry-0.13.old/opensync-plugin/src/vcard.h 2008-01-25 22:57:31.000000000 -0500
+++ barry-0.13.new/opensync-plugin/src/vcard.h 2008-08-08 17:30:03.000000000 -0400
@@ -55,6 +55,8 @@
void ParseAddress(vAttr &adr, Barry::PostalAddress &address);
void ParseCategories(vAttr &cat, Barry::CategoryList &cats);
+ std::string ParseBarryDate( const std::string& s );
+ std::string ParseVCardDate( const std::string& s );
public:
vCard();
~vCard();
diff -Naur barry-0.13.old/src/r_contact.cc barry-0.13.new/src/r_contact.cc
--- barry-0.13.old/src/r_contact.cc 2008-07-24 18:20:30.000000000 -0400
+++ barry-0.13.new/src/r_contact.cc 2008-08-08 17:30:25.000000000 -0400
@@ -90,6 +90,7 @@
#define CFC_HOME_POSTAL_CODE 71 // 0x47
#define CFC_HOME_COUNTRY 72 // 0x48
#define CFC_IMAGE 77 // 0x4D
+#define CFC_BIRTHDAY 82 // 0x52
#define CFC_INVALID_FIELD 255
// Contact code to field table
@@ -131,6 +132,7 @@
{ CFC_HOME_POSTAL_CODE, "HomePostalCode", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::PostalCode, },
{ CFC_HOME_COUNTRY, "HomeCountry",0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Country, },
{ CFC_IMAGE, "Image", 0,0, &Contact::Image, 0, 0 },
+ { CFC_BIRTHDAY, "Birthday", 0,0, &Contact::Birthday, 0, 0 },
{ CFC_INVALID_FIELD,"EndOfList", 0, 0, 0 }
};
@@ -363,6 +365,7 @@
UserDefined3.clear();
UserDefined4.clear();
Image.clear();
+ Birthday.clear();
WorkAddress.Clear();
HomeAddress.Clear();
diff -Naur barry-0.13.old/src/r_contact.h barry-0.13.new/src/r_contact.h
--- barry-0.13.old/src/r_contact.h 2008-07-24 18:20:30.000000000 -0400
+++ barry-0.13.new/src/r_contact.h 2008-08-08 17:30:16.000000000 -0400
@@ -92,7 +92,8 @@
UserDefined2,
UserDefined3,
UserDefined4,
- Image;
+ Image,
+ Birthday;
PostalAddress WorkAddress;
PostalAddress HomeAddress;
///
/// \file r_contact.cc
/// Blackberry database record parser class for contact records.
///
/*
Copyright (C) 2005-2008, Net Direct Inc. (http://www.netdirect.ca/)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License in the COPYING file at the
root directory of this project for more details.
*/
#include "r_contact.h"
#include "record-internal.h"
#include "protocol.h"
#include "protostructs.h"
#include "data.h"
#include "time.h"
#include "error.h"
#include "endian.h"
#include <ostream>
#include <iomanip>
#include <time.h>
#include <stdexcept>
#define __DEBUG_MODE__
#include "debug.h"
using namespace std;
using namespace Barry::Protocol;
namespace Barry {
///////////////////////////////////////////////////////////////////////////////
// Contact class
// Contact field codes
#define CFC_EMAIL 1
#define CFC_PHONE 2
#define CFC_FAX 3
#define CFC_WORK_PHONE 6
#define CFC_HOME_PHONE 7
#define CFC_MOBILE_PHONE 8
#define CFC_PAGER 9
#define CFC_PIN 10
#define CFC_RADIO 14 // 0x0e
#define CFC_WORK_PHONE_2 16 // 0x10
#define CFC_HOME_PHONE_2 17 // 0x11
#define CFC_OTHER_PHONE 18 // 0x12
#define CFC_NAME 32 // 0x20 used twice, in first/last name order
#define CFC_COMPANY 33
#define CFC_DEFAULT_COMM_METHOD 34
#define CFC_ADDRESS1 35
#define CFC_ADDRESS2 36
#define CFC_ADDRESS3 37
#define CFC_CITY 38
#define CFC_PROVINCE 39
#define CFC_POSTAL_CODE 40
#define CFC_COUNTRY 41
#define CFC_TITLE 42 // 0x2a
#define CFC_PUBLIC_KEY 43
#define CFC_GROUP_FLAG 44
#define CFC_GROUP_LINK 52
#define CFC_URL 54 // 0x36
#define CFC_PREFIX 55 // 0x37
#define CFC_CATEGORY 59 // 0x3B
#define CFC_HOME_ADDRESS1 61 // 0x3D
#define CFC_HOME_ADDRESS2 62 // 0x3E
// If the address 3 isn't mapped then it appears
// in the same field as address2 with a space
#define CFC_HOME_ADDRESS3 63 // 0x3F
#define CFC_NOTES 64 // 0x40
#define CFC_USER_DEFINED_1 65 // 0x41
#define CFC_USER_DEFINED_2 66 // 0x42
#define CFC_USER_DEFINED_3 67 // 0x43
#define CFC_USER_DEFINED_4 68 // 0x44
#define CFC_HOME_CITY 69 // 0x45
#define CFC_HOME_PROVINCE 70 // 0x46
#define CFC_HOME_POSTAL_CODE 71 // 0x47
#define CFC_HOME_COUNTRY 72 // 0x48
#define CFC_IMAGE 77 // 0x4D
#define CFC_BIRTHDAY 82 // 0x52
#define CFC_INVALID_FIELD 255
// Contact code to field table
FieldLink<Contact> ContactFieldLinks[] = {
{ CFC_PHONE, "Phone", 0,0, &Contact::Phone, 0, 0 },
{ CFC_FAX, "Fax", "facsimileTelephoneNumber",0, &Contact::Fax, 0, 0 },
{ CFC_WORK_PHONE, "WorkPhone", "telephoneNumber",0, &Contact::WorkPhone, 0, 0 },
{ CFC_HOME_PHONE, "HomePhone", "homePhone",0, &Contact::HomePhone, 0, 0 },
{ CFC_MOBILE_PHONE, "MobilePhone","mobile",0, &Contact::MobilePhone, 0, 0 },
{ CFC_PAGER, "Pager", "pager",0, &Contact::Pager, 0, 0 },
{ CFC_PIN, "PIN", 0,0, &Contact::PIN, 0, 0 },
{ CFC_RADIO, "Radio", 0,0, &Contact::Radio, 0, 0 },
{ CFC_WORK_PHONE_2, "WorkPhone2", 0,0, &Contact::WorkPhone2, 0, 0 },
{ CFC_HOME_PHONE_2, "HomePhone2", 0,0, &Contact::HomePhone2, 0, 0 },
{ CFC_OTHER_PHONE, "OtherPhone", 0,0, &Contact::OtherPhone, 0, 0 },
{ CFC_COMPANY, "Company", "o",0, &Contact::Company, 0, 0 },
{ CFC_DEFAULT_COMM_METHOD,"DefaultCommMethod",0,0, &Contact::DefaultCommunicationsMethod, 0, 0 },
{ CFC_ADDRESS1, "WorkAddress1", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address1 },
{ CFC_ADDRESS2, "WorkAddress2", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address2 },
{ CFC_ADDRESS3, "WorkAddress3", 0,0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Address3 },
{ CFC_CITY, "WorkCity", "l",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::City },
{ CFC_PROVINCE, "WorkProvince", "st",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Province },
{ CFC_POSTAL_CODE, "WorkPostalCode", "postalCode",0, 0, 0, 0, &Contact::WorkAddress, &PostalAddress::PostalCode },
{ CFC_COUNTRY, "WorkCountry", "c", "country", 0, 0, 0, &Contact::WorkAddress, &PostalAddress::Country },
{ CFC_TITLE, "JobTitle", "title",0, &Contact::JobTitle, 0, 0 },
{ CFC_PUBLIC_KEY, "PublicKey", 0,0, &Contact::PublicKey, 0, 0 },
{ CFC_URL, "URL", 0,0, &Contact::URL, 0, 0 },
{ CFC_PREFIX, "Prefix", 0,0, &Contact::Prefix, 0, 0 },
{ CFC_HOME_ADDRESS1,"HomeAddress1", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address1, },
{ CFC_HOME_ADDRESS2,"HomeAddress2", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address2, },
{ CFC_HOME_ADDRESS3,"HomeAddress3", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Address3, },
{ CFC_NOTES, "Notes", 0,0, &Contact::Notes, 0, 0 },
{ CFC_USER_DEFINED_1, "UserDefined1", 0,0, &Contact::UserDefined1, 0, 0 },
{ CFC_USER_DEFINED_2, "UserDefined2", 0,0, &Contact::UserDefined2, 0, 0 },
{ CFC_USER_DEFINED_3, "UserDefined3", 0,0, &Contact::UserDefined3, 0, 0 },
{ CFC_USER_DEFINED_4, "UserDefined4", 0,0, &Contact::UserDefined4, 0, 0 },
{ CFC_HOME_CITY, "HomeCity", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::City, },
{ CFC_HOME_PROVINCE,"HomeProvince", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Province, },
{ CFC_HOME_POSTAL_CODE, "HomePostalCode", 0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::PostalCode, },
{ CFC_HOME_COUNTRY, "HomeCountry",0,0, 0, 0, 0, &Contact::HomeAddress, &PostalAddress::Country, },
{ CFC_IMAGE, "Image", 0,0, &Contact::Image, 0, 0 },
{ CFC_BIRTHDAY, "Birthday", 0,0, &Contact::Birthday, 0, 0 },
{ CFC_INVALID_FIELD,"EndOfList", 0, 0, 0 }
};
Contact::Contact()
: RecType(Contact::GetDefaultRecType()),
RecordId(0),
m_FirstNameSeen(false)
{
}
Contact::~Contact()
{
}
const unsigned char* Contact::ParseField(const unsigned char *begin,
const unsigned char *end)
{
const CommonField *field = (const CommonField *) begin;
// advance and check size
begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
if( begin > end ) // if begin==end, we are ok
return begin;
if( !btohs(field->size) ) // if field has no size, something's up
return begin;
// cycle through the type table
for( FieldLink<Contact> *b = ContactFieldLinks;
b->type != CFC_INVALID_FIELD;
b++ )
{
if( b->type == field->type ) {
if( b->strMember ) {
std::string &s = this->*(b->strMember);
s = ParseFieldString(field);
return begin; // done!
}
else if( b->postMember && b->postField ) {
std::string &s = (this->*(b->postMember)).*(b->postField);
s = ParseFieldString(field);
return begin;
}
else {
break; // fall through to special handling
}
}
}
// if not found in the type table, check for special handling
switch( field->type )
{
case CFC_EMAIL: {
EmailAddresses.push_back( ParseFieldString(field) );
}
return begin;
case CFC_NAME: {
// can be used multiple times, for first/last names
std::string *name;
if( FirstName.size() || m_FirstNameSeen ) {
// first name already filled, use last name
name = &LastName;
m_FirstNameSeen = false;
}
else {
name = &FirstName;
m_FirstNameSeen = true;
}
*name = ParseFieldString(field);
}
return begin;
case CFC_GROUP_LINK:
// just add the unique ID to the list
GroupLinks.push_back(
GroupLink(field->u.link.uniqueId,
field->u.link.unknown));
return begin;
case CFC_GROUP_FLAG:
// ignore the group flag... the presense of group link items
// behaves as the flag in this class
return begin;
case CFC_CATEGORY: {
std::string catstring = ParseFieldString(field);
CategoryStr2List(catstring, Categories);
}
return begin;
}
// if still not handled, add to the Unknowns list
UnknownField uf;
uf.type = field->type;
uf.data.assign((const char*)field->u.raw, btohs(field->size));
Unknowns.push_back(uf);
// return new pointer for next field
return begin;
}
void Contact::ParseHeader(const Data &data, size_t &offset)
{
// no header to parse in Contact records
}
// this is called by the RecordParser<> class, which checks size for us
void Contact::ParseFields(const Data &data, size_t &offset)
{
const unsigned char *finish = ParseCommonFields(*this,
data.GetData() + offset, data.GetData() + data.GetSize());
offset += finish - (data.GetData() + offset);
}
void Contact::BuildHeader(Data &data, size_t &offset) const
{
// no header in Contact records
}
//
// BuildFields
//
/// Build fields part of record
///
void Contact::BuildFields(Data &data, size_t &offset) const
{
data.Zap();
// check if this is a group link record, and if so, output
// the group flag
if( GroupLinks.size() )
BuildField(data, offset, CFC_GROUP_FLAG, 'G');
// special fields not in type table
if( FirstName.size() )
BuildField(data, offset, CFC_NAME, FirstName);
if( LastName.size() ) {
if( !FirstName.size() ) {
// order matters with first/last name, and if
// last name exists, and first name doesn't,
// insert blank first name ahead of it
BuildField(data, offset, CFC_NAME, FirstName);
}
BuildField(data, offset, CFC_NAME, LastName);
}
// add all email addresses
EmailList::const_iterator eai = EmailAddresses.begin();
for( ; eai != EmailAddresses.end(); ++eai ) {
if( eai->size() ) {
BuildField(data, offset, CFC_EMAIL, *eai);
}
}
// cycle through the type table
for( FieldLink<Contact> *b = ContactFieldLinks;
b->type != CFC_INVALID_FIELD;
b++ )
{
// print only fields with data
if( b->strMember ) {
const std::string &field = this->*(b->strMember);
if( field.size() ) {
BuildField(data, offset, b->type, field);
}
}
else if( b->postMember && b->postField ) {
const std::string &field = (this->*(b->postMember)).*(b->postField);
if( field.size() ) {
BuildField(data, offset, b->type, field);
}
}
}
// save any group links
GroupLinksType::const_iterator
gb = GroupLinks.begin(), ge = GroupLinks.end();
for( ; gb != ge; gb++ ) {
Barry::Protocol::GroupLink link;
link.uniqueId = htobl(gb->Link);
link.unknown = htobs(gb->Unknown);
BuildField(data, offset, CFC_GROUP_LINK, link);
}
if( Categories.size() ) {
string store;
CategoryList2Str(Categories, store);
BuildField(data, offset, CFC_CATEGORY, store);
}
// and finally save unknowns
UnknownsType::const_iterator
ub = Unknowns.begin(), ue = Unknowns.end();
for( ; ub != ue; ub++ ) {
BuildField(data, offset, *ub);
}
data.ReleaseBuffer(offset);
}
void Contact::Clear()
{
RecType = Contact::GetDefaultRecType();
EmailAddresses.clear();
Phone.clear();
Fax.clear();
WorkPhone.clear();
HomePhone.clear();
MobilePhone.clear();
Pager.clear();
PIN.clear();
Radio.clear();
WorkPhone2.clear();
HomePhone2.clear();
OtherPhone.clear();
FirstName.clear();
LastName.clear();
Company.clear();
DefaultCommunicationsMethod.clear();
JobTitle.clear();
PublicKey.clear();
URL.clear();
Prefix.clear();
Notes.clear();
UserDefined1.clear();
UserDefined2.clear();
UserDefined3.clear();
UserDefined4.clear();
Image.clear();
Birthday.clear();
WorkAddress.Clear();
HomeAddress.Clear();
Categories.clear();
GroupLinks.clear();
Unknowns.clear();
m_FirstNameSeen = false;
}
//
// GetFullName
//
/// Helper function that returns a formatted full name
///
std::string Contact::GetFullName() const
{
std::string Full = FirstName;
if( Full.size() && LastName.size() )
Full += " ";
Full += LastName;
return Full;
}
//
// GetEmail
//
/// Helper function that always returns a valid string. The string
/// may be empty if there is no address at the specified index.
///
const std::string& Contact::GetEmail(unsigned int index) const
{
static const std::string blank;
if( index < EmailAddresses.size() )
return EmailAddresses[index];
return blank;
}
void Contact::Dump(std::ostream &os) const
{
ios::fmtflags oldflags = os.setf(ios::left);
char fill = os.fill(' ');
os << "Contact: 0x" << setbase(16) << GetID()
<< " (" << (unsigned int)RecType << ")\n";
// special fields not in type table
os << " " << setw(20) << "FirstName";
os << ": " << FirstName << "\n";
os << " " << setw(20) << "LastName";
os << ": " << LastName << "\n";
// cycle through email addresses
EmailList::const_iterator eai = EmailAddresses.begin();
for( ; eai != EmailAddresses.end(); ++eai ) {
if( eai->size() ) {
os << " Email : " << *eai << "\n";
}
}
// cycle through the type table
for( FieldLink<Contact> *b = ContactFieldLinks;
b->type != CFC_INVALID_FIELD;
b++ )
{
const std::string *pField = 0;
if( b->strMember ) {
pField = &(this->*(b->strMember));
}
else if( b->postMember && b->postField ) {
pField = &((this->*(b->postMember)).*(b->postField));
}
// print only fields with data
if( pField && pField->size() ) {
os << " " << setw(20) << b->name;
os << ": " << *pField << "\n";
}
}
if( Categories.size() ) {
string display;
CategoryList2Str(Categories, display);
os << " Categories : " << display << "\n";
}
// print any group links
GroupLinksType::const_iterator
gb = GroupLinks.begin(), ge = GroupLinks.end();
if( gb != ge )
os << " GroupLinks:\n";
for( ; gb != ge; gb++ ) {
os << " ID: 0x" << setbase(16) << gb->Link << "\n";
}
// and finally print unknowns
os << Unknowns;
// cleanup the stream
os.flags(oldflags);
os.fill(fill);
}
void Contact::SplitName(const std::string &full, std::string &first, std::string &last)
{
first.clear();
last.clear();
string::size_type pos = full.find_last_of(' ');
if( pos != string::npos ) {
// has space, assume last word is last name
last = full.c_str() + pos + 1;
first = full.substr(0, pos);
}
else {
// no space, assume only first name
first = full.substr(0);
}
}
void Contact::CategoryStr2List(const std::string &str,
Barry::CategoryList &list)
{
// start fresh
list.clear();
if( !str.size() )
return;
// parse the comma-delimited string to a list, stripping away
// any white space around each category name
string::size_type start = 0, end = 0, delim = str.find(',', start);
while( start != string::npos ) {
if( delim == string::npos )
end = str.size() - 1;
else
end = delim - 1;
// strip surrounding whitespace
while( str[start] == ' ' )
start++;
while( end && str[end] == ' ' )
end--;
if( start <= end ) {
string token = str.substr(start, end-start+1);
list.push_back(token);
}
// next
start = delim;
if( start != string::npos )
start++;
delim = str.find(',', start);
}
}
void Contact::CategoryList2Str(const Barry::CategoryList &list,
std::string &str)
{
str.clear();
Barry::CategoryList::const_iterator i = list.begin();
for( ; i != list.end(); ++i ) {
if( str.size() )
str += ", ";
str += *i;
}
}
} // namespace Barry
///
/// \file r_contact.h
/// Blackberry database record parser class for contact records.
///
/*
Copyright (C) 2005-2008, Net Direct Inc. (http://www.netdirect.ca/)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License in the COPYING file at the
root directory of this project for more details.
*/
#ifndef __BARRY_RECORD_CONTACT_H__
#define __BARRY_RECORD_CONTACT_H__
#include "dll.h"
#include "record.h"
#include <iosfwd>
#include <string>
#include <vector>
#include <map>
#include <stdint.h>
namespace Barry {
//
// NOTE: All classes here must be container-safe! Perhaps add sorting
// operators in the future.
//
struct BXEXPORT ContactGroupLink
{
uint32_t Link;
uint16_t Unknown;
ContactGroupLink() : Link(0), Unknown(0) {}
ContactGroupLink(uint32_t link, uint16_t unknown)
: Link(link), Unknown(unknown)
{}
};
typedef std::vector<std::string> CategoryList;
/// \addtogroup RecordParserClasses
/// @{
class BXEXPORT Contact
{
public:
typedef Barry::CategoryList CategoryList;
typedef ContactGroupLink GroupLink;
typedef std::vector<GroupLink> GroupLinksType;
typedef std::vector<UnknownField> UnknownsType;
typedef std::string EmailType;
typedef std::vector<EmailType> EmailList;
// contact specific data
uint8_t RecType;
uint32_t RecordId;
EmailList EmailAddresses;
std::string
Phone,
Fax,
WorkPhone,
HomePhone,
MobilePhone,
Pager,
PIN,
Radio,
WorkPhone2,
HomePhone2,
OtherPhone,
FirstName,
LastName,
Company,
DefaultCommunicationsMethod,
JobTitle,
PublicKey,
URL,
Prefix,
Notes,
UserDefined1,
UserDefined2,
UserDefined3,
UserDefined4,
Image,
Birthday;
PostalAddress WorkAddress;
PostalAddress HomeAddress;
// Categories are not allowed to have commas in them.
// A category name containing a comma will be split into
// two categories, not only by this library, but by the
// device itself.
CategoryList Categories;
GroupLinksType GroupLinks;
UnknownsType Unknowns;
private:
bool m_FirstNameSeen;
//protected:
public:
const unsigned char* ParseField(const unsigned char *begin,
const unsigned char *end);
public:
Contact();
~Contact();
uint32_t GetID() const { return RecordId; }
std::string GetFullName() const;
const std::string& GetEmail(unsigned int index = 0) const;
// Parser / Builder API (see parser.h / builder.h)
uint8_t GetRecType() const { return RecType; }
uint32_t GetUniqueId() const { return RecordId; }
void SetIds(uint8_t Type, uint32_t Id) { RecType = Type; RecordId = Id; }
void ParseHeader(const Data &data, size_t &offset);
void ParseFields(const Data &data, size_t &offset);
void BuildHeader(Data &data, size_t &offset) const;
void BuildFields(Data &data, size_t &offset) const;
void Clear(); // erase everything
void Dump(std::ostream &os) const;
// sorting - put group links at the end
bool operator<(const Contact &other) const {
return GroupLinks.size() == 0 && other.GroupLinks.size() > 0;
// // testing - put group links at the top
// return GroupLinks.size() > 0 && other.GroupLinks.size() == 0;
}
// database name
static const char * GetDBName() { return "Address Book"; }
static uint8_t GetDefaultRecType() { return 0; }
// helpers
static void SplitName(const std::string &full, std::string &first, std::string &last);
static void CategoryStr2List(const std::string &str, Barry::CategoryList &list);
static void CategoryList2Str(const Barry::CategoryList &list, std::string &str);
};
BXEXPORT inline std::ostream& operator<< (std::ostream &os, const Contact &contact) {
contact.Dump(os);
return os;
}
/// @}
} // namespace Barry
#endif
///
/// \file vcard.cc
/// Conversion routines for vcards
///
/*
Copyright (C) 2006-2008, Net Direct Inc. (http://www.netdirect.ca/)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License in the COPYING file at the
root directory of this project for more details.
*/
#include "vcard.h"
#include "environment.h"
#include "trace.h"
#include "vformat.h" // comes from opensync, but not a public header yet
#include <stdint.h>
#include <glib.h>
#include <string.h>
#include <sstream>
#include <ctype.h>
//////////////////////////////////////////////////////////////////////////////
// Utility functions
void ToLower(std::string &str)
{
size_t i = 0;
while( i < str.size() ) {
str[i] = tolower(str[i]);
i++;
}
}
//////////////////////////////////////////////////////////////////////////////
// vCard
vCard::vCard()
: m_gCardData(0)
{
}
vCard::~vCard()
{
if( m_gCardData ) {
g_free(m_gCardData);
}
}
void vCard::AddAddress(const char *rfc_type, const Barry::PostalAddress &address)
{
// add label first
vAttrPtr label = NewAttr("LABEL");
AddParam(label, "TYPE", rfc_type);
AddValue(label, address.GetLabel().c_str());
AddAttr(label);
// add breakout address form
vAttrPtr adr = NewAttr("ADR"); // RFC 2426, 3.2.1
AddParam(adr, "TYPE", rfc_type);
AddValue(adr, address.Address3.c_str()); // PO Box
AddValue(adr, address.Address2.c_str()); // Extended address
AddValue(adr, address.Address1.c_str()); // Street address
AddValue(adr, address.City.c_str()); // Locality (city)
AddValue(adr, address.Province.c_str()); // Region (province)
AddValue(adr, address.PostalCode.c_str()); // Postal code
AddValue(adr, address.Country.c_str()); // Country name
AddAttr(adr);
}
void vCard::AddCategories(const Barry::CategoryList &categories)
{
if( !categories.size() )
return;
vAttrPtr cat = NewAttr("CATEGORIES"); // RFC 2426, 3.6.1
Barry::CategoryList::const_iterator i = categories.begin();
for( ; i < categories.end(); ++i ) {
AddValue(cat, i->c_str());
}
AddAttr(cat);
}
/// Add phone conditionally, only if phone has data in it
void vCard::AddPhoneCond(const char *rfc_type, const std::string &phone)
{
if( phone.size() ) {
vAttrPtr tel = NewAttr("TEL", phone.c_str());
AddParam(tel, "TYPE", rfc_type);
AddAttr(tel);
}
}
void vCard::ParseAddress(vAttr &adr, Barry::PostalAddress &address)
{
// RFC 2426, 3.2.1
address.Address3 = adr.GetValue(0); // PO Box
address.Address2 = adr.GetValue(1); // Extended address
address.Address1 = adr.GetValue(2); // Street address
address.City = adr.GetValue(3); // Locality (city)
address.Province = adr.GetValue(4); // Region (province)
address.PostalCode = adr.GetValue(5); // Postal code
address.Country = adr.GetValue(6); // Country name
}
void vCard::ParseCategories(vAttr &cat, Barry::CategoryList &cats)
{
int i = 0;
std::string value = cat.GetValue(i);
while( value.size() ) {
cats.push_back(value);
i++;
value = cat.GetValue(i);
}
}
// Parse a Barry date in the form of DD/MM/YY into the format
// for VCard YYYYMMDD
std::string vCard::ParseBarryDate( const std::string& s )
{
std::string dateStr;
int m, d, y;
if( 3==sscanf( s.c_str(), "%d/%d/%d", &d, &m, &y ) )
{
char buf[32];
sprintf( buf, "%4d%02d%02d", y, m, d );
dateStr.assign( buf );
}
return dateStr;
}
// Parse a VCard date in the form of YYYYMMDD into the format
// for Barry DD/MM/YY
std::string vCard::ParseVCardDate( const std::string& s )
{
std::string dateStr;
if( s.size() )
{
int m, d, y;
if( 3==sscanf( s.c_str(), "%4d%2d%2d", &y, &m, &d ) )
{
char buf[32];
sprintf( buf, "%02d/%02d/%04d", d, m, y );
dateStr.assign( buf );
}
}
return dateStr;
}
// Main conversion routine for converting from Barry::Contact to
// a vCard string of data.
const std::string& vCard::ToVCard(const Barry::Contact &con)
{
Trace trace("vCard::ToVCard");
std::ostringstream oss;
con.Dump(oss);
trace.logf("ToVCard, initial Barry record: %s", oss.str().c_str());
// start fresh
Clear();
SetFormat( b_vformat_new() );
if( !Format() )
throw ConvertError("resource error allocating vformat");
// store the Barry object we're working with
m_BarryContact = con;
//
// begin building vCard data
//
AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Contact Record//EN"));
std::string fullname = con.GetFullName();
if( fullname.size() )
AddAttr(NewAttr("FN", fullname.c_str()));
if( con.FirstName.size() || con.LastName.size() ) {
vAttrPtr name = NewAttr("N"); // RFC 2426, 3.1.2
AddValue(name, con.LastName.c_str()); // Family Name
AddValue(name, con.FirstName.c_str()); // Given Name
AddValue(name, ""); // Additional Names
AddValue(name, con.Prefix.c_str()); // Honorific Prefixes
AddValue(name, ""); // Honorific Suffixes
AddAttr(name);
}
if( con.WorkAddress.HasData() )
AddAddress("work", con.WorkAddress);
if( con.HomeAddress.HasData() )
AddAddress("home", con.HomeAddress);
// add all applicable phone numbers... can't add the WorkPhone2
// since VCARD30 TEL fields only take one number, as far as I know
AddPhoneCond("pref", con.Phone);
AddPhoneCond("fax", con.Fax);
AddPhoneCond("work", con.WorkPhone);
AddPhoneCond("home", con.HomePhone);
AddPhoneCond("cell", con.MobilePhone);
AddPhoneCond("msg", con.Pager);
// add all email addresses, marking first one as "pref"
Barry::Contact::EmailList::const_iterator eai = con.EmailAddresses.begin();
for( unsigned int i = 0; eai != con.EmailAddresses.end(); ++eai, ++i ) {
const std::string& e = con.GetEmail(i);
if( e.size() ) {
vAttrPtr email = NewAttr("EMAIL", e.c_str());
if( i == 0 ) {
AddParam(email, "TYPE", "internet,pref");
}
else {
AddParam(email, "TYPE", "internet");
}
AddAttr(email);
}
}
if( con.JobTitle.size() ) {
AddAttr(NewAttr("TITLE", con.JobTitle.c_str()));
AddAttr(NewAttr("ROLE", con.JobTitle.c_str()));
}
// Image not supported, since vformat routines probably don't
// support binary VCARD fields....
if( con.Company.size() ) {
// RFC 2426, 3.5.5
vAttrPtr org = NewAttr("ORG", con.Company.c_str()); // Organization name
AddValue(org, ""); // Division name
AddAttr(org);
}
if( con.Birthday.size() )
AddAttr(NewAttr("BDAY", ParseBarryDate( con.Birthday ).c_str() ));
if( con.Notes.size() )
AddAttr(NewAttr("NOTE", con.Notes.c_str()));
if( con.URL.size() )
AddAttr(NewAttr("URL", con.URL.c_str()));
if( con.Categories.size() )
AddCategories(con.Categories);
// generate the raw VCARD data
m_gCardData = b_vformat_to_string(Format(), VFORMAT_CARD_30);
m_vCardData = m_gCardData;
trace.logf("ToVCard, resulting vcard data: %s", m_vCardData.c_str());
return m_vCardData;
}
// Main conversion routine for converting from vCard data string
// to a Barry::Contact object.
const Barry::Contact& vCard::ToBarry(const char *vcard, uint32_t RecordId)
{
using namespace std;
Trace trace("vCard::ToBarry");
trace.logf("ToBarry, working on vcard data: %s", vcard);
// start fresh
Clear();
// store the vCard raw data
m_vCardData = vcard;
// create format parser structures
SetFormat( b_vformat_new_from_string(vcard) );
if( !Format() )
throw ConvertError("resource error allocating vformat");
//
// Parse the vcard data
//
Barry::Contact &con = m_BarryContact;
con.SetIds(Barry::Contact::GetDefaultRecType(), RecordId);
vAttr name = GetAttrObj("N");
if( !name.Get() )
throw ConvertError("no FN field in VCARD data");
// RFC 2426, 3.1.2
con.LastName = name.GetValue(0); // Family Name
con.FirstName = name.GetValue(1); // Given Name
con.Prefix = name.GetValue(3); // Honorific Prefixes
vAttr adr = GetAttrObj("ADR");
for( int i = 0; adr.Get(); adr = GetAttrObj("ADR", ++i) )
{
std::string type = adr.GetParam("TYPE");
ToLower(type);
// do not use "else" here, since TYPE can have multiple keys
if( strstr(type.c_str(), "work") )
ParseAddress(adr, con.WorkAddress);
if( strstr(type.c_str(), "home") )
ParseAddress(adr, con.HomeAddress);
}
// add all applicable phone numbers... can't add the WorkPhone2
// since VCARD30 TEL fields only take one number, as far as I know
vAttr tel = GetAttrObj("TEL");
for( int i = 0; tel.Get(); tel = GetAttrObj("TEL", ++i) )
{
std::string stype = tel.GetParam("TYPE");
ToLower(stype);
const char *type = stype.c_str();
// do not use "else" here, since TYPE can have multiple keys
if( strstr(type, "pref") )
con.Phone = tel.GetValue();
if( strstr(type, "fax") )
con.Fax = tel.GetValue();
if( strstr(type, "work") )
con.WorkPhone = tel.GetValue();
if( strstr(type, "home") )
con.HomePhone = tel.GetValue();
if( strstr(type, "cell") )
con.MobilePhone = tel.GetValue();
if( strstr(type, "msg") )
con.Pager = tel.GetValue();
}
// scan for all email addresses... append addresses to the
// list by default, but prepend if its type is set to "pref"
// i.e. we want the preferred email address first
vAttr email = GetAttrObj("EMAIL");
for( int i = 0; email.Get(); email = GetAttrObj("EMAIL", ++i) )
{
std::string type = email.GetParam("TYPE");
ToLower(type);
bool of_interest = (i == 0 || strstr(type.c_str(), "pref"));
bool x400 = strstr(type.c_str(), "x400");
if( of_interest && !x400 ) {
con.EmailAddresses.insert(con.EmailAddresses.begin(), email.GetValue());
}
else {
con.EmailAddresses.push_back( email.GetValue() );
}
}
// figure out which company title we want to keep...
// favour the TITLE field, but if it's empty, use ROLE
con.JobTitle = GetAttr("TITLE");
if( !con.JobTitle.size() )
con.JobTitle = GetAttr("ROLE");
con.Company = GetAttr("ORG");
con.Notes = GetAttr("NOTE");
con.URL = GetAttr("URL");
con.Birthday = ParseVCardDate( GetAttr("BDAY") );
vAttr cat = GetAttrObj("CATEGORIES");
if( cat.Get() )
ParseCategories(cat, con.Categories);
return m_BarryContact;
}
// Transfers ownership of m_gCardData to the caller.
char* vCard::ExtractVCard()
{
char *ret = m_gCardData;
m_gCardData = 0;
return ret;
}
void vCard::Clear()
{
vBase::Clear();
m_vCardData.clear();
m_BarryContact.Clear();
if( m_gCardData ) {
g_free(m_gCardData);
m_gCardData = 0;
}
}
//////////////////////////////////////////////////////////////////////////////
//
VCardConverter::VCardConverter()
: m_Data(0)
{
}
VCardConverter::VCardConverter(uint32_t newRecordId)
: m_Data(0),
m_RecordId(newRecordId)
{
}
VCardConverter::~VCardConverter()
{
if( m_Data )
g_free(m_Data);
}
// Transfers ownership of m_Data to the caller
char* VCardConverter::ExtractData()
{
Trace trace("VCardConverter::ExtractData");
char *ret = m_Data;
m_Data = 0;
return ret;
}
bool VCardConverter::ParseData(const char *data)
{
Trace trace("VCardConverter::ParseData");
try {
vCard vcard;
m_Contact = vcard.ToBarry(data, m_RecordId);
}
catch( vCard::ConvertError &ce ) {
trace.logf("ERROR: vCard::ConvertError exception: %s", ce.what());
return false;
}
return true;
}
// Barry storage operator
void VCardConverter::operator()(const Barry::Contact &rec)
{
Trace trace("VCardConverter::operator()");
// Delete data if some already exists
if( m_Data ) {
g_free(m_Data);
m_Data = 0;
}
try {
vCard vcard;
vcard.ToVCard(rec);
m_Data = vcard.ExtractVCard();
}
catch( vCard::ConvertError &ce ) {
trace.logf("ERROR: vCard::ConvertError exception: %s", ce.what());
}
}
// Barry builder operator
bool VCardConverter::operator()(Barry::Contact &rec, unsigned int dbId)
{
Trace trace("VCardConverter::builder operator()");
rec = m_Contact;
return true;
}
// Handles calling of the Barry::Controller to fetch a specific
// record, indicated by index (into the RecordStateTable).
// Returns a g_malloc'd string of data containing the vcard30
// data. It is the responsibility of the caller to free it.
// This is intended to be passed into the GetChanges() function.
char* VCardConverter::GetRecordData(BarryEnvironment *env, unsigned int dbId,
Barry::RecordStateTable::IndexType index)
{
Trace trace("VCardConverter::GetRecordData()");
using namespace Barry;
VCardConverter contact2vcard;
RecordParser<Contact, VCardConverter> parser(contact2vcard);
env->m_pDesktop->GetRecord(dbId, index, parser);
return contact2vcard.ExtractData();
}
bool VCardConverter::CommitRecordData(BarryEnvironment *env, unsigned int dbId,
Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId,
const char *data, bool add, std::string &errmsg)
{
Trace trace("VCardConverter::CommitRecordData()");
uint32_t newRecordId;
if( add ) {
// use given id if possible
if( recordId && !env->m_ContactsSync.m_Table.GetIndex(recordId) ) {
// recordId is unique and non-zero
newRecordId = recordId;
}
else {
trace.log("Can't use recommended recordId, generating new one.");
newRecordId = env->m_ContactsSync.m_Table.MakeNewRecordId();
}
}
else {
newRecordId = env->m_ContactsSync.m_Table.StateMap[StateIndex].RecordId;
}
trace.logf("newRecordId: %lu", newRecordId);
VCardConverter convert(newRecordId);
if( !convert.ParseData(data) ) {
std::ostringstream oss;
oss << "unable to parse change data for new RecordId: "
<< newRecordId << " data: " << data;
errmsg = oss.str();
trace.logf(errmsg.c_str());
return false;
}
Barry::RecordBuilder<Barry::Contact, VCardConverter> builder(convert);
if( add ) {
trace.log("adding record");
env->m_pDesktop->AddRecord(dbId, builder);
}
else {
trace.log("setting record");
env->m_pDesktop->SetRecord(dbId, StateIndex, builder);
trace.log("clearing dirty flag");
env->m_pDesktop->ClearDirty(dbId, StateIndex);
}
return true;
}
///
/// \file vcard.h
/// Conversion routines for vcards
///
/*
Copyright (C) 2006-2008, Net Direct Inc. (http://www.netdirect.ca/)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License in the COPYING file at the
root directory of this project for more details.
*/
#ifndef __BARRY_SYNC_VCARD_H__
#define __BARRY_SYNC_VCARD_H__
#include <barry/barry.h>
#include <stdint.h>
#include <string>
#include "vbase.h"
#include "vformat.h"
// forward declarations
class BarryEnvironment;
//
// vCard
//
/// Class for converting between RFC 2425/2426 vCard data format,
/// and the Barry::Contact class.
///
class vCard : public vBase
{
// data to pass to external requests
char *m_gCardData; // dynamic memory returned by vformat()... can
// be used directly by the plugin, without
// overmuch allocation and freeing (see Extract())
std::string m_vCardData;// copy of m_gCardData, for C++ use
Barry::Contact m_BarryContact;
protected:
void AddAddress(const char *rfc_type, const Barry::PostalAddress &addr);
void AddCategories(const Barry::CategoryList &categories);
void AddPhoneCond(const char *rfc_type, const std::string &phone);
void ParseAddress(vAttr &adr, Barry::PostalAddress &address);
void ParseCategories(vAttr &cat, Barry::CategoryList &cats);
std::string ParseBarryDate( const std::string& s );
std::string ParseVCardDate( const std::string& s );
public:
vCard();
~vCard();
const std::string& ToVCard(const Barry::Contact &con);
const Barry::Contact& ToBarry(const char *vcal, uint32_t RecordId);
const std::string& GetVCard() const { return m_vCardData; }
const Barry::Contact& GetBarryContact() const { return m_BarryContact; }
char* ExtractVCard();
void Clear();
};
class VCardConverter
{
char *m_Data;
Barry::Contact m_Contact;
uint32_t m_RecordId;
public:
VCardConverter();
explicit VCardConverter(uint32_t newRecordId);
~VCardConverter();
// Transfers ownership of m_Data to the caller
char* ExtractData();
// Parses vevent data
bool ParseData(const char *data);
// Barry storage operator
void operator()(const Barry::Contact &rec);
// Barry builder operator
bool operator()(Barry::Contact &rec, unsigned int dbId);
// Handles calling of the Barry::Controller to fetch a specific
// record, indicated by index (into the RecordStateTable).
// Returns a g_malloc'd string of data containing the vevent20
// data. It is the responsibility of the caller to free it.
// This is intended to be passed into the GetChanges() function.
static char* GetRecordData(BarryEnvironment *env, unsigned int dbId,
Barry::RecordStateTable::IndexType index);
// Handles either adding or overwriting a calendar record,
// given vevent20 data in data, and the proper environmebnt,
// dbId, StateIndex. Set add to true if adding.
static bool CommitRecordData(BarryEnvironment *env, unsigned int dbId,
Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId,
const char *data, bool add, std::string &errmsg);
};
#endif
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Barry-devel mailing list
Barry-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/barry-devel