2 * Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
3 * Copyright (C) 2009 Intel Corporation
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) version 3.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 #include <syncevo/SyncConfig.h>
24 #include <syncevo/SyncSource.h>
25 #include <syncevo/SyncContext.h>
26 #include <syncevo/FileConfigTree.h>
27 #include <syncevo/VolatileConfigTree.h>
28 #include <syncevo/VolatileConfigNode.h>
29 #include <syncevo/DevNullConfigNode.h>
30 #include <syncevo/MultiplexConfigNode.h>
31 #include <syncevo/SingleFileConfigTree.h>
32 #include <syncevo/IniConfigNode.h>
33 #include <syncevo/Cmdline.h>
34 #include <syncevo/lcs.h>
35 #include <syncevo/ThreadSupport.h>
37 #include <synthesis/timeutil.h>
39 #include <boost/foreach.hpp>
48 #include <syncevo/declarations.h>
51 const char *const SourceAdminDataName = "adminData";
53 int ConfigVersions[CONFIG_LEVEL_MAX][CONFIG_VERSION_MAX] =
55 { CONFIG_ROOT_MIN_VERSION, CONFIG_ROOT_CUR_VERSION },
56 { CONFIG_CONTEXT_MIN_VERSION, CONFIG_CONTEXT_CUR_VERSION },
57 { CONFIG_PEER_MIN_VERSION, CONFIG_PEER_CUR_VERSION },
60 std::string ConfigLevel2String(ConfigLevel level)
63 case CONFIG_LEVEL_ROOT:
66 case CONFIG_LEVEL_CONTEXT:
67 return "context config";
69 case CONFIG_LEVEL_PEER:
73 return StringPrintf("config level %d (?)", level);
78 PropertySpecifier PropertySpecifier::StringToPropSpec(const std::string &spec, int flags)
80 PropertySpecifier res;
82 size_t slash = spec.find('/');
83 if (slash != spec.npos) {
84 // no normalization needed at the moment
85 res.m_source = spec.substr(0, slash);
90 size_t at = spec.find('@', slash);
91 if (at != spec.npos) {
93 if (spec.find('@', at + 1) != spec.npos) {
94 // has a second @ sign, must be config name
95 res.m_config = spec.substr(at + 1);
97 // context, include leading @ sign
98 res.m_config = spec.substr(at);
100 if (flags & NORMALIZE_CONFIG) {
101 res.m_config = SyncConfig::normalizeConfigString(res.m_config, SyncConfig::NORMALIZE_LONG_FORMAT);
106 res.m_property = spec.substr(slash, at - slash);
111 std::string PropertySpecifier::toString()
114 res.reserve(m_source.size() + 1 + m_property.size() + 1 + m_config.size());
116 if (!m_source.empty()) {
120 if (!m_config.empty()) {
121 if (m_config[0] != '@') {
130 string ConfigProperty::getName(const ConfigNode &node) const
132 if (m_names.empty()) {
136 if (m_names.size() == 1) {
137 // typical case for most properties
138 return m_names.front();
140 // pick the name already used in the node
141 BOOST_FOREACH(const std::string &name, m_names) {
143 if (node.getProperty(name, value)) {
148 // main name as fallback
149 return m_names.front();
152 void ConfigProperty::splitComment(const string &comment, list<string> &commentLines)
157 size_t end = comment.find('\n', start);
158 if (end == comment.npos) {
159 commentLines.push_back(comment.substr(start));
162 commentLines.push_back(comment.substr(start, end - start));
168 void ConfigProperty::throwValueError(const ConfigNode &node, const string &name, const string &value, const string &error) const
170 SyncContext::throwError(node.getName() + ": " + name + " = " + value + ": " + error);
173 std::string ConfigProperty::sharing2str(Sharing sharing)
179 case SOURCE_SET_SHARING:
189 string SyncConfig::normalizeConfigString(const string &config, NormalizeFlags flags)
191 string normal = config;
192 boost::to_lower(normal);
193 BOOST_FOREACH(char &character, normal) {
194 if (!isprint(character) ||
201 if (boost::ends_with(normal, "@default")) {
202 if (flags & NORMALIZE_SHORTHAND) {
203 normal.resize(normal.size() - strlen("@default"));
205 } else if (boost::ends_with(normal, "@")) {
206 normal.resize(normal.size() - 1);
208 size_t at = normal.rfind('@');
209 if (at == normal.npos &&
210 !(flags & NORMALIZE_IS_NEW)) {
211 // No explicit context. Pick the first server which matches
212 // when ignoring their context. Peer list is sorted by name,
213 // therefore shorter config names (= without context) are
214 // found first, as intended.
215 BOOST_FOREACH(const StringPair &entry, getConfigs()) {
216 string entry_peer, entry_context;
217 splitConfigString(entry.first, entry_peer, entry_context);
218 if (normal == entry_peer) {
219 // found a matching, existing config, use it
220 normal = entry.first;
225 if (!(flags & NORMALIZE_SHORTHAND) && normal.find('@') == normal.npos) {
226 // explicitly include @default context specifier
227 normal += "@default";
231 if (normal.empty()) {
232 // default context is meant with the empty string,
233 // better make that explicit
240 std::string SyncConfig::DeviceDescription::getFingerprint() const
242 std::string fingerprint;
244 /** In the case that we have the PnpInformation we prefer it over
245 * the mutable device name. The is true even if we only found the
246 * vendor component of the PnpInformation.
248 if (m_pnpInformation) {
249 if(m_pnpInformation->isKnownProduct())
250 fingerprint = m_pnpInformation->m_product;
252 fingerprint = m_pnpInformation->m_vendor;
255 fingerprint = m_deviceName;
261 bool SyncConfig::splitConfigString(const string &config, string &peer, string &context)
263 string::size_type at = config.rfind('@');
264 if (at != config.npos) {
265 peer = config.substr(0, at);
266 context = config.substr(at + 1);
275 static SyncConfig::ConfigWriteMode defaultConfigWriteMode()
277 return SyncContext::isStableRelease() ?
278 SyncConfig::MIGRATE_AUTOMATICALLY :
279 SyncConfig::ASK_USER_TO_MIGRATE;
282 SyncConfig::SyncConfig() :
283 m_layout(HTTP_SERVER_LAYOUT), // use more compact layout with shorter paths and less source nodes
284 m_configWriteMode(defaultConfigWriteMode())
286 // initialize properties
287 SyncConfig::getRegistry();
288 SyncSourceConfig::getRegistry();
291 m_contextPath = "volatile";
295 void SyncConfig::makeVolatile()
297 m_tree.reset(new VolatileConfigTree());
298 m_peerNode.reset(new VolatileConfigNode());
299 m_hiddenPeerNode = m_peerNode;
300 m_globalNode = m_peerNode;
301 m_contextNode = m_peerNode;
302 m_contextHiddenNode = m_peerNode;
303 m_props[false] = m_peerNode;
304 m_props[true] = m_peerNode;
307 void SyncConfig::makeEphemeral()
310 // m_hiddenPeerNode.reset(new VolatileConfigNode());
311 // m_contextHiddenNode = m_hiddenPeerNode;
314 SyncConfig::SyncConfig(const string &peer,
315 boost::shared_ptr<ConfigTree> tree,
316 const string &redirectPeerRootPath) :
317 m_layout(SHARED_LAYOUT),
318 m_redirectPeerRootPath(redirectPeerRootPath),
319 m_configWriteMode(defaultConfigWriteMode())
321 // initialize properties
322 SyncConfig::getRegistry();
323 SyncSourceConfig::getRegistry();
327 m_peer = normalizeConfigString(peer);
329 // except for SHARED_LAYOUT (set below),
330 // everything is below the directory called like
336 if (tree.get() != NULL) {
337 // existing tree points into simple configuration
339 m_layout = HTTP_SERVER_LAYOUT;
343 // search for configuration in various places...
345 string path = root + "/" + m_peerPath;
346 if (!access((path + "/spds/syncml/config.txt").c_str(), F_OK)) {
347 m_layout = SYNC4J_LAYOUT;
350 path = root + "/" + m_peerPath;
351 if (!access((path + "/config.ini").c_str(), F_OK) &&
352 !access((path + "/sources").c_str(), F_OK) &&
353 access((path + "/peers").c_str(), F_OK)) {
354 m_layout = HTTP_SERVER_LAYOUT;
356 // check whether config name specifies a context,
357 // otherwise use "default"
358 splitConfigString(m_peer, m_peerPath, m_contextPath);
359 if (!m_peerPath.empty()) {
360 m_peerPath = m_contextPath + "/peers/" + m_peerPath;
364 m_tree.reset(new FileConfigTree(root,
365 m_peerPath.empty() ? m_contextPath : m_peerPath,
370 boost::shared_ptr<ConfigNode> node;
373 // all properties reside in the same node
374 path = m_peerPath + "/spds/syncml";
375 node = m_tree->open(path, ConfigTree::visible);
376 m_peerNode.reset(new FilterConfigNode(node));
378 m_contextNode = m_peerNode;
380 m_contextHiddenNode =
383 m_props[false] = m_peerNode;
384 m_props[true].reset(new FilterConfigNode(m_hiddenPeerNode));
386 case HTTP_SERVER_LAYOUT: {
387 // properties which are normally considered shared are
388 // stored in the same nodes as the per-peer properties,
389 // except for global ones
391 node = m_tree->open(path, ConfigTree::visible);
392 m_globalNode.reset(new FilterConfigNode(node));
393 node = m_tree->open(path, ConfigTree::hidden);
394 m_globalHiddenNode = node;
397 node = m_tree->open(path, ConfigTree::visible);
398 m_peerNode.reset(new FilterConfigNode(node));
399 m_contextNode = m_peerNode;
401 m_contextHiddenNode =
402 m_tree->open(path, ConfigTree::hidden);
404 // similar multiplexing as for SHARED_LAYOUT,
405 // with two nodes underneath
406 boost::shared_ptr<MultiplexConfigNode> mnode;
407 mnode.reset(new MultiplexConfigNode(m_peerNode->getName(),
410 m_props[false] = mnode;
411 mnode->setNode(false, ConfigProperty::GLOBAL_SHARING,
413 mnode->setNode(false, ConfigProperty::SOURCE_SET_SHARING,
415 mnode->setNode(false, ConfigProperty::NO_SHARING,
417 mnode.reset(new MultiplexConfigNode(m_peerNode->getName(),
420 m_props[true] = mnode;
421 mnode->setNode(true, ConfigProperty::GLOBAL_SHARING,
423 mnode->setNode(true, ConfigProperty::SOURCE_SET_SHARING,
425 mnode->setNode(true, ConfigProperty::NO_SHARING,
430 // really use different nodes for everything
432 node = m_tree->open(path, ConfigTree::visible);
433 m_globalNode.reset(new FilterConfigNode(node));
434 node = m_tree->open(path, ConfigTree::hidden);
435 m_globalHiddenNode = node;
439 if (!m_redirectPeerRootPath.empty()) {
440 node.reset(new IniFileConfigNode(m_redirectPeerRootPath,
443 node = m_tree->add(m_redirectPeerRootPath + "/.internal.ini",
446 node.reset(new DevNullConfigNode(m_contextPath + " without peer config"));
449 node = m_tree->open(path, ConfigTree::visible);
451 m_peerNode.reset(new FilterConfigNode(node));
453 m_hiddenPeerNode = m_peerNode;
455 m_hiddenPeerNode = m_tree->open(path, ConfigTree::hidden);
458 path = m_contextPath;
459 node = m_tree->open(path, ConfigTree::visible);
460 m_contextNode.reset(new FilterConfigNode(node));
461 m_contextHiddenNode = m_tree->open(path, ConfigTree::hidden);
463 // Instantiate multiplexer with the most specific node name in
464 // the set, the peer node's name. This is slightly inaccurate:
465 // error messages generated for this node in will reference
466 // the wrong config.ini file for shared properties. But
467 // there no shared properties which can trigger such an error
468 // at the moment, so this is good enough for now (MB#8037).
469 boost::shared_ptr<MultiplexConfigNode> mnode;
470 mnode.reset(new MultiplexConfigNode(m_peerNode->getName(),
473 mnode->setHavePeerNodes(!m_peerPath.empty());
474 m_props[false] = mnode;
475 mnode->setNode(false, ConfigProperty::GLOBAL_SHARING,
477 mnode->setNode(false, ConfigProperty::SOURCE_SET_SHARING,
479 mnode->setNode(false, ConfigProperty::NO_SHARING,
482 mnode.reset(new MultiplexConfigNode(m_hiddenPeerNode->getName(),
485 mnode->setHavePeerNodes(!m_peerPath.empty());
486 m_props[true] = mnode;
487 mnode->setNode(true, ConfigProperty::SOURCE_SET_SHARING,
488 m_contextHiddenNode);
489 mnode->setNode(true, ConfigProperty::NO_SHARING,
491 mnode->setNode(true, ConfigProperty::GLOBAL_SHARING,
496 // read version check
497 for (ConfigLevel level = CONFIG_LEVEL_ROOT;
498 level < CONFIG_LEVEL_MAX;
499 level = (ConfigLevel)(level + 1)) {
501 if (getConfigVersion(level, CONFIG_MIN_VERSION) > ConfigVersions[level][CONFIG_CUR_VERSION]) {
502 SE_LOG_INFO(NULL, "config version check failed: %s has format %d, but this SyncEvolution release only supports format %d",
503 ConfigLevel2String(level).c_str(),
504 getConfigVersion(level, CONFIG_MIN_VERSION),
505 ConfigVersions[level][CONFIG_CUR_VERSION]);
506 // our code is too old to read the config, reject it
507 SE_THROW_EXCEPTION_STATUS(StatusException,
508 StringPrintf("SyncEvolution %s is too old to read configuration '%s', please upgrade SyncEvolution.",
509 VERSION, peer.c_str()),
510 STATUS_RELEASE_TOO_OLD);
515 // Note that the version check does not reject old configs because
516 // they are too old; so far, any release must be able to read any
520 void SyncConfig::prepareConfigForWrite()
522 // check versions before bumping to something incompatible with the
523 // previous user of the config
524 for (ConfigLevel level = CONFIG_LEVEL_ROOT;
525 level < CONFIG_LEVEL_MAX;
526 level = (ConfigLevel)(level + 1)) {
527 if (getLayout() < SHARED_LAYOUT &&
528 level < CONFIG_LEVEL_PEER) {
529 // old configs do not have explicit root or context,
530 // only check peer config itself
534 if (getConfigVersion(level, CONFIG_CUR_VERSION) < ConfigVersions[level][CONFIG_MIN_VERSION]) {
535 // release which created config will no longer be able to read
536 // updated config; either alert user or migrate automatically
539 case CONFIG_LEVEL_CONTEXT:
540 config = getContextName();
542 case CONFIG_LEVEL_PEER:
543 config = getConfigName();
545 case CONFIG_LEVEL_ROOT:
546 case CONFIG_LEVEL_MAX:
547 // keep compiler happy, not reached for _MAX
550 SE_LOG_INFO(NULL, "must change format of %s '%s' in backward-incompatible way",
551 ConfigLevel2String(level).c_str(),
553 if (m_configWriteMode == MIGRATE_AUTOMATICALLY) {
554 // migrate config and anything beneath it,
555 // so no further checking needed
559 SE_THROW_EXCEPTION_STATUS(StatusException,
560 StringPrintf("Proceeding would modify config '%s' such "
561 "that the previous SyncEvolution release "
562 "will not be able to use it. Stopping now. "
563 "Please explicitly acknowledge this step by "
564 "running the following command on the command "
565 "line: syncevolution --migrate '%s'",
568 STATUS_MIGRATION_NEEDED);
574 // now set current versions at all levels,
575 // but without reducing versions: if a config has format
576 // "cur = 10", then properties or features added in that
577 // format remain even if the config is (temporarily?) used
578 // by a SyncEvolution binary which has "cur = 5".
579 for (ConfigLevel level = CONFIG_LEVEL_ROOT;
580 level < CONFIG_LEVEL_MAX;
581 level = (ConfigLevel)(level + 1)) {
582 if (level == CONFIG_LEVEL_PEER &&
583 m_peerPath.empty()) {
584 // no need (and no possibility) to set per-peer version)
587 for (ConfigLimit limit = CONFIG_MIN_VERSION;
588 limit < CONFIG_VERSION_MAX;
589 limit = (ConfigLimit)(limit + 1)) {
590 // set if equal to ensure that version == 0 (the default)
592 if (getConfigVersion(level, limit) <= ConfigVersions[level][limit]) {
593 setConfigVersion(level, limit, ConfigVersions[level][limit]);
600 void SyncConfig::migrate(const std::string &config)
602 if (config.empty()) {
603 // migrating root not yet supported
604 SE_THROW("internal error, migrating config root not implemented");
606 // migrate using the higher-level logic in the Cmdline class
607 Cmdline migrate(m_peer.c_str(),
611 bool res = migrate.parse() && migrate.run();
613 SE_THROW(StringPrintf("migration of config '%s' failed", config.c_str()));
616 // files that our tree access may have changed, refresh our
622 string SyncConfig::getRootPath() const
624 return m_tree->getRootPath();
627 void SyncConfig::addPeers(const string &root,
628 const std::string &configname,
629 SyncConfig::ConfigList &res) {
630 FileConfigTree tree(root, "", SyncConfig::HTTP_SERVER_LAYOUT);
631 list<string> servers = tree.getChildren("");
632 BOOST_FOREACH(const string &server, servers) {
633 // sanity check: only list server directories which actually
634 // contain a configuration. To distinguish between a context
635 // (~/.config/syncevolution/default) and an HTTP server config
636 // (~/.config/syncevolution/scheduleworld), we check for the
637 // "peer" subdirectory that is only in the former.
639 // Contexts which don't have a peer are therefore incorrectly
640 // listed as a peer. Short of adding a special hidden file
641 // this can't be fixed. This is probably overkill and thus not
643 string peerPath = server + "/peers";
644 if (!access((root + "/" + peerPath).c_str(), F_OK)) {
645 // not a real HTTP server, search for peers
646 BOOST_FOREACH(const string &peer, tree.getChildren(peerPath)) {
647 res.push_back(pair<string, string> (normalizeConfigString(peer + "@" + server),
648 root + "/" + peerPath + "/" + peer));
650 } else if (!access((root + "/" + server + "/" + configname).c_str(), F_OK)) {
651 res.push_back(pair<string, string> (server, root + "/" + server));
656 /** returns true if a precedes b (strict weak ordering) */
657 static bool cmpConfigEntries(const StringPair &a, const StringPair &b)
659 string peerA, contextA, peerB, contextB;
660 SyncConfig::splitConfigString(a.first, peerA, contextA);
661 SyncConfig::splitConfigString(b.first, peerB, contextB);
663 res = contextA.compare(contextB);
665 res = peerA.compare(peerB);
667 res = a.second.compare(b.second);
673 SyncConfig::ConfigList SyncConfig::getConfigs()
677 addPeers(getOldRoot(), "config.txt", res);
678 addPeers(getNewRoot(), "config.ini", res);
680 // Sort the list by (context, peer name, path);
681 // better than returning it in random order.
682 // This sort order (compared to simple lexical
683 // sorting based on the full config name) has
684 // the advantage that peer names or contexts with
685 // suffix (foo.old vs. foo) come later.
686 res.sort(cmpConfigEntries);
691 static string SyncEvolutionTemplateDir()
693 string templateDir(TEMPLATE_DIR);
694 const char *envvar = getenv("SYNCEVOLUTION_TEMPLATE_DIR");
696 templateDir = envvar;
701 SyncConfig::TemplateList SyncConfig::matchPeerTemplates(const DeviceList &peers, bool fuzzyMatch)
704 // match against all possible templates without any assumption on directory
705 // layout, the match is entirely based on the metadata template.ini
706 string templateDir(SyncEvolutionTemplateDir());
707 std::queue <std::string, std::list<std::string> > directories;
709 directories.push(templateDir);
710 templateDir = SubstEnvironment("${XDG_CONFIG_HOME}/syncevolution-templates");
711 directories.push(templateDir);
712 while (!directories.empty()) {
713 string sDir = directories.front();
716 // check all sub directories
718 BOOST_FOREACH(const string &entry, dir) {
719 // ignore hidden files, . and ..
720 if (!boost::starts_with(entry, ".")) {
721 directories.push(sDir + "/" + entry);
725 TemplateConfig templateConf (sDir);
726 if (boost::ends_with(sDir, "~") ||
727 !templateConf.isTemplateConfig()) {
728 // ignore temporary files and files which do
729 // not contain a valid template
732 BOOST_FOREACH (const DeviceList::value_type &entry, peers){
733 std::string fingerprint(entry.getFingerprint());
734 // peerName should be empty if no reliable device info is on hand.
735 std::string peerName = entry.m_pnpInformation ? fingerprint : "";
737 int rank = templateConf.metaMatch (entry.getFingerprint(), entry.m_matchMode);
739 if (rank > TemplateConfig::NO_MATCH) {
740 result.push_back (boost::shared_ptr<TemplateDescription>(
741 new TemplateDescription(templateConf.getTemplateId(),
742 templateConf.getDescription(),
748 templateConf.getFingerprint(),
749 templateConf.getTemplateName()
753 } else if (rank == TemplateConfig::BEST_MATCH){
754 result.push_back (boost::shared_ptr<TemplateDescription>(
755 new TemplateDescription(templateConf.getTemplateId(),
756 templateConf.getDescription(),
762 templateConf.getFingerprint(),
763 templateConf.getTemplateName())
771 result.sort (TemplateDescription::compare_op);
776 boost::shared_ptr<SyncConfig> SyncConfig::createPeerTemplate(const string &server)
778 if (server.empty()) {
779 // Empty template name => no such template. This check is
780 // necessary because otherwise we end up with SyncConfig(""),
781 // which is a configuration where peer-specific properties
782 // cannot be set, triggering an errror in config->setDevID().
783 return boost::shared_ptr<SyncConfig>();
786 // case insensitive search for read-only file template config
787 string templateConfig(SyncEvolutionTemplateDir());
789 // before starting another fuzzy match process, first try to load the
790 // template directly taking the parameter as the path
791 if (server == "none") {
792 // nothing to read from, just set some defaults below
793 } else if (TemplateConfig::isTemplateConfig(server)) {
794 templateConfig = server;
796 SyncConfig::DeviceList devices;
797 devices.push_back (DeviceDescription("", server, MATCH_ALL));
799 TemplateList templates = matchPeerTemplates (devices, false);
800 if (!templates.empty()) {
801 templateConfig = templates.front()->m_path;
803 if (templateConfig.empty()) {
804 // return "not found"
805 return boost::shared_ptr<SyncConfig>();
809 boost::shared_ptr<ConfigTree> tree(new SingleFileConfigTree(templateConfig));
810 boost::shared_ptr<SyncConfig> config(new SyncConfig(server, tree));
811 boost::shared_ptr<PersistentSyncSourceConfig> source;
813 config->setDefaults(false);
814 config->setDevID(string("syncevolution-") + UUID());
816 // leave the rest empty for special "none" template
817 if (server == "none") {
822 if (config->getIconURI().empty()) {
823 string dirname, filename;
824 splitPath(templateConfig, dirname, filename);
825 ReadDir dir(getDirname(dirname));
827 // remove last suffix, regardless what it is
828 size_t pos = filename.rfind('.');
829 if (pos != filename.npos) {
830 filename.resize(pos);
834 BOOST_FOREACH(const string &entry, dir) {
835 if (boost::istarts_with(entry, filename)) {
836 config->setIconURI("file://" + dirname + "/" + entry);
842 // "default" maps to SyncEvolution server template, which is not
843 // consumer ready. When used as "default" by the GTK sync UI,
844 // the UI expects the "consumer ready" flag to be set. Do that
845 // here. Also unset the peer name, because otherwise it shows
847 if (server == "default") {
848 config->setConsumerReady(true);
849 config->setUserPeerName(InitStateString());
855 bool SyncConfig::exists() const
857 return m_peerPath.empty() ?
858 m_contextNode->exists() :
859 m_peerNode->exists();
862 bool SyncConfig::exists(ConfigLevel level) const
865 case CONFIG_LEVEL_ROOT:
866 return m_globalNode->exists();
868 case CONFIG_LEVEL_CONTEXT:
869 return m_contextNode->exists();
871 case CONFIG_LEVEL_PEER:
872 return m_peerNode->exists();
879 string SyncConfig::getContextName() const
881 string peer, context;
882 splitConfigString(getConfigName(), peer, context);
883 return string("@") + context;
886 string SyncConfig::getPeerName() const
888 string peer, context;
889 splitConfigString(getConfigName(), peer, context);
893 list<string> SyncConfig::getPeers() const
897 if (!hasPeerProperties()) {
898 FileConfigTree tree(getRootPath(), "", SHARED_LAYOUT);
899 res = tree.getChildren("peers");
905 void SyncConfig::preFlush(UserInterface &ui)
907 /* Iterator over all sync global and source properties
908 * one by one and check whether they need to save password */
910 /* save password in the global config node */
911 ConfigPropertyRegistry& registry = getRegistry();
912 BOOST_FOREACH(const ConfigProperty *prop, registry) {
913 prop->savePassword(ui, m_peer, *getProperties());
916 /** grep each source and save their password */
917 list<string> configuredSources = getSyncSources();
918 BOOST_FOREACH(const string &sourceName, configuredSources) {
919 //boost::shared_ptr<SyncSourceConfig> sc = getSyncSourceConfig(sourceName);
920 ConfigPropertyRegistry& registry = SyncSourceConfig::getRegistry();
921 SyncSourceNodes sourceNodes = getSyncSourceNodes(sourceName);
923 BOOST_FOREACH(const ConfigProperty *prop, registry) {
924 prop->savePassword(ui, m_peer, *getProperties(),
925 sourceName, sourceNodes.getProperties());
930 void SyncConfig::flush()
932 if (!isEphemeral()) {
937 void SyncConfig::remove()
939 boost::shared_ptr<ConfigTree> tree = m_tree;
941 // stop using the config nodes, they might get removed now
944 tree->remove(m_peerPath.empty() ?
949 boost::shared_ptr<PersistentSyncSourceConfig> SyncConfig::getSyncSourceConfig(const string &name)
951 SyncSourceNodes nodes = getSyncSourceNodes(name);
952 return boost::shared_ptr<PersistentSyncSourceConfig>(new PersistentSyncSourceConfig(name, nodes));
955 list<string> SyncConfig::getSyncSources() const
957 // Return *all* sources configured in this context,
958 // not just those configured for the peer. This
959 // is necessary so that sources created for some other peer
960 // show up for the current one, to prevent overwriting
961 // existing properties unintentionally.
962 // Returned sources are an union of:
963 // 1. contextpath/sources
964 // 2. peers/[one-peer]/sources
965 // 3. sources in source filter
967 list<string> sourceList;
968 if (m_layout == SHARED_LAYOUT) {
969 // get sources in context
970 sourceList = m_tree->getChildren(m_contextPath + "/sources");
971 sources.insert(sourceList.begin(), sourceList.end());
972 // get sources from peer if it's not empty and merge into
973 // full set of sources
974 if (!m_peerPath.empty()) {
975 sourceList = m_tree->getChildren(m_peerPath + "/sources");
976 sources.insert(sourceList.begin(), sourceList.end());
979 // get sources from peer
980 sourceList = m_tree->getChildren(m_peerPath +
981 (m_layout == SYNC4J_LAYOUT ?
984 sources.insert(sourceList.begin(), sourceList.end());
986 // get sources from filter and union them into returned sources
987 BOOST_FOREACH(const SourceProps::value_type &value, m_sourceFilters) {
988 if (value.first.empty()) {
989 // ignore filter for all sources
992 sources.insert(value.first);
995 // Convert back to simple list. As a nice side-effect of
996 // temporarily using a set, the final list is sorted.
997 return list<string>(sources.begin(), sources.end());
1000 SyncSourceNodes SyncConfig::getSyncSourceNodes(const string &name,
1001 const string &changeId)
1003 if (m_nodeCache.find(name) != m_nodeCache.end()) {
1004 // reuse existing set of nodes
1005 return m_nodeCache[name];
1008 /** shared source properties */
1009 boost::shared_ptr<FilterConfigNode> sharedNode;
1010 /** per-peer source properties */
1011 boost::shared_ptr<FilterConfigNode> peerNode;
1012 /** per-peer internal properties and meta data */
1013 boost::shared_ptr<ConfigNode> hiddenPeerNode,
1018 // store configs lower case even if the UI uses mixed case
1019 string lower = name;
1020 boost::to_lower(lower);
1022 boost::shared_ptr<ConfigNode> node;
1023 string sharedPath, peerPath;
1026 peerPath = m_peerPath + "/spds/sources/" + lower;
1028 case HTTP_SERVER_LAYOUT:
1029 peerPath = m_peerPath + "/sources/" + lower;
1032 if (!m_peerPath.empty()) {
1033 peerPath = m_peerPath + "/sources/" + lower;
1035 sharedPath = m_contextPath + string("/sources/") + lower;
1039 // Compatibility mode for reading configs which have "type" instead
1040 // of "backend/databaseFormat/syncFormat/forceSyncFormat": determine
1041 // the new values based on the old property, then inject the new
1042 // values into the SyncSourceNodes by adding an intermediate layer
1043 // of FilterConfigNodes. The top FilterConfigNode layer is the one
1044 // which might get modified, the one underneath remains hidden and
1045 // thus preserves the new values even if the caller does a setFilter().
1046 bool compatMode = getConfigVersion(CONFIG_LEVEL_CONTEXT, CONFIG_CUR_VERSION) < 1;
1047 SourceType sourceType;
1049 node = m_tree->open(peerPath.empty() ? sharedPath : peerPath, ConfigTree::visible);
1051 if (node->getProperty("type", type)) {
1052 sourceType = SourceType(type);
1054 // not set: avoid compatibility mode
1059 if (peerPath.empty()) {
1060 node.reset(new DevNullConfigNode(m_contextPath + " without peer configuration"));
1061 peerNode.reset(new FilterConfigNode(node));
1066 // Here we assume that m_tree is a FileConfigTree. Otherwise getRootPath()
1067 // will not point into a normal file system.
1068 cacheDir = m_tree->getRootPath() + "/" + peerPath + "/.cache";
1070 node = m_tree->open(peerPath, ConfigTree::visible);
1072 boost::shared_ptr<FilterConfigNode> compat(new FilterConfigNode(node));
1073 compat->addFilter("syncFormat",
1074 InitStateString(sourceType.m_format, !sourceType.m_format.empty()));
1075 compat->addFilter("forceSyncFormat",
1076 sourceType.m_forceFormat ?
1077 InitStateString("1", true) :
1078 InitStateString("0", false));
1079 if (sharedPath.empty()) {
1080 compat->addFilter("databaseFormat",
1081 InitStateString(sourceType.m_localFormat, !sourceType.m_localFormat.empty()));
1082 compat->addFilter("backend",
1083 InitStateString(sourceType.m_backend, !sourceType.m_backend.empty()));
1087 peerNode.reset(new FilterConfigNode(node, m_sourceFilters.createSourceFilter(name)));
1088 hiddenPeerNode = m_tree->open(peerPath, ConfigTree::hidden);
1089 trackingNode = m_tree->open(peerPath, ConfigTree::other, changeId);
1090 serverNode = m_tree->open(peerPath, ConfigTree::server, changeId);
1093 if (isEphemeral()) {
1094 // Throw away meta data.
1095 trackingNode.reset(new VolatileConfigNode);
1096 hiddenPeerNode.reset(new VolatileConfigNode);
1097 serverNode.reset(new VolatileConfigNode);
1098 } else if (!m_redirectPeerRootPath.empty()) {
1099 // Local sync: overwrite per-peer nodes with nodes inside the
1100 // parents tree. Otherwise different configs syncing locally
1101 // against the same context end up sharing .internal.ini and
1102 // .other.ini files inside that context.
1103 string path = m_redirectPeerRootPath + "/sources/" + lower;
1104 trackingNode.reset(new IniHashConfigNode(path,
1107 trackingNode = m_tree->add(path + "/.other.ini", trackingNode);
1108 boost::shared_ptr<ConfigNode> node(new IniHashConfigNode(path,
1111 hiddenPeerNode.reset(new FilterConfigNode(node));
1112 hiddenPeerNode = boost::static_pointer_cast<FilterConfigNode>(m_tree->add(path + "/.internal.ini", peerNode));
1113 if (peerPath.empty()) {
1114 hiddenPeerNode = peerNode;
1118 if (sharedPath.empty()) {
1119 sharedNode = peerNode;
1121 node = m_tree->open(sharedPath, ConfigTree::visible);
1123 boost::shared_ptr<FilterConfigNode> compat(new FilterConfigNode(node));
1124 compat->addFilter("databaseFormat",
1125 InitStateString(sourceType.m_localFormat, !sourceType.m_localFormat.empty()));
1126 compat->addFilter("backend",
1127 InitStateString(sourceType.m_backend, !sourceType.m_backend.empty()));
1130 sharedNode.reset(new FilterConfigNode(node, m_sourceFilters.createSourceFilter(name)));
1133 SyncSourceNodes nodes(!peerPath.empty(), sharedNode, peerNode, hiddenPeerNode, trackingNode, serverNode, cacheDir);
1134 m_nodeCache.insert(make_pair(name, nodes));
1138 ConstSyncSourceNodes SyncConfig::getSyncSourceNodes(const string &name,
1139 const string &changeId) const
1141 return const_cast<SyncConfig *>(this)->getSyncSourceNodes(name, changeId);
1144 SyncSourceNodes SyncConfig::getSyncSourceNodesNoTracking(const string &name)
1146 SyncSourceNodes nodes = getSyncSourceNodes(name);
1147 boost::shared_ptr<ConfigNode> dummy(new VolatileConfigNode());
1148 return SyncSourceNodes(nodes.m_havePeerNode,
1151 nodes.m_hiddenPeerNode,
1157 static ConfigProperty syncPropSyncURL("syncURL",
1158 "Identifies how to contact the peer,\n"
1159 "best explained with some examples.\n\n"
1160 "HTTP(S) SyncML servers::\n\n"
1161 " http://my.funambol.com/sync\n"
1162 " http://sync.scheduleworld.com/funambol/ds\n"
1163 " https://m.google.com/syncml\n\n"
1164 "OBEX over Bluetooth uses the MAC address, with\n"
1165 "the channel chosen automatically::\n\n"
1166 " obex-bt://00:0A:94:03:F3:7E\n\n"
1167 "If the automatism fails, the channel can also be specified::\n\n"
1168 " obex-bt://00:0A:94:03:F3:7E+16\n\n"
1169 "For peers contacting us via Bluetooth, the MAC address is\n"
1170 "used to identify it before the sync starts. Multiple\n"
1171 "urls can be specified in one syncURL property::\n\n"
1172 " obex-bt://00:0A:94:03:F3:7E obex-bt://00:01:02:03:04:05\n\n"
1173 "In the future this might be used to contact the peer\n"
1174 "via one of several transports; right now, only the first\n"
1175 "one is tried." // MB #9446
1178 static ConfigProperty syncPropDevID("deviceId",
1179 "The SyncML server gets this string and will use it to keep track of\n"
1180 "changes that still need to be synchronized with this particular\n"
1181 "client; it must be set to something unique (like the pseudo-random\n"
1182 "string created automatically for new configurations) among all clients\n"
1183 "accessing the same server.\n"
1184 "myFUNAMBOL also requires that the string starts with sc-pim-");
1185 static ConfigProperty syncPropUsername("username",
1186 "user name used for authorization with the SyncML server",
1188 static PasswordConfigProperty syncPropPassword("password",
1189 "password used for authorization with the peer;\n"
1190 "in addition to specifying it directly as plain text, it can\n"
1191 "also be read from the standard input or from an environment\n"
1192 "variable of your choice::\n\n"
1193 " plain text : password = <insert your password here>\n"
1194 " ask : password = -\n"
1195 " env variable: password = ${<name of environment variable>}\n");
1196 static BoolConfigProperty syncPropPreventSlowSync("preventSlowSync",
1197 "During a slow sync, the SyncML server must match all items\n"
1198 "of the client with its own items and detect which ones it\n"
1199 "already has based on properties of the items. This is slow\n"
1200 "(client must send all its data) and can lead to duplicates\n"
1201 "(when the server fails to match correctly).\n"
1202 "It is therefore sometimes desirable to wipe out data on one\n"
1203 "side with a refresh-from-client/server sync instead of doing\n"
1205 "When this option is enabled, slow syncs that could cause problems\n"
1206 "are not allowed to proceed. Instead, the affected sources are\n"
1207 "skipped, allowing the user to choose a suitable sync mode in\n"
1208 "the next run (slow sync selected explicitly, refresh sync).\n"
1209 "The following situations are handled:\n\n"
1210 "- running as client with no local data => unproblematic,\n"
1211 " slow sync is allowed to proceed automatically\n"
1212 "- running as client with local data => client has no\n"
1213 " information about server, so slow sync might be problematic\n"
1214 " and is prevented\n"
1215 "- client has data, server asks for slow sync because all its data\n"
1216 " was deleted (done by Memotoo and Mobical, because they treat\n"
1217 " this as 'user wants to start from scratch') => the sync would\n"
1218 " recreate all the client's data, even if the user really wanted\n"
1219 " to have it deleted, therefore slow sync is prevented\n",
1221 static BoolConfigProperty syncPropUseProxy("useProxy",
1222 "set to T to choose an HTTP proxy explicitly; otherwise the default\n"
1223 "proxy settings of the underlying HTTP transport mechanism are used;\n"
1224 "only relevant when contacting the peer via HTTP",
1226 static ConfigProperty syncPropProxyHost("proxyHost",
1227 "proxy URL (``http://<host>:<port>``)");
1228 static ConfigProperty syncPropProxyUsername("proxyUsername",
1229 "authentication for proxy: username");
1230 static ProxyPasswordConfigProperty syncPropProxyPassword("proxyPassword",
1231 "proxy password, can be specified in different ways,\n"
1232 "see SyncML server password for details\n",
1235 static StringConfigProperty syncPropClientAuthType("clientAuthType",
1236 "- empty or \"md5\" for secure method (recommended)\n"
1237 "- \"basic\" for insecure method\n"
1239 "This setting is only for debugging purpose and only\n"
1240 "has an effect during the initial sync of a client.\n"
1241 "Later it remembers the method that was supported by\n"
1242 "the server and uses that. When acting as server,\n"
1243 "clients contacting us can use both basic and md5\n"
1244 "authentication.\n",
1248 (Aliases("basic") + "syncml:auth-basic") +
1249 (Aliases("md5") + "syncml:auth-md5" + ""));
1250 static ULongConfigProperty syncPropMaxMsgSize("maxMsgSize",
1251 "The maximum size of each message can be set (maxMsgSize) and the\n"
1252 "peer can be told to never sent items larger than a certain\n"
1253 "threshold (maxObjSize). Presumably the peer has to truncate or\n"
1254 "skip larger items. Sizes are specified as number of bytes.",
1256 static UIntConfigProperty syncPropMaxObjSize("maxObjSize", "", "4000000");
1258 static BoolConfigProperty syncPropWBXML("enableWBXML",
1259 "use the more compact binary XML (WBXML) for messages between client and server;\n"
1260 "not applicable when the peer is a SyncML client, because then the client\n"
1261 "chooses the encoding",
1263 static BoolConfigProperty syncPropRefreshSync("enableRefreshSync",
1264 "Use the more advanced refresh-from-server sync mode to\n"
1265 "implement the refresh-from-remote operation. Some SyncML\n"
1266 "servers do not support this. Therefore the default is to\n"
1267 "delete local data before doing a slow sync, which has the\n"
1268 "same effect. However, some servers work better when they\n"
1269 "are told explicitly that the sync is a refresh sync. For\n"
1270 "example, Funambol's One Media server rejects too many slow\n"
1271 "syncs in a row with a 417 'retry later' error.\n",
1273 static ConfigProperty syncPropLogDir("logdir",
1274 "full path to directory where automatic backups and logs\n"
1275 "are stored for all synchronizations; if unset, then\n"
1276 "\"${XDG_CACHE_HOME}/syncevolution/<server>\" (which\n"
1277 "usually expands to ${HOME}/.cache/...) will be used;\n"
1278 "if \"none\", then no backups of the databases are made and any\n"
1279 "output is printed directly to the screen");
1280 static UIntConfigProperty syncPropMaxLogDirs("maxlogdirs",
1281 "Controls how many session directories are kept at most in the logdir.\n"
1282 "Unless set to zero, SyncEvolution will remove old directories and\n"
1283 "all their content to prevent the number of log directories from\n"
1284 "growing beyond the given limit. It tries to be intelligent and will\n"
1285 "remove sessions in which nothing interesting happened (no errors,\n"
1286 "no data changes) in favor of keeping sessions where something\n"
1287 "happened, even if those sessions are older.",
1289 static UIntConfigProperty syncPropLogLevel("loglevel",
1290 "level of detail for log messages:\n"
1291 "- 0 (or unset) = INFO messages without log file, DEBUG with log file\n"
1292 "- 1 = only ERROR messages\n"
1293 "- 2 = also INFO messages\n"
1294 "- 3 = also DEBUG messages\n"
1295 "> 3 = increasing amounts of debug messages for developers");
1296 static UIntConfigProperty syncPropNotifyLevel("notifyLevel",
1297 "Level of detail for desktop notifications. Currently such\n"
1298 "notifications are generated only for automatically started\n"
1301 "0 - suppress all notifications\n"
1302 "1 - show only errors\n"
1303 "2 - show information about changes and errors (in practice currently the same as level 3)\n"
1304 "3 - show all notifications, including starting a sync\n",
1306 static BoolConfigProperty syncPropPrintChanges("printChanges",
1307 "enables or disables the detailed (and sometimes slow) comparison\n"
1308 "of database content before and after a sync session",
1310 static BoolConfigProperty syncPropDumpData("dumpData",
1311 "enables or disables the automatic backup of database content\n"
1312 "before and after a sync session (always enabled if printChanges is enabled)",
1314 static SecondsConfigProperty syncPropRetryDuration("RetryDuration",
1315 "The total amount of time in seconds in which the SyncML\n"
1316 "client tries to get a response from the server.\n"
1317 "During this time, the client will resend messages\n"
1318 "in regular intervals (RetryInterval) if no response\n"
1319 "is received or the message could not be delivered due\n"
1320 "to transport problems. When this time is exceeded\n"
1321 "without a response, the synchronization aborts without\n"
1322 "sending further messages to the server.\n"
1324 "When acting as server, this setting controls how long\n"
1325 "a client is allowed to not send a message before the\n"
1326 "synchronization is aborted."
1328 static SecondsConfigProperty syncPropRetryInterval("RetryInterval",
1329 "The number of seconds between the start of SyncML message sending\n"
1330 "and the start of the retransmission. If the interval has\n"
1331 "already passed when a message send returns, the\n"
1332 "message is resent immediately. Resending without\n"
1333 "any delay will never succeed and therefore specifying 0\n"
1334 "disables retries.\n"
1336 "Servers cannot resend messages, so this setting has no\n"
1337 "effect in that case.\n"
1339 "The WebDAV backend also resends messages after a temporary\n"
1340 "network error. It uses exponential backoff to determine when\n"
1341 "the server is available again. This setting is divided by 24\n"
1342 "to obtain the initial delay (default: 2m => 5s), which is then\n"
1343 "doubled for each retry."
1345 static BoolConfigProperty syncPropPeerIsClient("PeerIsClient",
1346 "Indicates whether this configuration is about a\n"
1347 "client peer or server peer.\n",
1349 static SafeConfigProperty syncPropPeerName("PeerName",
1350 "An arbitrary name for the peer referenced by this config.\n"
1351 "Might be used by a GUI. The command line tool always uses the\n"
1352 "the configuration name.");
1353 static ConfigProperty syncPropSyncMLVersion("SyncMLVersion",
1354 "On a client, the latest commonly supported SyncML version\n"
1355 "is used when contacting a server. One of '1.0/1.1/1.2' can\n"
1356 "be used to pick a specific version explicitly.\n"
1358 "On a server, this option controls what kind of Server Alerted\n"
1359 "Notification is sent to the client to start a synchronization.\n"
1360 "By default, first the format from 1.2 is tried, then in case\n"
1361 "of failure, the older one from 1.1. 1.2/1.1 can be set\n"
1362 "explicitly, which disables the automatism.\n"
1364 "Instead or in adddition to the version, several keywords can\n"
1365 "be set in this property (separated by spaces or commas):\n"
1367 "- NOCTCAP - avoid sending CtCap meta information\n"
1368 "- NORESTART - disable the sync mode extension that SyncEvolution\n"
1369 " client and server use to negotiate whether both sides support\n"
1370 " running multiple sync iterations in the same session\n"
1371 "- REQUESTMAXTIME=<time> - override the rate at which the\n"
1372 " SyncML server sends preliminary replies while preparing\n"
1373 " local storages in the background. This helps to avoid timeouts\n"
1374 " in the SyncML client. Depends on multithreading.\n"
1375 #ifdef HAVE_THREAD_SUPPORT
1376 // test-dbus.py checks for 'is thread-safe'!
1377 " This SyncEvolution binary is thread-safe and thus this feature\n"
1378 " is enabled by default for HTTP servers, with a delay of 2 minutes\n"
1379 " between messages. Other servers (Bluetooth, local sync) should not\n"
1380 " need preliminary replies and the feature is disabled, although\n"
1381 " it can be enabled by setting the time explicitly.\n"
1383 " This SyncEvolution binary is not thread-safe and thus this feature\n"
1384 " is disabled by default, although it can be enabled if absolutely\n"
1385 " needed by setting the time explicitly.\n"
1387 " <time> can be specified like other durations in the config,\n"
1388 " for example as REQUESTMAXTIME=2m.\n"
1390 "Setting these flags should only be necessary as workaround for\n"
1394 static ConfigProperty syncPropRemoteIdentifier("remoteIdentifier",
1395 "the identifier sent to the remote peer for a server initiated sync.\n"
1396 "if not set, deviceId will be used instead\n",
1398 static ConfigProperty syncPropSSLServerCertificates("SSLServerCertificates",
1399 "A string specifying the location of the certificates\n"
1400 "used to authenticate the server. When empty, the\n"
1401 "system's default location will be searched.\n"
1403 "SSL support when acting as HTTP server is implemented\n"
1404 "by the HTTP server frontend, not with these properties.",
1405 SYNCEVOLUTION_SSL_SERVER_CERTIFICATES);
1406 static BoolConfigProperty syncPropSSLVerifyServer("SSLVerifyServer",
1407 "The client refuses to establish the connection unless\n"
1408 "the server presents a valid certificate. Disabling this\n"
1409 "option considerably reduces the security of SSL\n"
1410 "(man-in-the-middle attacks become possible) and is not\n"
1413 static BoolConfigProperty syncPropSSLVerifyHost("SSLVerifyHost",
1414 "The client refuses to establish the connection unless the\n"
1415 "server's certificate matches its host name. In cases where\n"
1416 "the certificate still seems to be valid it might make sense\n"
1417 "to disable this option and allow such connections.\n",
1420 static ConfigProperty syncPropWebURL("WebURL",
1421 "The URL of a web page with further information about the server.\n"
1422 "Used only by the GUI."
1425 static ConfigProperty syncPropIconURI("IconURI",
1426 "The URI of an icon representing the server graphically.\n"
1427 "Should be a 48x48 pixmap or a SVG (preferred).\n"
1428 "Used only by the GUI.");
1430 static BoolConfigProperty syncPropConsumerReady("ConsumerReady",
1431 "Set to true in a configuration template to indicate\n"
1432 "that the server works well enough and is available\n"
1433 "for normal users. Used by the GUI to limit the choice\n"
1434 "of configurations offered to users.\n"
1435 "Has no effect in a user's server configuration.\n",
1439 * Some guidelines for peerType = WebDAV:
1440 * - Such templates may only be used to create the 'target-config@<target>.
1441 * configurations. Typically <target> can be the same as the template's
1443 * - Because determining the default database in WebDAV can be difficult,
1444 * the GUI should allow the user to choose and set the "uri"
1445 * properties accordingly.
1446 * - A GUI should also create a <target> configuration for synchronizing
1447 * against the WebDAV backends.
1449 static ConfigProperty syncPropPeerType("peerType",
1450 "Defines what a configuration is meant to be used for.\n"
1451 "Used in templates and the resulting configs to tell a GUI\n"
1452 "that special handling may be necessary. GUIs should ignore\n"
1454 "The traditional SyncML configs use an empty value.\n"
1455 "\"WebDAV\" is used for the WebDAV side in a local synchronization.\n");
1457 static ULongConfigProperty syncPropHashCode("HashCode", "used by the SyncML library internally; do not modify");
1459 static ConfigProperty syncPropConfigDate("ConfigDate", "used by the SyncML library internally; do not modify");
1461 static SafeConfigProperty syncPropRemoteDevID("remoteDeviceId",
1462 "SyncML ID of our peer, empty if unknown; must be set only when\n"
1463 "the peer is a SyncML client contacting us via HTTP.\n"
1464 "Clients contacting us via OBEX/Bluetooth can be identified\n"
1465 "either via this remoteDeviceId property or by their MAC\n"
1466 "address, if that was set in the syncURL property.\n"
1468 "If this property is empty and the peer synchronizes with\n"
1469 "this configuration chosen by some other means, then its ID\n"
1470 "is recorded here automatically and later used to verify that\n"
1471 "the configuration is not accidentally used by a different\n"
1474 static SafeConfigProperty syncPropNonce("lastNonce",
1475 "MD5 nonce of our peer, empty if not set yet; do not edit, used internally");
1477 // used both as source and sync property, internal in both cases
1478 static SafeConfigProperty syncPropDeviceData("deviceData",
1479 "information about the peer in the format described in the\n"
1480 "Synthesis SDK manual under 'Session_SaveDeviceInfo'");
1482 static SafeConfigProperty globalPropDefaultPeer("defaultPeer",
1483 "the peer which is used by default in some frontends, like the sync-UI");
1485 static ConfigProperty globalPropKeyring("keyring",
1486 "Explicitly selects a certain safe password storage.\n"
1487 "Depending on how SyncEvolution was compiled and installed\n"
1488 "the following values are possible:\n"
1490 "GNOME\n GNOME Keyring\n"
1492 "yes/true/1\n pick one automatically\n"
1493 "no/false/0\n store passwords in SyncEvolution config files\n"
1495 "If unset, the default is to pick one automatically in\n"
1496 "the D-Bus server and not use any keyring in the command\n"
1497 "tool when running without that D-Bus server (because the\n"
1498 "keyring might not be usable without a desktop session).\n"
1499 "If support for only storage was compiled and installed,\n"
1500 "then that is the one which gets picked. Otherwise the\n"
1501 "default is to use GNOME Keyring (because distinguishing\n"
1502 "between KDE and GNOME sessions automatically is tricky).\n"
1504 "Note that using this option applies to *all* passwords in\n"
1505 "a configuration and that the --keyring command line option\n"
1506 "is merely an alias for setting the global property, so setting\n"
1507 "a single password as follows sets both `keyring` and\n"
1508 "`proxyPasswords`, and also moves the other passwords into the\n"
1509 "keyring, even if they were not stored there already:\n"
1511 " --keyring --configure proxyPassword=foo\n"
1513 "When passwords were stored in the keyring, their value is set to a single\n"
1514 "hyphen (\"-\") in the configuration. This means that when running a\n"
1515 "synchronization without the --keyring argument, the password has to be\n"
1516 "entered interactively. The --print-config output always shows \"-\" instead\n"
1517 "of retrieving the password from the keyring.\n",
1520 static StringConfigProperty syncPropAutoSync("autoSync",
1521 "Controls automatic synchronization. Currently,\n"
1522 "automatic synchronization is done by running\n"
1523 "a synchronization at regular intervals. This\n"
1524 "may drain the battery, in particular when\n"
1525 "using Bluetooth!\n"
1526 "Because a peer might be reachable via different\n"
1527 "transports at some point, this option provides\n"
1528 "detailed control over which transports may\n"
1529 "be used for automatic synchronization:\n\n"
1530 "0\n don't do auto sync\n"
1531 "1\n do automatic sync, using whatever transport\n"
1533 "http\n only via HTTP transport\n"
1534 "obex-bt\n only via Bluetooth transport\n"
1535 "http,obex-bt\n pick one of these\n",
1538 static SecondsConfigProperty syncPropAutoSyncInterval("autoSyncInterval",
1539 "This is the minimum number of seconds between two\n"
1540 "synchronizations that has to pass before starting\n"
1541 "an automatic synchronization. Can be specified using\n"
1542 "a 1h30m5s format.\n"
1544 "Before reducing this interval, consider that it will\n"
1545 "increase resource consumption on the local and remote\n"
1546 "side. Some SyncML server operators only allow a\n"
1547 "certain number of sessions per day.\n"
1548 "The value 0 has the effect of only running automatic\n"
1549 "synchronization when changes are detected (not\n"
1550 "implemented yet, therefore it basically disables\n"
1551 "automatic synchronization).\n",
1554 static SecondsConfigProperty syncPropAutoSyncDelay("autoSyncDelay",
1555 "An automatic sync will not be started unless the peer\n"
1556 "has been available for this duration, specified in seconds\n"
1557 "or 1h30m5s format.\n"
1559 "This prevents running a sync when network connectivity\n"
1560 "is unreliable or was recently established for some\n"
1561 "other purpose. It is also a heuristic that attempts\n"
1562 "to predict how long connectivity be available in the\n"
1563 "future, because it should better be available long\n"
1564 "enough to complete the synchronization.\n",
1567 /* config and on-disk file versionsing */
1568 static IntConfigProperty propRootMinVersion("rootMinVersion", "");
1569 static IntConfigProperty propRootCurVersion("rootCurVersion", "");
1570 static IntConfigProperty propContextMinVersion("contextMinVersion", "");
1571 static IntConfigProperty propContextCurVersion("contextCurVersion", "");
1572 static IntConfigProperty propPeerMinVersion("peerMinVersion", "");
1573 static IntConfigProperty propPeerCurVersion("peerCurVersion", "");
1575 static const IntConfigProperty *configVersioning[CONFIG_LEVEL_MAX][CONFIG_VERSION_MAX] = {
1576 { &propRootMinVersion, &propRootCurVersion },
1577 { &propContextMinVersion, &propContextCurVersion },
1578 { &propPeerMinVersion, &propPeerCurVersion }
1581 static const IntConfigProperty &getConfigVersionProp(ConfigLevel level, ConfigLimit limit)
1583 if (level < 0 || level >= CONFIG_LEVEL_MAX ||
1584 limit < 0 || limit >= CONFIG_VERSION_MAX) {
1585 SE_THROW("getConfigVersionProp: invalid args");
1587 return *configVersioning[level][limit];
1590 int SyncConfig::getConfigVersion(ConfigLevel level, ConfigLimit limit) const
1592 const IntConfigProperty &prop = getConfigVersionProp(level, limit);
1593 return prop.getPropertyValue(*getNode(prop));
1596 void SyncConfig::setConfigVersion(ConfigLevel level, ConfigLimit limit, int version)
1598 if (m_layout != SHARED_LAYOUT) {
1599 // old-style layouts have version 0 by default, no need
1600 // (and sometimes no possibility) to set this explicitly
1602 SE_THROW(StringPrintf("cannot bump config version in old-style config %s", m_peer.c_str()));
1605 const IntConfigProperty &prop = getConfigVersionProp(level, limit);
1606 prop.setProperty(*getNode(prop), version);
1613 * This constructor updates some of the properties above and then adds
1614 * them to the registry. This cannot be done inside getRegistry()
1615 * itself because that function may be invoked by other global
1616 * instances before the properties above were constructed (BMC
1619 static class RegisterSyncConfigProperties
1622 RegisterSyncConfigProperties()
1624 ConfigPropertyRegistry ®istry = SyncConfig::getRegistry();
1626 // temporarily move existing properties away so that the important
1627 // standard properties come first when using the traditional
1628 // push_back() way of adding them
1629 ConfigPropertyRegistry tmp;
1630 std::swap(registry, tmp);
1632 registry.push_back(&syncPropSyncURL);
1633 registry.push_back(&syncPropUsername);
1634 registry.push_back(&syncPropPassword);
1635 registry.push_back(&syncPropLogDir);
1636 registry.push_back(&syncPropLogLevel);
1637 registry.push_back(&syncPropNotifyLevel);
1638 registry.push_back(&syncPropPrintChanges);
1639 registry.push_back(&syncPropDumpData);
1640 registry.push_back(&syncPropMaxLogDirs);
1641 registry.push_back(&syncPropAutoSync);
1642 registry.push_back(&syncPropAutoSyncInterval);
1643 registry.push_back(&syncPropAutoSyncDelay);
1644 registry.push_back(&syncPropPreventSlowSync);
1645 registry.push_back(&syncPropUseProxy);
1646 registry.push_back(&syncPropProxyHost);
1647 registry.push_back(&syncPropProxyUsername);
1648 registry.push_back(&syncPropProxyPassword);
1649 registry.push_back(&syncPropClientAuthType);
1650 registry.push_back(&syncPropRetryDuration);
1651 registry.push_back(&syncPropRetryInterval);
1652 registry.push_back(&syncPropRemoteIdentifier);
1653 registry.push_back(&syncPropPeerIsClient);
1654 registry.push_back(&syncPropSyncMLVersion);
1655 registry.push_back(&syncPropPeerName);
1656 registry.push_back(&syncPropDevID);
1657 registry.push_back(&syncPropRemoteDevID);
1658 registry.push_back(&syncPropWBXML);
1659 registry.push_back(&syncPropRefreshSync);
1660 registry.push_back(&syncPropMaxMsgSize);
1661 registry.push_back(&syncPropMaxObjSize);
1662 registry.push_back(&syncPropSSLServerCertificates);
1663 registry.push_back(&syncPropSSLVerifyServer);
1664 registry.push_back(&syncPropSSLVerifyHost);
1665 registry.push_back(&syncPropWebURL);
1666 registry.push_back(&syncPropIconURI);
1667 registry.push_back(&syncPropConsumerReady);
1668 registry.push_back(&syncPropPeerType);
1669 registry.push_back(&syncPropHashCode);
1670 registry.push_back(&syncPropConfigDate);
1671 registry.push_back(&syncPropNonce);
1672 registry.push_back(&syncPropDeviceData);
1673 registry.push_back(&globalPropDefaultPeer);
1674 registry.push_back(&globalPropKeyring);
1677 // Must not be registered! Not valid for --sync-property and
1678 // must not be copied between configs.
1679 registry.push_back(&syncPropRootMinVersion);
1680 registry.push_back(&syncPropRootCurVersion);
1681 registry.push_back(&syncPropContextMinVersion);
1682 registry.push_back(&syncPropContextCurVersion);
1683 registry.push_back(&syncPropPeerMinVersion);
1684 registry.push_back(&syncPropPeerCurVersion);
1687 BOOST_FOREACH (const ConfigProperty *prop, tmp) {
1688 registry.push_back(prop);
1691 // obligatory sync properties
1693 // username/password used to be
1694 // considered obligatory, but are not anymore because there are
1695 // cases where they are not needed (local sync, Bluetooth)
1696 // syncPropUsername.setObligatory(true);
1697 // syncPropPassword.setObligatory(true);
1699 // created if not given:
1700 // syncPropDevID.setObligatory(true);
1701 syncPropSyncURL.setObligatory(true);
1703 // hidden sync properties
1704 syncPropHashCode.setHidden(true);
1705 syncPropConfigDate.setHidden(true);
1706 syncPropNonce.setHidden(true);
1707 syncPropDeviceData.setHidden(true);
1708 propRootMinVersion.setHidden(true);
1709 propRootCurVersion.setHidden(true);
1710 propContextMinVersion.setHidden(true);
1711 propContextCurVersion.setHidden(true);
1712 propPeerMinVersion.setHidden(true);
1713 propPeerCurVersion.setHidden(true);
1715 // global sync properties
1716 globalPropDefaultPeer.setSharing(ConfigProperty::GLOBAL_SHARING);
1717 globalPropKeyring.setSharing(ConfigProperty::GLOBAL_SHARING);
1718 propRootMinVersion.setSharing(ConfigProperty::GLOBAL_SHARING);
1719 propRootCurVersion.setSharing(ConfigProperty::GLOBAL_SHARING);
1721 // peer independent sync properties
1722 syncPropLogDir.setSharing(ConfigProperty::SOURCE_SET_SHARING);
1723 syncPropMaxLogDirs.setSharing(ConfigProperty::SOURCE_SET_SHARING);
1724 syncPropDevID.setSharing(ConfigProperty::SOURCE_SET_SHARING);
1725 propContextMinVersion.setSharing(ConfigProperty::SOURCE_SET_SHARING);
1726 propContextCurVersion.setSharing(ConfigProperty::SOURCE_SET_SHARING);
1728 } RegisterSyncConfigProperties;
1730 ConfigPropertyRegistry &SyncConfig::getRegistry()
1732 static ConfigPropertyRegistry registry;
1736 InitStateString SyncConfig::getSyncUsername() const { return syncPropUsername.getProperty(*getNode(syncPropUsername)); }
1737 void SyncConfig::setSyncUsername(const string &value, bool temporarily) { syncPropUsername.setProperty(*getNode(syncPropUsername), value, temporarily); }
1738 InitStateString SyncConfig::getSyncPassword() const {
1739 return syncPropPassword.getCachedProperty(*getNode(syncPropPassword), m_cachedPassword);
1741 void PasswordConfigProperty::checkPassword(UserInterface &ui,
1742 const string &serverName,
1743 FilterConfigNode &globalConfigNode,
1744 const string &sourceName,
1745 const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const
1747 string password, passwordSave;
1748 /* if no source config node, then it should only be password in the global config node */
1749 if(sourceConfigNode.get() == NULL) {
1750 password = getProperty(globalConfigNode);
1752 password = getProperty(*sourceConfigNode);
1755 string descr = getDescr(serverName,globalConfigNode,sourceName,sourceConfigNode);
1756 if (password == "-") {
1757 ConfigPasswordKey key = getPasswordKey(descr,serverName,globalConfigNode,sourceName,sourceConfigNode);
1758 passwordSave = ui.askPassword(getMainName(),descr, key);
1759 } else if(boost::starts_with(password, "${") &&
1760 boost::ends_with(password, "}")) {
1761 string envname = password.substr(2, password.size() - 3);
1762 const char *envval = getenv(envname.c_str());
1764 SyncContext::throwError(string("the environment variable '") +
1768 "' password is not set");
1770 passwordSave = envval;
1773 /* If password is from ui or environment variable, set them in the config node on fly
1774 * Previous impl use temp string to store them, this is not good for expansion in the backend */
1775 if(!passwordSave.empty()) {
1776 if(sourceConfigNode.get() == NULL) {
1777 globalConfigNode.addFilter(getMainName(), InitStateString(passwordSave, true));
1779 sourceConfigNode->addFilter(getMainName(), InitStateString(passwordSave, true));
1783 void PasswordConfigProperty::savePassword(UserInterface &ui,
1784 const string &serverName,
1785 FilterConfigNode &globalConfigNode,
1786 const string &sourceName,
1787 const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const
1789 /** here we don't invoke askPassword for this function has different logic from it */
1791 if(sourceConfigNode.get() == NULL) {
1792 password = getProperty(globalConfigNode);
1794 password = getProperty(*sourceConfigNode);
1796 /** if it has been stored or it has no value, do nothing */
1797 if(password == "-" || password == "") {
1799 } else if(boost::starts_with(password, "${") &&
1800 boost::ends_with(password, "}")) {
1801 /** we delay this calculation of environment variable for
1802 * it might be changed in the sync time. */
1805 string descr = getDescr(serverName,globalConfigNode,sourceName,sourceConfigNode);
1806 ConfigPasswordKey key = getPasswordKey(descr,serverName,globalConfigNode,sourceName,sourceConfigNode);
1807 if(ui.savePassword(getMainName(), password, key)) {
1809 if(sourceConfigNode.get() == NULL) {
1810 setProperty(globalConfigNode, value);
1812 setProperty(*sourceConfigNode,value);
1817 InitStateString PasswordConfigProperty::getCachedProperty(const ConfigNode &node,
1818 const string &cachedPassword)
1820 InitStateString password;
1822 if (!cachedPassword.empty()) {
1823 password = InitStateString(cachedPassword, true);
1825 password = getProperty(node);
1831 * remove some unnecessary parts of server URL.
1834 static void purifyServer(string &server)
1836 /** here we use server sync url without protocol prefix and
1837 * user account name as the key in the keyring */
1838 size_t start = server.find("://");
1839 /** we don't reserve protocol prefix for it may change*/
1840 if(start != server.npos) {
1841 server = server.substr(start + 3);
1845 ConfigPasswordKey PasswordConfigProperty::getPasswordKey(const string &descr,
1846 const string &serverName,
1847 FilterConfigNode &globalConfigNode,
1848 const string &sourceName,
1849 const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const
1851 ConfigPasswordKey key;
1852 key.server = syncPropSyncURL.getProperty(globalConfigNode);
1853 purifyServer(key.server);
1854 key.user = syncPropUsername.getProperty(globalConfigNode);
1857 void ProxyPasswordConfigProperty::checkPassword(UserInterface &ui,
1858 const string &serverName,
1859 FilterConfigNode &globalConfigNode,
1860 const string &sourceName,
1861 const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const
1863 /* if useProxy is set 'true', then check proxypassword */
1864 if(syncPropUseProxy.getPropertyValue(globalConfigNode)) {
1865 PasswordConfigProperty::checkPassword(ui, serverName, globalConfigNode, sourceName, sourceConfigNode);
1869 ConfigPasswordKey ProxyPasswordConfigProperty::getPasswordKey(const string &descr,
1870 const string &serverName,
1871 FilterConfigNode &globalConfigNode,
1872 const string &sourceName,
1873 const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const
1875 ConfigPasswordKey key;
1876 key.server = syncPropProxyHost.getProperty(globalConfigNode);
1877 key.user = syncPropProxyUsername.getProperty(globalConfigNode);
1881 void SyncConfig::setSyncPassword(const string &value, bool temporarily) { m_cachedPassword = ""; syncPropPassword.setProperty(*getNode(syncPropPassword), value, temporarily); }
1883 InitState<bool> SyncConfig::getPreventSlowSync() const {
1884 return syncPropPreventSlowSync.getPropertyValue(*getNode(syncPropPreventSlowSync));
1886 void SyncConfig::setPreventSlowSync(bool value, bool temporarily) { syncPropPreventSlowSync.setProperty(*getNode(syncPropPreventSlowSync), value, temporarily); }
1888 static const char *ProxyString = "http_proxy";
1890 /* Reads http_proxy from environment, if not available returns configured value */
1891 InitState<bool> SyncConfig::getUseProxy() const {
1892 InitState<bool> res = syncPropUseProxy.getPropertyValue(*getNode(syncPropUseProxy));
1893 if (!res.wasSet()) {
1894 // Not configured. Check environment.
1895 char *proxy = getenv(ProxyString);
1896 if (proxy && *proxy) {
1897 // Environment has it. Assume that it applies to us and
1898 // use it. TODO: check no_proxy against our sync URL.
1899 res = InitState<bool>(true, true);
1905 void SyncConfig::setUseProxy(bool value, bool temporarily) { syncPropUseProxy.setProperty(*getNode(syncPropUseProxy), value, temporarily); }
1907 /* If http_proxy set in the environment returns it, otherwise configured value */
1908 InitStateString SyncConfig::getProxyHost() const {
1909 char *proxy = getenv(ProxyString);
1911 return syncPropProxyHost.getProperty(*getNode(syncPropUseProxy));
1913 return InitStateString(proxy, true);
1917 void SyncConfig::setProxyHost(const string &value, bool temporarily) { syncPropProxyHost.setProperty(*getNode(syncPropProxyHost), value, temporarily); }
1919 InitStateString SyncConfig::getProxyUsername() const { return syncPropProxyUsername.getProperty(*getNode(syncPropProxyUsername)); }
1920 void SyncConfig::setProxyUsername(const string &value, bool temporarily) { syncPropProxyUsername.setProperty(*getNode(syncPropProxyUsername), value, temporarily); }
1922 InitStateString SyncConfig::getProxyPassword() const {
1923 return syncPropProxyPassword.getCachedProperty(*getNode(syncPropProxyPassword), m_cachedProxyPassword);
1925 void SyncConfig::setProxyPassword(const string &value, bool temporarily) { m_cachedProxyPassword = ""; syncPropProxyPassword.setProperty(*getNode(syncPropProxyPassword), value, temporarily); }
1926 InitState< vector<string> > SyncConfig::getSyncURL() const {
1927 InitStateString s = syncPropSyncURL.getProperty(*getNode(syncPropSyncURL));
1928 vector<string> urls;
1930 // workaround for g++ 4.3/4.4:
1931 // http://stackoverflow.com/questions/1168525/c-gcc4-4-warning-array-subscript-is-above-array-bounds
1932 static const string sep(" \t");
1933 boost::split(urls, s.get(), boost::is_any_of(sep));
1935 return InitState< vector<string> >(urls, s.wasSet());
1937 void SyncConfig::setSyncURL(const string &value, bool temporarily) { syncPropSyncURL.setProperty(*getNode(syncPropSyncURL), value, temporarily); }
1938 void SyncConfig::setSyncURL(const vector<string> &value, bool temporarily) {
1940 BOOST_FOREACH (string url, value) {
1943 return setSyncURL (urls.str(), temporarily);
1945 InitStateString SyncConfig::getClientAuthType() const { return syncPropClientAuthType.getProperty(*getNode(syncPropClientAuthType)); }
1946 void SyncConfig::setClientAuthType(const string &value, bool temporarily) { syncPropClientAuthType.setProperty(*getNode(syncPropClientAuthType), value, temporarily); }
1947 InitState<unsigned long > SyncConfig::getMaxMsgSize() const { return syncPropMaxMsgSize.getPropertyValue(*getNode(syncPropMaxMsgSize)); }
1948 void SyncConfig::setMaxMsgSize(unsigned long value, bool temporarily) { syncPropMaxMsgSize.setProperty(*getNode(syncPropMaxMsgSize), value, temporarily); }
1949 InitState<unsigned int > SyncConfig::getMaxObjSize() const { return syncPropMaxObjSize.getPropertyValue(*getNode(syncPropMaxObjSize)); }
1950 void SyncConfig::setMaxObjSize(unsigned int value, bool temporarily) { syncPropMaxObjSize.setProperty(*getNode(syncPropMaxObjSize), value, temporarily); }
1951 InitStateString SyncConfig::getDevID() const { return syncPropDevID.getProperty(*getNode(syncPropDevID)); }
1952 void SyncConfig::setDevID(const string &value, bool temporarily) { syncPropDevID.setProperty(*getNode(syncPropDevID), value, temporarily); }
1953 InitState<bool> SyncConfig::getWBXML() const { return syncPropWBXML.getPropertyValue(*getNode(syncPropWBXML)); }
1954 void SyncConfig::setWBXML(bool value, bool temporarily) { syncPropWBXML.setProperty(*getNode(syncPropWBXML), value, temporarily); }
1955 InitState<bool> SyncConfig::getRefreshSync() const { return syncPropRefreshSync.getPropertyValue(*getNode(syncPropRefreshSync)); }
1956 void SyncConfig::setRefreshSync(bool value, bool temporarily) { syncPropRefreshSync.setProperty(*getNode(syncPropRefreshSync), value, temporarily); }
1957 InitStateString SyncConfig::getLogDir() const { return syncPropLogDir.getProperty(*getNode(syncPropLogDir)); }
1958 void SyncConfig::setLogDir(const string &value, bool temporarily) { syncPropLogDir.setProperty(*getNode(syncPropLogDir), value, temporarily); }
1959 InitState<unsigned int> SyncConfig::getMaxLogDirs() const { return syncPropMaxLogDirs.getPropertyValue(*getNode(syncPropMaxLogDirs)); }
1960 void SyncConfig::setMaxLogDirs(unsigned int value, bool temporarily) { syncPropMaxLogDirs.setProperty(*getNode(syncPropMaxLogDirs), value, temporarily); }
1961 InitState<unsigned int> SyncConfig::getLogLevel() const { return syncPropLogLevel.getPropertyValue(*getNode(syncPropLogLevel)); }
1962 void SyncConfig::setLogLevel(unsigned int value, bool temporarily) { syncPropLogLevel.setProperty(*getNode(syncPropLogLevel), value, temporarily); }
1963 InitState<SyncConfig::NotifyLevel> SyncConfig::getNotifyLevel() const {
1964 InitState<unsigned int> res = syncPropNotifyLevel.getPropertyValue(*getNode(syncPropNotifyLevel));
1965 return InitState<SyncConfig::NotifyLevel>(static_cast<SyncConfig::NotifyLevel>(res.get()), res.wasSet());
1967 void SyncConfig::setNotifyLevel(SyncConfig::NotifyLevel value, bool temporarily) { syncPropNotifyLevel.setProperty(*getNode(syncPropNotifyLevel), value, temporarily); }
1968 InitState<unsigned int> SyncConfig::getRetryDuration() const {return syncPropRetryDuration.getPropertyValue(*getNode(syncPropRetryDuration));}
1969 void SyncConfig::setRetryDuration(unsigned int value, bool temporarily) { syncPropRetryDuration.setProperty(*getNode(syncPropRetryDuration), value, temporarily); }
1970 InitState<unsigned int> SyncConfig::getRetryInterval() const { return syncPropRetryInterval.getPropertyValue(*getNode(syncPropRetryInterval)); }
1971 void SyncConfig::setRetryInterval(unsigned int value, bool temporarily) { return syncPropRetryInterval.setProperty(*getNode(syncPropRetryInterval),value,temporarily); }
1973 /* used by Server Alerted Sync */
1974 InitStateString SyncConfig::getRemoteIdentifier() const { return syncPropRemoteIdentifier.getProperty(*getNode(syncPropRemoteIdentifier));}
1975 void SyncConfig::setRemoteIdentifier (const string &value, bool temporarily) { return syncPropRemoteIdentifier.setProperty (*getNode(syncPropRemoteIdentifier), value, temporarily); }
1977 InitState<bool> SyncConfig::getPeerIsClient() const { return syncPropPeerIsClient.getPropertyValue(*getNode(syncPropPeerIsClient)); }
1978 void SyncConfig::setPeerIsClient(bool value, bool temporarily) { syncPropPeerIsClient.setProperty(*getNode(syncPropPeerIsClient), value, temporarily); }
1980 InitStateString SyncConfig::getSyncMLVersion() const {
1981 InitState< std::set<std::string> > flags = getSyncMLFlags();
1982 static const char * const versions[] = { "1.2", "1.1", "1.0" };
1983 BOOST_FOREACH (const char *version, versions) {
1984 if (flags.find(version) != flags.end()) {
1985 return InitStateString(version, flags.wasSet());
1988 return InitStateString("", flags.wasSet());
1990 InitState<unsigned int> SyncConfig::getRequestMaxTime() const {
1991 InitState<unsigned int> requestmaxtime;
1992 InitState< std::set<std::string> > flags = getSyncMLFlags();
1993 BOOST_FOREACH(const std::string &flag, flags) {
1994 size_t offset = flag.find('=');
1995 if (offset != flag.npos) {
1996 std::string key = flag.substr(0, offset);
1997 if (boost::iequals(key, "RequestMaxTime")) {
1998 std::string value = flag.substr(offset + 1);
1999 unsigned int seconds;
2001 if (!SecondsConfigProperty::parseDuration(value, error, seconds)) {
2002 SE_THROW("invalid RequestMaxTime value in SyncMLVersion property: " + error);
2004 requestmaxtime = seconds;
2009 return requestmaxtime;
2011 InitState< std::set<std::string> > SyncConfig::getSyncMLFlags() const {
2012 InitStateString value = syncPropSyncMLVersion.getProperty(*getNode(syncPropSyncMLVersion));
2013 std::list<std::string> keywords;
2014 // Work around g++ 4.4 + "array out of bounds" when using is_any_of() on plain string.
2015 static const std::string delim(" ,");
2016 boost::split(keywords, value, boost::is_any_of(delim));
2017 std::set<std::string> flags;
2018 BOOST_FOREACH (std::string &keyword, keywords) {
2019 boost::to_lower(keyword);
2020 flags.insert(keyword);
2022 return InitState< std::set<std::string> >(flags, value.wasSet());
2025 InitStateString SyncConfig::getUserPeerName() const { return syncPropPeerName.getProperty(*getNode(syncPropPeerName)); }
2026 void SyncConfig::setUserPeerName(const InitStateString &name) { syncPropPeerName.setProperty(*getNode(syncPropPeerName), name); }
2028 InitState<bool> SyncConfig::getPrintChanges() const { return syncPropPrintChanges.getPropertyValue(*getNode(syncPropPrintChanges)); }
2029 void SyncConfig::setPrintChanges(bool value, bool temporarily) { syncPropPrintChanges.setProperty(*getNode(syncPropPrintChanges), value, temporarily); }
2030 InitState<bool> SyncConfig::getDumpData() const { return syncPropDumpData.getPropertyValue(*getNode(syncPropDumpData)); }
2031 void SyncConfig::setDumpData(bool value, bool temporarily) { syncPropDumpData.setProperty(*getNode(syncPropDumpData), value, temporarily); }
2032 InitStateString SyncConfig::getWebURL() const { return syncPropWebURL.getProperty(*getNode(syncPropWebURL)); }
2033 void SyncConfig::setWebURL(const std::string &url, bool temporarily) { syncPropWebURL.setProperty(*getNode(syncPropWebURL), url, temporarily); }
2034 InitStateString SyncConfig::getIconURI() const { return syncPropIconURI.getProperty(*getNode(syncPropIconURI)); }
2035 InitState<bool> SyncConfig::getConsumerReady() const { return syncPropConsumerReady.getPropertyValue(*getNode(syncPropConsumerReady)); }
2036 void SyncConfig::setConsumerReady(bool ready) { return syncPropConsumerReady.setProperty(*getNode(syncPropConsumerReady), ready); }
2037 void SyncConfig::setIconURI(const std::string &uri, bool temporarily) { syncPropIconURI.setProperty(*getNode(syncPropIconURI), uri, temporarily); }
2038 InitState<unsigned long> SyncConfig::getHashCode() const { return syncPropHashCode.getPropertyValue(*getNode(syncPropHashCode)); }
2039 void SyncConfig::setHashCode(unsigned long code) { syncPropHashCode.setProperty(*getNode(syncPropHashCode), code); }
2040 InitStateString SyncConfig::getConfigDate() const { return syncPropConfigDate.getProperty(*getNode(syncPropConfigDate)); }
2041 void SyncConfig::setConfigDate() {
2042 /* Set current timestamp as configdate */
2044 time_t ts = time(NULL);
2045 strftime(buffer, sizeof(buffer), "%Y%m%dT%H%M%SZ", gmtime(&ts));
2046 const std::string date(buffer);
2047 syncPropConfigDate.setProperty(*getNode(syncPropConfigDate), date);
2050 InitStateString SyncConfig::getSSLServerCertificates() const { return syncPropSSLServerCertificates.getProperty(*getNode(syncPropSSLServerCertificates)); }
2051 void SyncConfig::setSSLServerCertificates(const string &value, bool temporarily) { syncPropSSLServerCertificates.setProperty(*getNode(syncPropSSLServerCertificates), value, temporarily); }
2052 InitState<bool> SyncConfig::getSSLVerifyServer() const { return syncPropSSLVerifyServer.getPropertyValue(*getNode(syncPropSSLVerifyServer)); }
2053 void SyncConfig::setSSLVerifyServer(bool value, bool temporarily) { syncPropSSLVerifyServer.setProperty(*getNode(syncPropSSLVerifyServer), value, temporarily); }
2054 InitState<bool> SyncConfig::getSSLVerifyHost() const { return syncPropSSLVerifyHost.getPropertyValue(*getNode(syncPropSSLVerifyHost)); }
2055 void SyncConfig::setSSLVerifyHost(bool value, bool temporarily) { syncPropSSLVerifyHost.setProperty(*getNode(syncPropSSLVerifyHost), value, temporarily); }
2056 InitStateString SyncConfig::getRemoteDevID() const { return syncPropRemoteDevID.getProperty(*getNode(syncPropRemoteDevID)); }
2057 void SyncConfig::setRemoteDevID(const string &value) { syncPropRemoteDevID.setProperty(*getNode(syncPropRemoteDevID), value); }
2058 InitStateString SyncConfig::getNonce() const { return syncPropNonce.getProperty(*getNode(syncPropNonce)); }
2059 void SyncConfig::setNonce(const string &value) { syncPropNonce.setProperty(*getNode(syncPropNonce), value); }
2060 InitStateString SyncConfig::getDeviceData() const { return syncPropDeviceData.getProperty(*getNode(syncPropDeviceData)); }
2061 void SyncConfig::setDeviceData(const string &value) { syncPropDeviceData.setProperty(*getNode(syncPropDeviceData), value); }
2062 InitStateString SyncConfig::getDefaultPeer() const { return globalPropDefaultPeer.getProperty(*getNode(globalPropDefaultPeer)); }
2063 void SyncConfig::setDefaultPeer(const string &value) { globalPropDefaultPeer.setProperty(*getNode(globalPropDefaultPeer), value); }
2064 InitStateTri SyncConfig::getKeyring() const { return globalPropKeyring.getProperty(*getNode(globalPropKeyring)); }
2065 void SyncConfig::setKeyring(const string &value) { globalPropKeyring.setProperty(*getNode(globalPropKeyring), value); }
2067 InitStateString SyncConfig::getAutoSync() const { return syncPropAutoSync.getProperty(*getNode(syncPropAutoSync)); }
2068 void SyncConfig::setAutoSync(const string &value, bool temporarily) { syncPropAutoSync.setProperty(*getNode(syncPropAutoSync), value, temporarily); }
2069 InitState<unsigned int> SyncConfig::getAutoSyncInterval() const { return syncPropAutoSyncInterval.getPropertyValue(*getNode(syncPropAutoSyncInterval)); }
2070 void SyncConfig::setAutoSyncInterval(unsigned int value, bool temporarily) { syncPropAutoSyncInterval.setProperty(*getNode(syncPropAutoSyncInterval), value, temporarily); }
2071 InitState<unsigned int> SyncConfig::getAutoSyncDelay() const { return syncPropAutoSyncDelay.getPropertyValue(*getNode(syncPropAutoSyncDelay)); }
2072 void SyncConfig::setAutoSyncDelay(unsigned int value, bool temporarily) { syncPropAutoSyncDelay.setProperty(*getNode(syncPropAutoSyncDelay), value, temporarily); }
2074 std::string SyncConfig::findSSLServerCertificate()
2076 std::string paths = getSSLServerCertificates();
2077 std::vector< std::string > files;
2078 boost::split(files, paths, boost::is_any_of(":"));
2079 BOOST_FOREACH(std::string file, files) {
2080 if (!file.empty() && !access(file.c_str(), R_OK)) {
2088 void SyncConfig::setConfigFilter(bool sync,
2089 const std::string &source,
2090 const FilterConfigNode::ConfigFilter &filter)
2093 m_peerNode->setFilter(filter);
2094 if (m_peerNode != m_contextNode) {
2095 m_contextNode->setFilter(filter);
2097 if (m_globalNode != m_contextNode) {
2098 m_globalNode->setFilter(filter);
2101 m_nodeCache.clear();
2102 m_sourceFilters[source] = filter;
2106 boost::shared_ptr<FilterConfigNode>
2107 SyncConfig::getNode(const ConfigProperty &prop)
2109 switch (prop.getSharing()) {
2110 case ConfigProperty::GLOBAL_SHARING:
2111 if (prop.isHidden()) {
2112 return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(m_globalHiddenNode));
2114 return m_globalNode;
2117 case ConfigProperty::SOURCE_SET_SHARING:
2118 if (prop.isHidden()) {
2119 return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(m_contextHiddenNode));
2121 return m_contextNode;
2124 case ConfigProperty::NO_SHARING:
2125 if (prop.isHidden()) {
2126 return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(m_hiddenPeerNode));
2132 // should not be reached
2133 return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(boost::shared_ptr<ConfigNode>(new DevNullConfigNode("unknown sharing state of property"))));
2136 boost::shared_ptr<FilterConfigNode>
2137 SyncConfig::getNode(const std::string &propName)
2139 ConfigPropertyRegistry ®istry = getRegistry();
2140 const ConfigProperty *prop = registry.find(propName);
2142 return getNode(*prop);
2144 return boost::shared_ptr<FilterConfigNode>();
2148 static void setDefaultProps(const ConfigPropertyRegistry ®istry,
2149 boost::shared_ptr<FilterConfigNode> node,
2152 bool useObligatory = true)
2154 BOOST_FOREACH(const ConfigProperty *prop, registry) {
2155 InitStateString value = prop->getProperty(*node);
2156 if (!prop->isHidden() &&
2157 (unshared || prop->getSharing() != ConfigProperty::NO_SHARING) &&
2158 (force || !value.wasSet())) {
2159 if (useObligatory) {
2160 prop->setDefaultProperty(*node, prop->isObligatory());
2162 prop->setDefaultProperty(*node, false);
2168 void SyncConfig::setDefaults(bool force)
2170 setDefaultProps(getRegistry(), getProperties(),
2172 !m_peerPath.empty());
2175 void SyncConfig::setSourceDefaults(const string &name, bool force)
2177 SyncSourceNodes nodes = getSyncSourceNodes(name);
2178 setDefaultProps(SyncSourceConfig::getRegistry(),
2179 nodes.getProperties(),
2181 !m_peerPath.empty());
2184 void SyncConfig::removeSyncSource(const string &name)
2186 string lower = name;
2187 boost::to_lower(lower);
2190 if (m_layout == SHARED_LAYOUT) {
2191 if (m_peerPath.empty()) {
2192 // removed shared source properties...
2193 pathName = m_contextPath + "/sources/" + lower;
2194 m_tree->remove(pathName);
2195 // ... and the peer-specific ones of *all* peers
2196 BOOST_FOREACH(const std::string peer,
2197 m_tree->getChildren(m_contextPath + "/peers")) {
2198 m_tree->remove(m_contextPath + "/peers/" + peer + "/sources/" + lower);
2201 // remove only inside the selected peer
2202 m_tree->remove(m_peerPath + "/sources/" + lower);
2205 // remove the peer-specific ones
2206 pathName = m_peerPath +
2207 (m_layout == SYNC4J_LAYOUT ? "spds/sources/" : "sources/") +
2209 m_tree->remove(pathName);
2213 void SyncConfig::clearSyncSourceProperties(const string &name)
2215 SyncSourceNodes nodes = getSyncSourceNodes(name);
2216 setDefaultProps(SyncSourceConfig::getRegistry(),
2217 nodes.getProperties(),
2219 !m_peerPath.empty(),
2223 void SyncConfig::clearSyncProperties()
2225 setDefaultProps(getRegistry(), getProperties(),
2227 !m_peerPath.empty(),
2231 static void copyProperties(const ConfigNode &fromProps,
2232 ConfigNode &toProps,
2235 const ConfigPropertyRegistry &allProps)
2237 BOOST_FOREACH(const ConfigProperty *prop, allProps) {
2238 if (prop->isHidden() == hidden &&
2240 prop->getSharing() != ConfigProperty::NO_SHARING)) {
2241 InitStateString value = prop->getProperty(fromProps);
2242 string name = prop->getName(toProps);
2243 toProps.setProperty(name,
2245 prop->getComment());
2250 static void copyProperties(const ConfigNode &fromProps,
2251 ConfigNode &toProps)
2254 fromProps.readProperties(props);
2255 toProps.writeProperties(props);
2258 void SyncConfig::copy(const SyncConfig &other,
2259 const set<string> *sourceSet)
2261 for (int i = 0; i < 2; i++ ) {
2262 boost::shared_ptr<const FilterConfigNode> fromSyncProps(other.getProperties(i));
2263 boost::shared_ptr<FilterConfigNode> toSyncProps(this->getProperties(i));
2264 copyProperties(*fromSyncProps,
2267 !m_peerPath.empty(),
2268 SyncConfig::getRegistry());
2271 list<string> sources;
2273 sources = other.getSyncSources();
2275 BOOST_FOREACH(const string &sourceName, *sourceSet) {
2276 sources.push_back(sourceName);
2279 BOOST_FOREACH(const string &sourceName, sources) {
2280 ConstSyncSourceNodes fromNodes = other.getSyncSourceNodes(sourceName);
2281 SyncSourceNodes toNodes = this->getSyncSourceNodes(sourceName);
2283 for (int i = 0; i < 2; i++ ) {
2284 copyProperties(*fromNodes.getProperties(i),
2285 *toNodes.getProperties(i),
2287 !m_peerPath.empty(),
2288 SyncSourceConfig::getRegistry());
2290 copyProperties(*fromNodes.getTrackingNode(),
2291 *toNodes.getTrackingNode());
2292 copyProperties(*fromNodes.getServerNode(),
2293 *toNodes.getServerNode());
2297 InitStateString SyncConfig::getSwv() const { return VERSION; }
2298 InitStateString SyncConfig::getDevType() const { return DEVICE_TYPE; }
2301 SyncSourceConfig::SyncSourceConfig(const string &name, const SyncSourceNodes &nodes) :
2307 StringConfigProperty SyncSourceConfig::m_sourcePropSync("sync",
2308 "Requests a certain synchronization mode when initiating a sync:\n\n"
2310 " only send/receive changes since last sync\n"
2312 " exchange all items\n"
2313 " refresh-from-remote\n"
2314 " discard all local items and replace with\n"
2315 " the items on the peer\n"
2316 " refresh-from-local\n"
2317 " discard all items on the peer and replace\n"
2318 " with the local items\n"
2319 " one-way-from-remote\n"
2320 " transmit changes from peer\n"
2321 " one-way-from-local\n"
2322 " transmit local changes\n"
2323 " local-cache-slow (server only)\n"
2324 " mirror remote data locally, transferring all data\n"
2325 " local-cache-incremental (server only)\n"
2326 " mirror remote data locally, transferring only changes;\n"
2327 " falls back to local-cache-slow automatically if necessary\n"
2328 " disabled (or none)\n"
2329 " synchronization disabled\n"
2331 "refresh/one-way-from-server/client are also supported. Their use is\n"
2332 "discouraged because the direction of the data transfer depends\n"
2333 "on the role of the local side (can be server or client), which is\n"
2334 "not always obvious.\n"
2336 "When accepting a sync session in a SyncML server (HTTP server), only\n"
2337 "sources with sync != disabled are made available to the client,\n"
2338 "which chooses the final sync mode based on its own configuration.\n"
2339 "When accepting a sync session in a SyncML client (local sync with\n"
2340 "the server contacting SyncEvolution on a device), the sync mode\n"
2341 "specified in the client is typically overriden by the server.\n",
2345 (Aliases("two-way")) +
2348 (Aliases("refresh-from-local")) +
2349 (Aliases("refresh-from-remote") + "refresh") +
2350 (Aliases("one-way-from-local")) +
2351 (Aliases("one-way-from-remote") + "one-way") +
2353 (Aliases("refresh-from-client") + "refresh-client") +
2354 (Aliases("refresh-from-server") + "refresh-server") +
2355 (Aliases("one-way-from-client") + "one-way-client") +
2356 (Aliases("one-way-from-server") + "one-way-server") +
2357 (Aliases("local-cache-slow")) +
2358 (Aliases("local-cache-incremental") + "local-cache") +
2359 (Aliases("disabled") + "none"));
2361 static class SourceBackendConfigProperty : public StringConfigProperty {
2363 SourceBackendConfigProperty() :
2364 StringConfigProperty("backend",
2365 "Specifies the SyncEvolution backend and thus the\n"
2366 "data which is synchronized by this source. Each\n"
2367 "backend may support multiple databases (see 'database'\n"
2368 "property), different formats inside that database (see\n"
2369 "'databaseFormat'), and different formats when talking to\n"
2370 "the sync peer (see 'syncFormat' and 'forceSyncFormat').\n"
2372 "A special 'virtual' backend combines several other\n"
2373 "data sources and presents them as one set of items\n"
2374 "to the peer. For example, Nokia phones typically\n"
2375 "exchange tasks and events as part of one set of\n"
2378 "Right now such a virtual backend is limited to\n"
2379 "combining one calendar source with events and one\n"
2380 "task source. They have to be specified in the\n"
2381 "``database`` property, typically like this:\n"
2382 "``calendar,todo``\n"
2384 "Different sources combined in one virtual source must\n"
2385 "have a common format. As with other backends,\n"
2386 "the preferred format can be influenced via the 'syncFormat'\n"
2389 "Here's the full list of potentially supported backends,\n"
2390 "valid 'backend' values for each of them, and possible\n"
2391 "formats. Note that SyncEvolution installations usually\n"
2392 "support only a subset of the backends; that's why e.g.\n"
2393 "\"addressbook\" is unambiguous although there are multiple\n"
2394 "address book backends.\n"
2399 (Aliases("virtual")) +
2400 (Aliases("calendar") + "events") +
2401 (Aliases("addressbook") + "contacts") +
2402 (Aliases("todo") + "tasks") +
2403 (Aliases("memo") + "memos" + "notes"))
2406 virtual string getComment() const {
2407 stringstream enabled, disabled;
2410 SourceRegistry ®istry(SyncSource::getSourceRegistry());
2411 BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) {
2412 static const std::string whitespace(" \t\n");
2413 string comment = boost::trim_right_copy_if(sourceInfos->m_typeDescr,
2414 boost::is_any_of(whitespace));
2415 stringstream *curr = sourceInfos->m_enabled ? &enabled : &disabled;
2416 boost::replace_all(comment, "\n", "\n ");
2417 *curr << " " << comment << "\n";
2420 res << StringConfigProperty::getComment();
2421 if (enabled.str().size()) {
2422 res << "\n\nCurrently active::\n\n" << enabled.str();
2424 if (disabled.str().size()) {
2425 res << "\n\nCurrently inactive::\n\n" << disabled.str();
2428 return boost::trim_right_copy(res.str());
2431 virtual Values getValues() const {
2432 Values res(StringConfigProperty::getValues());
2434 const SourceRegistry ®istry(SyncSource::getSourceRegistry());
2435 BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) {
2436 copy(sourceInfos->m_typeValues.begin(),
2437 sourceInfos->m_typeValues.end(),
2438 back_inserter(res));
2443 } sourcePropBackend;
2445 StringConfigProperty sourcePropSyncFormat("syncFormat",
2446 "When there are alternative formats for the same data,\n"
2447 "each side of a sync offers all that it supports and marks one as\n"
2448 "preferred. If set, this property overrides the format\n"
2449 "that would normally be marked as preferred by a backend.\n"
2451 "Valid values depend on the backend. Here are some examples:\n"
2452 " contacts - text/vcard = vCard 3.0 format\n"
2453 " text/x-vcard = legacy vCard 2.1 format\n"
2454 " calendar - text/calendar = iCalendar 2.0 format\n"
2455 " text/x-vcalendar = legacy vCalendar 1.0 format\n"
2457 "Errors while starting to sync and parsing and/or storing\n"
2458 "items on either client or server can be caused by a mismatch between\n"
2459 "the sync format and uri at the peer.\n");
2461 static BoolConfigProperty sourcePropForceSyncFormat("forceSyncFormat",
2462 "Some peers get confused when offered multiple choices\n"
2463 "for the sync format or pick the less optimal one.\n"
2464 "In such a case, setting this property enforces that the\n"
2465 "preferred format specified with 'syncFormat' is\n"
2469 static ConfigProperty sourcePropDatabaseID(Aliases("database") + "evolutionsource",
2470 "Picks one of the backend's databases:\n"
2471 "depending on the backend, one can set the name\n"
2472 "and/or a unique identifier.\n\n"
2473 "Most backends have a default database,\n"
2474 "like for example the system address book.\n"
2475 "Not setting this property selects that default\n"
2477 "If the backend is a virtual data source,\n"
2478 "this field must contain comma seperated list of\n"
2479 "sub datasources actually used to store data.\n"
2480 "If your sub datastore has a comma in name, you\n"
2481 "must prevent taht comma from being mistaken as the\n"
2482 "separator by preceding it with a backslash, like this:\n"
2483 "``database=Source1PartA\\,PartB,Source2\\\\Backslash``\n"
2485 "To get a full list of available databases,\n"
2486 "run ``syncevolution --print-databases``. The name\n"
2487 "is printed in front of the colon, followed by\n"
2488 "an identifier in brackets. Usually the name is unique and can be\n"
2489 "used to reference the data source. The default\n"
2490 "data source is marked with <default> at the end\n"
2491 "of the line, if there is a default.\n");
2493 static StringConfigProperty sourcePropDatabaseFormat("databaseFormat",
2494 "Defines the data format to be used by the backend for its\n"
2495 "own storage. Typically backends only support one format\n"
2496 "and ignore this property, but for example the file backend\n"
2497 "uses it. See the 'backend' property for more information.\n");
2499 static ConfigProperty sourcePropURI("uri",
2500 "this is appended to the server's URL to identify the\n"
2501 "server's database; if unset, the source name is used as\n"
2504 static ConfigProperty sourcePropUser(Aliases("databaseUser") + "evolutionuser",
2505 "authentication for backend data source; password can be specified\n"
2506 "in multiple ways, see SyncML server password for details\n"
2508 "Warning: setting database user/password in cases where it is not\n"
2509 "needed, as for example with local Evolution calendars and addressbooks,\n"
2510 "can cause the Evolution backend to hang.");
2511 static DatabasePasswordConfigProperty sourcePropPassword(Aliases("databasePassword") + "evolutionpassword", "","", "backend");
2513 static ConfigProperty sourcePropAdminData(SourceAdminDataName,
2514 "used by the Synthesis library internally; do not modify");
2516 static IntConfigProperty sourcePropSynthesisID("synthesisID", "unique integer ID, necessary for libsynthesis", "0");
2519 * Same as RegisterSyncConfigProperties, only for SyncSource properties.
2521 static class RegisterSyncSourceConfigProperties
2524 RegisterSyncSourceConfigProperties()
2526 ConfigPropertyRegistry ®istry = SyncSourceConfig::getRegistry();
2528 // temporarily move existing properties away so that the important
2529 // standard properties come first when using the traditional
2530 // push_back() way of adding them
2531 ConfigPropertyRegistry tmp;
2532 std::swap(registry, tmp);
2534 registry.push_back(&SyncSourceConfig::m_sourcePropSync);
2535 registry.push_back(&sourcePropURI);
2536 registry.push_back(&sourcePropBackend);
2537 registry.push_back(&sourcePropSyncFormat);
2538 registry.push_back(&sourcePropForceSyncFormat);
2539 registry.push_back(&sourcePropDatabaseID);
2540 registry.push_back(&sourcePropDatabaseFormat);
2541 registry.push_back(&sourcePropUser);
2542 registry.push_back(&sourcePropPassword);
2543 registry.push_back(&sourcePropAdminData);
2544 registry.push_back(&sourcePropSynthesisID);
2546 BOOST_FOREACH (const ConfigProperty *prop, tmp) {
2547 registry.push_back(prop);
2550 // obligatory source properties
2551 SyncSourceConfig::m_sourcePropSync.setObligatory(true);
2553 // hidden source properties - only possible for
2554 // non-shared properties (other hidden nodes don't
2555 // exist at the moment)
2556 sourcePropAdminData.setHidden(true);
2557 sourcePropSynthesisID.setHidden(true);
2559 // No global source properties. Does not make sense
2562 // peer independent source properties
2563 sourcePropBackend.setSharing(ConfigProperty::SOURCE_SET_SHARING);
2564 sourcePropDatabaseID.setSharing(ConfigProperty::SOURCE_SET_SHARING);
2565 sourcePropDatabaseFormat.setSharing(ConfigProperty::SOURCE_SET_SHARING);
2566 sourcePropUser.setSharing(ConfigProperty::SOURCE_SET_SHARING);
2567 sourcePropPassword.setSharing(ConfigProperty::SOURCE_SET_SHARING);
2569 } RegisterSyncSourceConfigProperties;
2572 ConfigPropertyRegistry &SyncSourceConfig::getRegistry()
2574 static ConfigPropertyRegistry registry;
2578 SyncSourceNodes::SyncSourceNodes(bool havePeerNode,
2579 const boost::shared_ptr<FilterConfigNode> &sharedNode,
2580 const boost::shared_ptr<FilterConfigNode> &peerNode,
2581 const boost::shared_ptr<ConfigNode> &hiddenPeerNode,
2582 const boost::shared_ptr<ConfigNode> &trackingNode,
2583 const boost::shared_ptr<ConfigNode> &serverNode,
2584 const string &cacheDir) :
2585 m_havePeerNode(havePeerNode),
2586 m_sharedNode(sharedNode),
2587 m_peerNode(peerNode),
2588 m_hiddenPeerNode(hiddenPeerNode),
2589 m_trackingNode(trackingNode),
2590 m_serverNode(serverNode),
2591 m_cacheDir(cacheDir)
2593 boost::shared_ptr<MultiplexConfigNode> mnode;
2594 mnode.reset(new MultiplexConfigNode(m_peerNode->getName(),
2595 SyncSourceConfig::getRegistry(),
2597 mnode->setHavePeerNodes(havePeerNode);
2598 m_props[false] = mnode;
2599 mnode->setNode(false, ConfigProperty::SOURCE_SET_SHARING,
2601 mnode->setNode(false, ConfigProperty::NO_SHARING,
2603 // no multiplexing necessary for hidden peer properties yet
2604 m_props[true].reset(new FilterConfigNode(m_hiddenPeerNode));
2608 boost::shared_ptr<FilterConfigNode>
2609 SyncSourceNodes::getNode(const ConfigProperty &prop) const
2611 switch (prop.getSharing()) {
2612 case ConfigProperty::GLOBAL_SHARING:
2613 return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(boost::shared_ptr<ConfigNode>(new DevNullConfigNode("no global source properties"))));
2615 case ConfigProperty::SOURCE_SET_SHARING:
2616 if (prop.isHidden()) {
2617 return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(boost::shared_ptr<ConfigNode>(new DevNullConfigNode("no hidden source set properties"))));
2619 return m_sharedNode;
2622 case ConfigProperty::NO_SHARING:
2623 if (prop.isHidden()) {
2624 return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(m_hiddenPeerNode));
2630 return boost::shared_ptr<FilterConfigNode>();
2633 InitStateString SyncSourceConfig::getDatabaseID() const { return sourcePropDatabaseID.getProperty(*getNode(sourcePropDatabaseID)); }
2634 void SyncSourceConfig::setDatabaseID(const string &value, bool temporarily) { sourcePropDatabaseID.setProperty(*getNode(sourcePropDatabaseID), value, temporarily); }
2635 InitStateString SyncSourceConfig::getUser() const { return sourcePropUser.getProperty(*getNode(sourcePropUser)); }
2636 void SyncSourceConfig::setUser(const string &value, bool temporarily) { sourcePropUser.setProperty(*getNode(sourcePropUser), value, temporarily); }
2637 InitStateString SyncSourceConfig::getPassword() const {
2638 return sourcePropPassword.getCachedProperty(*getNode(sourcePropPassword), m_cachedPassword);
2640 void SyncSourceConfig::checkPassword(UserInterface &ui,
2641 const string &serverName,
2642 FilterConfigNode& globalConfigNode) {
2643 sourcePropPassword.checkPassword(ui, serverName, globalConfigNode, m_name, getNode(sourcePropPassword));
2645 void SyncSourceConfig::savePassword(UserInterface &ui,
2646 const string &serverName,
2647 FilterConfigNode& globalConfigNode) {
2648 sourcePropPassword.savePassword(ui, serverName, globalConfigNode, m_name, getNode(sourcePropPassword));
2650 void SyncSourceConfig::setPassword(const string &value, bool temporarily) { m_cachedPassword = ""; sourcePropPassword.setProperty(*getNode(sourcePropPassword), value, temporarily); }
2651 InitStateString SyncSourceConfig::getURI() const { return sourcePropURI.getProperty(*getNode(sourcePropURI)); }
2652 InitStateString SyncSourceConfig::getURINonEmpty() const {
2653 InitStateString uri = sourcePropURI.getProperty(*getNode(sourcePropURI));
2655 uri = InitStateString(m_name, false);
2659 void SyncSourceConfig::setURI(const string &value, bool temporarily) { sourcePropURI.setProperty(*getNode(sourcePropURI), value, temporarily); }
2660 InitStateString SyncSourceConfig::getSync() const { return m_sourcePropSync.getProperty(*getNode(m_sourcePropSync)); }
2661 void SyncSourceConfig::setSync(const string &value, bool temporarily) { m_sourcePropSync.setProperty(*getNode(m_sourcePropSync), value, temporarily); }
2663 SourceType::SourceType(const string &type)
2665 m_forceFormat = false;
2666 size_t colon = type.find(':');
2667 if (colon != type.npos) {
2668 m_backend = type.substr(0, colon);
2669 sourcePropBackend.normalizeValue(m_backend);
2670 string format = type.substr(colon + 1);
2671 if (boost::ends_with(format, "!")) {
2672 m_forceFormat = true;
2673 format.resize(format.size() - 1);
2675 colon = format.find(':');
2676 if (colon != format.npos) {
2677 // ignore obsolete Mime version
2678 m_format = format.substr(0, colon);
2682 // no difference between remote and local format
2683 m_localFormat = m_format;
2689 string SourceType::toString() const
2691 string type = m_backend;
2692 if (!m_format.empty()) {
2695 if (m_forceFormat) {
2702 InitState<SourceType> SyncSourceConfig::getSourceType(const SyncSourceNodes &nodes)
2704 // legacy "type" property is tried if the backend property is not set
2705 InitStateString backend = sourcePropBackend.getProperty(*nodes.getNode(sourcePropBackend));
2706 if (!backend.wasSet()) {
2708 if (nodes.getNode(sourcePropBackend)->getProperty("type", type)) {
2709 return InitState<SourceType>(SourceType(type), true);
2713 SourceType sourceType;
2714 sourceType.m_backend = backend;
2715 sourceType.m_localFormat = sourcePropDatabaseFormat.getProperty(*nodes.getNode(sourcePropDatabaseFormat));
2716 sourceType.m_format = sourcePropSyncFormat.getProperty(*nodes.getNode(sourcePropSyncFormat));
2717 sourceType.m_forceFormat = sourcePropForceSyncFormat.getPropertyValue(*nodes.getNode(sourcePropForceSyncFormat));
2718 return InitState<SourceType>(sourceType, backend.wasSet());
2720 InitState<SourceType> SyncSourceConfig::getSourceType() const { return getSourceType(m_nodes); }
2722 void SyncSourceConfig::setSourceType(const SourceType &type, bool temporarily)
2724 // writing always uses the new properties: the config must have
2725 // been converted to the new format before writing is allowed
2726 setBackend(type.m_backend, temporarily);
2727 setDatabaseFormat(type.m_localFormat, temporarily);
2728 setSyncFormat(InitStateString(type.m_format, !type.m_format.empty()), temporarily);
2729 setForceSyncFormat(type.m_forceFormat, temporarily);
2732 void SyncSourceConfig::setBackend(const std::string &value, bool temporarily)
2734 sourcePropBackend.setProperty(*getNode(sourcePropBackend),
2738 InitStateString SyncSourceConfig::getBackend() const
2740 return sourcePropBackend.getProperty(*getNode(sourcePropBackend));
2743 void SyncSourceConfig::setDatabaseFormat(const std::string &value, bool temporarily)
2745 sourcePropDatabaseFormat.setProperty(*getNode(sourcePropDatabaseFormat),
2749 InitStateString SyncSourceConfig::getDatabaseFormat() const
2751 return sourcePropDatabaseFormat.getProperty(*getNode(sourcePropDatabaseFormat));
2754 void SyncSourceConfig::setSyncFormat(const InitStateString &value, bool temporarily)
2756 sourcePropSyncFormat.setProperty(*getNode(sourcePropSyncFormat),
2760 InitStateString SyncSourceConfig::getSyncFormat() const
2762 return sourcePropSyncFormat.getProperty(*getNode(sourcePropSyncFormat));
2765 void SyncSourceConfig::setForceSyncFormat(bool value, bool temporarily)
2767 sourcePropForceSyncFormat.setProperty(*getNode(sourcePropForceSyncFormat),
2771 InitState<bool> SyncSourceConfig::getForceSyncFormat() const
2773 return sourcePropForceSyncFormat.getPropertyValue(*getNode(sourcePropForceSyncFormat));
2776 InitState<int> SyncSourceConfig::getSynthesisID() const {
2777 if (!m_synthesisID.wasSet()) {
2778 const_cast<InitState<int> &>(m_synthesisID) = sourcePropSynthesisID.getPropertyValue(*getNode(sourcePropSynthesisID));
2780 return m_synthesisID;
2782 void SyncSourceConfig::setSynthesisID(int value, bool temporarily) {
2783 m_synthesisID = value;
2784 sourcePropSynthesisID.setProperty(*getNode(sourcePropSynthesisID), value, temporarily);
2787 ConfigPasswordKey DatabasePasswordConfigProperty::getPasswordKey(const string &descr,
2788 const string &serverName,
2789 FilterConfigNode &globalConfigNode,
2790 const string &sourceName,
2791 const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const
2793 ConfigPasswordKey key;
2794 key.user = sourcePropUser.getProperty(*sourceConfigNode);
2795 std::string configName = SyncConfig::normalizeConfigString(serverName, SyncConfig::NORMALIZE_LONG_FORMAT);
2796 std::string peer, context;
2797 SyncConfig::splitConfigString(configName, peer, context);
2799 key.object += context;
2801 key.object += sourceName;
2802 key.object += " backend";
2806 // Used for built-in templates
2807 SyncConfig::TemplateDescription::TemplateDescription (const std::string &name, const std::string &description)
2808 : m_templateId (name), m_description (description)
2810 m_rank = TemplateConfig::LEVEL3_MATCH;
2813 m_matchedModel = name;
2816 /* Ranking of template description is controled by the rank field, larger the
2819 bool SyncConfig::TemplateDescription::compare_op (boost::shared_ptr<SyncConfig::TemplateDescription> &left, boost::shared_ptr<SyncConfig::TemplateDescription> &right)
2821 //first sort against the fingerprint string
2822 if (left->m_deviceName != right->m_deviceName) {
2823 return (left->m_deviceName < right->m_deviceName);
2825 // sort against the rank
2826 if (right->m_rank != left->m_rank) {
2827 return (right->m_rank < left->m_rank);
2829 // sort against the template id, case-insensitive (for eGroupware < Funambol)
2830 return boost::ilexicographical_compare(left->m_templateId, right->m_templateId);
2833 TemplateConfig::TemplateConfig(const string &path) :
2834 m_template(new SingleFileConfigTree(path))
2836 boost::shared_ptr<ConfigNode> metaNode = m_template->open("template.ini");
2837 metaNode->readProperties(m_metaProps);
2840 bool TemplateConfig::isTemplateConfig (const string &path)
2842 SingleFileConfigTree templ(path);
2843 boost::shared_ptr<ConfigNode> metaNode = templ.open("template.ini");
2844 if (!metaNode->exists()) {
2848 metaNode->readProperties(props);
2849 return !props.empty();
2852 bool TemplateConfig::isTemplateConfig() const
2854 return !m_metaProps.empty();
2857 int TemplateConfig::serverModeMatch (SyncConfig::MatchMode mode)
2859 if (mode != SyncConfig::MATCH_FOR_SERVER_MODE &&
2860 mode != SyncConfig::MATCH_FOR_CLIENT_MODE) {
2861 // no need to read config, peerIsClient doesn't matter
2862 // => fall back to BEST_MATCH directly
2866 boost::shared_ptr<ConfigNode> configNode = m_template->open("config.ini");
2867 std::string peerIsClient = configNode->readProperty ("peerIsClient");
2869 //not a match if serverMode does not match
2870 if ((peerIsClient.empty() || peerIsClient == "0") && mode == SyncConfig::MATCH_FOR_SERVER_MODE) {
2873 if (peerIsClient == "1" && mode == SyncConfig::MATCH_FOR_CLIENT_MODE){
2880 * The matching is based on Least common string algorithm,
2881 * with space, hyphen and underscore being treated as equal.
2883 int TemplateConfig::fingerprintMatch (const string &fingerprint)
2885 //if input "", match all
2886 if (fingerprint.empty()) {
2887 return LEVEL3_MATCH;
2890 std::string fingerprintProp = m_metaProps["fingerprint"];
2891 std::vector <string> subfingerprints = unescapeJoinedString (fingerprintProp, ',');
2892 std::string input = fingerprint;
2893 boost::to_lower(input);
2894 boost::replace_all(input, " ", "_");
2895 boost::replace_all(input, "-", "_");
2896 //return the largest match value
2898 BOOST_FOREACH (std::string sub, subfingerprints){
2899 std::vector< LCS::Entry <char> > result;
2900 std::string match = sub;
2901 boost::to_lower(match);
2902 boost::replace_all(match, " ", "_");
2903 boost::replace_all(match, "-", "_");
2904 LCS::lcs(match, input, std::back_inserter(result), LCS::accessor_sequence<std::string>());
2905 int score = result.size() *2 *BEST_MATCH /(sub.size() + fingerprint.size()) ;
2913 int TemplateConfig::metaMatch (const std::string &fingerprint, SyncConfig::MatchMode mode)
2915 int serverMatch = serverModeMatch (mode);
2916 if (serverMatch == NO_MATCH){
2919 int fMatch = fingerprintMatch (fingerprint);
2920 return (serverMatch *1 + fMatch *3) >>2;
2923 string TemplateConfig::getDescription(){
2924 return m_metaProps["description"];
2927 string TemplateConfig::getFingerprint(){
2928 return m_metaProps["fingerprint"];
2931 string TemplateConfig::getTemplateName() {
2932 return m_metaProps["templateName"];
2936 * A unique identifier for this template, it must be unique and retrieveable.
2937 * We use the first entry in the "fingerprint" property for cmdline and
2938 * replace spaces with underscores, to make it more command line friendly.
2940 string TemplateConfig::getTemplateId(){
2942 std::string fingerprintProp = m_metaProps["fingerprint"];
2943 if (!fingerprintProp.empty()){
2944 std::vector<std::string> subfingerprints = unescapeJoinedString (fingerprintProp, ',');
2945 m_id = subfingerprints[0];
2947 boost::replace_all(m_id, " ", "_");
2952 bool SecondsConfigProperty::checkValue(const string &value, string &error) const
2954 unsigned int seconds;
2955 return parseDuration(value, error, seconds);
2958 InitState<unsigned int> SecondsConfigProperty::getPropertyValue(const ConfigNode &node) const
2960 string name = getName(node);
2962 std::string value = node.readProperty(name);
2963 if (value.empty()) {
2965 value = getDefValue();
2970 unsigned int seconds;
2971 if (!parseDuration(value, error, seconds)) {
2972 throwValueError(node, name, value, error);
2974 return InitState<unsigned int>(seconds, wasSet);
2977 bool SecondsConfigProperty::parseDuration(const string &value, string &error, unsigned int &seconds)
2980 if (value.empty()) {
2981 // ambiguous - zero seconds?!
2982 error = "duration expected, empty string not valid";
2986 unsigned int current = 0;
2987 bool haveDigit = false;
2988 BOOST_FOREACH(char c, value) {
2990 current = current * 10 + (c - '0');
2993 unsigned int multiplier = 1;
2994 switch (toupper(c)) {
2996 multiplier = 365 * 24 * 60 * 60;
2999 multiplier = 24 * 60 * 60;
3002 multiplier = 60 * 60;
3015 error = StringPrintf("invalid character '%c'", c);
3018 if (!haveDigit && c != '+') {
3019 error = StringPrintf("unit character without preceeding number: %c", c);
3022 seconds += current * multiplier;
3032 #ifdef ENABLE_UNIT_TESTS
3034 class SyncConfigTest : public CppUnit::TestFixture {
3035 CPPUNIT_TEST_SUITE(SyncConfigTest);
3036 CPPUNIT_TEST(normalize);
3037 CPPUNIT_TEST(parseDuration);
3038 CPPUNIT_TEST(propertySpec);
3039 CPPUNIT_TEST_SUITE_END();
3044 // use same dir as CmdlineTest...
3045 ScopedEnvChange xdg("XDG_CONFIG_HOME", "CmdlineTest");
3046 ScopedEnvChange home("HOME", "CmdlineTest");
3048 rm_r("CmdlineTest");
3050 CPPUNIT_ASSERT_EQUAL(std::string("@default"),
3051 SyncConfig::normalizeConfigString(""));
3052 CPPUNIT_ASSERT_EQUAL(std::string("@default"),
3053 SyncConfig::normalizeConfigString("@default"));
3054 CPPUNIT_ASSERT_EQUAL(std::string("@default"),
3055 SyncConfig::normalizeConfigString("@DeFaULT"));
3056 CPPUNIT_ASSERT_EQUAL(std::string("foobar"),
3057 SyncConfig::normalizeConfigString("FooBar"));
3058 CPPUNIT_ASSERT_EQUAL(std::string("foobar@something"),
3059 SyncConfig::normalizeConfigString("FooBar@Something"));
3060 CPPUNIT_ASSERT_EQUAL(std::string("foo_bar_x_y_z"),
3061 SyncConfig::normalizeConfigString("Foo/bar\\x:y:z"));
3063 // keep @default if explicitly requested
3064 CPPUNIT_ASSERT_EQUAL(std::string("foobar@default"),
3065 SyncConfig::normalizeConfigString("FooBar", SyncConfig::NORMALIZE_LONG_FORMAT));
3067 // test config lookup
3068 SyncConfig foo_default("foo"), foo_other("foo@other"), bar("bar@other");
3069 foo_default.flush();
3072 CPPUNIT_ASSERT_EQUAL(std::string("foo"),
3073 SyncConfig::normalizeConfigString("foo"));
3074 CPPUNIT_ASSERT_EQUAL(std::string("foo"),
3075 SyncConfig::normalizeConfigString("foo@default"));
3076 CPPUNIT_ASSERT_EQUAL(std::string("foo@default"),
3077 SyncConfig::normalizeConfigString("foo", SyncConfig::NORMALIZE_LONG_FORMAT));
3078 CPPUNIT_ASSERT_EQUAL(std::string("foo@default"),
3079 SyncConfig::normalizeConfigString("foo@default", SyncConfig::NORMALIZE_LONG_FORMAT));
3080 CPPUNIT_ASSERT_EQUAL(std::string("foo@other"),
3081 SyncConfig::normalizeConfigString("foo@other"));
3082 foo_default.remove();
3083 CPPUNIT_ASSERT_EQUAL(std::string("foo@other"),
3084 SyncConfig::normalizeConfigString("foo"));
3085 CPPUNIT_ASSERT_EQUAL(std::string("foo@other"),
3086 SyncConfig::normalizeConfigString("foo", SyncConfig::NORMALIZE_LONG_FORMAT));
3089 void parseDuration()
3092 unsigned int seconds;
3093 unsigned int expected;
3095 CPPUNIT_ASSERT(!SecondsConfigProperty::parseDuration("foo", error, seconds));
3096 CPPUNIT_ASSERT_EQUAL(error, string("invalid character 'f'"));
3097 CPPUNIT_ASSERT(!SecondsConfigProperty::parseDuration("1g", error, seconds));
3098 CPPUNIT_ASSERT_EQUAL(error, string("invalid character 'g'"));
3099 CPPUNIT_ASSERT(!SecondsConfigProperty::parseDuration("", error, seconds));
3100 CPPUNIT_ASSERT_EQUAL(error, string("duration expected, empty string not valid"));
3103 CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("5", error, seconds));
3104 CPPUNIT_ASSERT_EQUAL(expected, seconds);
3105 CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("05", error, seconds));
3106 CPPUNIT_ASSERT_EQUAL(expected, seconds);
3107 CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("05s", error, seconds));
3108 CPPUNIT_ASSERT_EQUAL(expected, seconds);
3109 CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("5s", error, seconds));
3110 CPPUNIT_ASSERT_EQUAL(expected, seconds);
3112 expected = (((1 * 365 + 2) * 24 + 3) * 60 + 4) * 60 + 5;
3113 CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("1y2d3H4M5s", error, seconds));
3114 CPPUNIT_ASSERT_EQUAL(expected, seconds);
3115 CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("5 + 1y+2d + 3 H4M", error, seconds));
3116 CPPUNIT_ASSERT_EQUAL(expected, seconds);
3118 CPPUNIT_ASSERT(!SecondsConfigProperty::parseDuration("m", error, seconds));
3123 ScopedEnvChange xdg("XDG_CONFIG_HOME", "/dev/null");
3124 ScopedEnvChange home("HOME", "/dev/null");
3125 PropertySpecifier spec;
3127 spec = PropertySpecifier::StringToPropSpec("foo");
3128 CPPUNIT_ASSERT_EQUAL(string(""), spec.m_source);
3129 CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3130 CPPUNIT_ASSERT_EQUAL(string(""), spec.m_config);
3131 CPPUNIT_ASSERT_EQUAL(string("foo"), spec.toString());
3133 spec = PropertySpecifier::StringToPropSpec("source/foo@ContEXT");
3134 CPPUNIT_ASSERT_EQUAL(string("source"), spec.m_source);
3135 CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3136 CPPUNIT_ASSERT_EQUAL(string("@context"), spec.m_config);
3137 CPPUNIT_ASSERT_EQUAL(string("source/foo@context"), spec.toString());
3139 spec = PropertySpecifier::StringToPropSpec("source/foo@ContEXT", PropertySpecifier::NO_NORMALIZATION);
3140 CPPUNIT_ASSERT_EQUAL(string("source"), spec.m_source);
3141 CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3142 CPPUNIT_ASSERT_EQUAL(string("@ContEXT"), spec.m_config);
3143 CPPUNIT_ASSERT_EQUAL(string("source/foo@ContEXT"), spec.toString());
3145 spec = PropertySpecifier::StringToPropSpec("foo@peer@context");
3146 CPPUNIT_ASSERT_EQUAL(string(""), spec.m_source);
3147 CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3148 CPPUNIT_ASSERT_EQUAL(string("peer@context"), spec.m_config);
3149 CPPUNIT_ASSERT_EQUAL(string("foo@peer@context"), spec.toString());
3151 spec = PropertySpecifier::StringToPropSpec("foo@context");
3152 CPPUNIT_ASSERT_EQUAL(string(""), spec.m_source);
3153 CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3154 CPPUNIT_ASSERT_EQUAL(string("@context"), spec.m_config);
3155 CPPUNIT_ASSERT_EQUAL(string("foo@context"), spec.toString());
3157 spec = PropertySpecifier::StringToPropSpec("source/foo");
3158 CPPUNIT_ASSERT_EQUAL(string("source"), spec.m_source);
3159 CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3160 CPPUNIT_ASSERT_EQUAL(string(""), spec.m_config);
3161 CPPUNIT_ASSERT_EQUAL(string("source/foo"), spec.toString());
3163 spec = PropertySpecifier::StringToPropSpec("");
3164 CPPUNIT_ASSERT_EQUAL(string(""), spec.m_source);
3165 CPPUNIT_ASSERT_EQUAL(string(""), spec.m_property);
3166 CPPUNIT_ASSERT_EQUAL(string(""), spec.m_config);
3167 CPPUNIT_ASSERT_EQUAL(string(""), spec.toString());
3171 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(SyncConfigTest);
3173 #endif // ENABLE_UNIT_TESTS