engine: local cache sync mode
This patch introduces support for true one-way syncing ("caching"): the local datastore is meant to be an exact copy of the data on the remote side. The assumption is that no modifications are ever made locally outside of syncing. This is different from one-way sync modes, which allows local changes and only temporarily disables sending them to the remote side. Another goal of the new mode is to avoid data writes as much as possible. This new mode only works on the server side of a sync, where the engine has enough control over the data flow. Most of the changes are in libsynthesis. SyncEvolution only needs to enable the new mode, which is done via an extension of the "sync" property: - "local-cache-incremental" will do an incremental sync (if possible) or a slow sync (otherwise). This is usually the right mode to use, and thus has "local-cache" as alias. - "local-cache-slow" will always do a slow sync. Useful for debugging or after (accidentally) making changes on the server side. An incremental sync will ignore such changes because they are not meant to happen and thus leave client and sync out-of-sync! Both modes are recorded in the sync report of the local side. The target side is the client and records the normal "two-way" or "slow" sync modes. With the current SyncEvolution contact field list, first, middle and last name are used to find matches during any kind of slow sync. The organization field is ignored for matching during the initial slow sync and used in all following ones. That's okay, the difference won't matter in practice because the initial slow sync in PBAP caching will be done with no local data. The test achieve the same result in both cases by keeping the organization set in the reduced data set. It's also okay to include the property in the comparison, because it might help to distinguish between "John Doe" in different companies. It might be worthwhile to add more fields as match criteria, for example the birthday. Currently they are excluded, probably because they are not trusted to be supported by SyncML peers. In caching mode the situation is different, because all our data came from the peer. The downside is that in cases where matching has to be done all the time because change detection is not supported (PBAP), including the birthday as criteria will cause unnecessary contact removed/added events (and thus disk IO) when a contact was originally created without birthday locally and then a birthday gets added on the phone. Testing is done as part of the D-Bus testing framework, because usually this functionality will be used as part of the D-Bus server and writing tests in Python is easier. A new test class "TestLocalCache" contains the new tests. They include tests for removing extra items during a slow sync (testItemRemoval), adding new client items under various conditions (testItemAdd*) and updating/removing an item during incremental syncing (testItemUpdate/Delete*). Doing these changes during a slow sync could also be tested (not currently covered). The tests for removing properties (testPropertyRemoval*) cover removing almost all contact properties during an initial slow sync, a second slow sync (which is treated differently in libsynthesis, see merge=always and merge=slowsync), and an incremental sync.
This commit is contained in:
parent
2f25b65cbc
commit
dff2be3c9a
|
@ -3097,12 +3097,12 @@ protected:
|
|||
TestCmdline failure2("--sync", "foo", NULL);
|
||||
CPPUNIT_ASSERT(!failure2.m_cmdline->parse());
|
||||
CPPUNIT_ASSERT_EQUAL_DIFF("", failure2.m_out.str());
|
||||
CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] '--sync foo': not one of the valid values (two-way, slow, refresh-from-local, refresh-from-remote = refresh, one-way-from-local, one-way-from-remote = one-way, refresh-from-client = refresh-client, refresh-from-server = refresh-server, one-way-from-client = one-way-client, one-way-from-server = one-way-server, disabled = none)\n", failure2.m_err.str());
|
||||
CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] '--sync foo': not one of the valid values (two-way, slow, refresh-from-local, refresh-from-remote = refresh, one-way-from-local, one-way-from-remote = one-way, refresh-from-client = refresh-client, refresh-from-server = refresh-server, one-way-from-client = one-way-client, one-way-from-server = one-way-server, local-cache-slow, local-cache-incremental = local-cache, disabled = none)\n", failure2.m_err.str());
|
||||
|
||||
TestCmdline failure3("--sync=foo", NULL);
|
||||
CPPUNIT_ASSERT(!failure3.m_cmdline->parse());
|
||||
CPPUNIT_ASSERT_EQUAL_DIFF("", failure3.m_out.str());
|
||||
CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] '--sync=foo': not one of the valid values (two-way, slow, refresh-from-local, refresh-from-remote = refresh, one-way-from-local, one-way-from-remote = one-way, refresh-from-client = refresh-client, refresh-from-server = refresh-server, one-way-from-client = one-way-client, one-way-from-server = one-way-server, disabled = none)\n", failure3.m_err.str());
|
||||
CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] '--sync=foo': not one of the valid values (two-way, slow, refresh-from-local, refresh-from-remote = refresh, one-way-from-local, one-way-from-remote = one-way, refresh-from-client = refresh-client, refresh-from-server = refresh-server, one-way-from-client = one-way-client, one-way-from-server = one-way-server, local-cache-slow, local-cache-incremental = local-cache, disabled = none)\n", failure3.m_err.str());
|
||||
|
||||
TestCmdline help("--sync", " ?", NULL);
|
||||
help.doit();
|
||||
|
@ -3123,7 +3123,12 @@ protected:
|
|||
" transmit changes from peer\n"
|
||||
" one-way-from-local\n"
|
||||
" transmit local changes\n"
|
||||
" disabled (or none)\n"
|
||||
" local-cache-slow (server only)\n"
|
||||
" mirror remote data locally, transferring all data\n"
|
||||
" local-cache-incremental (server only)\n"
|
||||
" mirror remote data locally, transferring only changes;\n"
|
||||
" falls back to local-cache-slow automatically if necessary\n"
|
||||
" disabled (or none)\n"
|
||||
" synchronization disabled\n"
|
||||
" \n"
|
||||
" refresh/one-way-from-server/client are also supported. Their use is\n"
|
||||
|
|
|
@ -781,6 +781,17 @@ class LocalTransportAgentChild : public TransportAgent, private LoggerBase
|
|||
mode = SYNC_ONE_WAY_FROM_REMOTE;
|
||||
} else if (mode == SYNC_ONE_WAY_FROM_REMOTE) {
|
||||
mode = SYNC_ONE_WAY_FROM_LOCAL;
|
||||
} else if (mode == SYNC_LOCAL_CACHE_SLOW) {
|
||||
// Remote side is running in caching mode and
|
||||
// asking for refresh. Send all our data.
|
||||
mode = SYNC_SLOW;
|
||||
} else if (mode == SYNC_LOCAL_CACHE_INCREMENTAL) {
|
||||
// Remote side is running in caching mode and
|
||||
// asking for an update. Use two-way mode although
|
||||
// nothing is going to come back (simpler that way
|
||||
// than using one-way, which has special code
|
||||
// paths in libsynthesis).
|
||||
mode = SYNC_TWO_WAY;
|
||||
}
|
||||
targetSource.setSync(PrettyPrintSyncMode(mode, true), true);
|
||||
targetSource.setURI(sourceName, true);
|
||||
|
|
|
@ -2235,6 +2235,11 @@ StringConfigProperty SyncSourceConfig::m_sourcePropSync("sync",
|
|||
" transmit changes from peer\n"
|
||||
" one-way-from-local\n"
|
||||
" transmit local changes\n"
|
||||
" local-cache-slow (server only)\n"
|
||||
" mirror remote data locally, transferring all data\n"
|
||||
" local-cache-incremental (server only)\n"
|
||||
" mirror remote data locally, transferring only changes;\n"
|
||||
" falls back to local-cache-slow automatically if necessary\n"
|
||||
" disabled (or none)\n"
|
||||
" synchronization disabled\n"
|
||||
"\n"
|
||||
|
@ -2264,6 +2269,8 @@ StringConfigProperty SyncSourceConfig::m_sourcePropSync("sync",
|
|||
(Aliases("refresh-from-server") + "refresh-server") +
|
||||
(Aliases("one-way-from-client") + "one-way-client") +
|
||||
(Aliases("one-way-from-server") + "one-way-server") +
|
||||
(Aliases("local-cache-slow")) +
|
||||
(Aliases("local-cache-incremental") + "local-cache") +
|
||||
(Aliases("disabled") + "none"));
|
||||
|
||||
static class SourceBackendConfigProperty : public StringConfigProperty {
|
||||
|
|
|
@ -1647,16 +1647,20 @@ void SyncContext::displaySourceProgress(sysync::TProgressEventEnum type,
|
|||
case 0:
|
||||
mode = SIMPLE_SYNC_TWO_WAY;
|
||||
if (m_serverMode &&
|
||||
m_serverAlerted &&
|
||||
(sync == SYNC_ONE_WAY_FROM_SERVER ||
|
||||
sync == SYNC_ONE_WAY_FROM_LOCAL) {
|
||||
// As in the slow/refresh-from-server case below,
|
||||
// pretending to do a two-way incremental sync
|
||||
// is a correct way of executing the requested
|
||||
// one-way sync, as long as the client doesn't
|
||||
// send any of its own changes. The Synthesis
|
||||
// engine does that.
|
||||
mode = SIMPLE_SYNC_ONE_WAY_FROM_LOCAL;
|
||||
m_serverAlerted) {
|
||||
if (sync == SYNC_ONE_WAY_FROM_SERVER ||
|
||||
sync == SYNC_ONE_WAY_FROM_LOCAL) {
|
||||
// As in the slow/refresh-from-server case below,
|
||||
// pretending to do a two-way incremental sync
|
||||
// is a correct way of executing the requested
|
||||
// one-way sync, as long as the client doesn't
|
||||
// send any of its own changes. The Synthesis
|
||||
// engine does that.
|
||||
mode = SIMPLE_SYNC_ONE_WAY_FROM_LOCAL;
|
||||
} else if (sync == SYNC_LOCAL_CACHE_SLOW ||
|
||||
sync == SYNC_LOCAL_CACHE_INCREMENTAL) {
|
||||
mode = SIMPLE_SYNC_LOCAL_CACHE_INCREMENTAL;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
|
@ -1673,15 +1677,19 @@ void SyncContext::displaySourceProgress(sysync::TProgressEventEnum type,
|
|||
case 0:
|
||||
mode = SIMPLE_SYNC_SLOW;
|
||||
if (m_serverMode &&
|
||||
m_serverAlerted &&
|
||||
(sync == SYNC_REFRESH_FROM_SERVER ||
|
||||
sync == SYNC_REFRESH_FROM_LOCAL) {
|
||||
// We run as server and told the client to refresh
|
||||
// its data. A slow sync is how some clients (the
|
||||
// Synthesis engine included) execute that sync mode;
|
||||
// let's be optimistic and assume that the client
|
||||
// did as it was told and deleted its data.
|
||||
mode = SIMPLE_SYNC_REFRESH_FROM_LOCAL;
|
||||
m_serverAlerted) {
|
||||
if (sync == SYNC_REFRESH_FROM_SERVER ||
|
||||
sync == SYNC_REFRESH_FROM_LOCAL) {
|
||||
// We run as server and told the client to refresh
|
||||
// its data. A slow sync is how some clients (the
|
||||
// Synthesis engine included) execute that sync mode;
|
||||
// let's be optimistic and assume that the client
|
||||
// did as it was told and deleted its data.
|
||||
mode = SIMPLE_SYNC_REFRESH_FROM_LOCAL;
|
||||
} else if (sync == SYNC_LOCAL_CACHE_SLOW ||
|
||||
sync == SYNC_LOCAL_CACHE_INCREMENTAL) {
|
||||
mode = SIMPLE_SYNC_LOCAL_CACHE_SLOW;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
|
@ -2502,6 +2510,13 @@ void SyncContext::getConfigXML(string &xml, string &configname)
|
|||
if (source->getForceSlowSync()) {
|
||||
// we *want* a slow sync, but couldn't tell the client -> force it server-side
|
||||
datastores << " <alertscript> FORCESLOWSYNC(); </alertscript>\n";
|
||||
} else if (mode == SYNC_LOCAL_CACHE_SLOW ||
|
||||
mode == SYNC_LOCAL_CACHE_INCREMENTAL) {
|
||||
if (!m_serverMode) {
|
||||
SE_THROW("sync modes 'local-cache-*' are only supported on the server side");
|
||||
}
|
||||
datastores << " <alertscript>SETREFRESHONLY(1); SETCACHEDATA(1);</alertscript>\n";
|
||||
// datastores << " <datastoreinitscript>REFRESHONLY(); CACHEDATA(); SLOWSYNC(); ALERTCODE();</datastoreinitscript>\n";
|
||||
} else if (mode != SYNC_SLOW &&
|
||||
// slow-sync detection not implemented when running as server,
|
||||
// not even when initiating the sync (direct sync with phone)
|
||||
|
|
|
@ -72,6 +72,12 @@ SimpleSyncMode SimplifySyncMode(SyncMode mode, bool peerIsClient)
|
|||
case SA_SYNC_TWO_WAY:
|
||||
return SIMPLE_SYNC_TWO_WAY;
|
||||
|
||||
case SYNC_LOCAL_CACHE_SLOW:
|
||||
return SIMPLE_SYNC_LOCAL_CACHE_SLOW;
|
||||
|
||||
case SYNC_LOCAL_CACHE_INCREMENTAL:
|
||||
return SIMPLE_SYNC_LOCAL_CACHE_INCREMENTAL;
|
||||
|
||||
case SYNC_LAST:
|
||||
case SYNC_INVALID:
|
||||
return SIMPLE_SYNC_INVALID;
|
||||
|
@ -90,10 +96,12 @@ SANSyncMode AlertSyncMode(SyncMode mode, bool peerIsClient)
|
|||
return SA_INVALID;
|
||||
|
||||
case SYNC_SLOW:
|
||||
case SYNC_LOCAL_CACHE_SLOW:
|
||||
return SA_SLOW;
|
||||
|
||||
case SYNC_TWO_WAY:
|
||||
case SA_SYNC_TWO_WAY:
|
||||
case SYNC_LOCAL_CACHE_INCREMENTAL: // use two-way because it is more likely to be implemented
|
||||
return SA_TWO_WAY;
|
||||
|
||||
case SYNC_ONE_WAY_FROM_CLIENT:
|
||||
|
@ -161,6 +169,10 @@ std::string PrettyPrintSyncMode(SyncMode mode, bool userVisible)
|
|||
return userVisible ? "one-way-from-remote" : "SYNC_ONE_WAY_FROM_REMOTE";
|
||||
case SYNC_REFRESH_FROM_REMOTE:
|
||||
return userVisible ? "refresh-from-remote" : "SYNC_REFRESH_FROM_REMOTE";
|
||||
case SYNC_LOCAL_CACHE_SLOW:
|
||||
return userVisible ? "local-cache-slow" : "SYNC_LOCAL_CACHE_SLOW";
|
||||
case SYNC_LOCAL_CACHE_INCREMENTAL:
|
||||
return userVisible ? "local-cache-incremental" : "SYNC_LOCAL_CACHE_INCREMENTAL";
|
||||
default:
|
||||
std::stringstream res;
|
||||
|
||||
|
@ -193,6 +205,10 @@ SyncMode StringToSyncMode(const std::string &mode, bool serverAlerted)
|
|||
return SYNC_ONE_WAY_FROM_LOCAL;
|
||||
} else if (boost::iequals(mode, "disabled") || boost::iequals(mode, "SYNC_NONE")) {
|
||||
return SYNC_NONE;
|
||||
} else if (boost::iequals(mode, "local-cache-slow") || boost::iequals(mode, "SYNC_LOCAL_CACHE_SLOW")) {
|
||||
return SYNC_LOCAL_CACHE_SLOW;
|
||||
} else if (boost::iequals(mode, "local-cache-incremental") || boost::iequals(mode, "SYNC_LOCAL_CACHE_INCREMENTAL")) {
|
||||
return SYNC_LOCAL_CACHE_INCREMENTAL;
|
||||
} else {
|
||||
return SYNC_INVALID;
|
||||
}
|
||||
|
|
|
@ -64,6 +64,13 @@ enum SimpleSyncMode {
|
|||
SIMPLE_SYNC_ONE_WAY_FROM_REMOTE = 214,
|
||||
SIMPLE_SYNC_REFRESH_FROM_REMOTE = 215,
|
||||
|
||||
// custom modes in SyncEvolution
|
||||
|
||||
/** mirror data on server side, slow variant (client sends all items) */
|
||||
SIMPLE_SYNC_LOCAL_CACHE_SLOW = 218,
|
||||
/** mirror data on server side, incremental variant (client sends only changes) */
|
||||
SIMPLE_SYNC_LOCAL_CACHE_INCREMENTAL = 219,
|
||||
|
||||
SIMPLE_SYNC_INVALID = 255
|
||||
};
|
||||
|
||||
|
@ -97,6 +104,10 @@ enum SyncMode {
|
|||
SYNC_ONE_WAY_FROM_REMOTE = 214,
|
||||
SYNC_REFRESH_FROM_REMOTE = 215,
|
||||
|
||||
// custom mode
|
||||
SYNC_LOCAL_CACHE_SLOW = 218,
|
||||
SYNC_LOCAL_CACHE_INCREMENTAL = 219,
|
||||
|
||||
SYNC_LAST = 220,
|
||||
/** error situation (in contrast to SYNC_NONE) */
|
||||
SYNC_INVALID = 255
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#! /usr/bin/python -u
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: set fileencoding=utf-8 :#
|
||||
#
|
||||
# Copyright (C) 2009 Intel Corporation
|
||||
#
|
||||
|
@ -854,6 +856,7 @@ class DBusUtil(Timeout):
|
|||
DBusUtil.quit_events.append("session done")
|
||||
loop.quit()
|
||||
|
||||
DBusUtil.events = []
|
||||
bus.add_signal_receiver(progress,
|
||||
'ProgressChanged',
|
||||
'org.syncevolution.Session',
|
||||
|
@ -930,7 +933,7 @@ class DBusUtil(Timeout):
|
|||
while not until in self.prettyPrintEvents():
|
||||
loop.get_context().iteration(True)
|
||||
|
||||
def setUpLocalSyncConfigs(self, childPassword=None, enableCalendar=False):
|
||||
def setUpLocalSyncConfigs(self, childPassword=None, enableCalendar=False, preventSlowSync=None):
|
||||
# create file<->file configs
|
||||
self.setUpSession("target-config@client")
|
||||
addressbook = { "sync": "two-way",
|
||||
|
@ -945,6 +948,8 @@ class DBusUtil(Timeout):
|
|||
addressbook["databaseUser"] = "foo-user"
|
||||
addressbook["databasePassword"] = childPassword
|
||||
config = {"" : { "loglevel": "4" } }
|
||||
if preventSlowSync != None:
|
||||
config = {"" : { "preventSlowSync": preventSlowSync and "1" or "0" }}
|
||||
config["source/addressbook"] = addressbook
|
||||
if enableCalendar:
|
||||
config["source/calendar"] = calendar
|
||||
|
@ -1066,7 +1071,7 @@ status: idle, 0, {}
|
|||
else:
|
||||
self.assertEqual(error, realError)
|
||||
|
||||
def doCheckSync(self, expectedError=0, expectedResult=0, reportOptional=False, numReports=1):
|
||||
def doCheckSync(self, expectedError=0, expectedResult=0, reportOptional=False, numReports=1, checkPercent=True):
|
||||
# check recorded events in DBusUtil.events, first filter them
|
||||
statuses = []
|
||||
progresses = []
|
||||
|
@ -2149,7 +2154,9 @@ class TestSessionAPIsDummy(DBusUtil, unittest.TestCase):
|
|||
"refresh-from-local, refresh-from-remote = refresh, one-way-from-local, "
|
||||
"one-way-from-remote = one-way, refresh-from-client = refresh-client, "
|
||||
"refresh-from-server = refresh-server, one-way-from-client = one-way-client, "
|
||||
"one-way-from-server = one-way-server, disabled = none)'")
|
||||
"one-way-from-server = one-way-server, "
|
||||
"local-cache-slow, local-cache-incremental = local-cache, "
|
||||
"disabled = none)'")
|
||||
else:
|
||||
self.fail("no exception thrown")
|
||||
|
||||
|
@ -4036,6 +4043,578 @@ END:VCARD''')
|
|||
# Sync should have succeeded.
|
||||
self.assertSyncStatus('server', 200, None)
|
||||
|
||||
class TestLocalCache(unittest.TestCase, DBusUtil):
|
||||
"""Tests involving local sync and the local-cache mode."""
|
||||
|
||||
serverDB = os.path.join(xdg_root, "server")
|
||||
clientDB = os.path.join(xdg_root, "client")
|
||||
johnVCard = '''BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
FN:John Doe
|
||||
N:Doe;John
|
||||
ORG:Test Inc.
|
||||
END:VCARD'''
|
||||
johnComplexVCard = '''BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
URL:http://john.doe.com
|
||||
TITLE:Senior Tester
|
||||
ORG:Test Inc.;Testing;test#1
|
||||
ROLE:professional test case
|
||||
X-EVOLUTION-MANAGER:John Doe Senior
|
||||
X-EVOLUTION-ASSISTANT:John Doe Junior
|
||||
NICKNAME:user1
|
||||
BDAY:2006-01-08
|
||||
X-FOOBAR-EXTENSION;X-FOOBAR-PARAMETER=foobar:has to be stored internally by engine and preserved in testExtensions test\; never sent to a peer
|
||||
X-TEST;PARAMETER1=nonquoted;PARAMETER2="quoted because of spaces":Content with\nMultiple\nText lines\nand national chars: äöü
|
||||
X-EVOLUTION-ANNIVERSARY:2006-01-09
|
||||
X-EVOLUTION-SPOUSE:Joan Doe
|
||||
NOTE:This is a test case which uses almost all Evolution fields.
|
||||
FN:John Doe
|
||||
N:Doe;John;;;
|
||||
X-EVOLUTION-FILE-AS:Doe\, John
|
||||
CATEGORIES:TEST
|
||||
X-EVOLUTION-BLOG-URL:web log
|
||||
CALURI:calender
|
||||
FBURL:free/busy
|
||||
X-EVOLUTION-VIDEO-URL:chat
|
||||
X-MOZILLA-HTML:TRUE
|
||||
ADR;TYPE=WORK:Test Box #2;;Test Drive 2;Test Town;Upper Test County;12346;O
|
||||
ld Testovia
|
||||
LABEL;TYPE=WORK:Test Drive 2\nTest Town\, Upper Test County\n12346\nTest Bo
|
||||
x #2\nOld Testovia
|
||||
ADR;TYPE=HOME:Test Box #1;;Test Drive 1;Test Village;Lower Test County;1234
|
||||
5;Testovia
|
||||
LABEL;TYPE=HOME:Test Drive 1\nTest Village\, Lower Test County\n12345\nTest
|
||||
Box #1\nTestovia
|
||||
ADR:Test Box #3;;Test Drive 3;Test Megacity;Test County;12347;New Testonia
|
||||
LABEL;TYPE=OTHER:Test Drive 3\nTest Megacity\, Test County\n12347\nTest Box
|
||||
#3\nNew Testonia
|
||||
UID:pas-id-43C0ED3900000001
|
||||
EMAIL;TYPE=WORK;X-EVOLUTION-UI-SLOT=1:john.doe@work.com
|
||||
EMAIL;TYPE=HOME;X-EVOLUTION-UI-SLOT=2:john.doe@home.priv
|
||||
EMAIL;TYPE=OTHER;X-EVOLUTION-UI-SLOT=3:john.doe@other.world
|
||||
EMAIL;TYPE=OTHER;X-EVOLUTION-UI-SLOT=4:john.doe@yet.another.world
|
||||
TEL;TYPE=work;TYPE=Voice;X-EVOLUTION-UI-SLOT=1:business 1
|
||||
TEL;TYPE=homE;TYPE=VOICE;X-EVOLUTION-UI-SLOT=2:home 2
|
||||
TEL;TYPE=CELL;X-EVOLUTION-UI-SLOT=3:mobile 3
|
||||
TEL;TYPE=WORK;TYPE=FAX;X-EVOLUTION-UI-SLOT=4:businessfax 4
|
||||
TEL;TYPE=HOME;TYPE=FAX;X-EVOLUTION-UI-SLOT=5:homefax 5
|
||||
TEL;TYPE=PAGER;X-EVOLUTION-UI-SLOT=6:pager 6
|
||||
TEL;TYPE=CAR;X-EVOLUTION-UI-SLOT=7:car 7
|
||||
TEL;TYPE=PREF;X-EVOLUTION-UI-SLOT=8:primary 8
|
||||
X-AIM;X-EVOLUTION-UI-SLOT=1:AIM JOHN
|
||||
X-YAHOO;X-EVOLUTION-UI-SLOT=2:YAHOO JDOE
|
||||
X-ICQ;X-EVOLUTION-UI-SLOT=3:ICQ JD
|
||||
X-GROUPWISE;X-EVOLUTION-UI-SLOT=4:GROUPWISE DOE
|
||||
X-GADUGADU:GADUGADU DOE
|
||||
X-JABBER:JABBER DOE
|
||||
X-MSN:MSN DOE
|
||||
X-SKYPE:SKYPE DOE
|
||||
X-SIP:SIP DOE
|
||||
PHOTO;ENCODING=b;TYPE=JPEG:/9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAA
|
||||
AAgAAAAAAAD//gAXQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAFAwQEBAMFBAQEBQUFBgcM
|
||||
CAcHBwcPCwsJDBEPEhIRDxERExYcFxMUGhURERghGBodHR8fHxMXIiQiHiQcHh8e/9sAQwEF
|
||||
BQUHBgcOCAgOHhQRFB4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e
|
||||
Hh4eHh4eHh4e/8AAEQgAFwAkAwEiAAIRAQMRAf/EABkAAQADAQEAAAAAAAAAAAAAAAAGBwgE
|
||||
Bf/EADIQAAECBQMCAwQLAAAAAAAAAAECBAADBQYRBxIhEzEUFSIIFjNBGCRHUVZ3lqXD0+P/
|
||||
xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMR
|
||||
AD8AuX6UehP45/aXv9MTPTLVKxNSvMPcqu+a+XdLxf1SfJ6fU37PioTnOxfbOMc/KIZ7U/2V
|
||||
fmTR/wCaKlu6+blu/Ui72zxWtUmmUOrTaWwkWDT09FPR4K587OVrUfVsIwElPPPAbAjxr2um
|
||||
hWXbDu5rmfeApLPZ4hx0lzNm9aUJ9KAVHKlJHAPf7ozPLqWt9y6Z0EPGmoLNjTq48a1iaybJ
|
||||
YV52yEtCms5KJmAT61JXtJyUdyQTEc1WlMql7N1/oZ6jagVZVFfUyZPpFy5lvWcxU7Z03BUk
|
||||
GZLWJqVhPYLkIIPBEBtSEUyNAsjI1q1m/VP+UICwL/sqlXp7v+aOHsnyGttq218MtKd8+Ru2
|
||||
JXuScoO45Awe2CIi96aKW1cVyubkYVy6rTqz0J8a5t2qqZl0UjAMwYKScfPAJ+cIQHHP0Dth
|
||||
VFaMWt0XwxetnM50Ks2rsxL6ZMnJlJmb5hBBBEiVxjA28dznqo+hdksbQuS3Hs6tVtNzdM1Z
|
||||
/VH5nO3Bl/CJmYHKDynjv3zCEB5rLQNo0bIbydWNWxKljbLQLoWkISOAkBKAABCEID//2Q==
|
||||
END:VCARD
|
||||
'''
|
||||
joanVCard = '''BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
FN:Joan Doe
|
||||
N:Doe;Joan
|
||||
ORG:Test Inc.
|
||||
END:VCARD'''
|
||||
vcardFormat = '''BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
FN:John_%(index)02d Doe
|
||||
N:Doe;John_%(index)02d
|
||||
ORG:Test Inc.
|
||||
END:VCARD'''
|
||||
itemName = "test-dbus.vcf"
|
||||
itemNameFormat = "test-dbus-%d.vcf"
|
||||
|
||||
def run(self, result):
|
||||
self.runTest(result)
|
||||
|
||||
def setUp(self):
|
||||
self.setUpServer()
|
||||
|
||||
def setUpConfigs(self, childPassword=None):
|
||||
self.setUpLocalSyncConfigs(childPassword, preventSlowSync=False)
|
||||
|
||||
def checkInSync(self, numReports=2):
|
||||
'''verify that client and server do not need to transmit anything in an incremental sync'''
|
||||
self.sessionpath, self.session = self.createSession("server", True)
|
||||
self.setUpListeners(self.sessionpath)
|
||||
self.session.Sync("local-cache-incremental", {})
|
||||
loop.run()
|
||||
self.assertEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
|
||||
report = self.checkSync(numReports=numReports)
|
||||
self.assertEqual("local-cache-incremental", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
DBusUtil.quit_events = []
|
||||
self.sessionpath, self.session = self.createSession("target-config@client", True)
|
||||
reports = self.session.GetReports(0, 100, utf8_strings=True)
|
||||
self.assertEqual(numReports, len(reports))
|
||||
report = reports[0]
|
||||
self.assertEqual("two-way", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
|
||||
@timeout(100)
|
||||
def testItemRemoval(self):
|
||||
"""TestLocalCache.testItemRemoval - ensure that extra item on server gets removed"""
|
||||
self.setUpConfigs()
|
||||
os.makedirs(self.serverDB)
|
||||
output = open(os.path.join(self.serverDB, self.itemName), "w")
|
||||
output.write(self.johnVCard)
|
||||
output.close()
|
||||
self.setUpListeners(self.sessionpath)
|
||||
# ask for incremental caching, expecting it do be done in slow mode
|
||||
self.session.Sync("local-cache-incremental", {})
|
||||
loop.run()
|
||||
self.assertEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
|
||||
# check sync from server perspective
|
||||
report = self.checkSync()
|
||||
self.assertEqual("local-cache-slow", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("1", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.assertEqual(0, len(os.listdir(self.serverDB)))
|
||||
self.assertEqual(0, len(os.listdir(self.clientDB)))
|
||||
# check client report
|
||||
self.session.Detach()
|
||||
DBusUtil.quit_events = []
|
||||
self.sessionpath, self.session = self.createSession("target-config@client", True)
|
||||
reports = self.session.GetReports(0, 100, utf8_strings=True)
|
||||
self.assertEqual(1, len(reports))
|
||||
report = reports[0]
|
||||
self.assertEqual("slow", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
|
||||
self.checkInSync()
|
||||
|
||||
def doItemChange(self, numAdditional=0, syncFirst=False, change="Add"):
|
||||
self.setUpConfigs()
|
||||
entries = []
|
||||
os.makedirs(self.clientDB)
|
||||
os.makedirs(self.serverDB)
|
||||
|
||||
numReports = 1
|
||||
added = 0
|
||||
updated = 0
|
||||
deleted = 0
|
||||
|
||||
# Create additional items in client and server, before initial
|
||||
# sync. Creating items on the server later would violate the
|
||||
# rule that items on the server are only written during a
|
||||
# sync.
|
||||
for i in range(0, numAdditional):
|
||||
filename = self.itemNameFormat % i
|
||||
data = self.vcardFormat % { 'index': i }
|
||||
entries.append(filename)
|
||||
output = open(os.path.join(self.clientDB, filename), "w")
|
||||
output.write(data)
|
||||
output.close()
|
||||
output = open(os.path.join(self.serverDB, filename), "w")
|
||||
output.write(data)
|
||||
output.close()
|
||||
|
||||
if syncFirst:
|
||||
# get client and server into sync with empty databases
|
||||
self.setUpListeners(self.sessionpath)
|
||||
self.session.Sync("local-cache-incremental", {})
|
||||
loop.run()
|
||||
self.assertEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
|
||||
report = self.checkSync(numReports=numReports)
|
||||
self.assertEqual("local-cache-slow", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
DBusUtil.quit_events = []
|
||||
self.sessionpath, self.session = self.createSession("server", True)
|
||||
numReports = numReports + 1
|
||||
|
||||
# create named contact on client
|
||||
entries.append(self.itemName)
|
||||
output = open(os.path.join(self.clientDB, self.itemName), "w")
|
||||
output.write(self.johnVCard)
|
||||
output.close()
|
||||
|
||||
# ask for incremental caching, expecting it do be done in slow mode
|
||||
# or incremental, depending on whether both sides were in sync
|
||||
self.setUpListeners(self.sessionpath)
|
||||
self.session.Sync("local-cache-incremental", {})
|
||||
loop.run()
|
||||
self.assertEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
|
||||
|
||||
# check sync from server perspective
|
||||
report = self.checkSync(numReports=numReports)
|
||||
self.assertEqual(syncFirst and "local-cache-incremental" or "local-cache-slow",
|
||||
report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("1", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.assertEqual(1 + numAdditional, len(os.listdir(self.serverDB)))
|
||||
clientDBEntries = os.listdir(self.clientDB)
|
||||
clientDBEntries.sort()
|
||||
entries.sort()
|
||||
self.assertEqual(entries, clientDBEntries)
|
||||
|
||||
# check client report
|
||||
self.session.Detach()
|
||||
DBusUtil.quit_events = []
|
||||
self.sessionpath, self.session = self.createSession("target-config@client", True)
|
||||
reports = self.session.GetReports(0, 100, utf8_strings=True)
|
||||
self.assertEqual(numReports, len(reports))
|
||||
report = reports[0]
|
||||
self.assertEqual(syncFirst and "two-way" or "slow", report.get('source-addressbook-mode'))
|
||||
self.assertEqual(str(1 + (not syncFirst and numAdditional or 0)), report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
|
||||
numReports = numReports + 1
|
||||
|
||||
if change == "Add" or change == "Add+Slow":
|
||||
# client and server are now in sync
|
||||
self.checkInSync(numReports=numReports)
|
||||
elif change == "Update":
|
||||
# update item to something completely, using an incremental sync
|
||||
serverContent = os.listdir(self.serverDB)
|
||||
output = open(os.path.join(self.clientDB, self.itemName), "w")
|
||||
output.write(self.joanVCard)
|
||||
output.close()
|
||||
self.sessionpath, self.session = self.createSession("server", True)
|
||||
self.setUpListeners(self.sessionpath)
|
||||
self.session.Sync("local-cache-incremental", {})
|
||||
loop.run()
|
||||
self.assertEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
|
||||
report = self.checkSync(numReports=numReports)
|
||||
self.assertEqual("local-cache-incremental", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("1", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
self.assertEqual(serverContent, os.listdir(self.serverDB))
|
||||
clientDBEntries = os.listdir(self.clientDB)
|
||||
clientDBEntries.sort()
|
||||
self.assertEqual(entries, clientDBEntries)
|
||||
DBusUtil.quit_events = []
|
||||
self.sessionpath, self.session = self.createSession("target-config@client", True)
|
||||
reports = self.session.GetReports(0, 100, utf8_strings=True)
|
||||
self.assertEqual(numReports, len(reports))
|
||||
report = reports[0]
|
||||
self.assertEqual("two-way", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("1", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
elif change == "Delete":
|
||||
# remove item, using an incremental sync
|
||||
os.unlink(os.path.join(self.clientDB, self.itemName))
|
||||
self.sessionpath, self.session = self.createSession("server", True)
|
||||
self.setUpListeners(self.sessionpath)
|
||||
self.session.Sync("local-cache-incremental", {})
|
||||
loop.run()
|
||||
self.assertEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
|
||||
report = self.checkSync(numReports=numReports)
|
||||
self.assertEqual("local-cache-incremental", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("1", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
self.assertEqual(numAdditional, len(os.listdir(self.serverDB)))
|
||||
clientDBEntries = os.listdir(self.clientDB)
|
||||
clientDBEntries.sort()
|
||||
entries.remove(self.itemName)
|
||||
self.assertEqual(entries, clientDBEntries)
|
||||
DBusUtil.quit_events = []
|
||||
self.sessionpath, self.session = self.createSession("target-config@client", True)
|
||||
reports = self.session.GetReports(0, 100, utf8_strings=True)
|
||||
self.assertEqual(numReports, len(reports))
|
||||
report = reports[0]
|
||||
self.assertEqual("two-way", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("1", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
|
||||
numReports = numReports + 1
|
||||
|
||||
if change == "Add+Slow":
|
||||
# explicitly request a slow sync
|
||||
DBusUtil.quit_events = []
|
||||
self.sessionpath, self.session = self.createSession("server", True)
|
||||
self.setUpListeners(self.sessionpath)
|
||||
self.session.Sync("local-cache-slow", {})
|
||||
loop.run()
|
||||
self.assertEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
|
||||
report = self.checkSync(numReports=numReports)
|
||||
self.assertEqual("local-cache-slow", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
numReports = numReports + 1
|
||||
|
||||
|
||||
@timeout(100)
|
||||
def testItemAdd(self):
|
||||
"""TestLocalCache.testItemAdd - ensure that new item from client gets added in initial slow sync"""
|
||||
self.doItemChange()
|
||||
|
||||
@timeout(200)
|
||||
def testItemAdd100(self):
|
||||
"""TestLocalCache.testItemAdd100 - ensure that new item from client gets added in initial slow sync while leaving 100 items unchanged"""
|
||||
self.doItemChange(numAdditional=100)
|
||||
|
||||
@timeout(100)
|
||||
def testSyncMode(self):
|
||||
"""TestLocalCache.testSyncMode - ensure that requesting specific caching sync works"""
|
||||
self.doItemChange(change="Add+Slow")
|
||||
|
||||
@timeout(100)
|
||||
def testItemAddIncremental(self):
|
||||
"""TestLocalCache.testItemAddIncremental - ensure that new item from client gets added in incremental sync"""
|
||||
self.doItemChange(syncFirst=True)
|
||||
|
||||
@timeout(200)
|
||||
def testItemAdd100Incremental(self):
|
||||
"""TestLocalCache.testItemAdd100Incremental - ensure that new item from client gets added in incremental while leaving 100 items unchanged"""
|
||||
self.doItemChange(numAdditional=100, syncFirst=True)
|
||||
|
||||
@timeout(100)
|
||||
def testItemUpdate(self):
|
||||
"""TestLocalCache.testItemUpdate - ensure that an item can be updated incrementally"""
|
||||
self.doItemChange(change="Update")
|
||||
|
||||
@timeout(200)
|
||||
def testItemUpdate100(self):
|
||||
"""TestLocalCache.testItemUpdate100 - ensure that an item can be updated incrementally while leaving 100 items unchanged"""
|
||||
self.doItemChange(change="Update", numAdditional=100)
|
||||
|
||||
@timeout(100)
|
||||
def testItemDelete(self):
|
||||
"""TestLocalCache.testItemUpdate - ensure that an item can be deleted incrementally"""
|
||||
self.doItemChange(change="Delete")
|
||||
|
||||
@timeout(200)
|
||||
def testItemDelete100(self):
|
||||
"""TestLocalCache.testItemUpdate100 - ensure that an item can be deleted incrementally while leaving 100 items unchanged"""
|
||||
self.doItemChange(change="Delete", numAdditional=100)
|
||||
|
||||
def doPropertyRemoval(self, step=0, numAdditional=0):
|
||||
"""ensure that obsolete items of an item get removed, either during initial slow sync, second slow sync or incremental sync"""
|
||||
self.setUpConfigs()
|
||||
entries = []
|
||||
os.makedirs(self.clientDB)
|
||||
os.makedirs(self.serverDB)
|
||||
output = open(os.path.join(self.serverDB, self.itemName), "w")
|
||||
output.write(self.johnComplexVCard)
|
||||
output.close()
|
||||
entries.append(self.itemName)
|
||||
output = open(os.path.join(self.clientDB, self.itemName), "w")
|
||||
if step == 0:
|
||||
# Client has simple version of John,
|
||||
# slow sync applies update.
|
||||
data = self.johnVCard
|
||||
else:
|
||||
# Client has same data as on server,
|
||||
# slow sync changes nothing.
|
||||
data = self.johnComplexVCard
|
||||
output.write(data)
|
||||
output.close()
|
||||
|
||||
# create additional items in client and server
|
||||
for i in range(0, numAdditional):
|
||||
filename = self.itemNameFormat % i
|
||||
data = self.vcardFormat % { 'index': i }
|
||||
entries.append(filename)
|
||||
output = open(os.path.join(self.clientDB, filename), "w")
|
||||
output.write(data)
|
||||
output.close()
|
||||
output = open(os.path.join(self.serverDB, filename), "w")
|
||||
output.write(data)
|
||||
output.close()
|
||||
|
||||
self.setUpListeners(self.sessionpath)
|
||||
self.session.Sync("local-cache-incremental", {})
|
||||
loop.run()
|
||||
self.assertEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
|
||||
# check sync from server perspective
|
||||
report = self.checkSync()
|
||||
self.assertEqual("local-cache-slow", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual((step == 0) and "1" or "0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.assertEqual(1 + numAdditional, len(os.listdir(self.serverDB)))
|
||||
clientDBEntries = os.listdir(self.clientDB)
|
||||
clientDBEntries.sort()
|
||||
entries.sort()
|
||||
self.assertEqual(entries, clientDBEntries)
|
||||
# check client report
|
||||
self.session.Detach()
|
||||
DBusUtil.quit_events = []
|
||||
self.sessionpath, self.session = self.createSession("target-config@client", True)
|
||||
reports = self.session.GetReports(0, 100, utf8_strings=True)
|
||||
self.assertEqual(1, len(reports))
|
||||
report = reports[0]
|
||||
self.assertEqual("slow", report.get('source-addressbook-mode'))
|
||||
self.assertEqual(str(1 + numAdditional), report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
|
||||
if step == 0:
|
||||
# Work done already, just check that in sync.
|
||||
self.checkInSync()
|
||||
else:
|
||||
# update item to simple version, using an incremental sync
|
||||
# or another slow sync
|
||||
if step == 1:
|
||||
# force slow sync by removing client-side meta data
|
||||
shutil.rmtree(os.path.join(xdg_root, 'config', 'syncevolution', 'default', 'peers', 'server', '.@client', '.synthesis'))
|
||||
serverContent = os.listdir(self.serverDB)
|
||||
output = open(os.path.join(self.clientDB, self.itemName), "w")
|
||||
output.write(self.johnVCard)
|
||||
output.close()
|
||||
self.sessionpath, self.session = self.createSession("server", True)
|
||||
self.setUpListeners(self.sessionpath)
|
||||
self.session.Sync("local-cache-incremental", {})
|
||||
loop.run()
|
||||
self.assertEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
|
||||
report = self.checkSync(numReports=2)
|
||||
self.assertEqual(step == 1 and "local-cache-slow" or "local-cache-incremental", report.get('source-addressbook-mode'))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("1", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
self.assertEqual(serverContent, os.listdir(self.serverDB))
|
||||
clientDBEntries = os.listdir(self.clientDB)
|
||||
clientDBEntries.sort()
|
||||
self.assertEqual(entries, clientDBEntries)
|
||||
DBusUtil.quit_events = []
|
||||
self.sessionpath, self.session = self.createSession("target-config@client", True)
|
||||
reports = self.session.GetReports(0, 100, utf8_strings=True)
|
||||
self.assertEqual(2, len(reports))
|
||||
report = reports[0]
|
||||
self.assertEqual(step == 1 and "slow" or "two-way", report.get('source-addressbook-mode'))
|
||||
self.assertEqual(step == 1 and str(1 + numAdditional) or "0", report.get('source-addressbook-stat-remote-added-total', "0"))
|
||||
self.assertEqual(step == 1 and "0" or "1", report.get('source-addressbook-stat-remote-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-remote-removed-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-added-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-updated-total', "0"))
|
||||
self.assertEqual("0", report.get('source-addressbook-stat-local-removed-total', "0"))
|
||||
self.session.Detach()
|
||||
|
||||
if step == 1:
|
||||
# second sync was a slow sync, now we should be in sync
|
||||
self.checkInSync(numReports=3)
|
||||
|
||||
# Server item should be the simple one now, as in the client.
|
||||
sub = subprocess.Popen(['synccompare', self.clientDB, self.serverDB],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
stdout, stderr = sub.communicate()
|
||||
self.assertEqual(0, sub.returncode,
|
||||
msg=stdout)
|
||||
|
||||
@timeout(100)
|
||||
def testPropertyRemovalSlow(self):
|
||||
"""TestLocalCache.testPropertyRemovalSlow - ensure that obsolete item properties are removed during slow sync"""
|
||||
self.doPropertyRemoval()
|
||||
|
||||
@timeout(200)
|
||||
def testPropertyRemovalSlow100(self):
|
||||
"""TestLocalCache.testPropertyRemovalSlow100 - ensure that obsolete item properties are removed during slow sync while leaving 100 items unchanged"""
|
||||
self.doPropertyRemoval(numAdditional=100)
|
||||
|
||||
@timeout(100)
|
||||
def testPropertyRemovalSecondSlow(self):
|
||||
"""TestLocalCache.testPropertyRemovalSecondSlow - ensure that obsolete item properties are removed during non-initial slow sync"""
|
||||
self.doPropertyRemoval(step=1)
|
||||
|
||||
@timeout(200)
|
||||
def testPropertyRemovalSecondSlow100(self):
|
||||
"""TestLocalCache.testPropertyRemovalSecondSlow100 - ensure that obsolete item properties are removed during non-initial slow sync while leaving 100 items unchanged"""
|
||||
self.doPropertyRemoval(step=1, numAdditional=100)
|
||||
|
||||
@timeout(100)
|
||||
def testPropertyRemovalIncremental(self):
|
||||
"""TestLocalCache.testPropertyRemovalIncremental - ensure that obsolete item properties are removed during incremental sync"""
|
||||
self.doPropertyRemoval(step=2)
|
||||
|
||||
@timeout(200)
|
||||
def testPropertyRemovalIncremental100(self):
|
||||
"""TestLocalCache.testPropertyRemoval - ensure that obsolete item properties are removed during incremental sync while leaving 100 items unchanged"""
|
||||
self.doPropertyRemoval(step=2, numAdditional=100)
|
||||
|
||||
|
||||
class TestFileNotify(unittest.TestCase, DBusUtil):
|
||||
"""syncevo-dbus-server must stop if one of its files mapped into
|
||||
memory (executable, libraries) change. Furthermore it must restart
|
||||
|
@ -5604,7 +6183,7 @@ sources/xyz/config.ini:# databasePassword = """)
|
|||
sessionFlags=None,
|
||||
expectSuccess = False)
|
||||
self.assertEqualDiff('', out)
|
||||
self.assertEqualDiff("[ERROR] '--sync foo': not one of the valid values (two-way, slow, refresh-from-local, refresh-from-remote = refresh, one-way-from-local, one-way-from-remote = one-way, refresh-from-client = refresh-client, refresh-from-server = refresh-server, one-way-from-client = one-way-client, one-way-from-server = one-way-server, disabled = none)\n",
|
||||
self.assertEqualDiff("[ERROR] '--sync foo': not one of the valid values (two-way, slow, refresh-from-local, refresh-from-remote = refresh, one-way-from-local, one-way-from-remote = one-way, refresh-from-client = refresh-client, refresh-from-server = refresh-server, one-way-from-client = one-way-client, one-way-from-server = one-way-server, local-cache-slow, local-cache-incremental = local-cache, disabled = none)\n",
|
||||
stripOutput(err))
|
||||
|
||||
out, err, code = self.runCmdline(["--sync", " ?"],
|
||||
|
@ -5626,6 +6205,11 @@ sources/xyz/config.ini:# databasePassword = """)
|
|||
transmit changes from peer
|
||||
one-way-from-local
|
||||
transmit local changes
|
||||
local-cache-slow (server only)
|
||||
mirror remote data locally, transferring all data
|
||||
local-cache-incremental (server only)
|
||||
mirror remote data locally, transferring only changes;
|
||||
falls back to local-cache-slow automatically if necessary
|
||||
disabled (or none)
|
||||
synchronization disabled
|
||||
|
||||
|
|
Loading…
Reference in New Issue