config: replaced overloaded "type" with "backend/databaseFormat/syncFormat/forceSyncFormat" (BMC #1023)

The meaning of "type" was horribly complex and had effects on the
backend and the peer. It was impossible to specify the sync format to
be used for a specific peer independently of the local backend and its
format, so adding a peer to a context broke the context configuration
(BMC #1023).

This is now fixed by splitting "type" into four independent properties:
- backend = plugin which interfaces with the data
- databaseFormat = data format used inside backend, only relevant for file backend
- syncFormat = data format preferred when talking to peer
- forceSyncFormat = disable format auto-negotiation, use preferred format

With that split, it is now possible to specify the format in which the
file backend stores items independently of the format in which they
are exchanged with the peer.

Old configurations with "type" can still be read. The values specified
inside it are transparently mapped to the new properties. Command line
and D-Bus API users will only see the new properties.

The command line tool still accepts "type" as an alias for the four new
properties. Using that has the same disadvantage as before: it will modify
the context even if only modifying the peer was intended.

The D-Bus API accepts only the new properties. Clients using "type"
must be adapted to the new property names. Clients not using that
continue to run unchanged.

Writing into the configuration requires a migration of the peer config
*and* the context in which it is defined. That is necessary because
the new semantic (independent database format) cannot be stored in the
old format. The migration is handled by rewriting first the context,
then all peers defined inside it.

Other user-visible changes:
- updated help texts
- the canonical "backend" value for the file backend is just "file"
  instead of the long "Files in one directory", which is now an alias
  (used to be the other way around); done because "type = file"
  was expanded to the long name, which was a bit unexpected and showed
  how unintuitive the long name is

Internal changes:
- getMimeVersion() is still present, although it hasn't been used
  for a long time; FileSyncSource::getMimeVersion() now derives
  the version from the supported Mime types, in case that the
  function will be needed again in the future
- setSourceType() with string as argument was replaced with one
  taking a SourceType instance; to emulate the old behavior if
  desired, construct SourceType from an old-style string
- ConfigProperty methods need to be virtual so that derived classes
  like SourceBackendConfigProperty can generate content at runtime
  (a recent commit broke that feature)
- File templates were stripped down to the essential properties,
  with "type" replaced by the per-peer "syncFormat".  "type" would
  still have been accepted (so it is not necessary to adapt
  syncevo-phone-config right away), but has the original
  disadvantage of modifying "backend" and "databaseFormat".
This commit is contained in:
Patrick Ohly 2011-02-03 12:17:24 +01:00
parent 49707f9429
commit 388f72b369
17 changed files with 428 additions and 271 deletions

View File

@ -62,9 +62,7 @@ static RegisterSyncSource registerMe("iPhone/Mac OS X Address Book",
false,
#endif
createSource,
"Mac OS X or iPhone Address Book = addressbook = contacts = apple-contacts\n"
" vCard 2.1 (default) = text/x-vcard\n"
" vCard 3.0 = text/vcard\n",
"Mac OS X or iPhone Address Book = addressbook = contacts = apple-contacts\n",
Values() +
(Aliases("apple-contacts") + "Mac OS X Address Book" + "iPhone Address Book"));

View File

@ -63,8 +63,7 @@ static RegisterSyncSource registerMe("Evolution Address Book",
" vCard 2.1 (default) = text/x-vcard\n"
" vCard 3.0 = text/vcard\n"
" The later is the internal format of Evolution and preferred with\n"
" servers that support it. One such server is ScheduleWorld\n"
" together with the \"card3\" uri.\n",
" servers that support it.",
Values() +
(Aliases("Evolution Address Book") + "Evolution Contacts" + "evolution-contacts"));

View File

@ -40,6 +40,8 @@
#include <syncevo/util.h>
#include <boost/algorithm/string/predicate.hpp>
#include <sstream>
#include <fstream>
@ -50,18 +52,12 @@ SE_BEGIN_CXX
FileSyncSource::FileSyncSource(const SyncSourceParams &params,
const string &dataformat) :
TrackingSyncSource(params),
m_mimeType(dataformat),
m_entryCounter(0)
{
if (dataformat.empty()) {
throwError("a data format must be specified");
throwError("a database format must be specified");
}
size_t sep = dataformat.find(':');
if (sep == dataformat.npos) {
throwError(string("data format not specified as <mime type>:<mime version>: " + dataformat));
}
m_mimeType.assign(dataformat, 0, sep);
m_mimeVersion = dataformat.substr(sep + 1);
m_supportedTypes = dataformat;
}
std::string FileSyncSource::getMimeType() const
@ -71,7 +67,17 @@ std::string FileSyncSource::getMimeType() const
std::string FileSyncSource::getMimeVersion() const
{
return m_mimeVersion.c_str();
if (boost::iequals(m_mimeType, "text/vcard")) {
return "3.0";
} else if (boost::iequals(m_mimeType, "text/x-vcard")) {
return "2.1";
} else if (boost::iequals(m_mimeType, "text/calendar")) {
return "2.0";
} else if (boost::iequals(m_mimeType, "text/x-vcalendar")) {
return "1.0";
} else {
return "";
}
}
void FileSyncSource::open()

View File

@ -78,15 +78,13 @@ class FileSyncSource : public TrackingSyncSource, private boost::noncopyable
private:
/**
* @name values obtained from the source's type property
* @name values obtained from the source's "database format" configuration property
*
* Other sync sources only support one hard-coded type and
* don't need such variables.
*/
/**@{*/
string m_mimeType;
string m_mimeVersion;
string m_supportedTypes;
/**@}*/
/** directory selected via the database name in open(), reset in close() */

View File

@ -29,7 +29,7 @@ static SyncSource *createSource(const SyncSourceParams &params)
SourceType sourceType = SyncSource::getSourceType(params.m_nodes);
// The string returned by getSourceType() is always the one
// registered as main Aliases() below.
bool isMe = sourceType.m_backend == "Files in one directory";
bool isMe = sourceType.m_backend == "file";
#ifndef ENABLE_FILE
// tell SyncEvolution if the user wanted to use a disabled sync source,
@ -41,11 +41,9 @@ static SyncSource *createSource(const SyncSourceParams &params)
bool maybeMe = false /* sourceType.m_backend == "addressbook" */;
if (isMe || maybeMe) {
// The FileSyncSource always needs the data format
// parameter in sourceType.m_format.
if (/* sourceType.m_format == "" || sourceType.m_format == "text/x-vcard" */
sourceType.m_format.size()) {
return new FileSyncSource(params, sourceType.m_format);
// The FileSyncSource always needs the database format.
if (!sourceType.m_localFormat.empty()) {
return new FileSyncSource(params, sourceType.m_localFormat);
} else {
return NULL;
}
@ -63,22 +61,22 @@ static RegisterSyncSource registerMe("Files in one directory",
createSource,
"Files in one directory = file\n"
" Stores items in one directory as one file per item.\n"
" The directory is selected via evolutionsource=[file://]<path>.\n"
" The directory is selected via database=[file://]<path>.\n"
" It will only be created if the prefix is given, otherwise\n"
" it must exist already. Only items of the same type can\n"
" be synchronized and this type must be specified explicitly\n"
" with both mime type and version.\n"
" Examples for type:\n"
" file:text/plain:1.0\n"
" file:text/x-vcard:2.1\n"
" file:text/vcard:3.0\n"
" file:text/x-vcalendar:1.0\n"
" file:text/calendar:2.0\n"
" it must exist already.\n"
" The database format *must* be specified explicitly. It may be\n"
" different from the sync format, as long as there are\n"
" conversion rules (for example, vCard 2.1 <-> vCard 3.0). If\n"
" the sync format is empty, the database format is used.\n"
" Examples for databaseFormat + syncFormat:\n"
" text/plain + text/plain\n"
" text/x-vcard + text/vcard\n"
" text/calendar\n"
" Examples for evolutionsource:\n"
" /home/joe/datadir - directory must exist\n"
" file:///tmp/scratch - directory is created\n",
Values() +
(Aliases("Files in one directory") + "file"));
(Aliases("file") + "Files in one directory"));
#ifdef ENABLE_FILE
#ifdef ENABLE_UNIT_TESTS

View File

@ -53,12 +53,11 @@ static RegisterSyncSource registerMe("KCalExtended",
#endif
createSource,
"mkcal = KCalExtended = calendar\n"
" iCalendar 2.0 = text/calendar\n"
" \"evolutionsource\" normally is the name of a calendar\n"
" 'database' normally is the name of a calendar\n"
" inside the default calendar storage. If it starts\n"
" with the \"SyncEvolution_Test_\" prefix, it will be\n"
" with the 'SyncEvolution_Test_' prefix, it will be\n"
" created as needed, otherwise it must exist.\n"
" If it starts with the \"file://\" prefix, the default\n"
" If it starts with the 'file://' prefix, the default\n"
" calendar in the specified SQLite storage file will\n"
" created (if needed) and used.\n",
Values() +

View File

@ -63,13 +63,7 @@ static RegisterSyncSource registerMe("XMLRPC interface for data exchange",
#endif
createSource,
"XMLRPC interface = xmlrpc\n"
" Data exchange is done via an XMLRPC interface on the datastore.\n"
" Examples:\n"
" xmlrpc:text/plain:1.0\n"
" xmlrpc:text/x-vcard:2.1\n"
" xmlrpc:text/vcard:3.0\n"
" xmlrpc:text/x-vcalendar:1.0\n"
" xmlrpc:text/calendar:2.0\n",
" Data exchange is done via an XMLRPC interface on the datastore.\n",
Values() +
(Aliases("XMLRPC interface") + "xmlrpc"));

View File

@ -260,7 +260,7 @@ public:
boost::shared_ptr<SyncSourceConfig> scServerTemplate = from->getSyncSourceConfig(testconfig.sourceNameServerTemplate);
sc->setURI(scServerTemplate->getURI());
}
sc->setSourceType(testconfig.type);
sc->setSourceType(SourceType(testconfig.type));
}
// always set these properties: they might have changed since the last run
@ -502,7 +502,7 @@ private:
getSourceConfig(test, testConfig);
PersistentSyncSourceConfig sourceConfig(params.m_name, params.m_nodes);
sourceConfig.setSourceType(testConfig.type);
sourceConfig.setSourceType(SourceType(testConfig.type));
// downcasting here: anyone who registers his sources for testing
// must ensure that they are indeed TestingSyncSource instances

View File

@ -1430,7 +1430,8 @@ bool Cmdline::parseProp(PropertyType propertyType,
} else {
validProps = &m_validSyncProps;
}
} else if (isSourceProp) {
} else if (isSourceProp ||
boost::iequals(spec.m_property, "type")) {
validProps = &m_validSourceProps;
} else {
usage(true, StringPrintf("unrecognized property '%s' in %s", propname, args.c_str()));
@ -1454,7 +1455,27 @@ bool Cmdline::parseProp(PropertyType propertyType,
return listPropValues(*validProps, spec.m_property, cmdOpt(opt, param));
} else {
const ConfigProperty *prop = validProps->find(spec.m_property);
if (!prop) {
if (!prop && boost::iequals(spec.m_property, "type")) {
// compatiblity mode for "type": map to the properties which
// replaced it
prop = validProps->find("backend");
if (!prop) {
m_err << "ERROR: backend: no such property" << endl;
return false;
}
SourceType sourceType(paramstr);
string error;
if (!prop->checkValue(sourceType.m_backend, error)) {
m_err << "ERROR: " << args << ": " << error << endl;
return false;
}
ContextProps &props = m_props[spec.m_config];
props.m_sourceProps[spec.m_source]["backend"] = sourceType.m_backend;
props.m_sourceProps[spec.m_source]["databaseFormat"] = sourceType.m_localFormat;
props.m_sourceProps[spec.m_source]["syncFormat"] = sourceType.m_format;
props.m_sourceProps[spec.m_source]["forceSyncFormat"] = sourceType.m_forceFormat ? "1" : "0";
return true;
} else if (!prop) {
m_err << "ERROR: " << args << ": no such property" << endl;
return false;
} else {
@ -1486,7 +1507,13 @@ bool Cmdline::listPropValues(const ConfigPropertyRegistry &validProps,
const string &opt)
{
const ConfigProperty *prop = validProps.find(propName);
if (!prop) {
if (!prop && boost::iequals(propName, "type")) {
m_out << opt << endl;
m_out << " <backend>[:<format>[:<version][!]]" << endl;
m_out << " legacy property, replaced by 'backend', 'databaseFormat'," << endl;
m_out << " 'syncFormat', 'forceSyncFormat'" << endl;
return true;
} else if (!prop) {
m_err << "ERROR: "<< opt << ": no such property" << endl;
return false;
} else {
@ -1558,8 +1585,15 @@ void Cmdline::checkForPeerProps()
}
}
if (!peerProps.empty()) {
SyncContext::throwError(string("per-peer (unshared) properties not allowed: ") +
boost::join(peerProps, ", "));
string props = boost::join(peerProps, ", ");
if (props == "forceSyncFormat, syncFormat") {
// special case: these two properties might have been added by the
// legacy "sync" property, which applies to both shared and unshared
// properties => cannot determine that here anymore, so ignore it
} else {
SyncContext::throwError(string("per-peer (unshared) properties not allowed: ") +
props);
}
}
}
@ -2756,10 +2790,12 @@ protected:
"peers/scheduleworld/sources/xyz/.internal.ini:# adminData = \n"
"peers/scheduleworld/sources/xyz/.internal.ini:# synthesisID = 0\n"
"peers/scheduleworld/sources/xyz/config.ini:# sync = disabled\n"
"peers/scheduleworld/sources/xyz/config.ini:# type = select backend\n"
"peers/scheduleworld/sources/xyz/config.ini:uri = dummy\n"
"sources/xyz/config.ini:# type = select backend\n"
"peers/scheduleworld/sources/xyz/config.ini:# syncFormat = \n"
"peers/scheduleworld/sources/xyz/config.ini:# forceSyncFormat = 0\n"
"sources/xyz/config.ini:# backend = select backend\n"
"sources/xyz/config.ini:# database = \n"
"sources/xyz/config.ini:# databaseFormat = \n"
"sources/xyz/config.ini:# databaseUser = \n"
"sources/xyz/config.ini:# databasePassword = ";
sortConfig(expected);
@ -2828,7 +2864,8 @@ protected:
string expected = doConfigure(ScheduleWorldConfig(), "sources/addressbook/config.ini:");
{
// updating type for peer must also update type for context
// updating "type" for peer is mapped to updating "backend",
// "databaseFormat", "syncFormat", "forceSyncFormat"
TestCmdline cmdline("--configure",
"--source-property", "addressbook/type=file:text/vcard:3.0",
"scheduleworld",
@ -2836,28 +2873,39 @@ protected:
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
boost::replace_all(expected,
"type = addressbook:text/vcard",
"type = file:text/vcard:3.0");
boost::replace_first(expected,
"backend = addressbook",
"backend = file");
boost::replace_first(expected,
"# databaseFormat = ",
"databaseFormat = text/vcard");
boost::replace_first(expected,
"# forceSyncFormat = 0",
"forceSyncFormat = 0");
CPPUNIT_ASSERT_EQUAL_DIFF(expected,
filterConfig(printConfig("scheduleworld")));
string shared = filterConfig(printConfig("@default"));
CPPUNIT_ASSERT(shared.find("type = file:text/vcard:3.0") != shared.npos);
CPPUNIT_ASSERT(shared.find("backend = file") != shared.npos);
CPPUNIT_ASSERT(shared.find("databaseFormat = text/vcard") != shared.npos);
}
{
// updating type for context must not affect peer
TestCmdline cmdline("--configure",
"--source-property", "type=file:text/vcard:2.1",
"--source-property", "type=file:text/x-vcard:2.1",
"@default", "addressbook",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
boost::replace_first(expected,
"databaseFormat = text/vcard",
"databaseFormat = text/x-vcard");
CPPUNIT_ASSERT_EQUAL_DIFF(expected,
filterConfig(printConfig("scheduleworld")));
string shared = filterConfig(printConfig("@default"));
CPPUNIT_ASSERT(shared.find("type = file:text/vcard:2.1") != shared.npos);
CPPUNIT_ASSERT(shared.find("backend = file") != shared.npos);
CPPUNIT_ASSERT(shared.find("databaseFormat = text/x-vcard") != shared.npos);
}
string syncProperties("syncURL:\n"
@ -2932,11 +2980,17 @@ protected:
"defaultPeer:\n");
string sourceProperties("sync:\n"
"\n"
"type:\n"
"uri:\n"
"\n"
"backend:\n"
"\n"
"syncFormat:\n"
"\n"
"forceSyncFormat:\n"
"\n"
"database:\n"
"\n"
"uri:\n"
"databaseFormat:\n"
"\n"
"databaseUser:\n"
"databasePassword:\n");
@ -3000,7 +3054,7 @@ protected:
{
TestCmdline cmdline("--configure",
"--source-property", "database = file://tmp/test",
"--source-property", "type = file:text/vcard:3.0",
"--source-property", "type = file:text/x-vcard",
"@foobar",
"addressbook",
NULL);
@ -3016,8 +3070,9 @@ protected:
"config.ini:# logdir = \n"
"config.ini:# maxlogdirs = 10\n"
"config.ini:deviceId = fixed-devid\n"
"sources/addressbook/config.ini:type = file:text/vcard:3.0\n"
"sources/addressbook/config.ini:backend = file\n"
"sources/addressbook/config.ini:database = file://tmp/test\n"
"sources/addressbook/config.ini:databaseFormat = text/x-vcard\n"
"sources/addressbook/config.ini:# databaseUser = \n"
"sources/addressbook/config.ini:# databasePassword = \n",
CONFIG_CONTEXT_MIN_VERSION,
@ -3028,7 +3083,7 @@ protected:
{
TestCmdline cmdline("--configure",
"--source-property", "database@foobar = file://tmp/test2",
"--source-property", "type = calendar",
"--source-property", "backend = calendar",
"@foobar",
"calendar",
NULL);
@ -3037,13 +3092,14 @@ protected:
res = scanFiles(root);
removeRandomUUID(res);
expected +=
"sources/calendar/config.ini:type = calendar\n"
"sources/calendar/config.ini:backend = calendar\n"
"sources/calendar/config.ini:database = file://tmp/test2\n"
"sources/calendar/config.ini:# databaseFormat = \n"
"sources/calendar/config.ini:# databaseUser = \n"
"sources/calendar/config.ini:# databasePassword = \n";
CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
// add ScheduleWorld peer
// add ScheduleWorld peer: must reuse existing backend settings
{
TestCmdline cmdline("--configure",
"scheduleworld@foobar",
@ -3054,23 +3110,18 @@ protected:
removeRandomUUID(res);
expected = ScheduleWorldConfig();
boost::replace_all(expected,
"peers/scheduleworld/sources/addressbook/config.ini:type = addressbook:text/vcard",
"peers/scheduleworld/sources/addressbook/config.ini:type = file:text/vcard:3.0");
"addressbook/config.ini:backend = addressbook",
"addressbook/config.ini:backend = file");
boost::replace_all(expected,
"addressbook/config.ini:# database = ",
"addressbook/config.ini:database = file://tmp/test");
boost::replace_all(expected,
"addressbook/config.ini:# databaseFormat = ",
"addressbook/config.ini:databaseFormat = text/x-vcard");
boost::replace_all(expected,
"calendar/config.ini:# database = ",
"calendar/config.ini:database = file://tmp/test2");
sortConfig(expected);
// Known problem (BMC #1023): type is reset to what is in the template,
// should be preserved.
//
// Temporarily fix the "expected" result so
// that we can pass the rest of the test.
boost::replace_all(expected,
"peers/scheduleworld/sources/addressbook/config.ini:type = file:text/vcard:3.0",
"peers/scheduleworld/sources/addressbook/config.ini:type = addressbook:text/vcard");
CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
// disable all sources except for addressbook
@ -3100,9 +3151,11 @@ protected:
}
string syncevoroot = m_testDir + "/syncevolution/syncevo";
res = scanFiles(syncevoroot + "/sources/addressbook");
CPPUNIT_ASSERT(res.find("type = file:text/vcard:3.0") != res.npos);
CPPUNIT_ASSERT(res.find("backend = file\n") != res.npos);
CPPUNIT_ASSERT(res.find("databaseFormat = text/vcard\n") != res.npos);
res = scanFiles(syncevoroot + "/sources/calendar");
CPPUNIT_ASSERT(res.find("type = file:text/calendar:2.0") != res.npos);
CPPUNIT_ASSERT(res.find("backend = file\n") != res.npos);
CPPUNIT_ASSERT(res.find("databaseFormat = text/calendar\n") != res.npos);
}
void testOldConfigure() {
@ -3152,6 +3205,10 @@ protected:
boost::replace_first(expected, "# database = ", "database = xyz");
boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
// migrating "type" sets forceSyncFormat (always)
// and databaseFormat (if format was part of type, as for addressbook)
boost::replace_all(expected, "# forceSyncFormat = 0", "forceSyncFormat = 0");
boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
doConfigure(expected, "sources/addressbook/config.ini:");
}
@ -3274,6 +3331,10 @@ protected:
boost::replace_first(expected, "# database = ", "database = xyz");
boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
// migrating "type" sets forceSyncFormat (always)
// and databaseFormat (if format was part of type, as for addressbook)
boost::replace_all(expected, "# forceSyncFormat = 0", "forceSyncFormat = 0");
boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
string renamedConfig = scanFiles(oldRoot + ".old");
CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
@ -3305,6 +3366,8 @@ protected:
boost::replace_first(expected, "# database = ", "database = xyz");
boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
boost::replace_all(expected, "# forceSyncFormat = 0", "forceSyncFormat = 0");
boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
string renamedConfig = scanFiles(newRoot, "scheduleworld.old");
boost::replace_all(createdConfig, "/scheduleworld/", "/scheduleworld.old/");
@ -3335,6 +3398,8 @@ protected:
boost::replace_first(expected, "# database = ", "database = xyz");
boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
boost::replace_all(expected, "# forceSyncFormat = 0", "forceSyncFormat = 0");
boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
boost::replace_first(expected,
"peers/scheduleworld/sources/addressbook/config.ini",
"peers/scheduleworld/sources/addressbook/.other.ini:foo = bar\n"
@ -3372,6 +3437,8 @@ protected:
boost::replace_first(expected, "# database = ", "database = xyz");
boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
boost::replace_all(expected, "# forceSyncFormat = 0", "forceSyncFormat = 0");
boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
string renamedConfig = scanFiles(oldRoot + ".old");
CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
@ -3398,6 +3465,8 @@ protected:
boost::replace_first(expected, "# database = ", "database = xyz");
boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
boost::replace_all(expected, "# forceSyncFormat = 0", "forceSyncFormat = 0");
boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
renamedConfig = scanFiles(otherRoot, "scheduleworld.old");
boost::replace_all(expected, "/scheduleworld/", "/scheduleworld.old/");
@ -3567,40 +3636,48 @@ private:
"peers/scheduleworld/sources/addressbook/.internal.ini:# adminData = \n"
"peers/scheduleworld/sources/addressbook/.internal.ini:# synthesisID = 0\n"
"peers/scheduleworld/sources/addressbook/config.ini:sync = two-way\n"
"sources/addressbook/config.ini:type = addressbook:text/vcard\n"
"peers/scheduleworld/sources/addressbook/config.ini:type = addressbook:text/vcard\n"
"sources/addressbook/config.ini:# database = \n"
"peers/scheduleworld/sources/addressbook/config.ini:uri = card3\n"
"sources/addressbook/config.ini:backend = addressbook\n"
"peers/scheduleworld/sources/addressbook/config.ini:syncFormat = text/vcard\n"
"peers/scheduleworld/sources/addressbook/config.ini:# forceSyncFormat = 0\n"
"sources/addressbook/config.ini:# database = \n"
"sources/addressbook/config.ini:# databaseFormat = \n"
"sources/addressbook/config.ini:# databaseUser = \n"
"sources/addressbook/config.ini:# databasePassword = \n"
"peers/scheduleworld/sources/calendar/.internal.ini:# adminData = \n"
"peers/scheduleworld/sources/calendar/.internal.ini:# synthesisID = 0\n"
"peers/scheduleworld/sources/calendar/config.ini:sync = two-way\n"
"sources/calendar/config.ini:type = calendar\n"
"peers/scheduleworld/sources/calendar/config.ini:type = calendar\n"
"sources/calendar/config.ini:# database = \n"
"peers/scheduleworld/sources/calendar/config.ini:uri = cal2\n"
"sources/calendar/config.ini:backend = calendar\n"
"peers/scheduleworld/sources/calendar/config.ini:# syncFormat = \n"
"peers/scheduleworld/sources/calendar/config.ini:# forceSyncFormat = 0\n"
"sources/calendar/config.ini:# database = \n"
"sources/calendar/config.ini:# databaseFormat = \n"
"sources/calendar/config.ini:# databaseUser = \n"
"sources/calendar/config.ini:# databasePassword = \n"
"peers/scheduleworld/sources/memo/.internal.ini:# adminData = \n"
"peers/scheduleworld/sources/memo/.internal.ini:# synthesisID = 0\n"
"peers/scheduleworld/sources/memo/config.ini:sync = two-way\n"
"sources/memo/config.ini:type = memo\n"
"peers/scheduleworld/sources/memo/config.ini:type = memo\n"
"sources/memo/config.ini:# database = \n"
"peers/scheduleworld/sources/memo/config.ini:uri = note\n"
"sources/memo/config.ini:backend = memo\n"
"peers/scheduleworld/sources/memo/config.ini:# syncFormat = \n"
"peers/scheduleworld/sources/memo/config.ini:# forceSyncFormat = 0\n"
"sources/memo/config.ini:# database = \n"
"sources/memo/config.ini:# databaseFormat = \n"
"sources/memo/config.ini:# databaseUser = \n"
"sources/memo/config.ini:# databasePassword = \n"
"peers/scheduleworld/sources/todo/.internal.ini:# adminData = \n"
"peers/scheduleworld/sources/todo/.internal.ini:# synthesisID = 0\n"
"peers/scheduleworld/sources/todo/config.ini:sync = two-way\n"
"sources/todo/config.ini:type = todo\n"
"peers/scheduleworld/sources/todo/config.ini:type = todo\n"
"sources/todo/config.ini:# database = \n"
"peers/scheduleworld/sources/todo/config.ini:uri = task2\n"
"sources/todo/config.ini:backend = todo\n"
"peers/scheduleworld/sources/todo/config.ini:# syncFormat = \n"
"peers/scheduleworld/sources/todo/config.ini:# forceSyncFormat = 0\n"
"sources/todo/config.ini:# database = \n"
"sources/todo/config.ini:# databaseFormat = \n"
"sources/todo/config.ini:# databaseUser = \n"
"sources/todo/config.ini:# databasePassword = ",
peerMinVersion, peerCurVersion,
@ -3730,22 +3807,28 @@ private:
"addressbook/config.ini:uri = card3",
"addressbook/config.ini:uri = card");
boost::replace_all(config,
"addressbook/config.ini:type = addressbook:text/vcard",
"addressbook/config.ini:type = addressbook");
"addressbook/config.ini:syncFormat = text/vcard",
"addressbook/config.ini:# syncFormat = ");
boost::replace_first(config,
"calendar/config.ini:uri = cal2",
"calendar/config.ini:uri = event");
boost::replace_all(config,
"calendar/config.ini:type = calendar",
"calendar/config.ini:type = calendar:text/calendar!");
"calendar/config.ini:# syncFormat = ",
"calendar/config.ini:syncFormat = text/calendar");
boost::replace_all(config,
"calendar/config.ini:# forceSyncFormat = 0",
"calendar/config.ini:forceSyncFormat = 1");
boost::replace_first(config,
"todo/config.ini:uri = task2",
"todo/config.ini:uri = task");
boost::replace_all(config,
"todo/config.ini:type = todo",
"todo/config.ini:type = todo:text/calendar!");
"todo/config.ini:# syncFormat = ",
"todo/config.ini:syncFormat = text/calendar");
boost::replace_all(config,
"todo/config.ini:# forceSyncFormat = 0",
"todo/config.ini:forceSyncFormat = 1");
return config;
}
@ -3766,8 +3849,8 @@ private:
"addressbook/config.ini:uri = card3",
"addressbook/config.ini:uri = contacts");
boost::replace_all(config,
"addressbook/config.ini:type = addressbook:text/vcard",
"addressbook/config.ini:type = addressbook");
"addressbook/config.ini:syncFormat = text/vcard",
"addressbook/config.ini:# syncFormat = ");
boost::replace_first(config,
"calendar/config.ini:uri = cal2",

View File

@ -48,7 +48,7 @@ SE_BEGIN_CXX
const char *const SourceAdminDataName = "adminData";
static bool SourcePropSourceTypeIsSet(boost::shared_ptr<SyncSourceConfig> source);
static bool SourcePropBackendIsSet(boost::shared_ptr<SyncSourceConfig> source);
static bool SourcePropURIIsSet(boost::shared_ptr<SyncSourceConfig> source);
static bool SourcePropSyncIsSet(boost::shared_ptr<SyncSourceConfig> source);
@ -822,8 +822,8 @@ boost::shared_ptr<SyncConfig> SyncConfig::createPeerTemplate(const string &serve
config->setSourceDefaults("memo", false);
source = config->getSyncSourceConfig("addressbook");
if (!SourcePropSourceTypeIsSet(source)) {
source->setSourceType("addressbook");
if (!SourcePropBackendIsSet(source)) {
source->setBackend("addressbook");
}
if (!SourcePropURIIsSet(source)) {
source->setURI("card");
@ -833,8 +833,8 @@ boost::shared_ptr<SyncConfig> SyncConfig::createPeerTemplate(const string &serve
}
source = config->getSyncSourceConfig("calendar");
if (!SourcePropSourceTypeIsSet(source)) {
source->setSourceType("calendar");
if (!SourcePropBackendIsSet(source)) {
source->setBackend("calendar");
}
if (!SourcePropURIIsSet(source)) {
source->setURI("event");
@ -844,8 +844,8 @@ boost::shared_ptr<SyncConfig> SyncConfig::createPeerTemplate(const string &serve
}
source = config->getSyncSourceConfig("todo");
if (!SourcePropSourceTypeIsSet(source)) {
source->setSourceType("todo");
if (!SourcePropBackendIsSet(source)) {
source->setBackend("todo");
}
if (!SourcePropURIIsSet(source)) {
source->setURI("task");
@ -855,8 +855,8 @@ boost::shared_ptr<SyncConfig> SyncConfig::createPeerTemplate(const string &serve
}
source = config->getSyncSourceConfig("memo");
if (!SourcePropSourceTypeIsSet(source)) {
source->setSourceType("memo");
if (!SourcePropBackendIsSet(source)) {
source->setBackend("memo");
}
if (!SourcePropURIIsSet(source)) {
source->setURI("note");
@ -904,7 +904,8 @@ boost::shared_ptr<SyncConfig> SyncConfig::createPeerTemplate(const string &serve
// config->setConsumerReady(false);
source = config->getSyncSourceConfig("addressbook");
source->setURI("card3");
source->setSourceType("addressbook:text/vcard");
source->setBackend("addressbook");
source->setSyncFormat("text/vcard");
source = config->getSyncSourceConfig("calendar");
source->setURI("cal2");
source = config->getSyncSourceConfig("todo");
@ -920,11 +921,13 @@ boost::shared_ptr<SyncConfig> SyncConfig::createPeerTemplate(const string &serve
source = config->getSyncSourceConfig("calendar");
source->setSync("two-way");
source->setURI("event");
source->setSourceType("calendar:text/calendar!");
source->setSyncFormat("text/calendar");
source->setForceSyncFormat(true);
source = config->getSyncSourceConfig("todo");
source->setSync("two-way");
source->setURI("task");
source->setSourceType("todo:text/calendar!");
source->setSyncFormat("text/calendar");
source->setForceSyncFormat(true);
} else if (boost::iequals(server, "synthesis")) {
config->setSyncURL("http://www.synthesis.ch/sync");
config->setWebURL("http://www.synthesis.ch");
@ -965,7 +968,7 @@ boost::shared_ptr<SyncConfig> SyncConfig::createPeerTemplate(const string &serve
#endif
source = config->getSyncSourceConfig("addressbook");
source->setURI("contacts");
source->setSourceType("addressbook:text/x-vcard");
source->setSyncFormat("text/x-vcard");
/* Google support only addressbook sync via syncml */
source = config->getSyncSourceConfig("calendar");
source->setSync("none");
@ -1011,22 +1014,23 @@ boost::shared_ptr<SyncConfig> SyncConfig::createPeerTemplate(const string &serve
#endif
//prefer vcard 3.0
source = config->getSyncSourceConfig("addressbook");
source->setSourceType("addressbook:text/vcard");
source->setSyncFormat("text/vcard");
source->setURI("./Contact/Unfiled");
source = config->getSyncSourceConfig("calendar");
source->setSync("none");
source->setURI("");
//prefer vcalendar 1.0
source->setSourceType("calendar:text/x-vcalendar");
source->setSyncFormat("text/x-vcalendar");
source = config->getSyncSourceConfig("todo");
source->setSync("none");
source->setURI("");
//prefer vcalendar 1.0
source->setSourceType("todo:text/x-vcalendar");
source->setSyncFormat("text/x-vcalendar");
//A virtual datastore combining calendar and todo
source = config->getSyncSourceConfig("calendar+todo");
source->setURI("./EventTask/Tasks");
source->setSourceType("virtual:text/x-vcalendar");
source->setBackend("virtual");
source->setSyncFormat("text/x-vcalendar");
source->setDatabaseID("calendar,todo");
source->setSync("two-way");
//Memo is disabled
@ -1251,6 +1255,26 @@ SyncSourceNodes SyncConfig::getSyncSourceNodes(const string &name,
break;
}
// Compatibility mode for reading configs which have "type" instead
// of "backend/databaseFormat/syncFormat/forceSyncFormat": determine
// the new values based on the old property, then inject the new
// values into the SyncSourceNodes by adding an intermediate layer
// of FilterConfigNodes. The top FilterConfigNode layer is the one
// which might get modified, the one underneath remains hidden and
// thus preserves the new values even if the caller does a setFilter().
bool compatMode = getConfigVersion(CONFIG_LEVEL_CONTEXT, CONFIG_CUR_VERSION) < 1;
SourceType sourceType;
if (compatMode) {
node = m_tree->open(peerPath.empty() ? sharedPath : peerPath, ConfigTree::visible);
string type;
if (node->getProperty("type", type)) {
sourceType = SourceType(type);
} else {
// not set: avoid compatibility mode
compatMode = false;
}
}
if (peerPath.empty()) {
node.reset(new DevNullConfigNode(m_contextPath + " without peer configuration"));
peerNode.reset(new FilterConfigNode(node));
@ -1263,6 +1287,16 @@ SyncSourceNodes SyncConfig::getSyncSourceNodes(const string &name,
cacheDir = m_tree->getRootPath() + "/" + peerPath + "/.cache";
node = m_tree->open(peerPath, ConfigTree::visible);
if (compatMode) {
boost::shared_ptr<FilterConfigNode> compat(new FilterConfigNode(node));
compat->addFilter("syncFormat", sourceType.m_format);
compat->addFilter("forceSyncFormat", sourceType.m_forceFormat ? "1" : "0");
if (sharedPath.empty()) {
compat->addFilter("databaseFormat", sourceType.m_localFormat);
compat->addFilter("backend", sourceType.m_backend);
}
node = compat;
}
peerNode.reset(new FilterConfigNode(node, m_sourceFilters.createSourceFilter(name)));
hiddenPeerNode = m_tree->open(peerPath, ConfigTree::hidden);
trackingNode = m_tree->open(peerPath, ConfigTree::other, changeId);
@ -1293,6 +1327,12 @@ SyncSourceNodes SyncConfig::getSyncSourceNodes(const string &name,
sharedNode = peerNode;
} else {
node = m_tree->open(sharedPath, ConfigTree::visible);
if (compatMode) {
boost::shared_ptr<FilterConfigNode> compat(new FilterConfigNode(node));
compat->addFilter("databaseFormat", sourceType.m_localFormat);
compat->addFilter("backend", sourceType.m_backend);
node = compat;
}
sharedNode.reset(new FilterConfigNode(node, m_sourceFilters.createSourceFilter(name)));
}
@ -2317,21 +2357,16 @@ static bool SourcePropSyncIsSet(boost::shared_ptr<SyncSourceConfig> source)
}
static class SourceTypeConfigProperty : public StringConfigProperty {
static class SourceBackendConfigProperty : public StringConfigProperty {
public:
SourceTypeConfigProperty() :
StringConfigProperty("type",
SourceBackendConfigProperty() :
StringConfigProperty("backend",
"Specifies the SyncEvolution backend and thus the\n"
"data which is synchronized by this source. Some\n"
"backends can exchange data in multiple formats.\n"
"Some of them have a default format that is used\n"
"automatically unless specified differently.\n"
"Sometimes the format must be specified.\n"
"\n"
"This property can be set for individual peers as\n"
"well as for the context. Different peers in the\n"
"same context can use different formats, but the\n"
"backend must be consistent.\n"
"data which is synchronized by this source. Each\n"
"backend may support multiple databases (see 'database'\n"
"property), different formats inside that database (see\n"
"'databaseFormat'), and different formats when talking to\n"
"the sync peer (see 'syncFormat' and 'forceSyncFormat').\n"
"\n"
"A special 'virtual' backend combines several other\n"
"data sources and presents them as one set of items\n"
@ -2345,52 +2380,26 @@ public:
"'database' property, typically like this:\n"
" calendar,todo\n"
"\n"
"In all cases the format of this configuration is\n"
" <backend>[:format][!]\n"
"\n"
"Different sources combined in one virtual source must\n"
"have a common representation. As with other backends,\n"
"the preferred format can be influenced via the 'format'\n"
"have a common format. As with other backends,\n"
"the preferred format can be influenced via the 'syncFormat'\n"
"attribute.\n"
"\n"
"When there are alternative formats for the same data,\n"
"each side offers all that it supports and marks one as\n"
"preferred. The other side then picks the format that it\n"
"uses for sending data. Some peers get confused by this or\n"
"pick the less suitable format. In this case the trailing\n"
"exclamation mark can be used to configure exactly one format.\n"
"\n"
"Here are some valid examples:\n"
" contacts - synchronize address book with default vCard 2.1 format\n"
" contacts:text/vcard - address book with vCard 3.0 format\n"
" calendar - synchronize events in iCalendar 2.0 format\n"
" calendar:text/x-vcalendar - prefer legacy vCalendar 1.0 format\n"
" calendar:text/calendar! - allow only iCalendar 2.0\n"
" virtual:text/x-vcalendar - a virtual backend using vCalendar 1.0 format\n"
"\n"
"Errors while starting to sync and parsing and/or storing\n"
"items on either client or server can be caused by a mismatch between\n"
"type and uri.\n"
"\n"
"Here's the full list of potentially supported backends,\n"
"valid <backend> values for each of them, and possible\n"
"valid 'backend' values for each of them, and possible\n"
"formats. Note that SyncEvolution installations usually\n"
"support only a subset of the backends; that's why e.g.\n"
"\"addressbook\" is unambiguous although there are multiple\n"
"address book backends.\n",
"address book backends.\n"
"\n",
"select backend",
"",
Values() +
(Aliases("virtual")) +
(Aliases("calendar") + "events") +
(Aliases("calendar:text/calendar") + "text/calendar") +
(Aliases("calendar:text/x-vcalendar") + "text/x-vcalendar") +
(Aliases("addressbook") + "contacts") +
(Aliases("addressbook:text/x-vcard") + "text/x-vcard") +
(Aliases("addressbook:text/vcard") + "text/vcard") +
(Aliases("todo") + "tasks" + "text/x-todo") +
(Aliases("memo") + "memos" + "notes" + "text/plain") +
(Aliases("memo:text/calendar") + "text/x-journal"))
(Aliases("todo") + "tasks") +
(Aliases("memo") + "memos" + "notes"))
{}
virtual string getComment() const {
@ -2430,23 +2439,36 @@ public:
return res;
}
/** relax string checking: only the part before a colon has to match one of the aliases */
virtual bool checkValue(const string &value, string &error) const {
size_t colon = value.find(':');
if (colon != value.npos) {
string backend = value.substr(0, colon);
return StringConfigProperty::checkValue(backend, error);
} else {
return StringConfigProperty::checkValue(value, error);
}
}
} sourcePropSourceType;
static bool SourcePropSourceTypeIsSet(boost::shared_ptr<SyncSourceConfig> source)
} sourcePropBackend;
static bool SourcePropBackendIsSet(boost::shared_ptr<SyncSourceConfig> source)
{
return source->isSet(sourcePropSourceType);
return source->isSet(sourcePropBackend);
}
StringConfigProperty sourcePropSyncFormat("syncFormat",
"When there are alternative formats for the same data,\n"
"each side of a sync offers all that it supports and marks one as\n"
"preferred. If set, this property overrides the format\n"
"that would normally be marked as preferred by a backend.\n"
"\n"
"Valid values depend on the backend. Here are some examples:\n"
" contacts - text/vcard = vCard 3.0 format\n"
" text/x-vcard = legacy vCard 2.1 format\n"
" calendar - text/calendar = iCalendar 2.0 format\n"
" text/x-vcalendar = legacy vCalendar 1.0 format\n"
"\n"
"Errors while starting to sync and parsing and/or storing\n"
"items on either client or server can be caused by a mismatch between\n"
"the sync format and uri at the peer.\n");
static BoolConfigProperty sourcePropForceSyncFormat("forceSyncFormat",
"Some peers get confused when offered multiple choices\n"
"for the sync format or pick the less optimal one.\n"
"In such a case, setting this property enforces that the\n"
"preferred format specified with 'syncFormat' is\n"
"really used.",
"0");
static ConfigProperty sourcePropDatabaseID(Aliases("database") + "evolutionsource",
"Picks one of backend data sources:\n"
"enter either the name or the full URL.\n"
@ -2469,6 +2491,13 @@ static ConfigProperty sourcePropDatabaseID(Aliases("database") + "evolutionsourc
"used to reference the data source. The default\n"
"data source is marked with <default> after the\n"
"URL, if there is a default.\n");
static StringConfigProperty sourcePropDatabaseFormat("databaseFormat",
"Defines the data format to be used by the backend for its\n"
"own storage. Typically backends only support one format\n"
"and ignore this property, but for example the file backend\n"
"uses it. See the 'backend' property for more information.\n");
static ConfigProperty sourcePropURI("uri",
"this is appended to the server's URL to identify the\n"
"server's database");
@ -2498,9 +2527,12 @@ ConfigPropertyRegistry &SyncSourceConfig::getRegistry()
if (!initialized) {
registry.push_back(&SyncSourceConfig::m_sourcePropSync);
registry.push_back(&sourcePropSourceType);
registry.push_back(&sourcePropDatabaseID);
registry.push_back(&sourcePropURI);
registry.push_back(&sourcePropBackend);
registry.push_back(&sourcePropSyncFormat);
registry.push_back(&sourcePropForceSyncFormat);
registry.push_back(&sourcePropDatabaseID);
registry.push_back(&sourcePropDatabaseFormat);
registry.push_back(&sourcePropUser);
registry.push_back(&sourcePropPassword);
registry.push_back(&sourcePropAdminData);
@ -2519,14 +2551,12 @@ ConfigPropertyRegistry &SyncSourceConfig::getRegistry()
// conceptually.
// peer independent source properties
sourcePropBackend.setSharing(ConfigProperty::SOURCE_SET_SHARING);
sourcePropDatabaseID.setSharing(ConfigProperty::SOURCE_SET_SHARING);
sourcePropDatabaseFormat.setSharing(ConfigProperty::SOURCE_SET_SHARING);
sourcePropUser.setSharing(ConfigProperty::SOURCE_SET_SHARING);
sourcePropPassword.setSharing(ConfigProperty::SOURCE_SET_SHARING);
// Save "type" also in the shared nodes, so that the backend
// can be selected independently from a specific peer.
sourcePropSourceType.setFlags(ConfigProperty::SHARED_AND_UNSHARED);
initialized = true;
}
@ -2623,19 +2653,24 @@ SourceType::SourceType(const string &type)
m_forceFormat = false;
size_t colon = type.find(':');
if (colon != type.npos) {
string backend = type.substr(0, colon);
m_backend = type.substr(0, colon);
sourcePropBackend.normalizeValue(m_backend);
string format = type.substr(colon + 1);
sourcePropSourceType.normalizeValue(backend);
size_t formatLen = format.size();
if(format[formatLen - 1] == '!') {
if (boost::ends_with(format, "!")) {
m_forceFormat = true;
format = format.substr(0, formatLen - 1);
format.resize(format.size() - 1);
}
m_backend = backend;
m_format = format;
colon = format.find(':');
if (colon != format.npos) {
// ignore obsolete Mime version
m_format = format.substr(0, colon);
} else {
m_format = format;
}
// no difference between remote and local format
m_localFormat = m_format;
} else {
m_backend = type;
m_format = "";
}
}
@ -2652,16 +2687,81 @@ string SourceType::toString() const
return type;
}
SourceType SyncSourceConfig::getSourceType(const SyncSourceNodes &nodes) {
string type = sourcePropSourceType.getProperty(*nodes.getNode(sourcePropSourceType));
SourceType sourceType(type);
SourceType SyncSourceConfig::getSourceType(const SyncSourceNodes &nodes)
{
// legacy "type" property is tried if the backend property is not set
bool isDefault;
string backend = sourcePropBackend.getProperty(*nodes.getNode(sourcePropBackend), &isDefault);
if (isDefault) {
string type;
if (nodes.getNode(sourcePropBackend)->getProperty("type", type)) {
return SourceType(type);
}
}
SourceType sourceType;
sourceType.m_backend = backend;
sourceType.m_localFormat = sourcePropDatabaseFormat.getProperty(*nodes.getNode(sourcePropDatabaseFormat));
sourceType.m_format = sourcePropSyncFormat.getProperty(*nodes.getNode(sourcePropSyncFormat));
sourceType.m_forceFormat = sourcePropForceSyncFormat.getPropertyValue(*nodes.getNode(sourcePropForceSyncFormat));
return sourceType;
}
SourceType SyncSourceConfig::getSourceType() const { return getSourceType(m_nodes); }
void SyncSourceConfig::setSourceType(const SourceType &type, bool temporarily)
{
sourcePropSourceType.setProperty(*getNode(sourcePropSourceType), type.toString(), temporarily);
// writing always uses the new properties: the config must have
// been converted to the new format before writing is allowed
setBackend(type.m_backend, temporarily);
setDatabaseFormat(type.m_localFormat, temporarily);
setSyncFormat(type.m_format, temporarily);
setForceSyncFormat(type.m_forceFormat, temporarily);
}
void SyncSourceConfig::setBackend(const std::string &value, bool temporarily)
{
sourcePropBackend.setProperty(*getNode(sourcePropBackend),
value,
temporarily);
}
std::string SyncSourceConfig::getBackend() const
{
return sourcePropBackend.getProperty(*getNode(sourcePropBackend));
}
void SyncSourceConfig::setDatabaseFormat(const std::string &value, bool temporarily)
{
sourcePropDatabaseFormat.setProperty(*getNode(sourcePropDatabaseFormat),
value,
temporarily);
}
std::string SyncSourceConfig::getDatabaseFormat() const
{
return sourcePropDatabaseFormat.getProperty(*getNode(sourcePropDatabaseFormat));
}
void SyncSourceConfig::setSyncFormat(const std::string &value, bool temporarily)
{
sourcePropSyncFormat.setProperty(*getNode(sourcePropSyncFormat),
value,
temporarily);
}
std::string SyncSourceConfig::getSyncFormat() const
{
return sourcePropSyncFormat.getProperty(*getNode(sourcePropSyncFormat));
}
void SyncSourceConfig::setForceSyncFormat(bool value, bool temporarily)
{
sourcePropForceSyncFormat.setProperty(*getNode(sourcePropForceSyncFormat),
value,
temporarily);
}
bool SyncSourceConfig::getForceSyncFormat() const
{
return sourcePropForceSyncFormat.getPropertyValue(*getNode(sourcePropForceSyncFormat));
}
const int SyncSourceConfig::getSynthesisID() const { return sourcePropSynthesisID.getPropertyValue(*getNode(sourcePropSynthesisID)); }

View File

@ -266,10 +266,11 @@ class ConfigProperty {
/** primary name */
string getMainName() const { return m_names.front(); }
const Aliases &getNames() const { return m_names; }
string getComment() const { return m_comment; }
string getDefValue() const { return m_defValue; }
string getDescr() const { return m_descr; }
/* virtual so that derived classes like SourceBackendConfigProperty can generate the result dynamically */
virtual const Aliases &getNames() const { return m_names; }
virtual string getComment() const { return m_comment; }
virtual string getDefValue() const { return m_defValue; }
virtual string getDescr() const { return m_descr; }
/**
* Check whether the given value is okay.
@ -1799,7 +1800,10 @@ struct SourceType {
/**
* Parses the SyncEvolution <= 1.1 type specifier:
* <backend>[:<format>[!]]
* <backend>[:<format>[:<version>][!]]
*
* The <version> part is not stored anymore (was required by file
* backend, but not actually used).
*/
SourceType(const string &type);
@ -1809,10 +1813,8 @@ struct SourceType {
string toString() const;
string m_backend; /**< identifies the SyncEvolution backend (either via a generic term like "addressbook" or a specific one like "Evolution Contacts") */
#if 0 // to be enabled later
string m_localFormat; /**< the format to be used inside the backend for storing items; typically
hard-coded and not configurable */
#endif
string m_format; /**< the format to be used (typically a MIME type) when talking to our peer */
bool m_forceFormat; /**< force to use the client's preferred format instead giving the engine and server a choice */
};
@ -1907,20 +1909,21 @@ class SyncSourceConfig {
* configuration; different SyncSources then check whether
* they support that type. This call has to work before instantiating
* a source and thus gets passed a node to read from.
*
* @return the pair of <backend> and the (possibly empty)
* <format> specified in the "type" property; see
* sourcePropSourceType in SyncConfig.cpp
* for details
*/
static SourceType getSourceType(const SyncSourceNodes &nodes);
virtual SourceType getSourceType() const;
/** set the source type */
/** set source backend and formats in one step */
virtual void setSourceType(const SourceType &type, bool temporarily = false);
/** convenience function which accepts old-style "type" string (see SourceType) */
void setSourceType(const string &type, bool temporarily = false) { setSourceType(SourceType(type), temporarily); }
virtual void setBackend(const std::string &value, bool temporarily = false);
virtual std::string getBackend() const;
virtual void setDatabaseFormat(const std::string &value, bool temporarily = false);
virtual std::string getDatabaseFormat() const;
virtual void setSyncFormat(const std::string &value, bool temporarily = false);
virtual std::string getSyncFormat() const;
virtual void setForceSyncFormat(bool value, bool temporarily = false);
virtual bool getForceSyncFormat() const;
/**
* Returns the SyncSource URI: used in SyncML to address the data

View File

@ -24,7 +24,8 @@ uri = Notes
=== sources/calendar+todo/config.ini ===
sync = two-way
type = virtual:text/x-calendar:1.0
backend = virtual
syncFormat = text/x-vcalendar
evolutionsource = calendar,todo
uri = Calendar

View File

@ -10,21 +10,13 @@ remoteIdentifier = PC Suite
ConsumerReady = 1
=== sources/addressbook/config.ini ===
type = addressbook:text/vcard
sync = two-way
uri = Contact
=== sources/calendar/config.ini ===
type = calendar:text/x-vcalendar
sync = two-way
uri = Calendar
=== sources/todo/config.ini ===
type = todo:text/x-vcalendar
sync = two-way
uri = Task
=== sources/memo/config.ini ===
type = memo:text/plain
sync = two-way
uri = Memo

View File

@ -9,21 +9,13 @@ remoteIdentifier = PC Suite
ConsumerReady = 1
=== sources/addressbook/config.ini ===
type = addressbook:text/vcard
sync = two-way
uri = Contact
=== sources/calendar/config.ini ===
type = calendar:text/x-vcalendar
sync = two-way
uri = Calendar
=== sources/todo/config.ini ===
type = todo:text/x-vcalendar
sync = two-way
uri = Task
=== sources/memo/config.ini ===
type = memo:text/plain
sync = two-way
uri = Memo

View File

@ -10,18 +10,17 @@ ConsumerReady = TRUE
RetryInterval = 0
=== sources/addressbook/config.ini ===
type = addressbook
uri = card
=== sources/calendar/config.ini ===
uri = event
sync = two-way
type = calendar:text/calendar!
syncFormat = text/calendar
forceSyncFormat = 1
=== sources/todo/config.ini ===
uri = task
sync = two-way
type = todo:text/calendar!
syncFormat = text/calendar
forceSyncFormat = 1
=== sources/memo/config.ini ===
uri = note

View File

@ -7,8 +7,8 @@ syncURL = http://sync.scheduleworld.com/funambol/ds
WebURL = http://www.scheduleworld.com
=== sources/addressbook/config.ini ===
type = addressbook:text/vcard
uri = card3
syncFormat = text/vcard
=== sources/calendar/config.ini ===
uri = cal2

View File

@ -1111,19 +1111,19 @@ class TestSessionAPIsDummy(unittest.TestCase, DBusUtil):
"configName" : "dummy-test"
},
"source/addressbook" : { "sync" : "slow",
"type" : "addressbook",
"backend" : "addressbook",
"uri" : "card"
},
"source/calendar" : { "sync" : "disabled",
"type" : "calendar",
"backend" : "calendar",
"uri" : "cal"
},
"source/todo" : { "sync" : "disabled",
"type" : "todo",
"backend" : "todo",
"uri" : "task"
},
"source/memo" : { "sync" : "disabled",
"type" : "memo",
"backend" : "memo",
"uri" : "text"
}
}
@ -1347,7 +1347,7 @@ class TestSessionAPIsDummy(unittest.TestCase, DBusUtil):
self.setupConfig()
# we cannot set an invalid type here, so pick one which is likely
# to be not supported in syncevo-dbus-server
config = { "source/memo" : { "type" : "apple-contacts"} }
config = { "source/memo" : { "backend" : "apple-contacts"} }
self.session.SetConfig(True, False, config, utf8_strings=True)
try:
self.session.CheckSource("memo", utf8_strings=True)
@ -1358,9 +1358,11 @@ class TestSessionAPIsDummy(unittest.TestCase, DBusUtil):
self.fail("no exception thrown")
def testCheckSourceNoType(self):
""" test the right error is reported when the type is unset """
""" test the right error is reported when the source is unusable"""
self.setupConfig()
config = { "source/memo" : { "type" : "file:text/calendar:2.0", "database" : "file:///no/such/path" } }
config = { "source/memo" : { "backend" : "file",
"databaseFormat" : "text/calendar",
"database" : "file:///no/such/path" } }
self.session.SetConfig(True, False, config, utf8_strings=True)
try:
self.session.CheckSource("memo", utf8_strings=True)
@ -1379,7 +1381,7 @@ class TestSessionAPIsDummy(unittest.TestCase, DBusUtil):
def testCheckSourceUpdateConfigTemp(self):
""" test the config is temporary updated and in effect for GetDatabases in the current session. """
self.setupConfig()
tempConfig = {"source/temp" : { "type" : "calendar"}}
tempConfig = {"source/temp" : { "backend" : "calendar"}}
self.session.SetConfig(True, True, tempConfig, utf8_strings=True)
databases2 = self.session.CheckSource("temp", utf8_strings=True)
@ -1428,7 +1430,7 @@ class TestSessionAPIsDummy(unittest.TestCase, DBusUtil):
""" test the config is temporary updated and in effect for GetDatabases in the current session. """
self.setupConfig()
databases1 = self.session.GetDatabases("calendar", utf8_strings=True)
tempConfig = {"source/temp" : { "type" : "calendar"}}
tempConfig = {"source/temp" : { "backend" : "calendar"}}
self.session.SetConfig(True, True, tempConfig, utf8_strings=True)
databases2 = self.session.GetDatabases("temp", utf8_strings=True)
self.failUnlessEqual(databases2, databases1)
@ -1856,19 +1858,19 @@ class TestConnection(unittest.TestCase, DBusUtil):
"RetryDuration" : "10"
},
"source/addressbook" : { "sync" : "two-way",
"type" : "addressbook",
"backend" : "addressbook",
"uri" : "card"
},
"source/calendar" : { "sync" : "two-way",
"type" : "calendar",
"backend" : "calendar",
"uri" : "cal"
},
"source/todo" : { "sync" : "two-way",
"type" : "todo",
"backend" : "todo",
"uri" : "task"
},
"source/memo" : { "sync" : "two-way",
"type" : "memo",
"backend" : "memo",
"uri" : "text"
}
}
@ -2221,60 +2223,53 @@ class TestMultipleConfigs(unittest.TestCase, DBusUtil):
self.failUnlessEqual(config["source/addressbook"]["database"], "Work")
def testSharedType(self):
"""'type' must be set per-peer and shared"""
"""'type' consists of per-peer and shared properties"""
self.setupConfigs()
# writing for peer modifies "type" in "foo" and context
# writing for peer modifies properties in "foo" and context
self.setUpSession("Foo@deFAULT")
config = self.session.GetConfig(False, utf8_strings=True)
config["source/addressbook"]["type"] = "file:text/vcard:3.0"
config["source/addressbook"]["syncFormat"] = "text/vcard"
config["source/addressbook"]["backend"] = "file"
config["source/addressbook"]["databaseFormat"] = "text/x-vcard"
self.session.SetConfig(True, False,
config,
utf8_strings=True)
config = self.server.GetConfig("Foo", False, utf8_strings=True)
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
self.failUnlessEqual(config["source/addressbook"]["syncFormat"], "text/vcard")
config = self.server.GetConfig("@default", False, utf8_strings=True)
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
self.failUnlessEqual(config["source/addressbook"]["backend"], "file")
self.failUnlessEqual(config["source/addressbook"]["databaseFormat"], "text/x-vcard")
self.session.Detach()
# writing in context only changes the context
self.setUpSession("@deFAULT")
config = self.session.GetConfig(False, utf8_strings=True)
config["source/addressbook"]["type"] = "file:text/x-vcard:2.1"
self.session.SetConfig(True, False,
config,
utf8_strings=True)
config = self.server.GetConfig("Foo", False, utf8_strings=True)
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
config = self.server.GetConfig("@default", False, utf8_strings=True)
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/x-vcard:2.1")
def testSharedTypeOther(self):
"""'type' must not be overwritten when set in the context"""
# writing for peer modifies "type" in "foo" and context "@other"
"""shared backend properties must be preserved when adding peers"""
# writing peer modifies properties in "foo" and creates context "@other"
self.setUpSession("Foo@other")
config = self.server.GetConfig("ScheduleWorld@other", True, utf8_strings=True)
config["source/addressbook"]["type"] = "file:text/vcard:3.0"
config["source/addressbook"]["backend"] = "file"
config["source/addressbook"]["databaseFormat"] = "text/x-vcard"
self.session.SetConfig(False, False,
config,
utf8_strings=True)
config = self.server.GetConfig("Foo", False, utf8_strings=True)
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
self.failUnlessEqual(config["source/addressbook"]["backend"], "file")
config = self.server.GetConfig("@other", False, utf8_strings=True)
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
self.failUnlessEqual(config["source/addressbook"]["databaseFormat"], "text/x-vcard")
self.session.Detach()
# adding second client must preserve type
self.setUpSession("bar@other")
config = self.server.GetConfig("Funambol@other", True, utf8_strings=True)
self.failUnlessEqual(config["source/addressbook"]["type"], "addressbook")
self.failUnlessEqual(config["source/addressbook"]["backend"], "file")
self.session.SetConfig(False, False,
config,
utf8_strings=True)
config = self.server.GetConfig("bar", False, utf8_strings=True)
self.failUnlessEqual(config["source/addressbook"]["type"], "addressbook")
self.failUnlessEqual(config["source/addressbook"]["backend"], "file")
self.failUnlessEqual(config["source/addressbook"].get("syncFormat"), None)
config = self.server.GetConfig("@other", False, utf8_strings=True)
self.failUnlessEqual(config["source/addressbook"]["type"], "file:text/vcard:3.0")
self.failUnlessEqual(config["source/addressbook"]["databaseFormat"], "text/x-vcard")
def testOtherContext(self):
"""write into independent context"""