Added real sync testing, using RawFILESyncSource to store items locally. Compiles and runs on Linux, but tests mostly fail because FILESyncSource supports no change tracking yet.
See test/client-test.cpp as an example how the existing tests can be hooked up with other SyncSource implementations. The sync() function still needs to be implemented. git-svn-id: https://core.forge.funambol.org/svn/core/top-level/trunk/3x/client-api/native@12093 e8e8ed6c-164c-0410-afcf-9e9a7c7d8c10
This commit is contained in:
parent
b76e2dcd31
commit
a3137b52e7
|
@ -126,11 +126,34 @@ static void importItem(SyncSource *source, std::string &data)
|
|||
item.setData( data.c_str(), data.size() + 1 );
|
||||
item.setDataType( "raw" );
|
||||
SOURCE_ASSERT_EQUAL(source, 200, source->addItem(item));
|
||||
CPPUNIT_ASSERT(item.getKey() != NULL);
|
||||
CPPUNIT_ASSERT(item.getKey() != 0);
|
||||
CPPUNIT_ASSERT(strlen(item.getKey()) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* helper class to encapsulate ClientTest::Config::createsource_t
|
||||
* pointer and the corresponding parameters
|
||||
*/
|
||||
class CreateSource {
|
||||
public:
|
||||
CreateSource(ClientTest::Config::createsource_t createSourceParam, ClientTest &clientParam, int sourceParam, bool isSourceAParam) :
|
||||
createSource(createSourceParam),
|
||||
client(clientParam),
|
||||
source(sourceParam),
|
||||
isSourceA(isSourceAParam) {}
|
||||
|
||||
SyncSource *operator() () {
|
||||
CPPUNIT_ASSERT(createSource);
|
||||
return createSource(client, source, isSourceA);
|
||||
}
|
||||
|
||||
const ClientTest::Config::createsource_t createSource;
|
||||
ClientTest &client;
|
||||
const int source;
|
||||
const bool isSourceA;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* convenience macro for adding a test name like a function,
|
||||
|
@ -151,13 +174,23 @@ public:
|
|||
/** the client we are testing */
|
||||
ClientTest &client;
|
||||
|
||||
/** configuration for the source that we are testing in that client */
|
||||
ClientTest::Config config;
|
||||
/** number of the source we are testing in that client */
|
||||
const int source;
|
||||
|
||||
/** configuration that corresponds to source */
|
||||
const ClientTest::Config config;
|
||||
|
||||
LocalTests(const std::string &name, ClientTest &cl, ClientTest::Config &co) :
|
||||
/** helper funclets to create sources */
|
||||
CreateSource createSourceA, createSourceB;
|
||||
|
||||
LocalTests(const std::string &name, ClientTest &cl, int sourceParam, ClientTest::Config &co) :
|
||||
CppUnit::TestSuite(name),
|
||||
client(cl),
|
||||
config(co) {}
|
||||
source(sourceParam),
|
||||
config(co),
|
||||
createSourceA(co.createSourceA, cl, sourceParam, true),
|
||||
createSourceB(co.createSourceB, cl, sourceParam, false)
|
||||
{}
|
||||
|
||||
/** adds the supported tests to the instance itself */
|
||||
void addTests() {
|
||||
|
@ -197,11 +230,9 @@ public:
|
|||
* opens source and inserts the given item; can be called
|
||||
* regardless whether the data source already contains items or not
|
||||
*/
|
||||
void insert(ClientTest::Config::createsource_t createSource, const char *data) {
|
||||
CPPUNIT_ASSERT(createSource);
|
||||
|
||||
void insert(CreateSource createSource, const char *data) {
|
||||
// create source
|
||||
std::auto_ptr<SyncSource> source((*createSource)(client));
|
||||
std::auto_ptr<SyncSource> source(createSource());
|
||||
CPPUNIT_ASSERT(source.get() != 0);
|
||||
|
||||
// count number of already existing items
|
||||
|
@ -221,10 +252,10 @@ public:
|
|||
// two possible results:
|
||||
// - a new item was added
|
||||
// - the item was matched against an existing one
|
||||
CPPUNIT_ASSERT_NO_THROW(source.reset((*createSource)(client)));
|
||||
CPPUNIT_ASSERT_NO_THROW(source.reset(createSource()));
|
||||
CPPUNIT_ASSERT(source.get() != 0);
|
||||
SOURCE_ASSERT(source.get(), source->beginSync() == 0);
|
||||
CPPUNIT_ASSERT(status == STC_OK || status == STC_CONFLICT_RESOLVED_WITH_MERGE);
|
||||
CPPUNIT_ASSERT(status == STC_OK || status == STC_ITEM_ADDED || status == STC_CONFLICT_RESOLVED_WITH_MERGE);
|
||||
CPPUNIT_ASSERT_EQUAL(numItems + (status == STC_CONFLICT_RESOLVED_WITH_MERGE ? 0 : 1),
|
||||
countItems(source.get()));
|
||||
CPPUNIT_ASSERT(countNewItems(source.get()) == 0);
|
||||
|
@ -246,12 +277,12 @@ public:
|
|||
}
|
||||
|
||||
/** assumes that exactly one element is currently inserted and updates it with the given item */
|
||||
void update(ClientTest::Config::createsource_t createSource, const char *data) {
|
||||
CPPUNIT_ASSERT(createSource);
|
||||
void update(CreateSource createSource, const char *data) {
|
||||
CPPUNIT_ASSERT(createSource.createSource);
|
||||
CPPUNIT_ASSERT(data);
|
||||
|
||||
// create source
|
||||
std::auto_ptr<SyncSource> source((*createSource)(client));
|
||||
std::auto_ptr<SyncSource> source(createSource());
|
||||
CPPUNIT_ASSERT(source.get() != 0);
|
||||
SOURCE_ASSERT(source.get(), source->beginSync() == 0);
|
||||
|
||||
|
@ -266,7 +297,7 @@ public:
|
|||
CPPUNIT_ASSERT_NO_THROW(source.reset());
|
||||
|
||||
// check that the right changes are reported when reopening the source
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset((*createSource)(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource()));
|
||||
SOURCE_ASSERT(source.get(), source->beginSync() == 0 );
|
||||
CPPUNIT_ASSERT_EQUAL(1, countItems(source.get()));
|
||||
CPPUNIT_ASSERT_EQUAL(0, countNewItems(source.get()));
|
||||
|
@ -280,11 +311,11 @@ public:
|
|||
}
|
||||
|
||||
/** deletes all items locally via sync source */
|
||||
void deleteAll(ClientTest::Config::createsource_t createSource) {
|
||||
CPPUNIT_ASSERT(createSource);
|
||||
void deleteAll(CreateSource createSource) {
|
||||
CPPUNIT_ASSERT(createSource.createSource);
|
||||
|
||||
// create source
|
||||
std::auto_ptr<SyncSource> source((*createSource)(client));
|
||||
std::auto_ptr<SyncSource> source(createSource());
|
||||
CPPUNIT_ASSERT(source.get() != 0);
|
||||
SOURCE_ASSERT(source.get(), source->beginSync() == 0);
|
||||
|
||||
|
@ -300,7 +331,7 @@ public:
|
|||
CPPUNIT_ASSERT_NO_THROW(source.reset());
|
||||
|
||||
// check that all items are gone
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset((*createSource)(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_MESSAGE(
|
||||
"should be empty now",
|
||||
|
@ -332,7 +363,7 @@ public:
|
|||
sourceFile = getCurrentTest() + ".source.test.dat";
|
||||
simplifyFilename(sourceFile);
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(config.createSourceA(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, config.dump(client, *source.get(), sourceFile.c_str()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->endSync());
|
||||
|
@ -358,12 +389,12 @@ public:
|
|||
* @param size minimum size for new items
|
||||
* @return number of items inserted
|
||||
*/
|
||||
int insertManyItems(ClientTest::Config::createsource_t createSource, int startIndex = 1, int numItems = 0, int size = -1) {
|
||||
int insertManyItems(CreateSource createSource, int startIndex = 1, int numItems = 0, int size = -1) {
|
||||
CPPUNIT_ASSERT(config.templateItem);
|
||||
CPPUNIT_ASSERT(config.uniqueProperties);
|
||||
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(config.createSourceA(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
CPPUNIT_ASSERT(startIndex > 1 || !countItems(source.get()));
|
||||
|
||||
|
@ -445,7 +476,7 @@ public:
|
|||
// check requirements
|
||||
CPPUNIT_ASSERT(config.createSourceA);
|
||||
|
||||
std::auto_ptr<SyncSource> source((*config.createSourceA)(client));
|
||||
std::auto_ptr<SyncSource> source(createSourceA());
|
||||
CPPUNIT_ASSERT(source.get() != 0);
|
||||
CPPUNIT_ASSERT_NO_THROW(source.reset());
|
||||
}
|
||||
|
@ -456,7 +487,7 @@ public:
|
|||
CPPUNIT_ASSERT(config.createSourceA);
|
||||
|
||||
// open source
|
||||
std::auto_ptr<SyncSource> source(config.createSourceA(client));
|
||||
std::auto_ptr<SyncSource> source(createSourceA());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_MESSAGE(
|
||||
"iterating twice should produce identical results",
|
||||
|
@ -470,7 +501,7 @@ public:
|
|||
CPPUNIT_ASSERT(config.insertItem);
|
||||
CPPUNIT_ASSERT(config.createSourceA);
|
||||
|
||||
insert(config.createSourceA, config.insertItem);
|
||||
insert(createSourceA, config.insertItem);
|
||||
}
|
||||
|
||||
// delete all items
|
||||
|
@ -480,8 +511,8 @@ public:
|
|||
CPPUNIT_ASSERT(config.createSourceA);
|
||||
|
||||
// make sure there is something to delete, then delete again
|
||||
insert(config.createSourceA, config.insertItem);
|
||||
deleteAll(config.createSourceA);
|
||||
insert(createSourceA, config.insertItem);
|
||||
deleteAll(createSourceA);
|
||||
}
|
||||
|
||||
// clean database, then insert
|
||||
|
@ -498,7 +529,7 @@ public:
|
|||
|
||||
testLocalDeleteAll();
|
||||
testSimpleInsert();
|
||||
update(config.createSourceA, config.updateItem);
|
||||
update(createSourceA, config.updateItem);
|
||||
}
|
||||
|
||||
// complex sequence of changes
|
||||
|
@ -511,13 +542,13 @@ public:
|
|||
|
||||
// clean changes in sync source B by creating and closing it
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->endSync());
|
||||
CPPUNIT_ASSERT_NO_THROW(source.reset());
|
||||
|
||||
// no new changes now
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
|
||||
|
@ -529,8 +560,8 @@ public:
|
|||
CPPUNIT_ASSERT_NO_THROW(source.reset());
|
||||
|
||||
// delete item again via sync source A
|
||||
deleteAll(config.createSourceA);
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(config.createSourceB(client)));
|
||||
deleteAll(createSourceA);
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
|
||||
|
@ -546,7 +577,7 @@ public:
|
|||
|
||||
// insert another item via sync source A
|
||||
testSimpleInsert();
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
|
||||
|
@ -562,8 +593,8 @@ public:
|
|||
CPPUNIT_ASSERT_NO_THROW(source.reset());
|
||||
|
||||
// update item via sync source A
|
||||
update(config.createSourceA, config.updateItem);
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(config.createSourceB(client)));
|
||||
update(createSourceA, config.updateItem);
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
|
||||
|
@ -590,7 +621,7 @@ public:
|
|||
|
||||
// import via sync source A
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(config.createSourceA(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, config.import(client, *source.get(), config.testcases));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->endSync());
|
||||
|
@ -598,7 +629,7 @@ public:
|
|||
|
||||
// export again and compare against original file
|
||||
std::auto_ptr<SyncSource> copy;
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(config.createSourceA(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceA()));
|
||||
compareDatabases(config.testcases, *copy.get());
|
||||
CPPUNIT_ASSERT_NO_THROW(source.reset());
|
||||
}
|
||||
|
@ -618,21 +649,21 @@ public:
|
|||
CPPUNIT_ASSERT(config.templateItem);
|
||||
CPPUNIT_ASSERT(config.uniqueProperties);
|
||||
|
||||
deleteAll(config.createSourceA);
|
||||
deleteAll(createSourceA);
|
||||
|
||||
// check that everything is empty, also resets change counter of sync source B
|
||||
std::auto_ptr<SyncSource> copy;
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), 0, copy->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), 0, copy->endSync());
|
||||
CPPUNIT_ASSERT_NO_THROW(copy.reset());
|
||||
|
||||
// now insert plenty of items
|
||||
int numItems = insertManyItems(config.createSourceA);
|
||||
int numItems = insertManyItems(createSourceA);
|
||||
|
||||
// check that exactly this number of items is listed as new
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), 0, copy->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), numItems, countItems(copy.get()));
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), numItems, countNewItems(copy.get()));
|
||||
|
@ -642,10 +673,10 @@ public:
|
|||
CPPUNIT_ASSERT_NO_THROW(copy.reset());
|
||||
|
||||
// delete all items
|
||||
deleteAll(config.createSourceA);
|
||||
deleteAll(createSourceA);
|
||||
|
||||
// verify again
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
|
||||
|
@ -677,7 +708,7 @@ public:
|
|||
|
||||
if (config.sourceName) {
|
||||
sourceArray[sources.size()] = *it;
|
||||
sources.push_back(std::pair<int,LocalTests *>(*it, new LocalTests(config.sourceName, cl, config)));
|
||||
sources.push_back(std::pair<int,LocalTests *>(*it, new LocalTests(config.sourceName, cl, *it, config)));
|
||||
}
|
||||
}
|
||||
sourceArray[sources.size()] = -1;
|
||||
|
@ -706,7 +737,7 @@ public:
|
|||
/** adds the supported tests to the instance itself */
|
||||
void addTests() {
|
||||
if (sources.size()) {
|
||||
ClientTest::Config &config(sources[0].second->config);
|
||||
const ClientTest::Config &config(sources[0].second->config);
|
||||
|
||||
ADD_TEST(SyncTests, testTwoWaySync);
|
||||
ADD_TEST(SyncTests, testSlowSync);
|
||||
|
@ -784,7 +815,7 @@ private:
|
|||
it1 != sources.end() && it2 != accessClientB->sources.end();
|
||||
++it1, ++it2) {
|
||||
std::auto_ptr<SyncSource> copy;
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it2->second->config.createSourceB(accessClientB->client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it2->second->createSourceB()));
|
||||
it1->second->compareDatabases(NULL, *copy.get());
|
||||
CPPUNIT_ASSERT_NO_THROW(copy.reset());
|
||||
}
|
||||
|
@ -800,19 +831,19 @@ private:
|
|||
case DELETE_ALL_SYNC:
|
||||
// a refresh from server would slightly reduce the amount of data exchanged, but not all servers support it
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->deleteAll(it->second->config.createSourceA);
|
||||
it->second->deleteAll(it->second->createSourceA);
|
||||
}
|
||||
sync(SYNC_TWO_WAY, ".deleteall.init");
|
||||
// now that client and server are in sync, delete locally and sync again
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->deleteAll(it->second->config.createSourceA);
|
||||
it->second->deleteAll(it->second->createSourceA);
|
||||
}
|
||||
sync(SYNC_TWO_WAY, ".deleteall.twoway");
|
||||
break;
|
||||
case DELETE_ALL_REFRESH:
|
||||
// delete locally and then tell the server to "copy" the empty databases
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->deleteAll(it->second->config.createSourceA);
|
||||
it->second->deleteAll(it->second->createSourceA);
|
||||
}
|
||||
sync(SYNC_REFRESH_FROM_CLIENT, ".deleteall.refreshserver");
|
||||
break;
|
||||
|
@ -848,7 +879,7 @@ private:
|
|||
void refreshClient() {
|
||||
source_it it;
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->deleteAll(it->second->config.createSourceA);
|
||||
it->second->deleteAll(it->second->createSourceA);
|
||||
}
|
||||
sync(SYNC_SLOW, ".refresh");
|
||||
}
|
||||
|
@ -892,7 +923,7 @@ private:
|
|||
// nothing stored locally?
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceA(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->endSync());
|
||||
|
@ -903,7 +934,7 @@ private:
|
|||
sync(SYNC_SLOW, ".check.client.log");
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceA(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->endSync());
|
||||
|
@ -927,7 +958,7 @@ private:
|
|||
// check
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceA(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->endSync());
|
||||
|
@ -948,7 +979,7 @@ private:
|
|||
it->second->testSimpleInsert();
|
||||
}
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->deleteAll(it->second->config.createSourceA);
|
||||
it->second->deleteAll(it->second->createSourceA);
|
||||
}
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->testSimpleInsert();
|
||||
|
@ -975,7 +1006,7 @@ private:
|
|||
|
||||
source_it it;
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->update(it->second->config.createSourceA, it->second->config.updateItem);
|
||||
it->second->update(it->second->createSourceA, it->second->config.updateItem);
|
||||
}
|
||||
|
||||
sync(SYNC_TWO_WAY, ".update");
|
||||
|
@ -993,7 +1024,7 @@ private:
|
|||
|
||||
source_it it;
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->update(it->second->config.createSourceA, it->second->config.complexUpdateItem);
|
||||
it->second->update(it->second->createSourceA, it->second->config.complexUpdateItem);
|
||||
}
|
||||
|
||||
sync(SYNC_TWO_WAY, ".update");
|
||||
|
@ -1011,7 +1042,7 @@ private:
|
|||
// delete it on A
|
||||
source_it it;
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->deleteAll(it->second->config.createSourceA);
|
||||
it->second->deleteAll(it->second->createSourceA);
|
||||
}
|
||||
|
||||
// transfer change from A to server to B
|
||||
|
@ -1021,7 +1052,7 @@ private:
|
|||
// check client B: shouldn't have any items now
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
std::auto_ptr<SyncSource> copy;
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it->second->config.createSourceA(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it->second->createSourceA()));
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), 0, copy->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), 0, copy->endSync());
|
||||
|
@ -1038,12 +1069,12 @@ private:
|
|||
// update in client A
|
||||
source_it it;
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->update(it->second->config.createSourceA, it->second->config.mergeItem1);
|
||||
it->second->update(it->second->createSourceA, it->second->config.mergeItem1);
|
||||
}
|
||||
|
||||
// update in client B
|
||||
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
|
||||
it->second->update(it->second->config.createSourceA, it->second->config.mergeItem2);
|
||||
it->second->update(it->second->createSourceA, it->second->config.mergeItem2);
|
||||
}
|
||||
|
||||
// send change to server from client A (no conflict), then from B (conflict)
|
||||
|
@ -1053,7 +1084,7 @@ private:
|
|||
// figure out how the conflict during ".recv" was handled
|
||||
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
|
||||
std::auto_ptr<SyncSource> copy;
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it->second->config.createSourceA(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it->second->createSourceA()));
|
||||
SOURCE_ASSERT_EQUAL(copy.get(), 0, copy->beginSync());
|
||||
int numItems;
|
||||
SOURCE_ASSERT_NO_FAILURE(copy.get(), numItems = countItems(copy.get()));
|
||||
|
@ -1116,7 +1147,7 @@ private:
|
|||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->endSync());
|
||||
|
@ -1126,7 +1157,7 @@ private:
|
|||
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(accessClientB->client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->endSync());
|
||||
|
@ -1136,13 +1167,13 @@ private:
|
|||
|
||||
// add one item on first client, copy to server, and check change tracking via second source
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->insertManyItems(it->second->config.createSourceA, 1, 1);
|
||||
it->second->insertManyItems(it->second->createSourceA, 1, 1);
|
||||
}
|
||||
sync(SYNC_TWO_WAY, ".send");
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
|
||||
|
@ -1156,11 +1187,11 @@ private:
|
|||
// add a different item on second client, one-way-from-server
|
||||
// => one item added locally, none sent to server
|
||||
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
|
||||
it->second->insertManyItems(it->second->config.createSourceA, 2, 1);
|
||||
it->second->insertManyItems(it->second->createSourceA, 2, 1);
|
||||
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(accessClientB->client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
|
||||
|
@ -1174,7 +1205,7 @@ private:
|
|||
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(accessClientB->client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 2, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
|
||||
|
@ -1191,7 +1222,7 @@ private:
|
|||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
|
||||
|
@ -1204,11 +1235,11 @@ private:
|
|||
|
||||
// delete items on clientA, sync to server
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->deleteAll(it->second->config.createSourceA);
|
||||
it->second->deleteAll(it->second->createSourceA);
|
||||
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
|
||||
|
@ -1222,7 +1253,7 @@ private:
|
|||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
|
||||
|
@ -1239,7 +1270,7 @@ private:
|
|||
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(accessClientB->client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
|
||||
|
@ -1271,7 +1302,7 @@ private:
|
|||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->endSync());
|
||||
|
@ -1281,7 +1312,7 @@ private:
|
|||
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(accessClientB->client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->endSync());
|
||||
|
@ -1291,13 +1322,13 @@ private:
|
|||
|
||||
// add one item on first client, copy to server, and check change tracking via second source
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->insertManyItems(it->second->config.createSourceA, 1, 1);
|
||||
it->second->insertManyItems(it->second->createSourceA, 1, 1);
|
||||
}
|
||||
sync(SYNC_TWO_WAY, ".send");
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
|
||||
|
@ -1311,11 +1342,11 @@ private:
|
|||
// add a different item on second client, one-way-from-client
|
||||
// => no item added locally, one sent to server
|
||||
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
|
||||
it->second->insertManyItems(it->second->config.createSourceA, 2, 1);
|
||||
it->second->insertManyItems(it->second->createSourceA, 2, 1);
|
||||
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(accessClientB->client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
|
||||
|
@ -1329,7 +1360,7 @@ private:
|
|||
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(accessClientB->client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
|
||||
|
@ -1346,7 +1377,7 @@ private:
|
|||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 2, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
|
||||
|
@ -1359,11 +1390,11 @@ private:
|
|||
|
||||
// delete items on client B, sync to server
|
||||
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
|
||||
it->second->deleteAll(it->second->config.createSourceA);
|
||||
it->second->deleteAll(it->second->createSourceA);
|
||||
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(accessClientB->client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
|
||||
|
@ -1377,7 +1408,7 @@ private:
|
|||
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(accessClientB->client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
|
||||
|
@ -1394,7 +1425,7 @@ private:
|
|||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
if (it->second->config.createSourceB) {
|
||||
std::auto_ptr<SyncSource> source;
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->config.createSourceB(client)));
|
||||
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, source->beginSync());
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
|
||||
SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
|
||||
|
@ -1443,13 +1474,13 @@ private:
|
|||
// add item
|
||||
source_it it;
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->insert(it->second->config.createSourceA, it->second->config.insertItem);
|
||||
it->second->insert(it->second->createSourceA, it->second->config.insertItem);
|
||||
}
|
||||
sync(SYNC_TWO_WAY, ".add");
|
||||
|
||||
// update it
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->insert(it->second->config.createSourceB, it->second->config.updateItem);
|
||||
it->second->insert(it->second->createSourceB, it->second->config.updateItem);
|
||||
}
|
||||
sync(SYNC_TWO_WAY, ".update");
|
||||
|
||||
|
@ -1492,7 +1523,7 @@ private:
|
|||
// import artificial data
|
||||
source_it it;
|
||||
for (it = sources.begin(); it != sources.end(); ++it) {
|
||||
it->second->insertManyItems(it->second->config.createSourceA);
|
||||
it->second->insertManyItems(it->second->createSourceA);
|
||||
}
|
||||
|
||||
// send data to server
|
||||
|
@ -1532,7 +1563,7 @@ private:
|
|||
for (int i = 0; i < 2; i++ ) {
|
||||
int size = 1;
|
||||
while (size < 2 * maxMsgSize) {
|
||||
it->second->insertManyItems(it->second->config.createSourceA, item, 1, strlen(it->second->config.templateItem) + 10 + size);
|
||||
it->second->insertManyItems(it->second->createSourceA, item, 1, strlen(it->second->config.templateItem) + 10 + size);
|
||||
size *= 2;
|
||||
item++;
|
||||
}
|
||||
|
@ -1565,8 +1596,15 @@ private:
|
|||
const char *encoding = 0) {
|
||||
int res = 0;
|
||||
static int syncCounter = 0;
|
||||
static std::string lastTest;
|
||||
std::stringstream logstream;
|
||||
|
||||
// reset counter when switching tests
|
||||
if (lastTest != getCurrentTest()) {
|
||||
syncCounter = 0;
|
||||
lastTest = getCurrentTest();
|
||||
}
|
||||
|
||||
logstream << std::setw(4) << std::setfill('0') << syncCounter << "_" << getCurrentTest() << logprefix;
|
||||
std::string logname = logstream.str();
|
||||
simplifyFilename(logname);
|
||||
|
@ -1616,7 +1654,7 @@ public:
|
|||
client.getSourceConfig(source, config);
|
||||
if (config.sourceName) {
|
||||
LocalTests *sourcetests =
|
||||
new LocalTests(tests->getName() + "::" + config.sourceName, client, config);
|
||||
new LocalTests(tests->getName() + "::" + config.sourceName, client, source, config);
|
||||
sourcetests->addTests();
|
||||
tests->addTest(sourcetests);
|
||||
}
|
||||
|
@ -1717,6 +1755,13 @@ int ClientTest::import(ClientTest &client, SyncSource &source, const char *file)
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool ClientTest::compare(ClientTest &client, const char *fileA, const char *fileB)
|
||||
{
|
||||
std::string cmdstr = std::string("perl synccompare.pl ") + fileA + " " + fileB;
|
||||
return system(cmdstr.c_str()) == 0;
|
||||
}
|
||||
|
||||
|
||||
#ifndef WIN32
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
@ -1740,3 +1785,244 @@ void ClientTest::postSync(int res, const std::string &logname)
|
|||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClientTest::getTestData(const char *type, Config &config)
|
||||
{
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
if (!strcmp(type, "vcard30")) {
|
||||
config.sourceName = "vcard30";
|
||||
config.type = "text/vcard";
|
||||
config.insertItem =
|
||||
"BEGIN:VCARD\n"
|
||||
"VERSION:3.0\n"
|
||||
"TITLE:tester\n"
|
||||
"FN:John Doe\n"
|
||||
"N:Doe;John;;;\n"
|
||||
"TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
|
||||
"X-EVOLUTION-FILE-AS:Doe\\, John\n"
|
||||
"X-MOZILLA-HTML:FALSE\n"
|
||||
"NOTE:\n"
|
||||
"END:VCARD\n";
|
||||
config.updateItem =
|
||||
"BEGIN:VCARD\n"
|
||||
"VERSION:3.0\n"
|
||||
"TITLE:tester\n"
|
||||
"FN:Joan Doe\n"
|
||||
"N:Doe;Joan;;;\n"
|
||||
"X-EVOLUTION-FILE-AS:Doe\\, Joan\n"
|
||||
"TEL;TYPE=WORK;TYPE=VOICE:business 2\n"
|
||||
"BDAY:2006-01-08\n"
|
||||
"X-MOZILLA-HTML:TRUE\n"
|
||||
"END:VCARD\n";
|
||||
/* adds a second phone number: */
|
||||
config.complexUpdateItem =
|
||||
"BEGIN:VCARD\n"
|
||||
"VERSION:3.0\n"
|
||||
"TITLE:tester\n"
|
||||
"FN:Joan Doe\n"
|
||||
"N:Doe;Joan;;;\n"
|
||||
"X-EVOLUTION-FILE-AS:Doe\\, Joan\n"
|
||||
"TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
|
||||
"TEL;TYPE=HOME;TYPE=VOICE:home 2\n"
|
||||
"BDAY:2006-01-08\n"
|
||||
"X-MOZILLA-HTML:TRUE\n"
|
||||
"END:VCARD\n";
|
||||
/* add a telephone number, email and X-AIM to initial item */
|
||||
config.mergeItem1 =
|
||||
"BEGIN:VCARD\n"
|
||||
"VERSION:3.0\n"
|
||||
"TITLE:tester\n"
|
||||
"FN:John Doe\n"
|
||||
"N:Doe;John;;;\n"
|
||||
"X-EVOLUTION-FILE-AS:Doe\\, John\n"
|
||||
"X-MOZILLA-HTML:FALSE\n"
|
||||
"TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
|
||||
"EMAIL:john.doe@work.com\n"
|
||||
"X-AIM:AIM JOHN\n"
|
||||
"END:VCARD\n";
|
||||
config.mergeItem2 =
|
||||
"BEGIN:VCARD\n"
|
||||
"VERSION:3.0\n"
|
||||
"TITLE:developer\n"
|
||||
"FN:John Doe\n"
|
||||
"N:Doe;John;;;\n"
|
||||
"X-EVOLUTION-FILE-AS:Doe\\, John\n"
|
||||
"X-MOZILLA-HTML:TRUE\n"
|
||||
"BDAY:2006-01-08\n"
|
||||
"END:VCARD\n";
|
||||
config.templateItem = config.insertItem;
|
||||
config.uniqueProperties = "FN:N:X-EVOLUTION-FILE-AS";
|
||||
config.sizeProperty = "NOTE";
|
||||
config.import = import;
|
||||
config.dump = dump;
|
||||
config.compare = compare;
|
||||
config.testcases = "testcases/vcard30.vcf";
|
||||
} else if(!strcmp(type, "ical20")) {
|
||||
config.sourceName = "ical20";
|
||||
config.type = "text/x-vcalendar";
|
||||
config.insertItem =
|
||||
"BEGIN:VCALENDAR\n"
|
||||
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
|
||||
"VERSION:2.0\n"
|
||||
"METHOD:PUBLISH\n"
|
||||
"BEGIN:VEVENT\n"
|
||||
"SUMMARY:phone meeting\n"
|
||||
"DTEND;20060406T163000Z\n"
|
||||
"DTSTART;20060406T160000Z\n"
|
||||
"UID:1234567890!@#$%^&*()<>@dummy\n"
|
||||
"DTSTAMP:20060406T211449Z\n"
|
||||
"LAST-MODIFIED:20060409T213201\n"
|
||||
"CREATED:20060409T213201\n"
|
||||
"LOCATION:my office\n"
|
||||
"DESCRIPTION:let's talk\n"
|
||||
"CLASS:PUBLIC\n"
|
||||
"TRANSP:OPAQUE\n"
|
||||
"SEQUENCE:1\n"
|
||||
"END:VEVENT\n"
|
||||
"END:VCALENDAR\n";
|
||||
config.updateItem =
|
||||
"BEGIN:VCALENDAR\n"
|
||||
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
|
||||
"VERSION:2.0\n"
|
||||
"METHOD:PUBLISH\n"
|
||||
"BEGIN:VEVENT\n"
|
||||
"SUMMARY:meeting on site\n"
|
||||
"DTEND;20060406T163000Z\n"
|
||||
"DTSTART;20060406T160000Z\n"
|
||||
"UID:1234567890!@#$%^&*()<>@dummy\n"
|
||||
"DTSTAMP:20060406T211449Z\n"
|
||||
"LAST-MODIFIED:20060409T213201\n"
|
||||
"CREATED:20060409T213201\n"
|
||||
"LOCATION:big meeting room\n"
|
||||
"DESCRIPTION:nice to see you\n"
|
||||
"CLASS:PUBLIC\n"
|
||||
"TRANSP:OPAQUE\n"
|
||||
"SEQUENCE:1\n"
|
||||
"END:VEVENT\n"
|
||||
"END:VCALENDAR\n";
|
||||
/* change location in insertItem in testMerge() */
|
||||
config.mergeItem1 =
|
||||
"BEGIN:VCALENDAR\n"
|
||||
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
|
||||
"VERSION:2.0\n"
|
||||
"METHOD:PUBLISH\n"
|
||||
"BEGIN:VEVENT\n"
|
||||
"SUMMARY:phone meeting\n"
|
||||
"DTEND;20060406T163000Z\n"
|
||||
"DTSTART;20060406T160000Z\n"
|
||||
"UID:1234567890!@#$%^&*()<>@dummy\n"
|
||||
"DTSTAMP:20060406T211449Z\n"
|
||||
"LAST-MODIFIED:20060409T213201\n"
|
||||
"CREATED:20060409T213201\n"
|
||||
"LOCATION:calling from home\n"
|
||||
"DESCRIPTION:let's talk\n"
|
||||
"CLASS:PUBLIC\n"
|
||||
"TRANSP:OPAQUE\n"
|
||||
"SEQUENCE:1\n"
|
||||
"END:VEVENT\n"
|
||||
"END:VCALENDAR\n";
|
||||
config.mergeItem2 =
|
||||
"BEGIN:VCALENDAR\n"
|
||||
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
|
||||
"VERSION:2.0\n"
|
||||
"METHOD:PUBLISH\n"
|
||||
"BEGIN:VEVENT\n"
|
||||
"SUMMARY:phone meeting\n"
|
||||
"DTEND;20060406T163000Z\n"
|
||||
"DTSTART;20060406T160000Z\n"
|
||||
"UID:1234567890!@#$%^&*()<>@dummy\n"
|
||||
"DTSTAMP:20060406T211449Z\n"
|
||||
"LAST-MODIFIED:20060409T213201\n"
|
||||
"CREATED:20060409T213201\n"
|
||||
"LOCATION:my office\n"
|
||||
"DESCRIPTION:what the heck\\, let's even shout a bit\n"
|
||||
"CLASS:PUBLIC\n"
|
||||
"TRANSP:OPAQUE\n"
|
||||
"SEQUENCE:1\n"
|
||||
"END:VEVENT\n"
|
||||
"END:VCALENDAR\n";
|
||||
config.templateItem = config.insertItem;
|
||||
config.uniqueProperties = "SUMMARY:UID";
|
||||
config.sizeProperty = "DESCRIPTION";
|
||||
config.import = import;
|
||||
config.dump = dump;
|
||||
config.compare = compare;
|
||||
config.testcases = "testcases/ical20.ics";
|
||||
} else if(!strcmp(type, "itodo20")) {
|
||||
config.sourceName = "itodo20";
|
||||
config.type = "text/x-vcalendar";
|
||||
config.insertItem =
|
||||
"BEGIN:VCALENDAR\n"
|
||||
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
|
||||
"VERSION:2.0\n"
|
||||
"METHOD:PUBLISH\n"
|
||||
"BEGIN:VTODO\n"
|
||||
"UID:20060417T173712Z-4360-727-1-2730@gollum\n"
|
||||
"DTSTAMP:20060417T173712Z\n"
|
||||
"SUMMARY:do me\n"
|
||||
"DESCRIPTION:to be done\n"
|
||||
"PRIORITY:0\n"
|
||||
"STATUS:IN-PROCESS\n"
|
||||
"CREATED:20060417T173712\n"
|
||||
"LAST-MODIFIED:20060417T173712\n"
|
||||
"END:VTODO\n"
|
||||
"END:VCALENDAR\n";
|
||||
config.updateItem =
|
||||
"BEGIN:VCALENDAR\n"
|
||||
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
|
||||
"VERSION:2.0\n"
|
||||
"METHOD:PUBLISH\n"
|
||||
"BEGIN:VTODO\n"
|
||||
"UID:20060417T173712Z-4360-727-1-2730@gollum\n"
|
||||
"DTSTAMP:20060417T173712Z\n"
|
||||
"SUMMARY:do me ASAP\n"
|
||||
"DESCRIPTION:to be done\n"
|
||||
"PRIORITY:1\n"
|
||||
"STATUS:IN-PROCESS\n"
|
||||
"CREATED:20060417T173712\n"
|
||||
"LAST-MODIFIED:20060417T173712\n"
|
||||
"END:VTODO\n"
|
||||
"END:VCALENDAR\n";
|
||||
/* change summary in insertItem in testMerge() */
|
||||
config.mergeItem1 =
|
||||
"BEGIN:VCALENDAR\n"
|
||||
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
|
||||
"VERSION:2.0\n"
|
||||
"METHOD:PUBLISH\n"
|
||||
"BEGIN:VTODO\n"
|
||||
"UID:20060417T173712Z-4360-727-1-2730@gollum\n"
|
||||
"DTSTAMP:20060417T173712Z\n"
|
||||
"SUMMARY:do me please\\, please\n"
|
||||
"DESCRIPTION:to be done\n"
|
||||
"PRIORITY:0\n"
|
||||
"STATUS:IN-PROCESS\n"
|
||||
"CREATED:20060417T173712\n"
|
||||
"LAST-MODIFIED:20060417T173712\n"
|
||||
"END:VTODO\n"
|
||||
"END:VCALENDAR\n";
|
||||
config.mergeItem2 =
|
||||
"BEGIN:VCALENDAR\n"
|
||||
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
|
||||
"VERSION:2.0\n"
|
||||
"METHOD:PUBLISH\n"
|
||||
"BEGIN:VTODO\n"
|
||||
"UID:20060417T173712Z-4360-727-1-2730@gollum\n"
|
||||
"DTSTAMP:20060417T173712Z\n"
|
||||
"SUMMARY:do me\n"
|
||||
"DESCRIPTION:to be done\n"
|
||||
"PRIORITY:7\n"
|
||||
"STATUS:IN-PROCESS\n"
|
||||
"CREATED:20060417T173712\n"
|
||||
"LAST-MODIFIED:20060417T173712\n"
|
||||
"END:VTODO\n"
|
||||
"END:VCALENDAR\n";
|
||||
config.templateItem = config.insertItem;
|
||||
config.uniqueProperties = "SUMMARY:UID";
|
||||
config.sizeProperty = "DESCRIPTION";
|
||||
config.import = import;
|
||||
config.dump = dump;
|
||||
config.compare = compare;
|
||||
config.testcases = "testcases/itodo20.ics";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,6 +89,28 @@ class ClientTest {
|
|||
*/
|
||||
static int import(ClientTest &client, SyncSource &source, const char *file);
|
||||
|
||||
/**
|
||||
* utility function for comparing vCard and iCal files with the external
|
||||
* synccompare.pl Perl script
|
||||
*/
|
||||
static bool compare(ClientTest &client, const char *fileA, const char *fileB);
|
||||
|
||||
struct Config;
|
||||
|
||||
/**
|
||||
* A derived class can use this call to get default test
|
||||
* cases, but still has to add callbacks which create sources
|
||||
* and execute a sync session.
|
||||
*
|
||||
* Some of the test cases are compiled into the library, other
|
||||
* depend on the auxiliary files from the "test" directory.
|
||||
* Currently supported types:
|
||||
* - vcard30 = vCard 3.0 contacts
|
||||
* - ical20 = iCal 2.0 events
|
||||
* - itodo20 = iCal 2.0 tasks
|
||||
*/
|
||||
static void getTestData(const char *type, Config &config);
|
||||
|
||||
/**
|
||||
* Information about a data source. For the sake of simplicity all
|
||||
* items pointed to are owned by the ClientTest and must
|
||||
|
@ -121,8 +143,11 @@ class ClientTest {
|
|||
* the sync source's desctructor should not thow exceptions.
|
||||
*
|
||||
* @param client the same instance to which this config belongs
|
||||
* @param source index of the data source (from 0 to ClientTest::getNumSources() - 1)
|
||||
* @param isSourceA true if the requested SyncSource is the first one accessing that
|
||||
* data, otherwise the second
|
||||
*/
|
||||
typedef SyncSource *(*createsource_t)(ClientTest &client);
|
||||
typedef SyncSource *(*createsource_t)(ClientTest &client, int source, bool isSourceA);
|
||||
|
||||
/**
|
||||
* Creates a sync source which references the primary database;
|
||||
|
@ -249,6 +274,12 @@ class ClientTest {
|
|||
* a file with test cases in the format expected by import and compare
|
||||
*/
|
||||
const char *testcases;
|
||||
|
||||
/**
|
||||
* the item type normally used by the source (not used by the tests
|
||||
* themselves; client-test.cpp uses it to initialize source configs)
|
||||
*/
|
||||
const char *type;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright (C) 2006 Funambol
|
||||
*
|
||||
* 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 for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/**
|
||||
* This code uses the ClientTest and FILESyncSource to test real
|
||||
* synchronization against a server. More than one FILESyncSource can
|
||||
* be active at once and each of them may (but does not have to be)
|
||||
* used for different kinds of data. The name of the source determines
|
||||
* which data is stored in it: it must be something supported by the
|
||||
* ClientTest class, because that is where the test data comes from.
|
||||
*
|
||||
* At least the following kinds of data are currently supported by the
|
||||
* ClientTest class (see ClientTest::getSourceConfig() for more
|
||||
* information):
|
||||
* - vcard30 = vCard 3.0 contacts
|
||||
* - ical20 = iCalendar 2.0 calendar events
|
||||
* - itodo20 = iCalendar 2.0 tasks
|
||||
*
|
||||
* Configuration is done by environment variables which indicate which
|
||||
* part below the root node "client-test" of the the configuration tree to use;
|
||||
* beyond that everything needed for synchronization is read from the
|
||||
* configuration tree.
|
||||
*
|
||||
* - CLIENT_TEST_SERVER = maps to name of root node in configuration tree
|
||||
* - CLIENT_TEST_SOURCES = comma separated list of active sources,
|
||||
* names as listed above
|
||||
* - CLIENT_TEST_DELAY = number of seconds to sleep between syncs, required
|
||||
* by some servers
|
||||
* - CLIENT_TEST_LOG = logfile name of a server, can be empty:
|
||||
* if given, then the content of that file will be
|
||||
* copied and stored together with the client log
|
||||
* (only works on Unix)
|
||||
*
|
||||
* For example, on Linux running
|
||||
* CLIENT_TEST_SERVER=funambol CLIENT_TEST_SOURCES=vcard30,ical20 ./client-test
|
||||
* expects the following configuration layout:
|
||||
* ~/.sync4j/client-test/
|
||||
* funambol_1/spds/
|
||||
* syncml/config.text
|
||||
* sources/
|
||||
* vcard30/config.txt
|
||||
* ical20/config.txt
|
||||
* funambol_1/spds/
|
||||
* <same as for funambol_1>
|
||||
*
|
||||
* If any of the configuration nodes does not exist yet, then it will
|
||||
* be created, but further information may have to be added, in
|
||||
* particular:
|
||||
* - server URL
|
||||
* - server user name, password
|
||||
* - sources uri
|
||||
*
|
||||
* The two configurations are used to simulate synchronization between
|
||||
* two different clients.
|
||||
*
|
||||
* The file sources will store their items in sub directories of
|
||||
* a "client-data" directory created in the current working directory.
|
||||
*
|
||||
* Here is an example of using the CLIENT_TEST_LOG:
|
||||
* CLIENT_TEST_SERVER=funambol \
|
||||
* CLIENT_TEST_LOG=/opt/Funambol-3.0/ds-server/logs/funambol_ds.log \
|
||||
* CLIENT_TEST_SOURCES=vcard30 \
|
||||
* ./client-test
|
||||
* will create files with the suffix .client.1.log for synchronizations with
|
||||
* the first client and .client.2.log for the second client. The base name
|
||||
* of these files is unique, so the corresponding part of the server log
|
||||
* is stored with the same base name and .server.log as suffix.
|
||||
*/
|
||||
|
||||
#include "spds/RawFILESyncSource.h"
|
||||
#include "client/DMTClientConfig.h"
|
||||
#include "test/ClientTest.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
class TestFileSource : public ClientTest {
|
||||
public:
|
||||
TestFileSource(const std::string &id) :
|
||||
ClientTest(getenv("CLIENT_TEST_DELAY") ? atoi(getenv("CLIENT_TEST_DELAY")) : 0,
|
||||
getenv("CLIENT_TEST_LOG") ? getenv("CLIENT_TEST_LOG") : ""),
|
||||
clientID(id) {
|
||||
const char *sourcelist = getenv("CLIENT_TEST_SOURCES");
|
||||
const char *server = getenv("CLIENT_TEST_SERVER");
|
||||
|
||||
/* set up source list */
|
||||
if (!sourcelist) {
|
||||
sourcelist = "";
|
||||
}
|
||||
const char *eostr = strchr(sourcelist, ',');
|
||||
const char *start = sourcelist;
|
||||
|
||||
while (eostr) {
|
||||
sources.push_back(std::string(start, 0, eostr - start));
|
||||
start = eostr + 1;
|
||||
eostr = strchr(start, ',');
|
||||
}
|
||||
if (start[0]) {
|
||||
sources.push_back(start);
|
||||
}
|
||||
|
||||
/* check server */
|
||||
if (!server) {
|
||||
server = "funambol";
|
||||
}
|
||||
|
||||
// get configuration and set obligatory fields
|
||||
std::string root = std::string("client-test/") + server + "_" + id;
|
||||
config.reset(new DMTClientConfig(root.c_str()));
|
||||
config->read();
|
||||
DeviceConfig &dc(config->getDeviceConfig());
|
||||
if (!strlen(dc.getDevID())) {
|
||||
config->setClientDefaults();
|
||||
dc.setDevID(id == "1" ? "sc-api-nat" : "sc-pim-ppc");
|
||||
}
|
||||
for (int source = 0; source < sources.size(); source++) {
|
||||
|
||||
SyncSourceConfig* sc = config->getSyncSourceConfig(sources[source].c_str());
|
||||
if (!sc) {
|
||||
config->setSourceDefaults(sources[source].c_str());
|
||||
sc = config->getSyncSourceConfig(sources[source].c_str());
|
||||
CPPUNIT_ASSERT(sc);
|
||||
}
|
||||
|
||||
ClientTest::Config testconfig;
|
||||
getSourceConfig(source, testconfig);
|
||||
CPPUNIT_ASSERT(testconfig.type);
|
||||
sc->setType(testconfig.type);
|
||||
}
|
||||
config->save();
|
||||
|
||||
if (id == "1") {
|
||||
/* we are the primary client, create a second one */
|
||||
clientB.reset(new TestFileSource("2"));
|
||||
}
|
||||
}
|
||||
|
||||
virtual int getNumSources() {
|
||||
return sources.size();
|
||||
}
|
||||
|
||||
virtual void getSourceConfig(int source, Config &config) {
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
getTestData(sources[source].c_str(), config);
|
||||
config.createSourceA =
|
||||
config.createSourceB = createSource;
|
||||
}
|
||||
|
||||
virtual ClientTest* getClientB() {
|
||||
return clientB.get();
|
||||
}
|
||||
|
||||
virtual bool isB64Enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual int sync(const int*, SyncMode, long int, long int, bool, const char*) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
private:
|
||||
/** either "1" or "2" for first respectively second client */
|
||||
std::string clientID;
|
||||
|
||||
/** only in "1": pointer to second client */
|
||||
std::auto_ptr<TestFileSource> clientB;
|
||||
|
||||
/** vector of enabled sync sources, identified by a name which SyncClient::getConfig() supports */
|
||||
std::vector<std::string> sources;
|
||||
|
||||
/** configuration tree itself */
|
||||
std::auto_ptr<DMTClientConfig> config;
|
||||
|
||||
static SyncSource *createSource(ClientTest &client, int source, bool isSourceA) {
|
||||
class RawFILESyncSourceWithReport : public RawFILESyncSource {
|
||||
public:
|
||||
RawFILESyncSourceWithReport(const WCHAR* name, SyncSourceConfig* sc) :
|
||||
RawFILESyncSource(name, sc) {
|
||||
setReport(&report);
|
||||
}
|
||||
private:
|
||||
SyncSourceReport report;
|
||||
};
|
||||
|
||||
TestFileSource &testFileSource((TestFileSource &)client);
|
||||
CPPUNIT_ASSERT(source < testFileSource.sources.size());
|
||||
FILESyncSource *ss = new RawFILESyncSourceWithReport(testFileSource.sources[source].c_str(),
|
||||
testFileSource.config->getSyncSourceConfig(testFileSource.sources[source].c_str()));
|
||||
mkdir(testFileSource.sources[source].c_str(), S_IRWXU);
|
||||
ss->setDir(testFileSource.sources[source].c_str());
|
||||
|
||||
return ss;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* the only purpose of this class is to own the first TestFileSource
|
||||
* and to register its tests at program startup
|
||||
*/
|
||||
static class RegisterTest {
|
||||
public:
|
||||
RegisterTest() :
|
||||
testFileSource("1") {
|
||||
testFileSource.registerTests();
|
||||
}
|
||||
|
||||
private:
|
||||
TestFileSource testFileSource;
|
||||
} registerTest;
|
|
@ -0,0 +1,338 @@
|
|||
#! /usr/bin/perl -w
|
||||
#
|
||||
# Copyright (C) 2006 Funambol
|
||||
# Author: Patrick Ohly
|
||||
#
|
||||
# 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 for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
use strict;
|
||||
use encoding 'utf8';
|
||||
|
||||
# ignore differences caused by specific servers?
|
||||
my $server = $ENV{TEST_EVOLUTION_SERVER} || "";
|
||||
my $scheduleworld = $server =~ /scheduleworld/;
|
||||
my $synthesis = $server =~ /synthesis/;
|
||||
my $egroupware = $server =~ /egroupware/;
|
||||
my $funambol = $server =~ /funambol/;
|
||||
|
||||
sub Usage {
|
||||
print "$0 <vcards.vcf\n";
|
||||
print " normalizes one file (stdin or single argument), prints to stdout\n";
|
||||
print "$0 vcards1.vcf vcards2.vcf\n";
|
||||
print " compares the two files\n";
|
||||
print "Also works for iCalendar files.\n";
|
||||
}
|
||||
|
||||
sub Normalize {
|
||||
my $in = shift;
|
||||
my $out = shift;
|
||||
my $width = shift;
|
||||
|
||||
$_ = join( "", <$in> );
|
||||
s/\r//g;
|
||||
|
||||
my @items = ();
|
||||
|
||||
foreach $_ ( split( /(?:(?<=\nEND:VCARD)|(?<=\nEND:VCALENDAR))\n*/ ) ) {
|
||||
# undo line continuation
|
||||
s/\n\s//gs;
|
||||
# ignore charset specifications, assume UTF-8
|
||||
s/;CHARSET="?UTF-8"?//g;
|
||||
|
||||
# UID may differ, but only in vCards
|
||||
s/(VCARD.*)^UID:[^\n]*\n/$1/msg;
|
||||
|
||||
# the distinction between an empty and a missing property
|
||||
# is vague and handled differently, so ignore empty properties
|
||||
s/^[^:\n]*:;*\n//mg;
|
||||
|
||||
# use separate TYPE= fields
|
||||
while( s/^(\w*)([^:\n]*);TYPE=(\w*),(\w*)/$1$2;TYPE=$3;TYPE=$4/mg ) {}
|
||||
|
||||
# replace parameters with a sorted parameter list
|
||||
s!^([^;:\n]*);(.*?):!$1 . ";" . join(';',sort(split(/;/, $2))) . ":"!meg;
|
||||
|
||||
# Map non-standard ADR;TYPE=OTHER to PARCEL, just like SyncEvolution does
|
||||
s/^ADR;TYPE=OTHER/ADR;TYPE=PARCEL/mg;
|
||||
# Ignore remaining "other" email, address and telephone type - this is
|
||||
# an Evolution specific extension which might not be preserved.
|
||||
s/^(ADR|EMAIL|TEL)([^:\n]*);TYPE=OTHER/$1$2/mg;
|
||||
# TYPE=PREF on the other hand is not used by Evolution, but
|
||||
# might be sent back.
|
||||
s/^(ADR|EMAIL)([^:\n]*);TYPE=PREF/$1$2/mg;
|
||||
# Evolution does not need TYPE=INTERNET for email
|
||||
s/^(EMAIL)([^:\n]*);TYPE=INTERNET/$1$2/mg;
|
||||
# ignore TYPE=PREF in address, does not matter in Evolution
|
||||
s/^((ADR|LABEL)[^:\n]*);TYPE=PREF/$1/mg;
|
||||
# ignore extra separators in multi-value fields
|
||||
s/^((ORG|N|(ADR[^:\n]*?)):.*?);*$/$1/mg;
|
||||
# the type of certain fields is ignore by Evolution
|
||||
s/^X-(AIM|GROUPWISE|ICQ|YAHOO);TYPE=HOME/X-$1/gm;
|
||||
# Evolution ignores an additional pager type
|
||||
s/^TEL;TYPE=PAGER;TYPE=WORK/TEL;TYPE=PAGER/gm;
|
||||
# PAGER property is sent by Evolution, but otherwise ignored
|
||||
s/^LABEL[;:].*\n//mg;
|
||||
# TYPE=VOICE is the default in Evolution and may or may not appear in the vcard;
|
||||
# this simplification is a bit too agressive and hides the problematic
|
||||
# TYPE=PREF,VOICE combination which Evolution does not handle :-/
|
||||
s/^TEL([^:\n]*);TYPE=VOICE,([^:\n]*):/TEL$1;TYPE=$2:/mg;
|
||||
s/^TEL([^:\n]*);TYPE=([^;:\n]*),VOICE([^:\n]*):/TEL$1;TYPE=$2$3:/mg;
|
||||
s/^TEL([^:\n]*);TYPE=VOICE([^:\n]*):/TEL$1$2:/mg;
|
||||
# don't care about the TYPE property of PHOTOs
|
||||
s/^PHOTO;(.*)TYPE=[A-Z]*/PHOTO;$1/mg;
|
||||
# encoding is not case sensitive, skip white space in the middle of binary data
|
||||
if (s/^PHOTO;.*?ENCODING=(b|B|BASE64).*?:\s*/PHOTO;ENCODING=B: /mgi) {
|
||||
while (s/^PHOTO(.*?): (\S+)[\t ]+(\S+)/PHOTO$1: $2$3/mg) {}
|
||||
}
|
||||
# ignore extra day factor in front of weekday
|
||||
s/^RRULE:(.*)BYDAY=\+?1(\D)/RRULE:$1BYDAY=$2/mg;
|
||||
# remove default VALUE=DATE-TIME
|
||||
s/^(DTSTART|DTEND)([^:\n]*);VALUE=DATE-TIME/$1$2/mg;
|
||||
|
||||
# remove fields which may differ
|
||||
s/^(PRODID|CREATED|DTSTAMP|LAST-MODIFIED|REV):.*\r?\n?//gm;
|
||||
# remove optional fields
|
||||
s/^(METHOD|X-WSS-COMPONENT|X-WSS-LUID):.*\r?\n?//gm;
|
||||
|
||||
if ($scheduleworld || $egroupware || $synthesis) {
|
||||
# does not preserve X-EVOLUTION-UI-SLOT=
|
||||
s/^(\w+)([^:\n]*);X-EVOLUTION-UI-SLOT=\d+/$1$2/mg;
|
||||
}
|
||||
|
||||
if ($scheduleworld) {
|
||||
# cannot distinguish EMAIL types
|
||||
s/^EMAIL;TYPE=\w*/EMAIL/mg;
|
||||
# replaces certain TZIDs with more up-to-date ones
|
||||
s;TZID(=|:)/(scheduleworld.com|softwarestudio.org)/Olson_\d+_\d+/;TZID$1/foo.com/Olson_20000101_1/;mg;
|
||||
}
|
||||
|
||||
if ($scheduleworld || $synthesis) {
|
||||
# only preserves ORG "Company", but loses "Department" and "Office"
|
||||
s/^ORG:([^;:\n]+)(;[^\n]*)/ORG:$1/mg;
|
||||
}
|
||||
|
||||
if ($synthesis) {
|
||||
# does not preserve certain properties
|
||||
s/^(FN|BDAY|X-MOZILLA-HTML|X-EVOLUTION-FILE-AS|X-AIM|NICKNAME|PHOTO|CALURI)(;[^:;\n]*)*:.*\r?\n?//gm;
|
||||
# default ADR is HOME
|
||||
s/^ADR;TYPE=HOME/ADR/gm;
|
||||
# only some parts of N are preserved
|
||||
s/^N\:(.*)/@_ = split(\/(?<!\\);\/, $1); "N:$_[0];" . ($_[1] || "") . ";;" . ($_[3] || "")/gme;
|
||||
# this vcard contains too many ADR and PHONE entries - ignore it
|
||||
if (/This is a test case which uses almost all Evolution fields/) {
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
if ($egroupware) {
|
||||
# CLASS:PUBLIC is added if none exists (as in our test cases),
|
||||
# several properties not preserved
|
||||
s/^(BDAY|CATEGORIES|FBURL|PHOTO|FN|X-[A-Z-]*|CALURI|CLASS|NICKNAME|UID|TRANSP|PRIORITY|SEQUENCE)(;[^:;\n]*)*:.*\r?\n?//gm;
|
||||
# org gets truncated
|
||||
s/^ORG:([^;:\n]*);.*/ORG:$1/gm;
|
||||
}
|
||||
|
||||
if ($funambol) {
|
||||
# several properties are not preserved
|
||||
s/^(FN|X-MOZILLA-HTML|PHOTO)(;[^:;\n]*)*:.*\r?\n?//gm;
|
||||
}
|
||||
|
||||
if ($funambol || $egroupware) {
|
||||
# NOTE may be truncated due to length resistrictions
|
||||
s/^(NOTE(;[^:;\n]*)*:.{0,160}).*(\r?\n?)/$1$3/gm;
|
||||
}
|
||||
|
||||
my @formatted = ();
|
||||
|
||||
# Modify lines to cover not more than
|
||||
# $width characters by folding lines (as done for the N or SUMMARY above),
|
||||
# but also indent each inner BEGIN/END block by 2 spaces
|
||||
# and finally sort the lines.
|
||||
# We need to keep a stack of open blocks in @formatted:
|
||||
# - BEGIN creates another open block
|
||||
# - END closes it, sorts it, and adds as single string to the parent block
|
||||
push @formatted, [];
|
||||
foreach $_ (split /\n/, $_) {
|
||||
if (/^BEGIN:/) {
|
||||
# start a new block
|
||||
push @formatted, [];
|
||||
}
|
||||
|
||||
my $spaces = " " x ($#formatted - 1);
|
||||
my $thiswidth = $width -1 - length($spaces);
|
||||
$thiswidth = 1 if $thiswidth <= 0;
|
||||
s/(.{$thiswidth})(?!$)/$1\n /g;
|
||||
s/^(.*)$/$spaces$1/mg;
|
||||
push @{$formatted[$#formatted]}, $_;
|
||||
|
||||
if (/^\s*END:/) {
|
||||
my $block = pop @formatted;
|
||||
my $begin = shift @{$block};
|
||||
my $end = pop @{$block};
|
||||
|
||||
# Keep begin/end as first/last line,
|
||||
# inbetween sort, but so that N or SUMMARY are
|
||||
# at the top. This ensures that the order of items
|
||||
# is the same, even if individual properties differ.
|
||||
# Also put indented blocks at the end, not the top.
|
||||
sub numspaces {
|
||||
my $str = shift;
|
||||
$str =~ /^(\s*)/;
|
||||
return length($1);
|
||||
}
|
||||
$_ = join("\n",
|
||||
$begin,
|
||||
sort( { $a =~ /^\s*(N|SUMMARY):/ ? -1 :
|
||||
$b =~ /^\s*(N|SUMMARY):/ ? 1 :
|
||||
($a =~ /^\s/ && $b =~ /^\S/) ? 1 :
|
||||
numspaces($a) == numspaces($b) ? $a cmp $b :
|
||||
numspaces($a) - numspaces($b) }
|
||||
@{$block} ),
|
||||
$end);
|
||||
push @{$formatted[$#formatted]}, $_;
|
||||
}
|
||||
}
|
||||
|
||||
push @items, ${$formatted[0]}[0];
|
||||
}
|
||||
|
||||
print $out join( "\n\n", sort @items ), "\n";
|
||||
}
|
||||
|
||||
# number of columns available for output:
|
||||
# try tput without printing the shells error if not found,
|
||||
# default to 80
|
||||
my $columns = `which tput >/dev/null && tput cols 2>/dev/null`;
|
||||
if ($? || !$columns) {
|
||||
$columns = 80;
|
||||
}
|
||||
|
||||
if($#ARGV > 1) {
|
||||
# error
|
||||
Usage();
|
||||
exit 1;
|
||||
} elsif($#ARGV == 1) {
|
||||
# comparison
|
||||
|
||||
my ($file1, $file2) = ($ARGV[0], $ARGV[1]);
|
||||
my $normal1 = `mktemp`;
|
||||
my $normal2 = `mktemp`;
|
||||
chomp($normal1);
|
||||
chomp($normal2);
|
||||
|
||||
open(IN1, "<:utf8", $file1) || die "$file1: $!";
|
||||
open(IN2, "<:utf8", $file2) || die "$file2: $!";
|
||||
open(OUT1, ">:utf8", $normal1) || die "$normal1: $!";
|
||||
open(OUT2, ">:utf8", $normal2) || die "$normal2: $!";
|
||||
my $singlewidth = int(($columns - 3) / 2);
|
||||
$columns = $singlewidth * 2 + 3;
|
||||
Normalize(*IN1{IO}, *OUT1{IO}, $singlewidth);
|
||||
Normalize(*IN2{IO}, *OUT2{IO}, $singlewidth);
|
||||
close(IN1);
|
||||
close(IN2);
|
||||
close(OUT1);
|
||||
close(OUT2);
|
||||
|
||||
# Produce output where each line is marked as old (aka remove) with o,
|
||||
# as new (aka added) with n, and as unchanged with u at the beginning.
|
||||
# This allows simpler processing below.
|
||||
$_ = `diff "--old-line-format=o %L" "--new-line-format=n %L" "--unchanged-line-format=u %L" "$normal1" "$normal2"`;
|
||||
my $res = $?;
|
||||
|
||||
if ($res) {
|
||||
# fix confusing output like:
|
||||
# BEGIN:VCARD BEGIN:VCARD
|
||||
# > N:new;entry
|
||||
# > FN:new
|
||||
# > END:VCARD
|
||||
# >
|
||||
# > BEGIN:VCARD
|
||||
# and replace it with:
|
||||
# > BEGIN:VCARD
|
||||
# > N:new;entry
|
||||
# > FN:new
|
||||
# > END:VCARD
|
||||
#
|
||||
# BEGIN:VCARD BEGIN:VCARD
|
||||
#
|
||||
# With the o/n/u markup this presents itself as:
|
||||
# u BEGIN:VCARD
|
||||
# n N:new;entry
|
||||
# n FN:new
|
||||
# n END:VCARD
|
||||
# n
|
||||
# n BEGIN:VCARD
|
||||
#
|
||||
|
||||
while( s/^u BEGIN:(VCARD|VCALENDAR)\n((?:^n .*\n)+?)^n BEGIN:/n BEGIN:$1\n$2u BEGIN:/m) {}
|
||||
|
||||
# same for the other way around
|
||||
while( s/^u BEGIN:(VCARD|VCALENDAR)\n((?:^o .*\n)+?)^o BEGIN:/o BEGIN:$1\n$2u BEGIN:/m) {}
|
||||
|
||||
# split at end of each record
|
||||
my $spaces = " " x $singlewidth;
|
||||
foreach $_ (split /(?:(?<=. END:VCARD\n)|(?<=. END:VCALENDAR\n))(?:^. \n)*/m, $_) {
|
||||
# ignore unchanged records
|
||||
if (!length($_) || /^((u [^\n]*\n)*(u [^\n]*?))$/s) {
|
||||
next;
|
||||
}
|
||||
|
||||
# make all lines equally long in terms of printable characters
|
||||
s/^(.*)$/$1 . (" " x ($singlewidth + 2 - length($1)))/gme;
|
||||
|
||||
# convert into side-by-side output
|
||||
my @buffer = ();
|
||||
foreach $_ (split /\n/, $_) {
|
||||
if (/^u (.*)/) {
|
||||
print join(" <\n", @buffer), " <\n" if $#buffer >= 0;
|
||||
@buffer = ();
|
||||
print $1, " ", $1, "\n";
|
||||
} elsif (/^o (.*)/) {
|
||||
# preserve in buffer for potential merging with "n "
|
||||
push @buffer, $1;
|
||||
} else {
|
||||
/^n (.*)/;
|
||||
# have line to be merged with?
|
||||
if ($#buffer >= 0) {
|
||||
print shift @buffer, " | ", $1, "\n";
|
||||
} else {
|
||||
print join(" <\n", @buffer), " <\n" if $#buffer >= 0;
|
||||
print $spaces, " > ", $1, "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
print join(" <\n", @buffer), " <\n" if $#buffer >= 0;
|
||||
@buffer = ();
|
||||
|
||||
print "-" x $columns, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
unlink($normal1);
|
||||
unlink($normal2);
|
||||
exit($res ? 1 : 0);
|
||||
} else {
|
||||
# normalize
|
||||
my $in;
|
||||
if( $#ARGV >= 0 ) {
|
||||
open(IN, "<$ARGV[0]") || die "$ARGV[0]: $!";
|
||||
$in = *IN{IO};
|
||||
} else {
|
||||
$in = *STDIN{IO};
|
||||
}
|
||||
|
||||
Normalize($in, *STDOUT{IO}, $columns);
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:/softwarestudio.org/Olson_20011030_5/Europe/Berlin
|
||||
X-LIC-LOCATION:Europe/Berlin
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
TZNAME:CEST
|
||||
DTSTART:19700329T020000
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:CET
|
||||
DTSTART:19701025T030000
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
UID:20060617T160610Z-9375-727-1-139@gollum
|
||||
DTSTAMP:20060617T160610Z
|
||||
DTSTART;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:
|
||||
20060406T180000
|
||||
DTEND;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:
|
||||
20060406T183000
|
||||
TRANSP:OPAQUE
|
||||
SEQUENCE:4
|
||||
SUMMARY:recurr at end of month
|
||||
CLASS:PUBLIC
|
||||
CREATED:20060617T160656
|
||||
LAST-MODIFIED:20060617T160656
|
||||
RRULE:FREQ=MONTHLY;COUNT=2;INTERVAL=1;BYDAY=SU;BYSETPOS=-1
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:/softwarestudio.org/Olson_20011030_5/Europe/Berlin
|
||||
X-LIC-LOCATION:Europe/Berlin
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
TZNAME:CEST
|
||||
DTSTART:19700329T020000
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
TZNAME:CET
|
||||
DTSTART:19701025T030000
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:phone meeting
|
||||
DTEND;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:
|
||||
20060406T163000
|
||||
DTSTART;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:
|
||||
20060406T160000
|
||||
UID:20060406T211449Z-4562-727-1-63@gollum
|
||||
DTSTAMP:20060406T211449Z
|
||||
LAST-MODIFIED:20060416T203532Z
|
||||
CREATED:20060416T203532Z
|
||||
LOCATION:my office
|
||||
DESCRIPTION:let's talk
|
||||
CLASS:PUBLIC
|
||||
TRANSP:OPAQUE
|
||||
SEQUENCE:1
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
UID:20060416T203656Z-4272-727-1-241@gollum
|
||||
DTSTAMP:20060416T203656Z
|
||||
DTSTART:20060406T170000Z
|
||||
DTEND:20060406T173000Z
|
||||
TRANSP:OPAQUE
|
||||
SEQUENCE:3
|
||||
SUMMARY:recurrence weekly\, limited
|
||||
CLASS:PUBLIC
|
||||
CREATED:20060416T203724Z
|
||||
LAST-MODIFIED:20060416T203758Z
|
||||
DESCRIPTION:recurrs four times due its end date
|
||||
RRULE:FREQ=WEEKLY;UNTIL=20060427;INTERVAL=1;BYDAY=TH
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
UID:20060416T203551Z-4272-727-1-240@gollum
|
||||
DTSTAMP:20060416T203551Z
|
||||
DTSTART:20060406T163000Z
|
||||
DTEND:20060406T170000Z
|
||||
TRANSP:OPAQUE
|
||||
SEQUENCE:3
|
||||
SUMMARY:recurrence daily unlimited
|
||||
CLASS:PUBLIC
|
||||
CREATED:20060416T203646Z
|
||||
LAST-MODIFIED:20060416T203806Z
|
||||
RRULE:FREQ=DAILY;INTERVAL=1
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
UID:20060416T203813Z-4272-727-1-242@gollum
|
||||
DTSTAMP:20060416T203813Z
|
||||
DTSTART:20060406T173000Z
|
||||
DTEND:20060406T180000Z
|
||||
TRANSP:OPAQUE
|
||||
SEQUENCE:3
|
||||
SUMMARY:recurrence monthly\, 6th day\, limited
|
||||
CLASS:PUBLIC
|
||||
CREATED:20060416T203924Z
|
||||
LAST-MODIFIED:20060416T203949Z
|
||||
DESCRIPTION:recurrs three times on the 6th of each month
|
||||
RRULE;X-EVOLUTION-ENDDATE=20060606T153000Z:FREQ=MONTHLY;COUNT=3;
|
||||
INTERVAL=1;BYMONTHDAY=6
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
UID:20060416T203954Z-4272-727-1-243@gollum
|
||||
DTSTAMP:20060416T203954Z
|
||||
DTSTART:20060406T180000Z
|
||||
DTEND:20060406T183000Z
|
||||
TRANSP:OPAQUE
|
||||
SEQUENCE:2
|
||||
SUMMARY:recurrence\, yearly\, two times
|
||||
CLASS:PUBLIC
|
||||
RRULE;X-EVOLUTION-ENDDATE=20070406T160000Z:FREQ=YEARLY;COUNT=2;INTERVAL=1
|
||||
CREATED:20060416T204021Z
|
||||
LAST-MODIFIED:20060416T204021Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
UID:20060416T204026Z-4272-727-1-244@gollum
|
||||
DTSTAMP:20060416T204026Z
|
||||
DTSTART;VALUE=DATE:20060406
|
||||
DTEND;VALUE=DATE:20060407
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:2
|
||||
SUMMARY:all day event
|
||||
CLASS:PUBLIC
|
||||
CREATED:20060416T204042Z
|
||||
LAST-MODIFIED:20060416T204042Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
UID:20060416T204047Z-4272-727-1-245@gollum
|
||||
DTSTAMP:20060416T204047Z
|
||||
DTSTART;VALUE=DATE:20060406
|
||||
DTEND;VALUE=DATE:20060408
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:2
|
||||
SUMMARY:two day event
|
||||
CLASS:PUBLIC
|
||||
CREATED:20060416T204104Z
|
||||
LAST-MODIFIED:20060416T204104Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
UID:20060416T204647Z-4272-727-1-248@gollum
|
||||
DTSTAMP:20060416T204647Z
|
||||
DTSTART:20060406T183000Z
|
||||
DTEND:20060406T190000Z
|
||||
TRANSP:OPAQUE
|
||||
SEQUENCE:2
|
||||
SUMMARY:recurrence\, weekly\, with exceptions
|
||||
DESCRIPTION:recurrs seven times\, excluding (but counting) Friday and
|
||||
Saturday
|
||||
CLASS:PUBLIC
|
||||
RRULE;X-EVOLUTION-ENDDATE=20060412T163000Z:FREQ=DAILY;COUNT=7;INTERVAL=1
|
||||
EXDATE;VALUE=DATE:20060408
|
||||
EXDATE;VALUE=DATE:20060407
|
||||
CREATED:20060416T204808Z
|
||||
LAST-MODIFIED:20060416T204808Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
UID:20060416T204136Z-4272-727-1-247@gollum
|
||||
DTSTAMP:20060416T204136Z
|
||||
DTSTART:20060406T190000Z
|
||||
DTEND:20060406T193000Z
|
||||
TRANSP:TRANSPARENT
|
||||
SEQUENCE:4
|
||||
SUMMARY:all fields
|
||||
LOCATION:virtual
|
||||
CATEGORIES:Business\,test
|
||||
CLASS:PRIVATE
|
||||
CREATED:20060416T204625Z
|
||||
LAST-MODIFIED:20060416T204833Z
|
||||
DESCRIPTION:this is an appointment with plenty of fields set\, and
|
||||
special attributes...\n\nempty line above\, line\nbreak\n\nspecial
|
||||
characters:\na-umlaut ä\nexclamation mark !\nampersand disabled\nhash
|
||||
#\nleft angle bracket disabled\nright angle bracket disabled\nleft square
|
||||
bracket [\nright square bracket ]\nleft bracket (\nright bracket
|
||||
)\nbackslash \\\nbackslash lf \\n\nbackslash cr \\r\nstar *\ncarret
|
||||
^\npercent %\ntilde ~\ntick `\nbacktick `\ndouble quotation - not tested
|
||||
because Evolution encodes it incorrectly\nsingle quotation '\ncolon :\n
|
||||
semicolon \;\ncomma \,\n
|
||||
BEGIN:VALARM
|
||||
X-EVOLUTION-ALARM-UID:20060416T204833Z-4250-727-1-85@gollum
|
||||
DESCRIPTION:all fields
|
||||
ACTION:DISPLAY
|
||||
TRIGGER;VALUE=DURATION;RELATED=START:-PT1H
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
UID:20060416T204922Z-4272-727-1-250@gollum
|
||||
DTSTAMP:20060416T204922Z
|
||||
DTSTART:20060406T193000Z
|
||||
DTEND:20060406T200000Z
|
||||
TRANSP:OPAQUE
|
||||
SEQUENCE:2
|
||||
SUMMARY:meeting invitation
|
||||
CLASS:PUBLIC
|
||||
ORGANIZER;CN=Patrick Ohly:MAILTO:Patrick.Ohly@gmx.de
|
||||
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;
|
||||
RSVP=TRUE;CN=Patrick Ohly;LANGUAGE=en:MAILTO:Patrick.Ohly@gmx.de
|
||||
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;
|
||||
RSVP=TRUE;LANGUAGE=en:MAILTO:john@bar.com
|
||||
CREATED:20060416T205003Z
|
||||
LAST-MODIFIED:20060416T205003Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:New York
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:-0400
|
||||
TZOFFSETTO:-0500
|
||||
TZNAME:EST
|
||||
DTSTART:19701025T020000
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
|
||||
END:STANDARD
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:-0500
|
||||
TZOFFSETTO:-0400
|
||||
TZNAME:EDT
|
||||
DTSTART:19700405T020000
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=4
|
||||
END:DAYLIGHT
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
UID:20060416T205224Z-4272-727-1-251@gollum
|
||||
DTSTAMP:20060416T205224Z
|
||||
DTSTART;TZID=New York:20060406T140000
|
||||
DTEND;TZID=New York:20060406T143000
|
||||
TRANSP:OPAQUE
|
||||
SEQUENCE:2
|
||||
SUMMARY:timezone New York with custom definition
|
||||
CLASS:PUBLIC
|
||||
CREATED:20060416T205301Z
|
||||
LAST-MODIFIED:20060416T205301Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
|
@ -0,0 +1,39 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:New York
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:-0400
|
||||
TZOFFSETTO:-0500
|
||||
TZNAME:EST
|
||||
DTSTART:19701025T020000
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
|
||||
END:STANDARD
|
||||
BEGIN:DAYLIGHT
|
||||
TZOFFSETFROM:-0500
|
||||
TZOFFSETTO:-0400
|
||||
TZNAME:EDT
|
||||
DTSTART:19700405T020000
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=4
|
||||
END:DAYLIGHT
|
||||
END:VTIMEZONE
|
||||
BEGIN:VTODO
|
||||
UID:20060417T174205Z-4360-727-1-2731@gollum
|
||||
DTSTAMP:20060417T174205Z
|
||||
SUMMARY:test task with plenty of fields
|
||||
PRIORITY:5
|
||||
CREATED:20060417T174205
|
||||
LAST-MODIFIED:20060417T174205
|
||||
DESCRIPTION:test description
|
||||
DUE;TZID=New York:20060422T140000
|
||||
DTSTART;TZID=New York:20060410T163000
|
||||
CLASS:PRIVATE
|
||||
CATEGORIES:Business\,Waiting
|
||||
PERCENT-COMPLETE:50
|
||||
STATUS:IN-PROCESS
|
||||
URL:http://foo.com
|
||||
SEQUENCE:1
|
||||
END:VTODO
|
||||
END:VCALENDAR
|
|
@ -0,0 +1,420 @@
|
|||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
NICKNAME:user16
|
||||
NOTE:test case with empty email
|
||||
FN:incomplete
|
||||
N:incomplete
|
||||
EMAIL:
|
||||
X-EVOLUTION-FILE-AS:incomplete
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
NICKNAME:user11
|
||||
NOTE:This is a long line without any special characters. This is a simpler
|
||||
example that should require folding in vcards. Does folding insert a crlf
|
||||
before a space or does it insert crlf _plus_ a space? vCard 2.1 inserts
|
||||
before a space\, 3.0 inserts line break plus space.
|
||||
FN:long line
|
||||
N:line;long;;;
|
||||
X-EVOLUTION-FILE-AS:line\, long
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
NICKNAME:user12
|
||||
NOTE:ampersand entity &\; less-than entity <\;
|
||||
FN:xml entities
|
||||
N:xml;entities;;;
|
||||
X-EVOLUTION-FILE-AS:xml\, entities
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
NICKNAME:user13
|
||||
NOTE:a colon is not a special character so here it comes : and not quoting necessary
|
||||
FN:colon
|
||||
N:colon;unquoted;;;
|
||||
X-EVOLUTION-FILE-AS:colon\, unquoted
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
NICKNAME:user14
|
||||
NOTE:here are some quotation marks: single ' double " back ` - none of them is special
|
||||
FN:quotation marks
|
||||
N:marks;quotation;;;
|
||||
X-EVOLUTION-FILE-AS:marks\, quotation
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
NICKNAME:user15
|
||||
NOTE:Spouse's Name: foobar
|
||||
FN:spouse name
|
||||
N:name;spouse;;;
|
||||
X-EVOLUTION-FILE-AS:spouse\, name
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
NICKNAME:user9
|
||||
NOTE;CHARSET="UTF-8":Tests charset specification with quotation marks.
|
||||
FN:charset
|
||||
N:set;char;;;
|
||||
X-EVOLUTION-FILE-AS:set\, char
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
URL:
|
||||
TITLE:
|
||||
ROLE:
|
||||
X-EVOLUTION-MANAGER:
|
||||
X-EVOLUTION-ASSISTANT:
|
||||
NICKNAME:user2
|
||||
X-EVOLUTION-SPOUSE:
|
||||
NOTE:This user tests some of the advanced aspects of vcards:\n- non-ASCII c
|
||||
haracters (with umlauts in the name)\n- line break (in this note and the
|
||||
mailing address)\n- long lines (in this note)\n- special characters (in
|
||||
this note)\n- tabs (in this note)\n\nVery long line\, very very long th
|
||||
is time... still not finished... blah blah blah blah blah 1 2 3 4 5 6 7
|
||||
8 9 10 11 12 13 14 15 16\n\ncomma \,\ncolon :\nsemicolon \;\nbackslash
|
||||
\\\n\nThe same\, in the middle of a line:\ncomma \, comma\ncolon : col
|
||||
on\nsemicolon \; semicolon\nbackslash \\ backslash\n\nA tab tab done\n
|
||||
line starts with tab\n
|
||||
FN:Umlaut Ä Ö Ü ß
|
||||
N:Ü;Ä;Ö;Umlaut;ß
|
||||
X-EVOLUTION-FILE-AS:Ü\, Ä
|
||||
CATEGORIES:Business
|
||||
X-EVOLUTION-BLOG-URL:
|
||||
CALURI:
|
||||
FBURL:
|
||||
X-EVOLUTION-VIDEO-URL:
|
||||
X-MOZILLA-HTML:FALSE
|
||||
ADR;TYPE=HOME:test 5;Line 2\n;Umlaut Ä in Line 1;test 1;test 3;test 2;test 4
|
||||
LABEL;TYPE=HOME:Umlaut Ä in Line 1\nLine 2\n\ntest 1\, test 3\ntest 2\ntest 5\ntest 4
|
||||
UID:pas-id-43C0EF0A00000002
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
URL:
|
||||
TITLE:
|
||||
ROLE:
|
||||
X-EVOLUTION-MANAGER:
|
||||
X-EVOLUTION-ASSISTANT:
|
||||
NICKNAME:user8
|
||||
X-EVOLUTION-SPOUSE:
|
||||
NOTE:Here are some special characters: comma \, colon : semicolon \;
|
||||
FN:special characters
|
||||
N:characters;special;;;
|
||||
X-EVOLUTION-FILE-AS:characters\, special
|
||||
X-EVOLUTION-BLOG-URL:
|
||||
CALURI:
|
||||
FBURL:
|
||||
X-EVOLUTION-VIDEO-URL:
|
||||
X-MOZILLA-HTML:FALSE
|
||||
UID:pas-id-43C15E84000001AC
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
URL:
|
||||
TITLE:
|
||||
ROLE:
|
||||
X-EVOLUTION-MANAGER:
|
||||
X-EVOLUTION-ASSISTANT:
|
||||
NICKNAME:user7
|
||||
X-EVOLUTION-SPOUSE:
|
||||
NOTE:This test case uses line breaks. This is line 1.\nLine 2.\n\nLine brea
|
||||
ks in vcard 2.1 are encoded as =0D=0A.\nThat means the = has to be encod
|
||||
ed itself...
|
||||
FN:line breaks
|
||||
N:breaks;line;;;
|
||||
X-EVOLUTION-FILE-AS:breaks\, line
|
||||
X-EVOLUTION-BLOG-URL:
|
||||
CALURI:
|
||||
FBURL:
|
||||
X-EVOLUTION-VIDEO-URL:
|
||||
X-MOZILLA-HTML:FALSE
|
||||
ADR;TYPE=HOME:;Address Line 2\nAddress Line 3;Address Line 1;;;;
|
||||
LABEL;TYPE=HOME:Address Line 1\nAddress Line 2\nAddress Line 3
|
||||
UID:pas-id-43C15DFB000001AB
|
||||
END:VCARD
|
||||
|
||||
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-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:FALSE
|
||||
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;TYPE=OTHER:Test Box #3;;Test Drive 3;Test Megacity;Test County;12347;Ne
|
||||
w 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;TYPE=HOME;X-EVOLUTION-UI-SLOT=1:AIM JOHN
|
||||
X-YAHOO;TYPE=HOME;X-EVOLUTION-UI-SLOT=2:YAHOO JDOE
|
||||
X-ICQ;TYPE=HOME;X-EVOLUTION-UI-SLOT=3:ICQ JD
|
||||
X-GROUPWISE;TYPE=HOME;X-EVOLUTION-UI-SLOT=4:GROUPWISE DOE
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
URL:
|
||||
TITLE:
|
||||
ROLE:
|
||||
X-EVOLUTION-MANAGER:
|
||||
X-EVOLUTION-ASSISTANT:
|
||||
NICKNAME:user5
|
||||
X-EVOLUTION-SPOUSE:
|
||||
NOTE:image in JPG format
|
||||
FN:Ms. JPG
|
||||
N:;JPG;;Ms.;
|
||||
X-EVOLUTION-FILE-AS:JPG
|
||||
X-EVOLUTION-BLOG-URL:
|
||||
CALURI:
|
||||
FBURL:
|
||||
X-EVOLUTION-VIDEO-URL:
|
||||
X-MOZILLA-HTML:FALSE
|
||||
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==
|
||||
UID:pas-id-43C0F0B500000005
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
URL:
|
||||
TITLE:
|
||||
ROLE:
|
||||
X-EVOLUTION-MANAGER:
|
||||
X-EVOLUTION-ASSISTANT:
|
||||
NICKNAME:user4
|
||||
X-EVOLUTION-SPOUSE:
|
||||
NOTE:image in PNG format
|
||||
FN:Mrs. PNG
|
||||
N:;PNG;;Mrs.;
|
||||
X-EVOLUTION-FILE-AS:PNG
|
||||
X-EVOLUTION-BLOG-URL:
|
||||
CALURI:
|
||||
FBURL:
|
||||
X-EVOLUTION-VIDEO-URL:
|
||||
X-MOZILLA-HTML:FALSE
|
||||
PHOTO;ENCODING=b;TYPE=PNG:iVBORw0KGgoAAAANSUhEUgAAACQAAAAXCAYAAABj7u2bAAAAB
|
||||
mJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1gEICjgdiWkBO
|
||||
QAAAB10RVh0Q29tbWVudABDcmVhdGVkIHdpdGggVGhlIEdJTVDvZCVuAAABaElEQVRIx+3Wu
|
||||
0tcURAG8F98gRKTYGORRqwksJV/QOqFFIFgKgsRYbHV1larDQQCKQxpUscyhUmXJuCSNpYWP
|
||||
sAU6wPxHW6aWbgsu+ve3RUs7geHc+fON3O+M4c5HHLkyHG/eISkg5heIGmUr++hVWigyY6TH
|
||||
lejbWSt0Bv8QBXX2MF7jKU4IyjjJ45xg31sYKZuw7Xv9Gh6vvXO9QbBtbGNJ8Ert+AlTURkF
|
||||
jQX9g5e4ykGUcBm+FaDexx2MUQOYhIL2Lpj09oV9CvsQgPuePj+hP037BL6M6yRSdDZHWVOc
|
||||
BHcEv7FvyN8xxqmeynovA1Baf4UVvANhyn/Uq8E/Q57ssNufhvx1QZrDHfS9p9i3sQsnscdN
|
||||
owXWEQlOBXMYyI4j3EavqFUzpOYl4OTqUJ9+NzmkbXyb6Ryfumm7Wso4it2cYXL6K6PeBmcV
|
||||
8E5iEvxPDjv8CyVaxQfsIfbqGIlf17k6Bb/Ae0cnahfg6KuAAAAAElFTkSuQmCC
|
||||
UID:pas-id-43C0F07900000004
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
URL:
|
||||
TITLE:
|
||||
ROLE:
|
||||
X-EVOLUTION-MANAGER:
|
||||
X-EVOLUTION-ASSISTANT:
|
||||
NICKNAME:user6
|
||||
X-EVOLUTION-SPOUSE:
|
||||
NOTE:The first name is "First \; special \;".
|
||||
FN:Mr. First \; special \; middle Last
|
||||
N:Last;First \; special \;;middle;Mr.;
|
||||
X-EVOLUTION-FILE-AS:Last\, First \; special \;
|
||||
X-EVOLUTION-BLOG-URL:
|
||||
CALURI:
|
||||
FBURL:
|
||||
X-EVOLUTION-VIDEO-URL:
|
||||
X-MOZILLA-HTML:FALSE
|
||||
UID:pas-id-43C15D55000001AA
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
URL:
|
||||
TITLE:
|
||||
ROLE:
|
||||
X-EVOLUTION-MANAGER:
|
||||
X-EVOLUTION-ASSISTANT:
|
||||
NICKNAME:user3
|
||||
X-EVOLUTION-SPOUSE:
|
||||
NOTE:image in GIF format
|
||||
FN:Mr. GIF
|
||||
N:;GIF;;Mr.;
|
||||
X-EVOLUTION-FILE-AS:GIF
|
||||
X-EVOLUTION-BLOG-URL:
|
||||
CALURI:
|
||||
FBURL:
|
||||
X-EVOLUTION-VIDEO-URL:
|
||||
X-MOZILLA-HTML:FALSE
|
||||
PHOTO;ENCODING=b;TYPE=GIF:R0lGODlhJAAXAIABAAAAAP///yH+FUNyZWF0ZWQgd2l0aCBUa
|
||||
GUgR0lNUAAh+QQBCgABACwAAAAAJAAXAAACVYyPqcvtD6OctNqLFdi8b/sd3giAJRNmqXaKH
|
||||
TIaZJKSpx3McLtyeSuTAWm34e+4WBGFuJ/P1QjZek9ksjiRGqFCTW5pZblmzdiO+GJWncqM+
|
||||
w2PwwsAOw==
|
||||
UID:pas-id-43C0F04B00000003
|
||||
END:VCARD
|
||||
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
NICKNAME:user10
|
||||
X-EVOLUTION-SPOUSE:
|
||||
NOTE:large vcard with plenty of special chars < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
& < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & < & <
|
||||
FN:large vcard
|
||||
N:;vcard;;large;
|
||||
X-EVOLUTION-FILE-AS:large
|
||||
END:VCARD
|
Loading…
Reference in New Issue