added conflict handling

git-svn-id: https://zeitsenke.de/svn/SyncEvolution/trunk@22 15ad00c4-1369-45f4-8270-35d70d36bdcd
This commit is contained in:
Patrick Ohly 2006-01-23 21:51:43 +00:00
parent 09c19a61df
commit 608a5b261f
4 changed files with 220 additions and 29 deletions

30
README
View file

@ -27,12 +27,18 @@ With a server that fully supports SyncML and the vCard standard the
following works:
- copy a complete database to the server and restore it
from the server later
- delete or modify an item locally and then make the same change
- delete or modify an item locally, then make the same change
on the server
- delete, modify or add items on the server (by synchronizing with
another device), then make the same change locally
another client), then make the same change locally
- conflict resolution (where two clients modify the same item,
then sync with the server) is handled by the server, but
sync4jevolution has support which ensures that no data is lost
by creating duplicates (see Conflict Resolution below)
This software is still experimental. Make a backup of your
Although all of the features are covered by unit testing and
have been verified to work, this software is still experimental.
Make a backup of your
$HOME/.evolution/addressbook
$HOME/.evolution/calender
directories before running it for the first time. In older Evolution
@ -189,6 +195,24 @@ Make a backup of the .evolution/addressbook directory.
Did I mention that you should make backups?
Conflict Resolution
-------------------
If two clients make changes to the same item, the first one to
sync will get the server to copy its changes. The second one
then runs into a conflict when it tries to push its own changes
into the server.
The SyncML server now has to decide how to proceed. If the server
decides to continue with its own copy and asks to overwrite the
locally modified copy (the default with Sync4j),
sync4jevolution will make a local copy first. This leads to
duplicates which have to be merged manually on the client side
where the conflict occurred. Currently there is no support
inside sync4jevolution: there is only an ERROR entry in the
log. A summary at the end of syncing would be better, or
even opening GUI dialogs to resolve the conflicts immediately...
Tracking Changes inside Evolution
---------------------------------

View file

