Hi Chris,

Finally, I have implemented your logic.

I think  that it's interessant to fix this bug for your next release
0.15. Because with Evolution, this bug is quiet boring :(

Regards,

Le vendredi 10 juillet 2009 à 13:16 -0400, Chris Frey a écrit :
> On Fri, Jul 10, 2009 at 07:05:28PM +0200, Nicolas wrote:
> > I agree with your solution.
> > 
> > You can implemented it :)
> 
> *chuckle*  It's on my list :-)
> 
> - Chris
> 
> 
> ------------------------------------------------------------------------------
> Enter the BlackBerry Developer Challenge  
> This is your chance to win up to $100,000 in prizes! For a limited time, 
> vendors submitting new applications to BlackBerry App World(TM) will have
> the opportunity to enter the BlackBerry Developer Challenge. See full prize  
> details at: http://p.sf.net/sfu/Challenge
> _______________________________________________
> Barry-devel mailing list
> Barry-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/barry-devel

-- 
Nicolas VIVIEN

Attachment: 0004-Fix-phone-duplication.patch.gz
Description: GNU Zip compressed data

///
/// \file	vcard.cc
///		Conversion routines for vcards
///

/*
    Copyright (C) 2006-2009, 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

static 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.  This version
/// does not add a TYPE parameter to the item.
void vCard::AddPhoneCond(const std::string &phone)
{
	if( phone.size() ) {
		vAttrPtr tel = NewAttr("TEL", phone.c_str());
		AddAttr(tel);
	}
}

/// 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);
	}
}



// 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()));
	}
	else {
		//
		// RFC 2426, 3.1.1 states that FN MUST be present in the
		// vcard object.  Unfortunately, the Blackberry doesn't
		// require a name, only a name or company name.
		//
		// In this case we do nothing, and generate an invalid
		// vcard, since if we try to fix our output here, we'll
		// likely end up with duplicated company names in the
		// Blackberry record after a few syncs.
		//
	}

	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... there can be multiple
	// TEL fields, even with the same TYPE value... therefore, the
	// second TEL field with a TYPE=work, will be stored in WorkPhone2
	AddPhoneCond("voice,pref", con.Phone);
	AddPhoneCond("fax", con.Fax);
	AddPhoneCond("voice,work", con.WorkPhone);
	AddPhoneCond("voice,work", con.WorkPhone2);
	AddPhoneCond("voice,home", con.HomePhone);
	AddPhoneCond("voice,home", con.HomePhone2);
	AddPhoneCond("msg,cell", con.MobilePhone);
	AddPhoneCond("msg,pager", con.Pager);
	AddPhoneCond("voice", con.OtherPhone);

	// 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.HasData() )
		AddAttr(NewAttr("BDAY", con.Birthday.ToYYYYMMDD().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);

	// Image / Photo
	if (con.Image.size()) {
		vAttrPtr photo = NewAttr("PHOTO");
		AddEncodedValue(photo, VF_ENCODING_BASE64, con.Image.c_str(), con.Image.size());
		AddParam(photo, "ENCODING", "BASE64");
		AddAttr(photo);
	}

	// 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() ) {
							// 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.GetAllParams("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... there can be multiple
	// TEL fields, even with the same TYPE value... therefore, the
	// second TEL field with a TYPE=work, will be stored in WorkPhone2
	vAttr tel = GetAttrObj("TEL");
	for( int i = 0; tel.Get(); tel = GetAttrObj("TEL", ++i) )
	{
		// grab all parameter values for this param name
		std::string stype = tel.GetAllParams("TYPE");

		// turn to lower case for comparison
		// FIXME - is this i18n safe?
		ToLower(stype);

		// state
		const char *type = stype.c_str();
		bool used = false;

		// If phone number has the "pref" flag
		if( strstr(type, "pref") ) {
			// Always use cell phone if the "pref" flag is set
			if( strstr(type, "cell") ) {
				con.Phone = tel.GetValue();
				used = true;
			}
			// Otherwise, the phone has to be "voice" type
			else if( strstr(type, "voice") && con.Phone.size() == 0 ) {
				con.Phone = tel.GetValue();
				used = true;
			}
		}

		// For each known phone type
		// Fax :
		if( strstr(type, "fax") && (strstr(type, "pref") || con.Fax.size() == 0) ) {
			con.Fax = tel.GetValue();
			used = true;
		}
		// Mobile phone :
		else if( strstr(type, "cell") && (strstr(type, "pref") || con.MobilePhone.size() == 0) ) {
			con.MobilePhone = tel.GetValue();
			used = true;
		}
		// Pager :
		else if( strstr(type, "pager") && (strstr(type, "pref") || con.Pager.size() == 0) ) {
			con.Pager = tel.GetValue();
			used = true;
		}
		// Check for any TEL-ignore types, and use other phone field if possible 
		// cell/pcs/car      MobilePhone field only
		// bbs/video/modem   entire TEL ignored by Barry
		// isdn              entire TEL ignored by Barry
		else if( strstr(type, "pcs") || strstr(type, "car") ) {
		}
		else if( strstr(type, "bbs") || strstr(type, "video") || strstr(type, "modem") ) {
		}
		else if( strstr(type, "isdn") ) {
		}
		// Voice telephone :
		else { 
			if( strstr(type, "work") ) {
				if( strstr(type, "pref") ) {
					con.WorkPhone2 = con.WorkPhone;
					con.WorkPhone = tel.GetValue();
					used = true;
				}
				else if( con.WorkPhone.size() == 0 ) {
					con.WorkPhone = tel.GetValue();
					used = true;
				}
				else if( con.WorkPhone2.size() == 0 ) {
					con.WorkPhone2 = tel.GetValue();
					used = true;
				}
			}

			if( strstr(type, "home") ) {
				if( strstr(type, "pref") ) {
					con.HomePhone2 = con.HomePhone;
					con.HomePhone = tel.GetValue();
					used = true;
				}
				if( con.HomePhone.size() == 0 ) {
					con.HomePhone = tel.GetValue();
					used = true;
				}
				else if( con.HomePhone2.size() == 0 ) {
					con.HomePhone2 = tel.GetValue();
					used = true;
				}
			}
		}

		// if this value has not been claimed by any of the
		// cases above, claim it now as "OtherPhone"
		if( !used && con.OtherPhone.size() == 0 ) {
			con.OtherPhone = tel.GetValue();
		}
	}
	// Final check: If Phone is blank, and OtherPhone has a value, swap.
	if( con.OtherPhone.size() && !con.Phone.size() ) {
		con.Phone.swap(con.OtherPhone);
	}

	// 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.GetAllParams("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");
	if( GetAttr("BDAY").size() && !con.Birthday.FromYYYYMMDD( GetAttr("BDAY") ) )
		throw ConvertError("Unable to parse BDAY field");

	// Photo vCard ?
	vAttr photo = GetAttrObj("PHOTO");
	if (photo.Get()) {
		std::string sencoding = photo.GetAllParams("ENCODING");

		ToLower(sencoding);

		const char *encoding = sencoding.c_str();

		if (strstr(encoding, "quoted-printable")) {
			photo.Get()->encoding = VF_ENCODING_QP;

			con.Image = photo.GetDecodedValue();
		}
		else if (strstr(encoding, "b")) {
			photo.Get()->encoding = VF_ENCODING_BASE64;

			con.Image = photo.GetDecodedValue();
		}
		// Else
		// We ignore the photo, I don't know decoded !
	}

	vAttr cat = GetAttrObj("CATEGORIES");
	if( cat.Get() )
		ParseCategories(cat, con.Categories);

	// Last sanity check: Blackberry requires that at least
	// name or Company has data.
	if( !con.GetFullName().size() && !con.Company.size() )
		throw ConvertError("FN and ORG fields both blank in VCARD data");

	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.log(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;
}

------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day 
trial. Simplify your report design, integration and deployment - and focus on 
what you do best, core application coding. Discover what's new with 
Crystal Reports now.  http://p.sf.net/sfu/bobj-july
_______________________________________________
Barry-devel mailing list
Barry-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/barry-devel

Reply via email to