added conflict handling
git-svn-id: https://zeitsenke.de/svn/SyncEvolution/trunk@22 15ad00c4-1369-45f4-8270-35d70d36bdcd
This commit is contained in:
parent
09c19a61df
commit
608a5b261f
30
README
30
README
|
@ -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
|
||||
---------------------------------
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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[] = {
|
||||
|
|
Loading…
Reference in a new issue