@ -307,6 +307,59 @@ string EvolutionContactSource::preparseVCard(SyncItem& item)
return data;
}
void EvolutionContactSource::setItemStatus(const char *key, int status)
{
switch (status) {
case STC_CONFLICT_RESOLVED_WITH_SERVER_DATA: {
// make a copy before allowing the server to overwrite it
char buffer[200];
sprintf(buffer,
"contact %.80s: conflict, will be replaced by server contact - create copy\n",
key);
LOG.error(buffer);
EContact *contact;
GError *gerror = NULL;
if (! e_book_get_contact( m_addressbook,
key,
&contact,
&gerror ) ) {
sprintf(buffer,
"item %.80s: reading original for copy failed\n",
key);
LOG.error(buffer);
break;
}
EContact *copy = e_contact_duplicate(contact);
if(!copy ||
! e_book_add_contact(m_addressbook,
copy,
&gerror)) {
sprintf(buffer,
"item %.80s: making copy failed\n",
key);
LOG.error(buffer);
break;
}
break;
}
default:
EvolutionSyncSource::setItemStatus(key, status);
break;
}
if (status < 200 || status > 300) {
char buffer[200];
sprintf(buffer,
"unexpected SyncML status response %d for item %.80s\n",
status, key);
LOG.error(buffer);
m_hasFailed = true;
}
}
int EvolutionContactSource::addItem(SyncItem& item)
{
try {
@ -344,12 +397,39 @@ int EvolutionContactSource::updateItem(SyncItem& item)
gptr<EContact, GObject> contact(e_contact_new_from_vcard(data.c_str()));
if( contact ) {
GError *gerror = NULL;
// The following code commits the new_from_vcard contact using the
// existing UID. It has been observed in Evolution 2.0.4 that the
// changes were then not "noticed" properly by the Evolution GUI.
//
// The code below was supposed to "notify" Evolution of the change by
// loaded the updated contact, modifying it, committing, restoring
// and committing once more, but that did not solve the problem.
//
// TODO: test with current Evolution
e_contact_set( contact, E_CONTACT_UID, item.getKey() );
if ( e_book_commit_contact(m_addressbook, contact, &gerror) ) {
const char *uid = (const char *)e_contact_get_const(contact, E_CONTACT_UID);
if (uid) {
item.setKey( uid );
}
#if 0
EContact *refresh_contact;
if (! e_book_get_contact( m_addressbook,
uid,
&refresh_contact,
&gerror ) ) {
throwError( string( "reading refresh contact" ) + uid,
gerror );
}
string nick = (const char *)e_contact_get_const(refresh_contact, E_CONTACT_NICKNAME);
string nick_mod = nick + "_";
e_contact_set(refresh_contact, E_CONTACT_NICKNAME, (void *)nick_mod.c_str());
e_book_commit_contact(m_addressbook, refresh_contact, &gerror);
e_contact_set(refresh_contact, E_CONTACT_NICKNAME, (void *)nick.c_str());
e_book_commit_contact(m_addressbook, refresh_contact, &gerror);
#endif
} else {
throwError( string( "updating contact" ) + item.getKey(), gerror );
}

View file

@ -64,6 +64,7 @@ class EvolutionContactSource : public EvolutionSyncSource
//
// implementation of SyncSource
//
virtual void setItemStatus(const char *key, int status);
virtual int addItem(SyncItem& item);
virtual int updateItem(SyncItem& item);
virtual int deleteItem(SyncItem& item);

View file

@ -122,6 +122,7 @@ class TestEvolution : public CppUnit::TestFixture
CPPUNIT_TEST( testCopy );
CPPUNIT_TEST( testUpdate );
CPPUNIT_TEST( testDelete );
CPPUNIT_TEST( testMerge );
CPPUNIT_TEST( testVCard );
CPPUNIT_TEST_SUITE_END();
@ -139,7 +140,28 @@ class TestEvolution : public CppUnit::TestFixture
string m_serverLog;
/** assumes that one element is currently inserted and updates it */
void contactUpdate();
void contactUpdate( int config = 0, const char *vcard =
"BEGIN:VCARD\n"
"VERSION:3.0\n"
"URL:\n"
"TITLE:\n"
"ROLE:\n"
"X-EVOLUTION-MANAGER:\n"
"X-EVOLUTION-ASSISTANT:\n"
"NICKNAME:user1\n"
"X-EVOLUTION-SPOUSE:\n"
"NOTE:\n"
"FN:Joan Doe\n"
"N:Doe;Joan;;;\n"
"X-EVOLUTION-FILE-AS:Doe\\, Joan\n"
"X-EVOLUTION-BLOG-URL:\n"
"BDAY:2006-01-08\n"
"CALURI:\n"
"FBURL:\n"
"X-EVOLUTION-VIDEO-URL:\n"
"X-MOZILLA-HTML:FALSE\n"
"END:VCARD\n"
);
/** performs one sync operation */
void doSync(const string &logfile, int config, SyncMode syncMode);
@ -216,6 +238,9 @@ public:
void testUpdate();
// test that a two-way sync deletes the copy of an item in the other database
void testDelete();
// test what the server does when it finds that different
// fields of the same item have been modified
void testMerge();
// creates several contact test cases, transmits them back and forth and
// then compares which of them have been preserved
void testVCard();
@ -240,7 +265,7 @@ void TestEvolution::testContactSimpleInsert()
"BEGIN:VCARD\n"
"VERSION:3.0\n"
"URL:\n"
"TITLE:\n"
"TITLE:tester\n"
"ROLE:\n"
"X-EVOLUTION-MANAGER:\n"
"X-EVOLUTION-ASSISTANT:\n"
@ -347,36 +372,15 @@ void TestEvolution::testContactComplexInsert()
testContactIterateTwice();
}
void TestEvolution::contactUpdate()
void TestEvolution::contactUpdate( int config, const char *vcard )
{
EvolutionContactSource source( string( "dummy" ),
m_changeIds[0],
m_contactNames[0] );
m_changeIds[config],
m_contactNames[config] );
EVOLUTION_ASSERT_NO_THROW( source, source.open() );
EVOLUTION_ASSERT( source, source.beginSync() == 0 );
SyncItem *item;
EVOLUTION_ASSERT_NO_THROW( source, item = source.getFirstItem() );
const char *vcard =
"BEGIN:VCARD\n"
"VERSION:3.0\n"
"URL:\n"
"TITLE:\n"
"ROLE:\n"
"X-EVOLUTION-MANAGER:\n"
"X-EVOLUTION-ASSISTANT:\n"
"NICKNAME:user1\n"
"X-EVOLUTION-SPOUSE:\n"
"NOTE:\n"
"FN:Joan Doe\n"
"N:Doe;Joan;;;\n"
"X-EVOLUTION-FILE-AS:Doe\\, Joan\n"
"X-EVOLUTION-BLOG-URL:\n"
"BDAY:2006-01-08\n"
"CALURI:\n"
"FBURL:\n"
"X-EVOLUTION-VIDEO-URL:\n"
"X-MOZILLA-HTML:FALSE\n"
"END:VCARD\n";
item->setData( vcard, strlen( vcard ) + 1 );
EVOLUTION_ASSERT_NO_THROW( source, source.updateItem( *item ) );
EVOLUTION_ASSERT_NO_THROW( source, source.close() );
@ -632,6 +636,88 @@ void TestEvolution::testDelete()
CPPUNIT_ASSERT( countItems( source ) == 0 );
}
void TestEvolution::testMerge()
{
doCopy( "testMerge.copy" );
// add a telephone number
contactUpdate( 0,
"BEGIN:VCARD\n"
"VERSION:3.0\n"
"URL:\n"
"TITLE:tester\n"
"ROLE:\n"
"X-EVOLUTION-MANAGER:\n"
"X-EVOLUTION-ASSISTANT:\n"
"NICKNAME:user1\n"
"X-EVOLUTION-SPOUSE:\n"
"NOTE:\n"
"FN:John Doe\n"
"N:Doe;John;;;\n"
"X-EVOLUTION-FILE-AS:Doe\\, John\n"
"X-EVOLUTION-BLOG-URL:\n"
"CALURI:\n"
"FBURL:\n"
"X-EVOLUTION-VIDEO-URL:\n"
"X-MOZILLA-HTML:FALSE\n"
"TEL;TYPE=WORK:business 1\n"
"END:VCARD\n" );
// add a birthday and modify the title
contactUpdate( 1,
"BEGIN:VCARD\n"
"VERSION:3.0\n"
"URL:\n"
"TITLE:developer\n"
"ROLE:\n"
"X-EVOLUTION-MANAGER:\n"
"X-EVOLUTION-ASSISTANT:\n"
"NICKNAME:user1\n"
"X-EVOLUTION-SPOUSE:\n"
"NOTE:\n"
"FN:John Doe\n"
"N:Doe;John;;;\n"
"X-EVOLUTION-FILE-AS:Doe\\, John\n"
"X-EVOLUTION-BLOG-URL:\n"
"CALURI:\n"
"FBURL:\n"
"X-EVOLUTION-VIDEO-URL:\n"
"X-MOZILLA-HTML:FALSE\n"
"BDAY:2006-01-08\n"
"END:VCARD\n" );
doSync( "testMerge.send.0.client.log", 0, SYNC_TWO_WAY );
doSync( "testMerge.recv.1.client.log", 1, SYNC_TWO_WAY );
doSync( "testMerge.recv.0.client.log", 0, SYNC_TWO_WAY );
// check that both address books are identical (regardless of actual content):
// disabled because the address books won't be identical with Sync4j.
// What happens instead is that the server sends a
// STC_CONFLICT_RESOLVED_WITH_SERVER_DATA and
// EvolutionContactSource::setItemStatus() creates a copy.
// TODO: check what the server did (from testMerge.recv.1.client.log) and
// test either for identical address books or how many items exist
// compareAddressbooks( 1 );
// this code here assumes STC_CONFLICT_RESOLVED_WITH_SERVER_DATA
EvolutionContactSource client0(
string( "dummy" ),
m_changeIds[0],
m_contactNames[0] );
EVOLUTION_ASSERT_NO_THROW( client0, client0.open() );
EVOLUTION_ASSERT( client0, client0.beginSync() == 0 );
CPPUNIT_ASSERT( 1 == countItems( client0 ) );
EvolutionContactSource client1(
string( "dummy" ),
m_changeIds[1],
m_contactNames[1] );
EVOLUTION_ASSERT_NO_THROW( client1, client1.open() );
EVOLUTION_ASSERT( client1, client1.beginSync() == 0 );
CPPUNIT_ASSERT( 2 == countItems( client1 ) );
}
static int compareContacts( const string &name, EContact *sourceContact, EContact *copiedContact )
{
const EContactField essential[] = {