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
21 #include <syncevo/Cmdline.h>
22 #include <syncevo/CmdlineSyncClient.h>
23 #include <syncevo/FilterConfigNode.h>
24 #include <syncevo/VolatileConfigNode.h>
25 #include <syncevo/IniConfigNode.h>
26 #include <syncevo/SyncSource.h>
27 #include <syncevo/SyncContext.h>
28 #include <syncevo/util.h>
31 #include <synthesis/SDK_util.h>
46 #include <boost/shared_ptr.hpp>
47 #include <boost/algorithm/string/join.hpp>
48 #include <boost/algorithm/string/split.hpp>
49 #include <boost/algorithm/string.hpp>
50 #include <boost/tokenizer.hpp>
51 #include <boost/foreach.hpp>
52 #include <boost/range.hpp>
55 #include <syncevo/declarations.h>
59 // synopsis and options char strings
60 #include "CmdlineHelp.c"
62 Cmdline::Cmdline(int argc, const char * const * argv) :
65 m_validSyncProps(SyncConfig::getRegistry()),
66 m_validSourceProps(SyncSourceConfig::getRegistry())
69 Cmdline::Cmdline(const vector<string> &args) :
71 m_validSyncProps(SyncConfig::getRegistry()),
72 m_validSourceProps(SyncSourceConfig::getRegistry())
75 m_argvArray.reset(new const char *[args.size()]);
76 for(int i = 0; i < m_argc; i++) {
77 m_argvArray[i] = m_args[i].c_str();
79 m_argv = m_argvArray.get();
82 Cmdline::Cmdline(const char *arg, ...) :
83 m_validSyncProps(SyncConfig::getRegistry()),
84 m_validSourceProps(SyncSourceConfig::getRegistry())
87 va_start(argList, arg);
88 for (const char *curr = arg;
90 curr = va_arg(argList, const char *)) {
91 m_args.push_back(curr);
94 m_argc = m_args.size();
95 m_argvArray.reset(new const char *[m_args.size()]);
96 for (int i = 0; i < m_argc; i++) {
97 m_argvArray[i] = m_args[i].c_str();
99 m_argv = m_argvArray.get();
102 bool Cmdline::parse()
104 vector<string> parsed;
105 return parse(parsed);
109 * Detects "--sync foo", "--sync=foo", "-s foo".
111 static bool IsKeyword(const std::string arg,
112 const char *longWord,
113 const char *shortWord)
115 return boost::istarts_with(arg, std::string(longWord) + "=") ||
116 boost::iequals(arg, longWord) ||
117 boost::iequals(arg, shortWord);
120 bool Cmdline::parse(vector<string> &parsed)
124 parsed.push_back(m_argv[0]);
126 m_delimiter = "\n\n";
128 // All command line options which ask for a specific operation,
129 // like --restore, --print-config, ... Used to detect conflicting
131 vector<string> operations;
135 while (opt < m_argc) {
136 parsed.push_back(m_argv[opt]);
137 if (boost::iequals(m_argv[opt], "--")) {
138 // separator between options and <config> <source>:
139 // swallow it and leave option parsing
143 if (m_argv[opt][0] != '-') {
144 if (strchr(m_argv[opt], '=')) {
145 // property assignment
146 if (!parseProp(UNKNOWN_PROPERTY_TYPE,
159 if (IsKeyword(m_argv[opt], "--sync", "-s")) {
160 if (!parseAssignment(opt, parsed, SOURCE_PROPERTY_TYPE, "sync", NULL)) {
164 // disable requirement to add --run explicitly in order to
165 // be compatible with traditional command lines
167 } else if (IsKeyword(m_argv[opt], "--keyring", "-k")) {
168 if (!parseAssignment(opt, parsed, SYNC_PROPERTY_TYPE, "keyring", "true")) {
171 } else if(boost::iequals(m_argv[opt], "--sync-property") ||
172 boost::iequals(m_argv[opt], "-y")) {
174 if (!parseProp(SYNC_PROPERTY_TYPE,
175 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
178 parsed.push_back(m_argv[opt]);
179 } else if(boost::iequals(m_argv[opt], "--source-property") ||
180 boost::iequals(m_argv[opt], "-z")) {
182 if (!parseProp(SOURCE_PROPERTY_TYPE,
183 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
186 parsed.push_back(m_argv[opt]);
187 }else if(boost::iequals(m_argv[opt], "--template") ||
188 boost::iequals(m_argv[opt], "-l")) {
191 usage(false, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
194 parsed.push_back(m_argv[opt]);
195 m_template = m_argv[opt];
197 string temp = boost::trim_copy (m_template);
198 if (temp.find ("?") == 0){
199 m_printTemplates = true;
201 m_template = temp.substr (1);
203 } else if(boost::iequals(m_argv[opt], "--print-databases")) {
204 operations.push_back(m_argv[opt]);
205 m_printDatabases = true;
206 } else if(boost::iequals(m_argv[opt], "--print-servers") ||
207 boost::iequals(m_argv[opt], "--print-peers") ||
208 boost::iequals(m_argv[opt], "--print-configs")) {
209 operations.push_back(m_argv[opt]);
210 m_printServers = true;
211 } else if(boost::iequals(m_argv[opt], "--print-config") ||
212 boost::iequals(m_argv[opt], "-p")) {
213 operations.push_back(m_argv[opt]);
214 m_printConfig = true;
215 } else if(boost::iequals(m_argv[opt], "--print-sessions")) {
216 operations.push_back(m_argv[opt]);
217 m_printSessions = true;
218 } else if(boost::iequals(m_argv[opt], "--configure") ||
219 boost::iequals(m_argv[opt], "-c")) {
220 operations.push_back(m_argv[opt]);
222 } else if(boost::iequals(m_argv[opt], "--remove")) {
223 operations.push_back(m_argv[opt]);
225 } else if(boost::iequals(m_argv[opt], "--run") ||
226 boost::iequals(m_argv[opt], "-r")) {
227 operations.push_back(m_argv[opt]);
229 } else if(boost::iequals(m_argv[opt], "--restore")) {
230 operations.push_back(m_argv[opt]);
233 usage(false, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
236 m_restore = m_argv[opt];
237 if (m_restore.empty()) {
238 usage(false, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
241 //if can't convert it successfully, it's an invalid path
242 if (!relToAbs(m_restore)) {
243 usage(false, string("parameter '") + m_restore + "' for " + cmdOpt(m_argv[opt - 1]) + " must be log directory");
246 parsed.push_back(m_restore);
247 } else if(boost::iequals(m_argv[opt], "--before")) {
249 } else if(boost::iequals(m_argv[opt], "--after")) {
251 } else if (boost::iequals(m_argv[opt], "--print-items")) {
252 operations.push_back(m_argv[opt]);
253 m_printItems = m_accessItems = true;
254 } else if ((boost::iequals(m_argv[opt], "--export") && (m_export = true)) ||
255 (boost::iequals(m_argv[opt], "--import") && (m_import = true)) ||
256 (boost::iequals(m_argv[opt], "--update") && (m_update = true))) {
257 operations.push_back(m_argv[opt]);
258 m_accessItems = true;
260 if (opt >= m_argc || !m_argv[opt][0]) {
261 usage(false, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
264 m_itemPath = m_argv[opt];
265 if (m_itemPath != "-") {
267 splitPath(m_itemPath, dir, file);
271 if (!relToAbs(dir)) {
272 SyncContext::throwError(dir, errno);
274 m_itemPath = dir + "/" + file;
276 parsed.push_back(m_itemPath);
277 } else if (boost::iequals(m_argv[opt], "--delimiter")) {
280 usage(false, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
283 m_delimiter = m_argv[opt];
284 parsed.push_back(m_delimiter);
285 } else if (boost::iequals(m_argv[opt], "--delete-items")) {
286 operations.push_back(m_argv[opt]);
287 m_deleteItems = m_accessItems = true;
288 } else if(boost::iequals(m_argv[opt], "--dry-run")) {
290 } else if(boost::iequals(m_argv[opt], "--migrate")) {
291 operations.push_back(m_argv[opt]);
293 } else if(boost::iequals(m_argv[opt], "--status") ||
294 boost::iequals(m_argv[opt], "-t")) {
295 operations.push_back(m_argv[opt]);
297 } else if(boost::iequals(m_argv[opt], "--quiet") ||
298 boost::iequals(m_argv[opt], "-q")) {
300 } else if(boost::iequals(m_argv[opt], "--help") ||
301 boost::iequals(m_argv[opt], "-h")) {
303 } else if(boost::iequals(m_argv[opt], "--version")) {
304 operations.push_back(m_argv[opt]);
306 } else if (parseBool(opt, "--daemon", NULL, true, m_useDaemon, ok)) {
310 } else if(boost::iequals(m_argv[opt], "--monitor")||
311 boost::iequals(m_argv[opt], "-m")) {
312 operations.push_back(m_argv[opt]);
314 } else if (boost::iequals(m_argv[opt], "--luids")) {
315 // all following parameters are luids; can't be combined
316 // with setting config and source name
317 while (++opt < m_argc) {
318 parsed.push_back(m_argv[opt]);
319 m_luids.push_back(CmdlineLUID::toLUID(m_argv[opt]));
322 usage(false, string(m_argv[opt]) + ": unknown parameter");
329 m_server = m_argv[opt++];
330 while (opt < m_argc) {
331 parsed.push_back(m_argv[opt]);
332 if (m_sources.empty() ||
334 m_sources.insert(m_argv[opt++]);
336 // first additional parameter was source, rest are luids
337 m_luids.push_back(CmdlineLUID::toLUID(m_argv[opt++]));
342 // check whether we have conflicting operations requested by user
343 if (operations.size() > 1) {
344 usage(false, boost::join(operations, " ") + ": mutually exclusive operations");
348 // common sanity checking for item listing/import/export/update
350 if ((m_import || m_update) && m_dryrun) {
351 usage(false, operations[0] + ": --dry-run not supported");
359 bool Cmdline::parseBool(int opt, const char *longName, const char *shortName,
360 bool def, Bool &value,
363 string option = m_argv[opt];
365 size_t pos = option.find('=');
366 if (pos != option.npos) {
367 param = option.substr(pos + 1);
370 if ((longName && boost::iequals(option, longName)) ||
371 (shortName && boost::iequals(option, shortName))) {
375 } else if (boost::iequals(param, "t") ||
376 boost::iequals(param, "1") ||
377 boost::iequals(param, "true") ||
378 boost::iequals(param, "yes")) {
380 } else if (boost::iequals(param, "f") ||
381 boost::iequals(param, "0") ||
382 boost::iequals(param, "false") ||
383 boost::iequals(param, "no")) {
386 usage(false, string("parameter in '") + m_argv[opt] + "' must be 1/t/true/yes or 0/f/false/no");
392 // keep searching for match
397 bool Cmdline::isSync()
399 // make sure command line arguments really try to run sync
400 if (m_usage || m_version ||
401 m_printServers || boost::trim_copy(m_server) == "?" ||
402 m_printTemplates || m_dontrun ||
403 m_argc == 1 || (m_useDaemon.wasSet() && m_argc == 2) ||
405 m_printConfig || m_remove ||
406 (m_server == "" && m_argc > 1) ||
407 m_configure || m_migrate ||
408 m_status || m_printSessions ||
409 !m_restore.empty() ||
412 (!m_run && m_props.hasProperties(FullProps::IGNORE_GLOBAL_PROPS))) {
419 bool Cmdline::isRestore() const
421 return !m_restore.empty();
424 bool Cmdline::dontRun() const
426 // this mimics the if() checks in run()
427 if (m_usage || m_version ||
428 m_printServers || boost::trim_copy(m_server) == "?" ||
436 void Cmdline::makeObsolete(boost::shared_ptr<SyncConfig> &from)
438 string oldname = from->getRootPath();
439 string newname, suffix;
440 for (int counter = 0; true; counter++) {
441 ostringstream newsuffix;
444 newsuffix << "." << counter;
446 suffix = newsuffix.str();
447 newname = oldname + suffix;
448 if (from->hasPeerProperties()) {
449 boost::shared_ptr<SyncConfig> renamed(new SyncConfig(from->getPeerName() + suffix));
450 if (renamed->exists()) {
451 // don't pick a config name which has the same peer name
452 // as some other, existing config
457 // now renaming should succeed, but let's check anyway
458 if (!rename(oldname.c_str(),
461 } else if (errno != EEXIST && errno != ENOTEMPTY) {
462 SE_THROW(StringPrintf("renaming %s to %s: %s",
469 string newConfigName;
470 string oldContext = from->getContextName();
471 if (from->hasPeerProperties()) {
472 newConfigName = from->getPeerName() + suffix + oldContext;
474 newConfigName = oldContext + suffix;
476 from.reset(new SyncConfig(newConfigName));
479 void Cmdline::copyConfig(const boost::shared_ptr<SyncConfig> &from,
480 const boost::shared_ptr<SyncConfig> &to,
481 const set<string> &selectedSources)
483 const set<string> *sources = NULL;
484 set<string> allSources;
485 if (!selectedSources.empty()) {
486 // use explicitly selected sources
487 sources = &selectedSources;
489 // need an explicit list of all sources which will be copied,
490 // for the createFilters() call below
491 BOOST_FOREACH(const std::string &source, from->getSyncSources()) {
492 allSources.insert(source);
494 sources = &allSources;
497 // Apply config changes on-the-fly. Regardless what we do
498 // (changing an existing config, migrating, creating from
499 // a template), existing shared properties in the desired
500 // context must be preserved unless explicitly overwritten.
501 // Therefore read those, update with command line properties,
502 // then set as filter.
503 ConfigProps syncFilter;
504 SourceProps sourceFilters;
505 m_props.createFilters(to->getContextName(), to->getConfigName(), sources, syncFilter, sourceFilters);
506 from->setConfigFilter(true, "", syncFilter);
507 BOOST_FOREACH(const SourceProps::value_type &entry, sourceFilters) {
508 from->setConfigFilter(false, entry.first, entry.second);
511 // Modify behavior of target UI before using it: set keyring
512 // property separately.
513 InitStateTri keyring = from->getKeyring();
514 if (keyring.wasSet()) {
515 to->setKeyring(keyring);
518 // Write into the requested configuration, creating it if necessary.
519 to->prepareConfigForWrite();
520 to->copy(*from, sources);
523 void Cmdline::finishCopy(const boost::shared_ptr<SyncConfig> &from,
524 const boost::shared_ptr<SyncContext> &to)
526 // give a change to do something before flushing configs to files
527 to->preFlush(to->getUserInterfaceNonNull());
529 // done, now write it
530 m_configModified = true;
535 from->hasPeerProperties()) {
537 // also copy .synthesis dir
538 string fromDir, toDir;
539 fromDir = from->getRootPath() + "/.synthesis";
540 toDir = to->getRootPath() + "/.synthesis";
541 if (isDir(fromDir)) {
542 cp_r(fromDir, toDir);
545 // Succeeded so far, remove "ConsumerReady" flag from migrated
546 // config to hide that old config from normal UI users. Must
547 // do this without going through SyncConfig, because that
548 // would bump the version.
549 BoolConfigProperty ready("ConsumerReady", "", "0");
550 // Also disable auto-syncing in the migrated config.
551 StringConfigProperty autosync("autoSync", "", "");
553 IniFileConfigNode node(from->getRootPath(), "config.ini", false);
554 if (ready.getPropertyValue(node)) {
555 ready.setProperty(node, false);
557 if (!autosync.getProperty(node).empty()) {
558 autosync.setProperty(node, InitStateString("0", true));
563 // same for very old configs
565 IniFileConfigNode node(from->getRootPath() + "/spds/syncml", "config.txt", false);
566 if (!autosync.getProperty(node).empty()) {
567 autosync.setProperty(node, InitStateString("0", true));
572 // Set ConsumerReady for migrated SyncEvolution < 1.2
573 // configs, because in older releases all existing
574 // configurations where shown. SyncEvolution 1.2 is more
575 // strict and assumes that ConsumerReady must be set
576 // explicitly. The sync-ui always has set the flag for
577 // configs created or modified with it, but the command
578 // line did not. Matches similar code in
579 // syncevo-dbus-server.
580 if (from->getConfigVersion(CONFIG_LEVEL_PEER, CONFIG_CUR_VERSION) == 0 /* SyncEvolution < 1.2 */) {
581 to->setConsumerReady(true);
587 void Cmdline::migratePeer(const std::string &fromPeer, const std::string &toPeer)
589 boost::shared_ptr<SyncConfig> from(new SyncConfig(fromPeer));
591 // hack: move to different target config for createSyncClient()
593 boost::shared_ptr<SyncContext> to(createSyncClient());
595 // Special case for Memotoo: explicitly set preferred sync format
596 // to vCard 3.0 as part of the SyncEvolution 1.1.x -> 1.2 migration,
597 // because it works better. Template was also updated in 1.2, but
598 // that alone wouldn't improve existing configs.
599 if (from->getConfigVersion(CONFIG_LEVEL_PEER, CONFIG_CUR_VERSION) == 0) {
600 vector<string> urls = from->getSyncURL();
601 if (urls.size() == 1 &&
602 urls[0] == "http://sync.memotoo.com/syncML") {
603 boost::shared_ptr<SyncContext> to(createSyncClient());
604 m_props[to->getContextName()].m_sourceProps["addressbook"].insert(make_pair("syncFormat", "text/vcard"));
608 copyConfig(from, to, set<string>());
609 finishCopy(from, to);
613 * Finds first instance of delimiter string in other string. In
614 * addition, it treats "\n\n" in a special way: that delimiter also
617 class FindDelimiter {
618 const string m_delimiter;
620 FindDelimiter(const string &delimiter) :
621 m_delimiter(delimiter)
623 boost::iterator_range<string::iterator> operator()(string::iterator begin,
624 string::iterator end)
626 if (m_delimiter == "\n\n") {
627 // match both "\n\n" and "\n\r\n"
628 while (end - begin >= 2) {
629 if (*begin == '\n') {
630 if (*(begin + 1) == '\n') {
631 return boost::iterator_range<string::iterator>(begin, begin + 2);
632 } else if (end - begin >= 3 &&
633 *(begin + 1) == '\r' &&
634 *(begin + 2) == '\n') {
635 return boost::iterator_range<string::iterator>(begin, begin + 3);
640 return boost::iterator_range<string::iterator>(end, end);
642 boost::sub_range<string> range(begin, end);
643 return boost::find_first(range, m_delimiter);
648 bool Cmdline::run() {
649 // --dry-run is only supported by some operations.
650 // Be very strict about it and make sure it is off in all
651 // potentially harmful operations, otherwise users might
652 // expect it to have an effect when it doesn't.
654 // TODO: check filter properties for invalid config and source
659 } else if (m_version) {
660 SE_LOG_SHOW(NULL, NULL, "SyncEvolution %s%s\n%s%s",
662 SyncContext::isStableRelease() ? "" : " (pre-release)",
664 SyncSource::backendsInfo().c_str());
665 } else if (m_printServers || boost::trim_copy(m_server) == "?") {
666 dumpConfigs("Configured servers:",
667 SyncConfig::getConfigs());
668 } else if (m_printTemplates) {
669 SyncConfig::DeviceList devices;
670 if (m_template.empty()){
671 devices.push_back (SyncConfig::DeviceDescription("", "", SyncConfig::MATCH_FOR_CLIENT_MODE));
672 dumpConfigTemplates("Available configuration templates (servers):",
673 SyncConfig::getPeerTemplates(devices), false);
675 //limiting at templates for syncml clients only.
676 devices.push_back (SyncConfig::DeviceDescription("", m_template, SyncConfig::MATCH_FOR_SERVER_MODE));
677 dumpConfigTemplates("Available configuration templates (clients):",
678 SyncConfig::matchPeerTemplates(devices), true);
680 } else if (m_dontrun) {
681 // user asked for information
682 } else if (m_printDatabases) {
684 const SourceRegistry ®istry(SyncSource::getSourceRegistry());
685 boost::shared_ptr<SyncSourceNodes> nodes;
687 boost::shared_ptr<SyncContext> context;
688 FilterConfigNode::ConfigFilter sourceFilter = m_props.createSourceFilter(m_server, "");
689 FilterConfigNode::ConfigFilter::const_iterator backend = sourceFilter.find("backend");
691 if (!m_server.empty()) {
692 // list for specific backend chosen via config
693 if (m_sources.size() != 1) {
694 SE_THROW(StringPrintf("must specify exactly one source after the config name '%s'",
697 context.reset(new SyncContext(m_server));
698 if (!context->exists()) {
699 SE_THROW(StringPrintf("config '%s' does not exist", m_server.c_str()));
701 nodes.reset(new SyncSourceNodes(context->getSyncSourceNodesNoTracking(*m_sources.begin())));
702 header = StringPrintf("%s/%s", m_server.c_str(), m_sources.begin()->c_str());
703 if (!nodes->dataConfigExists()) {
704 SE_THROW(StringPrintf("%s does not exist",
708 context.reset(new SyncContext);
709 boost::shared_ptr<FilterConfigNode> sharedNode(new VolatileConfigNode());
710 boost::shared_ptr<FilterConfigNode> configNode(new VolatileConfigNode());
711 boost::shared_ptr<FilterConfigNode> hiddenNode(new VolatileConfigNode());
712 boost::shared_ptr<FilterConfigNode> trackingNode(new VolatileConfigNode());
713 boost::shared_ptr<FilterConfigNode> serverNode(new VolatileConfigNode());
714 nodes.reset(new SyncSourceNodes(true, sharedNode, configNode, hiddenNode, trackingNode, serverNode, ""));
715 header = backend != sourceFilter.end() ?
719 nodes->getProperties()->setFilter(sourceFilter);
720 FilterConfigNode::ConfigFilter syncFilter = m_props.createSyncFilter(m_server);
721 context->setConfigFilter(true, "", syncFilter);
723 SyncSourceParams params("list", *nodes, context);
724 if (!m_server.empty() || backend != sourceFilter.end()) {
725 // list for specific backend
726 auto_ptr<SyncSource> source(SyncSource::createSource(params, false, NULL));
727 if (source.get() != NULL) {
728 listSources(*source, header);
729 SE_LOG_SHOW(NULL, NULL, "\n");
731 SE_LOG_SHOW(NULL, NULL, "%s:\n cannot list databases", header.c_str());
734 // list for all backends
735 BOOST_FOREACH(const RegisterSyncSource *source, registry) {
736 BOOST_FOREACH(const Values::value_type &alias, source->m_typeValues) {
737 if (!alias.empty() && source->m_enabled) {
738 SourceType type(*alias.begin());
739 nodes->getProperties()->setProperty("backend", type.m_backend);
740 std::string header = boost::join(alias, " = ");
742 auto_ptr<SyncSource> source(SyncSource::createSource(params, false));
744 // silently skip backends like the "file" backend which do not support
745 // listing databases and return NULL unless configured properly
747 listSources(*source, header);
748 SE_LOG_SHOW(NULL, NULL, "\n");
751 SE_LOG_ERROR(NULL, NULL, "%s:\nlisting databases failed", header.c_str());
758 } else if (m_printConfig) {
759 boost::shared_ptr<SyncConfig> config;
760 ConfigProps syncFilter;
761 SourceProps sourceFilters;
763 if (m_template.empty()) {
764 if (m_server.empty()) {
765 usage(false, "--print-config requires either a --template or a server name.");
768 config.reset(new SyncConfig(m_server));
769 if (!config->exists()) {
770 SE_LOG_ERROR(NULL, NULL, "Server '%s' has not been configured yet.", m_server.c_str());
774 // No need to include a context or additional sources,
775 // because reading the m_server config already includes
776 // the right information.
777 m_props.createFilters("", m_server, NULL, syncFilter, sourceFilters);
779 string peer, context;
780 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_template,
781 SyncConfig::NormalizeFlags(SyncConfig::NORMALIZE_SHORTHAND|SyncConfig::NORMALIZE_IS_NEW)),
783 config = SyncConfig::createPeerTemplate(peer);
785 SE_LOG_ERROR(NULL, NULL, "No configuration template for '%s' available.", m_template.c_str());
789 // When instantiating a template, include the properties
790 // of the target context as filter to preserve shared
791 // properties, the final name inside that context as
792 // peer config name, and the sources defined in the template.
793 list<string> sourcelist = config->getSyncSources();
794 set<string> sourceset(sourcelist.begin(), sourcelist.end());
795 m_props.createFilters(std::string("@") + context, "",
797 syncFilter, sourceFilters);
800 // determine whether we dump a peer or a context
801 int flags = DUMP_PROPS_NORMAL;
802 string peer, context;
803 SyncConfig::splitConfigString(config->getConfigName(), peer, context);
805 flags |= HIDE_PER_PEER;
809 if (m_sources.empty() ||
810 m_sources.find("main") != m_sources.end()) {
811 boost::shared_ptr<FilterConfigNode> syncProps(config->getProperties());
812 syncProps->setFilter(syncFilter);
813 dumpProperties(*syncProps, config->getRegistry(), flags);
816 list<string> sources = config->getSyncSources();
818 BOOST_FOREACH(const string &name, sources) {
819 if (m_sources.empty() ||
820 m_sources.find(name) != m_sources.end()) {
821 SE_LOG_SHOW(NULL, NULL, "[%s]", name.c_str());
822 SyncSourceNodes nodes = config->getSyncSourceNodes(name);
823 boost::shared_ptr<FilterConfigNode> sourceProps = nodes.getProperties();
824 sourceProps->setFilter(sourceFilters.createSourceFilter(name));
825 dumpProperties(*sourceProps, SyncSourceConfig::getRegistry(),
826 flags | ((name != *(--sources.end())) ? HIDE_LEGEND : DUMP_PROPS_NORMAL));
829 } else if (m_configure || m_migrate) {
830 if (!needConfigName()) {
834 SyncContext::throwError("--dry-run not supported for configuration changes");
837 // name of renamed config ("foo.old") after migration
840 // True if the target configuration is a context like @default
841 // or @foobar. Relevant in several places in the following
843 bool configureContext = false;
845 bool fromScratch = false;
846 string peer, context;
847 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_server), peer, context);
849 configureContext = true;
853 // Make m_server a fully-qualified name. Useful in error
854 // messages and essential for migrating "foo" where "foo"
855 // happens to map to "foo@bar". Otherwise "foo" will be
856 // mapped incorrectly to "foo@default" after renaming
857 // "foo@bar" to "foo.old@bar".
859 // The inverse problem can occur for "foo@default": after
860 // renaming, "foo" without "@default" would be mapped to
861 // "foo@somewhere-else" if such a config exists.
862 m_server = peer + "@" + context;
864 // Both config changes and migration are implemented as copying from
865 // another config (template resp. old one). Migration also moves
866 // the old config. The target configuration is determined by m_server,
867 // but the exact semantic of it depends on the operation.
868 boost::shared_ptr<SyncConfig> from;
869 boost::shared_ptr<SyncContext> to;
872 if (!m_sources.empty()) {
873 SE_LOG_ERROR(NULL, NULL, "cannot migrate individual sources");
877 string oldContext = context;
878 from.reset(new SyncConfig(m_server));
879 if (!from->exists()) {
880 // for migration into a different context, search for config without context
882 from.reset(new SyncConfig(peer));
883 if (!from->exists()) {
884 SE_LOG_ERROR(NULL, NULL, "Server '%s' has not been configured yet.", m_server.c_str());
889 // Check if we are migrating an individual peer inside
890 // a context which itself is too old. In that case,
891 // the whole context and everything inside it needs to
893 if (!configureContext) {
894 bool obsoleteContext = false;
895 if (from->getLayout() < SyncConfig::SHARED_LAYOUT) {
896 // check whether @default context exists and is too old;
897 // in that case migrate it first
898 SyncConfig target("@default");
899 if (target.exists() &&
900 target.getConfigVersion(CONFIG_LEVEL_CONTEXT, CONFIG_CUR_VERSION) <
901 CONFIG_CONTEXT_MIN_VERSION) {
902 // migrate all peers inside @default *and* the one outside
904 m_server = "@default";
905 obsoleteContext = true;
908 // config already is inside a context; need to check that context
909 if (from->getConfigVersion(CONFIG_LEVEL_CONTEXT, CONFIG_CUR_VERSION) <
910 CONFIG_CONTEXT_MIN_VERSION) {
911 m_server = string("@") + context;
912 obsoleteContext = true;
915 if (obsoleteContext) {
916 // hack: move to different config and back later
917 from.reset(new SyncConfig(m_server));
919 configureContext = true;
923 // rename on disk and point "from" to it
926 // modify the config referenced by the (possibly modified) m_server
927 to.reset(createSyncClient());
929 from.reset(new SyncConfig(m_server));
930 // m_server known, modify it
931 to.reset(createSyncClient());
933 if (!from->exists()) {
934 // creating from scratch, look for template
936 string configTemplate;
937 if (m_template.empty()) {
938 if (configureContext) {
939 // configuring a context, template doesn't matter =>
940 // use default "SyncEvolution" template
942 peer = "SyncEvolution";
943 from = SyncConfig::createPeerTemplate(peer);
944 } else if (peer == "target-config") {
945 // Configuring the source context for local sync
946 // => determine template based on context name.
947 configTemplate = context;
948 from = SyncConfig::createPeerTemplate(context);
950 // template is the peer name
951 configTemplate = m_server;
952 from = SyncConfig::createPeerTemplate(peer);
955 // Template is specified explicitly. It must not contain a context,
956 // because the context comes from the config name.
957 configTemplate = m_template;
958 if (SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(configTemplate,
959 SyncConfig::NormalizeFlags(SyncConfig::NORMALIZE_SHORTHAND|SyncConfig::NORMALIZE_IS_NEW)),
961 SE_LOG_ERROR(NULL, NULL, "Template %s must not specify a context.", configTemplate.c_str());
965 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_server), tmp, context);
966 from = SyncConfig::createPeerTemplate(peer);
968 list<string> missing;
970 // check if all obligatory sync properties are specified; needed
971 // for both the "is complete" check and the error message below
972 ConfigProps syncProps = m_props.createSyncFilter(to->getContextName());
973 bool complete = true;
974 BOOST_FOREACH(const ConfigProperty *prop, SyncConfig::getRegistry()) {
975 if (prop->isObligatory() &&
976 syncProps.find(prop->getMainName()) == syncProps.end()) {
977 missing.push_back(prop->getMainName());
982 // if everything was specified and no invalid template name was given, allow user
983 // to proceed with "none" template; if a template was specified, we skip
984 // this and go directly to the code below which prints an error message
986 m_template.empty()) {
987 from = SyncConfig::createPeerTemplate("none");
991 SE_LOG_ERROR(NULL, NULL, "No configuration template for '%s' available.", configTemplate.c_str());
992 if (m_template.empty()) {
993 SE_LOG_INFO(NULL, NULL,
994 "Use '--template none' and/or specify relevant properties on the command line to create a configuration without a template. Need values for: %s",
995 boost::join(missing, ", ").c_str());
996 } else if (missing.empty()) {
997 SE_LOG_INFO(NULL, NULL, "All relevant properties seem to be set, omit the --template parameter to proceed.");
999 SE_LOG_INFO(NULL, NULL, "\n");
1000 SyncConfig::DeviceList devices;
1001 devices.push_back(SyncConfig::DeviceDescription("", "", SyncConfig::MATCH_ALL));
1002 dumpConfigTemplates("Available configuration templates (clients and servers):",
1003 SyncConfig::getPeerTemplates(devices),
1011 // Which sources are configured is determined as follows:
1012 // - all sources in the template by default (empty set), except when
1013 // - sources are listed explicitly, and either
1014 // - updating an existing config or
1015 // - configuring a context.
1017 // This implies that when configuring a peer from scratch, all
1018 // sources in the template will be created, with command line
1019 // source properties applied to all of them. This might not be
1020 // what we want, but because this is how we have done it
1021 // traditionally, I keep this behavior for now.
1023 // When migrating, m_sources is empty and thus the whole set of
1024 // sources will be migrated. Checking it here for clarity's sake.
1025 set<string> sources;
1027 !m_sources.empty() &&
1028 (!fromScratch || configureContext)) {
1029 sources = m_sources;
1032 // Also copy (aka create) sources listed on the command line if
1033 // creating from scratch and
1034 // - "--template none" enables the "do what I want" mode or
1035 // - source properties apply to it.
1036 // Creating from scratch with other sources is a possible typo
1037 // and will trigger an error below.
1039 BOOST_FOREACH(const string &source, m_sources) {
1040 if (m_template == "none" ||
1041 !m_props.createSourceFilter(to->getContextName(), source).empty()) {
1042 sources.insert(source);
1047 // Special case for migration away from "type": older
1048 // SyncEvolution could cope with "type" only set correctly for
1049 // peers. Real-world case: Memotoo config, context had "type =
1050 // calendar" set for address book.
1052 // Setting "backend" based on an incorrect "type" from the
1053 // context would lead to a broken, unusable config. Solution:
1054 // take "backend" and "databaseFormat" from a peer config when
1055 // migrating a context.
1057 // Note that peers are assumed to be consistent. Not attempt is
1058 // made to detect a config which has inconsistent peer configs.
1059 if (m_migrate && configureContext &&
1060 from->getConfigVersion(CONFIG_LEVEL_CONTEXT, CONFIG_CUR_VERSION) == 0) {
1061 list<string> peers = from->getPeers();
1062 peers.sort(); // make code below deterministic
1064 BOOST_FOREACH(const std::string source, from->getSyncSources()) {
1065 BOOST_FOREACH(const string &peer, peers) {
1066 IniFileConfigNode node(from->getRootPath() + "/peers/" + peer + "/sources/" + source,
1069 string sync = node.readProperty("sync");
1071 boost::iequals(sync, "none") ||
1072 boost::iequals(sync, "disabled")) {
1073 // ignore this peer, it doesn't use the source
1077 SourceType type(node.readProperty("type"));
1078 if (!type.m_backend.empty()) {
1079 // found some "type": use "backend" and
1080 // "dataFormat" in filter, unless the user
1081 // already set a value there
1082 ConfigProps syncFilter;
1083 SourceProps sourceFilters;
1086 m_props.createFilters(to->getContextName(), "",
1087 &set, syncFilter, sourceFilters);
1088 const ConfigProps &sourceFilter = sourceFilters[source];
1089 if (sourceFilter.find("backend") == sourceFilter.end()) {
1090 m_props[to->getContextName()].m_sourceProps[source]["backend"] = type.m_backend;
1092 if (!type.m_localFormat.empty() &&
1093 sourceFilter.find("databaseFormat") == sourceFilter.end()) {
1094 m_props[to->getContextName()].m_sourceProps[source]["databaseFormat"] = type.m_localFormat;
1096 // use it without bothering to keep looking
1097 // (no consistenty check!)
1104 // copy and filter into the target config: createSyncClient()
1105 // creates a SyncContext for m_server, with propert
1106 // implementation of the password handling methods in derived
1107 // classes (D-Bus server, real command line)
1108 copyConfig(from, to, sources);
1110 // Sources are active now according to the server default.
1111 // Disable all sources not selected by user (if any selected)
1112 // and those which have no database.
1114 list<string> configuredSources = to->getSyncSources();
1115 set<string> sources = m_sources;
1117 BOOST_FOREACH(const string &source, configuredSources) {
1118 boost::shared_ptr<PersistentSyncSourceConfig> sourceConfig(to->getSyncSourceConfig(source));
1119 string disable = "";
1120 set<string>::iterator entry = sources.find(source);
1121 bool selected = entry != sources.end();
1123 if (!m_sources.empty() &&
1125 disable = "not selected";
1127 if (entry != sources.end()) {
1128 // The command line parameter matched a valid source.
1129 // All entries left afterwards must have been typos.
1130 sources.erase(entry);
1133 // check whether the sync source works
1134 SyncSourceParams params(source, to->getSyncSourceNodes(source), to);
1135 auto_ptr<SyncSource> syncSource(SyncSource::createSource(params, false, to.get()));
1136 if (syncSource.get() == NULL) {
1137 disable = "no backend available";
1140 SyncSource::Databases databases = syncSource->getDatabases();
1141 if (databases.empty()) {
1142 disable = "no database to synchronize";
1145 disable = "backend failed";
1150 // Do sanity checking of source (can it be enabled?),
1151 // but only set the sync mode if configuring a peer.
1152 // A context-only config doesn't have the "sync"
1155 if (!disable.empty()) {
1156 // abort if the user explicitly asked for the sync source
1157 // and it cannot be enabled, otherwise disable it silently
1159 SyncContext::throwError(source + ": " + disable);
1161 syncMode = "disabled";
1162 } else if (selected) {
1163 // user absolutely wants it: enable even if off by default
1164 ConfigProps filter = m_props.createSourceFilter(m_server, source);
1165 ConfigProps::const_iterator sync = filter.find("sync");
1166 syncMode = sync == filter.end() ? "two-way" : sync->second;
1168 if (!syncMode.empty() &&
1169 !configureContext) {
1170 sourceConfig->setSync(syncMode);
1174 if (!sources.empty()) {
1175 SyncContext::throwError(string("no such source(s): ") + boost::join(sources, " "));
1179 // flush, move .synthesis dir, set ConsumerReady, ...
1180 finishCopy(from, to);
1182 // Now also migrate all peers inside context?
1183 if (configureContext && m_migrate) {
1184 BOOST_FOREACH(const string &peer, from->getPeers()) {
1185 migratePeer(peer + from->getContextName(), peer + to->getContextName());
1187 if (!origPeer.empty()) {
1188 migratePeer(origPeer, origPeer + to->getContextName());
1191 } else if (m_remove) {
1192 if (!needConfigName()) {
1196 SyncContext::throwError("--dry-run not supported for removing configurations");
1199 // extra sanity check
1200 if (!m_sources.empty() ||
1201 m_props.hasProperties(FullProps::IGNORE_GLOBAL_PROPS)) {
1202 usage(false, "too many parameters for --remove");
1205 boost::shared_ptr<SyncConfig> config;
1206 config.reset(new SyncConfig(m_server));
1207 if (!config->exists()) {
1208 SyncContext::throwError(string("no such configuration: ") + m_server);
1211 m_configModified = true;
1214 } else if (m_accessItems) {
1215 // need access to specific source
1216 boost::shared_ptr<SyncContext> context;
1217 context.reset(createSyncClient());
1219 // operating on exactly one source (can be optional)
1221 bool haveSourceName = !m_sources.empty();
1222 if (haveSourceName) {
1223 sourceName = *m_sources.begin();
1227 context->setConfigFilter(true, "", m_props.createSyncFilter(m_server));
1228 context->setConfigFilter(false, "", m_props.createSourceFilter(m_server, sourceName));
1230 SyncSourceNodes sourceNodes = context->getSyncSourceNodesNoTracking(sourceName);
1231 SyncSourceParams params(sourceName, sourceNodes, context);
1232 cxxptr<SyncSource> source;
1235 source.set(SyncSource::createSource(params, true));
1236 } catch (const StatusException &ex) {
1237 // Creating the source failed. Detect some common reasons for this
1238 // and log those instead. None of these situations are fatal by themselves,
1239 // but in combination they are a problem.
1240 if (ex.syncMLStatus() == SyncMLStatus(sysync::LOCERR_CFGPARSE)) {
1241 std::list<std::string> explanation;
1243 explanation.push_back(ex.what());
1244 if (!m_server.empty() && !context->exists()) {
1245 explanation.push_back(StringPrintf("configuration '%s' does not exist", m_server.c_str()));
1247 if (haveSourceName && !sourceNodes.exists()) {
1248 explanation.push_back(StringPrintf("source '%s' does not exist", sourceName.c_str()));
1249 } else if (!haveSourceName) {
1250 explanation.push_back("no source selected");
1252 SyncSourceConfig sourceConfig(sourceName, sourceNodes);
1253 if (!sourceConfig.getBackend().wasSet()) {
1254 explanation.push_back("backend property not set");
1256 SyncContext::throwError(SyncMLStatus(sysync::LOCERR_CFGPARSE),
1257 boost::join(explanation, "\n"));
1263 sysync::TSyError err;
1264 #define CHECK_ERROR(_op) if (err) { SE_THROW_EXCEPTION_STATUS(StatusException, string(source->getName()) + ": " + (_op), SyncMLStatus(err)); }
1266 // acquire passwords before doing anything (interactive password
1267 // access not supported for the command line)
1269 ConfigPropertyRegistry& registry = SyncConfig::getRegistry();
1270 BOOST_FOREACH(const ConfigProperty *prop, registry) {
1271 prop->checkPassword(context->getUserInterfaceNonNull(), m_server, *context->getProperties());
1275 ConfigPropertyRegistry ®istry = SyncSourceConfig::getRegistry();
1276 BOOST_FOREACH(const ConfigProperty *prop, registry) {
1277 prop->checkPassword(context->getUserInterfaceNonNull(), m_server, *context->getProperties(),
1278 source->getName(), sourceNodes.getProperties());
1283 const SyncSource::Operations &ops = source->getOperations();
1285 SyncSourceLogging *logging = dynamic_cast<SyncSourceLogging *>(source.get());
1286 if (!ops.m_startDataRead ||
1287 !ops.m_readNextItem) {
1288 source->throwError("reading items not supported");
1291 err = ops.m_startDataRead(*source, "", "");
1292 CHECK_ERROR("reading items");
1294 readLUIDs(source, luids);
1295 BOOST_FOREACH(string &luid, luids) {
1298 description = logging->getDescription(luid);
1300 SE_LOG_SHOW(NULL, NULL, "%s%s%s",
1301 CmdlineLUID::fromLUID(luid).c_str(),
1302 description.empty() ? "" : ": ",
1303 description.c_str());
1305 } else if (m_deleteItems) {
1306 if (!ops.m_deleteItem) {
1307 source->throwError("deleting items not supported");
1310 bool deleteAll = std::find(m_luids.begin(), m_luids.end(), "*") != m_luids.end();
1311 err = ops.m_startDataRead(*source, "", "");
1312 CHECK_ERROR("reading items");
1314 readLUIDs(source, luids);
1318 if (ops.m_endDataRead) {
1319 err = ops.m_endDataRead(*source);
1320 CHECK_ERROR("stop reading items");
1322 if (ops.m_startDataWrite) {
1323 err = ops.m_startDataWrite(*source);
1324 CHECK_ERROR("writing items");
1326 BOOST_FOREACH(const string &luid, luids) {
1327 sysync::ItemIDType id;
1328 id.item = (char *)luid.c_str();
1329 err = ops.m_deleteItem(*source, &id);
1330 CHECK_ERROR("deleting item");
1333 err = ops.m_endDataWrite(*source, true, &token);
1337 CHECK_ERROR("stop writing items");
1339 SyncSourceRaw *raw = dynamic_cast<SyncSourceRaw *>(source.get());
1341 source->throwError("reading/writing items directly not supported");
1343 if (m_import || m_update) {
1344 err = ops.m_startDataRead(*source, "", "");
1345 CHECK_ERROR("reading items");
1346 if (ops.m_endDataRead) {
1347 err = ops.m_endDataRead(*source);
1348 CHECK_ERROR("stop reading items");
1350 if (ops.m_startDataWrite) {
1351 err = ops.m_startDataWrite(*source);
1352 CHECK_ERROR("writing items");
1355 cxxptr<ifstream> inFile;
1356 if (m_itemPath =="-" ||
1357 !isDir(m_itemPath)) {
1360 if (m_itemPath == "-") {
1361 context->getUserInterfaceNonNull().readStdin(content);
1362 } else if (!ReadFile(m_itemPath, content)) {
1363 SyncContext::throwError(m_itemPath, errno);
1365 if (m_delimiter == "none") {
1367 if (m_luids.size() != 1) {
1368 SyncContext::throwError("need exactly one LUID parameter");
1370 luid = *m_luids.begin();
1373 SE_LOG_SHOW(NULL, NULL, "#0: %s",
1374 insertItem(raw, luid, content).getEncoded().c_str());
1376 typedef boost::split_iterator<string::iterator> string_split_iterator;
1378 FindDelimiter finder(m_delimiter);
1380 // when updating, check number of luids in advance
1382 unsigned long total = 0;
1383 for (string_split_iterator it =
1384 boost::make_split_iterator(content, finder);
1385 it != string_split_iterator();
1389 if (total != m_luids.size()) {
1390 SyncContext::throwError(StringPrintf("%lu items != %lu luids, must match => aborting",
1391 total, (unsigned long)m_luids.size()));
1394 list<string>::const_iterator luidit = m_luids.begin();
1395 for (string_split_iterator it =
1396 boost::make_split_iterator(content, finder);
1397 it != string_split_iterator();
1401 if (luidit == m_luids.end()) {
1402 // was checked above
1403 SyncContext::throwError("internal error, not enough luids");
1408 SE_LOG_SHOW(NULL, NULL, "#%d: %s",
1412 string(it->begin(), it->end())).getEncoded().c_str());
1417 ReadDir dir(m_itemPath);
1419 BOOST_FOREACH(const string &entry, dir) {
1421 string path = m_itemPath + "/" + entry;
1422 if (!ReadFile(path, content)) {
1423 SyncContext::throwError(path, errno);
1425 SE_LOG_SHOW(NULL, NULL, "#%d: %s: %s",
1428 insertItem(raw, "", content).getEncoded().c_str());
1432 err = ops.m_endDataWrite(*source, true, &token);
1436 CHECK_ERROR("stop writing items");
1437 } else if (m_export) {
1438 err = ops.m_startDataRead(*source, "", "");
1439 CHECK_ERROR("reading items");
1441 ostream *out = NULL;
1442 cxxptr<ofstream> outFile;
1443 if (m_itemPath == "-") {
1444 // not actually used, falls back to SE_LOG_SHOW()
1446 } else if(!isDir(m_itemPath)) {
1447 outFile.set(new ofstream(m_itemPath.c_str()));
1450 if (m_luids.empty()) {
1451 readLUIDs(source, m_luids);
1453 bool haveItem = false; // have written one item
1454 bool haveNewline = false; // that item had a newline at the end
1455 BOOST_FOREACH(const string &luid, m_luids) {
1457 raw->readItemRaw(luid, item);
1459 // write into directory
1460 string fullPath = m_itemPath + "/" + luid;
1461 ofstream file((m_itemPath + "/" + luid).c_str());
1465 SyncContext::throwError(fullPath, errno);
1468 std::string delimiter;
1470 if (m_delimiter.size() > 1 &&
1472 m_delimiter[0] == '\n') {
1473 // already wrote initial newline, skip it
1474 delimiter = m_delimiter.substr(1);
1476 delimiter = m_delimiter;
1479 if (out == &std::cout) {
1480 // special case, use logging infrastructure
1481 SE_LOG_SHOW(NULL, NULL, "%s%s",
1484 // always prints newline
1489 haveNewline = boost::ends_with(item, "\n");
1496 if (outFile->bad()) {
1497 SyncContext::throwError(m_itemPath, errno);
1504 if (!needConfigName()) {
1508 std::set<std::string> unmatchedSources;
1509 boost::shared_ptr<SyncContext> context;
1510 context.reset(createSyncClient());
1511 context->setConfigProps(m_props);
1512 context->setQuiet(m_quiet);
1513 context->setDryRun(m_dryrun);
1514 context->setConfigFilter(true, "", m_props.createSyncFilter(m_server));
1515 if (m_sources.empty()) {
1516 // Special semantic of 'no source selected': apply
1517 // filter (if any exists) only to sources which are
1518 // *active*. Configuration of inactive sources is left
1519 // unchanged. This way we don't activate sync sources
1520 // accidentally when the sync mode is modified
1522 BOOST_FOREACH(const std::string &source,
1523 context->getSyncSources()) {
1524 boost::shared_ptr<PersistentSyncSourceConfig> source_config =
1525 context->getSyncSourceConfig(source);
1526 if (!source_config->isDisabled()) {
1527 context->setConfigFilter(false, source, m_props.createSourceFilter(m_server, source));
1531 // apply (possibly empty) source filter to selected sources
1532 BOOST_FOREACH(const std::string &source,
1534 boost::shared_ptr<PersistentSyncSourceConfig> source_config =
1535 context->getSyncSourceConfig(source);
1536 ConfigProps filter = m_props.createSourceFilter(m_server, source);
1537 if (!source_config || !source_config->exists()) {
1538 // invalid source name in m_sources, remember and
1539 // report this below
1540 unmatchedSources.insert(source);
1541 } else if (filter.find("sync") == filter.end()) {
1542 // Sync mode is not set, must override the
1543 // "sync=disabled" set below with the original
1544 // sync mode for the source or (if that is also
1545 // "disabled") with "two-way". The latter is part
1546 // of the command line semantic that listing a
1547 // source activates it.
1548 string sync = source_config->getSync();
1550 sync == "disabled" ? "two-way" : sync;
1551 context->setConfigFilter(false, source, filter);
1553 // sync mode is set, can use m_sourceProps
1554 // directly to apply it
1555 context->setConfigFilter(false, source, filter);
1559 // temporarily disable the rest
1560 FilterConfigNode::ConfigFilter disabled;
1561 disabled["sync"] = InitStateString("disabled", true);
1562 context->setConfigFilter(false, "", disabled);
1565 // check whether there were any sources specified which do not exist
1566 if (unmatchedSources.size()) {
1567 context->throwError(string("no such source(s): ") + boost::join(unmatchedSources, " "));
1572 } else if (m_printSessions) {
1573 vector<string> dirs;
1574 context->getSessions(dirs);
1576 BOOST_FOREACH(const string &dir, dirs) {
1579 } else if(!m_quiet) {
1580 SE_LOG_SHOW(NULL, NULL, "\n");
1582 SE_LOG_SHOW(NULL, NULL, "%s", dir.c_str());
1585 context->readSessionInfo(dir, report);
1588 SE_LOG_SHOW(NULL, NULL, "%s", out.str().c_str());
1591 } else if (!m_restore.empty()) {
1592 // sanity checks: either --after or --before must be given, sources must be selected
1593 if ((!m_after && !m_before) ||
1594 (m_after && m_before)) {
1595 usage(false, "--restore <log dir> must be used with either --after (restore database as it was after that sync) or --before (restore data from before sync)");
1598 if (m_sources.empty()) {
1599 usage(false, "Sources must be selected explicitly for --restore to prevent accidental restore.");
1602 context->restore(m_restore,
1604 SyncContext::DATABASE_AFTER_SYNC :
1605 SyncContext::DATABASE_BEFORE_SYNC);
1608 usage(false, "--dry-run not supported for running a synchronization");
1612 // safety catch: if props are given, then --run
1615 (m_props.hasProperties(FullProps::IGNORE_GLOBAL_PROPS))) {
1616 usage(false, "Properties specified, but neither '--configure' nor '--run' - what did you want?");
1620 return (context->sync(&m_report) == STATUS_OK);
1627 void Cmdline::readLUIDs(SyncSource *source, list<string> &luids)
1629 const SyncSource::Operations &ops = source->getOperations();
1630 sysync::ItemIDType id;
1631 sysync::sInt32 status;
1632 sysync::TSyError err = ops.m_readNextItem(*source, &id, &status, true);
1633 CHECK_ERROR("next item");
1634 while (status != sysync::ReadNextItem_EOF) {
1635 luids.push_back(id.item);
1636 StrDispose(id.item);
1637 StrDispose(id.parent);
1638 err = ops.m_readNextItem(*source, &id, &status, false);
1639 CHECK_ERROR("next item");
1643 CmdlineLUID Cmdline::insertItem(SyncSourceRaw *source, const string &luid, const string &data)
1645 SyncSourceRaw::InsertItemResult res = source->insertItemRaw(luid, data);
1647 cluid.setLUID(res.m_luid);
1651 string Cmdline::cmdOpt(const char *opt, const char *param)
1657 // parameter was provided as part of option
1658 bool included = opt && param &&
1659 boost::ends_with(std::string(opt),
1660 std::string("=") + param);
1661 if (!included && opt && param) {
1664 if (!included && param) {
1671 bool Cmdline::parseProp(PropertyType propertyType,
1674 const char *propname)
1676 std::string args = cmdOpt(opt, param);
1679 usage(false, string("missing parameter for ") + args);
1683 // determine property name and parameter for it
1689 } else if (boost::trim_copy(string(param)) == "?") {
1692 const char *equal = strchr(param, '=');
1694 usage(false, string("the '=<value>' part is missing in: ") + args);
1697 propstr.assign(param, equal - param);
1698 paramstr.assign(equal + 1);
1700 boost::trim(propstr);
1701 boost::trim_left(paramstr);
1703 // parse full property string
1704 PropertySpecifier spec = PropertySpecifier::StringToPropSpec(propstr);
1706 // determine property type and registry
1707 const ConfigPropertyRegistry *validProps = NULL;
1708 switch (propertyType) {
1709 case SYNC_PROPERTY_TYPE:
1710 validProps = &m_validSyncProps;
1712 case SOURCE_PROPERTY_TYPE:
1713 validProps = &m_validSourceProps;
1715 case UNKNOWN_PROPERTY_TYPE:
1716 // must guess based on both registries
1717 if (!propstr.empty()) {
1718 bool isSyncProp = m_validSyncProps.find(spec.m_property) != NULL;
1719 bool isSourceProp = m_validSourceProps.find(spec.m_property) != NULL;
1723 usage(false, StringPrintf("property '%s' in %s could be both a sync and a source property, use --sync-property or --source-property to disambiguate it", propname, args.c_str()));
1726 validProps = &m_validSyncProps;
1728 } else if (isSourceProp ||
1729 boost::iequals(spec.m_property, "type")) {
1730 validProps = &m_validSourceProps;
1733 usage(false, StringPrintf("unrecognized property '%s' in %s", propname, args.c_str()));
1735 usage(false, StringPrintf("unrecognized property in %s", args.c_str()));
1740 usage(false, StringPrintf("a property name must be given in %s", args.c_str()));
1745 if (boost::trim_copy(string(param)) == "?") {
1748 return listPropValues(*validProps, spec.m_property, opt ? opt : "");
1750 return listProperties(*validProps, opt ? opt : "");
1753 if (boost::trim_copy(paramstr) == "?") {
1755 return listPropValues(*validProps, spec.m_property, args);
1757 const ConfigProperty *prop = validProps->find(spec.m_property);
1758 if (!prop && boost::iequals(spec.m_property, "type")) {
1759 // compatiblity mode for "type": map to the properties which
1761 prop = validProps->find("backend");
1763 SE_LOG_ERROR(NULL, NULL, "backend: no such property");
1766 SourceType sourceType(paramstr);
1768 if (!prop->checkValue(sourceType.m_backend, error)) {
1769 SE_LOG_ERROR(NULL, NULL, "%s: %s", args.c_str(), error.c_str());
1772 ContextProps &props = m_props[spec.m_config];
1773 props.m_sourceProps[spec.m_source]["backend"] =
1774 InitStateString(sourceType.m_backend,
1775 !sourceType.m_backend.empty());
1776 props.m_sourceProps[spec.m_source]["databaseFormat"] =
1777 InitStateString(sourceType.m_localFormat,
1778 !sourceType.m_localFormat.empty());
1779 props.m_sourceProps[spec.m_source]["syncFormat"] =
1780 InitStateString(sourceType.m_format,
1781 !sourceType.m_format.empty());
1782 props.m_sourceProps[spec.m_source]["forceSyncFormat"] =
1783 sourceType.m_forceFormat ?
1784 InitStateString("1", true) :
1785 InitStateString("0", false);
1788 SE_LOG_ERROR(NULL, NULL, "%s: no such property", args.c_str());
1792 if (!prop->checkValue(paramstr, error)) {
1793 SE_LOG_ERROR(NULL, NULL, "%s: %s", args.c_str(), error.c_str());
1796 ContextProps &props = m_props[spec.m_config];
1797 if (validProps == &m_validSyncProps) {
1798 // complain if sync property includes source prefix
1799 if (!spec.m_source.empty()) {
1800 SE_LOG_ERROR(NULL, NULL, "%s: source name '%s' not allowed in sync property",
1802 spec.m_source.c_str());
1805 props.m_syncProps[spec.m_property] = paramstr;
1807 props.m_sourceProps[spec.m_source][spec.m_property] = paramstr;
1816 bool Cmdline::parseAssignment(int &opt, vector<string> &parsed,
1817 PropertyType propertyType,
1818 const char *propname,
1822 bool haveParam = false;
1823 string cmdopt(m_argv[opt]);
1824 size_t off = cmdopt.find('=');
1825 if (off != cmdopt.npos) {
1826 // value embedded in option
1827 param = cmdopt.substr(off + 1);
1829 } else if (!def && ++opt < m_argc) {
1830 // assume next entry is parameter
1831 param = m_argv[opt];
1832 parsed.push_back(m_argv[opt]);
1840 return parseProp(propertyType,
1842 haveParam ? param.c_str() : NULL,
1846 bool Cmdline::listPropValues(const ConfigPropertyRegistry &validProps,
1847 const string &propName,
1850 const ConfigProperty *prop = validProps.find(propName);
1851 if (!prop && boost::iequals(propName, "type")) {
1852 SE_LOG_SHOW(NULL, NULL,
1854 " <backend>[:<format>[:<version][!]]\n"
1855 " legacy property, replaced by 'backend', 'databaseFormat',\n"
1856 " 'syncFormat', 'forceSyncFormat'",
1860 SE_LOG_ERROR(NULL, NULL, "%s: no such property", opt.c_str());
1865 string comment = prop->getComment();
1867 if (comment != "") {
1868 list<string> commentLines;
1869 ConfigProperty::splitComment(comment, commentLines);
1870 BOOST_FOREACH(const string &line, commentLines) {
1871 out << " " << line << endl;
1874 out << " no documentation available" << endl;
1876 SE_LOG_SHOW(NULL, NULL, "%s", out.str().c_str());
1881 bool Cmdline::listProperties(const ConfigPropertyRegistry &validProps,
1884 // The first of several related properties has a comment.
1885 // Remember that comment and print it as late as possible,
1886 // that way related properties preceed their comment.
1888 bool needComma = false;
1890 BOOST_FOREACH(const ConfigProperty *prop, validProps) {
1891 if (!prop->isHidden()) {
1892 string newComment = prop->getComment();
1894 if (newComment != "") {
1895 if (!comment.empty()) {
1897 dumpComment(out, " ", comment);
1901 comment = newComment;
1903 std::string def = prop->getDefValue();
1907 ConfigProperty::Sharing sharing = prop->getSharing();
1911 out << boost::join(prop->getNames(), " = ")
1912 << " (" << def << ", "
1913 << ConfigProperty::sharing2str(sharing)
1914 << (prop->isObligatory() ? ", required" : "")
1920 dumpComment(out, " ", comment);
1921 SE_LOG_SHOW(NULL, NULL, "%s", out.str().c_str());
1925 static void findPeerProps(FilterConfigNode::ConfigFilter &filter,
1926 ConfigPropertyRegistry ®istry,
1927 set<string> &peerProps)
1929 BOOST_FOREACH(StringPair entry, filter) {
1930 const ConfigProperty *prop = registry.find(entry.first);
1932 prop->getSharing() == ConfigProperty::NO_SHARING) {
1933 peerProps.insert(entry.first);
1938 void Cmdline::checkForPeerProps()
1940 set<string> peerProps;
1942 BOOST_FOREACH(FullProps::value_type &entry, m_props) {
1943 ContextProps &props = entry.second;
1945 findPeerProps(props.m_syncProps, SyncConfig::getRegistry(), peerProps);
1946 BOOST_FOREACH(SourceProps::value_type &entry, props.m_sourceProps) {
1947 findPeerProps(entry.second, SyncSourceConfig::getRegistry(), peerProps);
1950 if (!peerProps.empty()) {
1951 string props = boost::join(peerProps, ", ");
1952 if (props == "forceSyncFormat, syncFormat") {
1953 // special case: these two properties might have been added by the
1954 // legacy "sync" property, which applies to both shared and unshared
1955 // properties => cannot determine that here anymore, so ignore it
1957 SyncContext::throwError(string("per-peer (unshared) properties not allowed: ") +
1963 void Cmdline::listSources(SyncSource &syncSource, const string &header)
1966 out << header << ":\n";
1968 if (syncSource.isInactive()) {
1969 out << "not enabled during compilation or not usable in the current environment\n";
1971 SyncSource::Databases databases = syncSource.getDatabases();
1973 BOOST_FOREACH(const SyncSource::Database &database, databases) {
1974 out << " " << database.m_name << " (" << database.m_uri << ")";
1975 if (database.m_isDefault) {
1976 out << " <default>";
1981 SE_LOG_SHOW(NULL, NULL, "%s", out.str().c_str());
1984 void Cmdline::dumpConfigs(const string &preamble,
1985 const SyncConfig::ConfigList &servers)
1988 out << preamble << endl;
1989 BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server,servers) {
1990 out << " " << server.first << " = " << server.second <<endl;
1992 if (!servers.size()) {
1993 out << " none" << endl;
1995 SE_LOG_SHOW(NULL, NULL, "%s", out.str().c_str());
1998 void Cmdline::dumpConfigTemplates(const string &preamble,
1999 const SyncConfig::TemplateList &templates,
2001 Logger::Level level)
2004 out << preamble << endl;
2005 out << " " << "template name" << " = " << "template description";
2007 out << " " << "matching score in percent (100% = exact match)";
2011 BOOST_FOREACH(const SyncConfig::TemplateList::value_type server,templates) {
2012 out << " " << server->m_templateId << " = " << server->m_description;
2014 out << " " << server->m_rank *20 << "%";
2018 if (!templates.size()) {
2019 out << " none" << endl;
2021 SE_LOG(level, NULL, NULL, "%s", out.str().c_str());
2024 void Cmdline::dumpProperties(const ConfigNode &configuredProps,
2025 const ConfigPropertyRegistry &allProps,
2028 list<string> perPeer, perContext, global;
2031 BOOST_FOREACH(const ConfigProperty *prop, allProps) {
2032 if (prop->isHidden() ||
2033 ((flags & HIDE_PER_PEER) &&
2034 prop->getSharing() == ConfigProperty::NO_SHARING)) {
2038 string comment = prop->getComment();
2039 if (!comment.empty()) {
2041 dumpComment(out, "# ", comment);
2044 InitStateString value = prop->getProperty(configuredProps);
2045 if (!value.wasSet()) {
2048 out << prop->getMainName() << " = " << value.get() << endl;
2050 list<string> *type = NULL;
2051 switch (prop->getSharing()) {
2052 case ConfigProperty::GLOBAL_SHARING:
2055 case ConfigProperty::SOURCE_SET_SHARING:
2058 case ConfigProperty::NO_SHARING:
2063 type->push_back(prop->getMainName());
2067 if (!m_quiet && !(flags & HIDE_LEGEND)) {
2068 if (!perPeer.empty() ||
2069 !perContext.empty() ||
2073 if (!perPeer.empty()) {
2074 out << "# per-peer (unshared) properties: " << boost::join(perPeer, ", ") << endl;
2076 if (!perContext.empty()) {
2077 out << "# shared by peers in same context: " << boost::join(perContext, ", ") << endl;
2079 if (!global.empty()) {
2080 out << "# global properties: " << boost::join(global, ", ") << endl;
2084 SE_LOG_SHOW(NULL, NULL, "%s", out.str().c_str());
2087 void Cmdline::dumpComment(ostream &stream,
2088 const string &prefix,
2089 const string &comment)
2091 list<string> commentLines;
2092 ConfigProperty::splitComment(comment, commentLines);
2093 BOOST_FOREACH(const string &line, commentLines) {
2094 stream << prefix << line << endl;
2098 void Cmdline::usage(bool full, const string &error, const string ¶m)
2100 SE_LOG_SHOW(NULL, NULL, "%s", synopsis);
2102 SE_LOG_SHOW(NULL, NULL, "\nOptions:\n%s", options);
2106 SE_LOG_SHOW(NULL, NULL, "\n");
2107 SE_LOG_ERROR(NULL, NULL, "%s", error.c_str());
2110 SE_LOG_INFO(NULL, NULL, "use '%s%s?' to get a list of valid parameters",
2112 boost::ends_with(param, "=") ? "" : " ");
2116 bool Cmdline::needConfigName()
2118 if (m_server.empty()) {
2119 usage(false, "No configuration name specified.");
2127 SyncContext* Cmdline::createSyncClient() {
2128 return new SyncContext(m_server, true);
2131 #ifdef ENABLE_UNIT_TESTS
2133 /** simple line-by-line diff */
2134 static string diffStrings(const string &lhs, const string &rhs)
2138 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
2139 string_split_iterator lit =
2140 boost::make_split_iterator(lhs, boost::first_finder("\n", boost::is_iequal()));
2141 string_split_iterator rit =
2142 boost::make_split_iterator(rhs, boost::first_finder("\n", boost::is_iequal()));
2143 while (lit != string_split_iterator() &&
2144 rit != string_split_iterator()) {
2146 res << "< " << *lit << endl;
2147 res << "> " << *rit << endl;
2153 while (lit != string_split_iterator()) {
2154 res << "< " << *lit << endl;
2158 while (rit != string_split_iterator()) {
2159 res << "> " << *rit << endl;
2166 # define CPPUNIT_ASSERT_EQUAL_DIFF( expected, actual ) \
2168 string expected_ = (expected); \
2169 string actual_ = (actual); \
2170 if (expected_ != actual_) { \
2171 CPPUNIT_NS::Message cpputMsg_(string("expected:\n") + \
2173 cpputMsg_.addDetail(string("actual:\n") + \
2175 cpputMsg_.addDetail(string("diff:\n") + \
2176 diffStrings(expected_, actual_)); \
2177 CPPUNIT_NS::Asserter::fail( cpputMsg_, \
2178 CPPUNIT_SOURCELINE() ); \
2183 static bool isPropAssignment(const string &buffer) {
2184 // ignore these comments (occur in type description)
2185 if (boost::starts_with(buffer, "KCalExtended = ") ||
2186 boost::starts_with(buffer, "mkcal = ") ||
2187 boost::starts_with(buffer, "QtContacts = ")) {
2192 while (start < buffer.size() &&
2193 !isspace(buffer[start])) {
2196 if (start + 3 <= buffer.size() &&
2197 buffer.substr(start, 3) == " = ") {
2204 // remove pure comment lines from buffer,
2205 // also empty lines,
2206 // also defaultPeer and keyring (because reference properties do not include global props)
2207 static string filterConfig(const string &buffer)
2211 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
2212 for (string_split_iterator it =
2213 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
2214 it != string_split_iterator();
2216 string line = boost::copy_range<string>(*it);
2217 if (!line.empty() &&
2218 line.find("defaultPeer =") == line.npos &&
2219 line.find("keyring =") == line.npos &&
2220 (!boost::starts_with(line, "# ") ||
2221 isPropAssignment(line.substr(2)))) {
2222 res << line << endl;
2229 static string removeComments(const string &buffer)
2233 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
2234 for (string_split_iterator it =
2235 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
2236 it != string_split_iterator();
2238 string line = boost::copy_range<string>(*it);
2239 if (!line.empty() &&
2240 !boost::starts_with(line, "#")) {
2241 res << line << endl;
2248 // remove comment lines from scanFiles() output
2249 static string filterFiles(const string &buffer)
2253 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
2254 for (string_split_iterator it =
2255 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
2256 it != string_split_iterator();
2258 string line = boost::copy_range<string>(*it);
2259 if (line.find(":#") == line.npos) {
2261 // do not add extra newline after last newline
2262 if (!line.empty() || it->end() < buffer.end()) {
2272 static string injectValues(const string &buffer)
2274 string res = buffer;
2277 // username/password not set in templates, only in configs created
2278 // via the command line - not anymore, but if it ever comes back,
2279 // here's the place for it
2280 boost::replace_first(res,
2282 "username = your SyncML server account name");
2283 boost::replace_first(res,
2285 "password = your SyncML server password");
2290 // remove lines indented with spaces
2291 static string filterIndented(const string &buffer)
2296 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
2297 for (string_split_iterator it =
2298 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
2299 it != string_split_iterator();
2301 if (!boost::starts_with(*it, " ")) {
2314 // sort lines by file, preserving order inside each line
2315 static void sortConfig(string &config)
2317 // file name, line number, property
2318 typedef pair<string, pair<int, string> > line_t;
2319 vector<line_t> lines;
2320 typedef boost::split_iterator<string::iterator> string_split_iterator;
2322 for (string_split_iterator it =
2323 boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal()));
2324 it != string_split_iterator();
2326 string line(it->begin(), it->end());
2331 size_t colon = line.find(':');
2332 string prefix = line.substr(0, colon);
2333 lines.push_back(make_pair(prefix, make_pair(linenr, line.substr(colon))));
2336 // stable sort because of line number
2337 sort(lines.begin(), lines.end());
2339 size_t len = config.size();
2341 config.reserve(len);
2342 BOOST_FOREACH(const line_t &line, lines) {
2343 config += line.first;
2344 config += line.second.second;
2349 // convert the internal config dump to .ini style (--print-config)
2350 static string internalToIni(const string &config)
2355 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
2356 for (string_split_iterator it =
2357 boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal()));
2358 it != string_split_iterator();
2360 string line(it->begin(), it->end());
2365 size_t colon = line.find(':');
2366 string prefix = line.substr(0, colon);
2368 // internal values are not part of the --print-config output
2369 if (boost::contains(prefix, ".internal.ini") ||
2370 boost::contains(line, "= internal value")) {
2374 // --print-config also doesn't duplicate the "type" property
2375 // => remove the shared property
2376 if (boost::contains(line, ":type = ") &&
2377 boost::starts_with(line, "sources/")) {
2381 // sources/<name>/config.ini or
2382 // spds/sources/<name>/config.ini
2383 size_t endslash = prefix.rfind('/');
2384 if (endslash != line.npos && endslash > 1) {
2385 size_t slash = prefix.rfind('/', endslash - 1);
2386 if (slash != line.npos) {
2387 string newsource = prefix.substr(slash + 1, endslash - slash - 1);
2388 if (newsource != section &&
2389 prefix.find("/sources/") != prefix.npos &&
2390 newsource != "syncml") {
2391 res << "[" << newsource << "]" << endl;
2392 section = newsource;
2396 string assignment = line.substr(colon + 1);
2397 // substitude aliases with generic values
2398 boost::replace_first(assignment, "= syncml:auth-md5", "= md5");
2399 boost::replace_first(assignment, "= syncml:auth-basix", "= basic");
2400 res << assignment << endl;
2406 /** result of removeComments(filterRandomUUID(filterConfig())) for Google Calendar template/config */
2407 static const std::string googlecaldav =
2408 "syncURL = https://www.google.com/calendar/dav/%u/user/?SyncEvolution=Google\n"
2409 "printChanges = 0\n"
2411 "deviceId = fixed-devid\n"
2412 "IconURI = image://themedimage/icons/services/google-calendar\n"
2413 "ConsumerReady = 1\n"
2414 "peerType = WebDAV\n"
2417 "backend = CalDAV\n";
2419 /** result of removeComments(filterRandomUUID(filterConfig())) for Yahoo Calendar + Contacts */
2420 static const std::string yahoo =
2421 "printChanges = 0\n"
2423 "deviceId = fixed-devid\n"
2424 "IconURI = image://themedimage/icons/services/yahoo\n"
2425 "ConsumerReady = 1\n"
2426 "peerType = WebDAV\n"
2429 "backend = CardDAV\n"
2432 "backend = CalDAV\n";
2435 * Testing is based on a text representation of a directory
2436 * hierarchy where each line is of the format
2437 * <file path>:<line in file>
2439 * The order of files is alphabetical, of lines in the file as
2440 * in the file. Lines in the file without line break cannot
2443 * The root of the hierarchy is not part of the representation
2446 class CmdlineTest : public CppUnit::TestFixture {
2447 CPPUNIT_TEST_SUITE(CmdlineTest);
2448 CPPUNIT_TEST(testFramework);
2449 CPPUNIT_TEST(testSetupScheduleWorld);
2450 CPPUNIT_TEST(testFutureConfig);
2451 CPPUNIT_TEST(testPeerConfigMigration);
2452 CPPUNIT_TEST(testContextConfigMigration);
2453 CPPUNIT_TEST(testSetupDefault);
2454 CPPUNIT_TEST(testSetupRenamed);
2455 CPPUNIT_TEST(testSetupFunambol);
2456 CPPUNIT_TEST(testSetupSynthesis);
2457 CPPUNIT_TEST(testPrintServers);
2458 CPPUNIT_TEST(testPrintConfig);
2459 CPPUNIT_TEST(testPrintFileTemplates);
2460 CPPUNIT_TEST(testPrintFileTemplatesConfig);
2461 CPPUNIT_TEST(testTemplate);
2462 CPPUNIT_TEST(testMatchTemplate);
2463 CPPUNIT_TEST(testAddSource);
2464 CPPUNIT_TEST(testSync);
2465 CPPUNIT_TEST(testKeyring);
2466 CPPUNIT_TEST(testWebDAV);
2467 CPPUNIT_TEST(testConfigure);
2468 CPPUNIT_TEST(testConfigureTemplates);
2469 CPPUNIT_TEST(testConfigureSources);
2470 CPPUNIT_TEST(testOldConfigure);
2471 CPPUNIT_TEST(testMigrate);
2472 CPPUNIT_TEST(testMigrateContext);
2473 CPPUNIT_TEST(testMigrateAutoSync);
2474 CPPUNIT_TEST(testItemOperations);
2475 CPPUNIT_TEST_SUITE_END();
2479 m_testDir("CmdlineTest")
2491 /** verify that createFiles/scanFiles themselves work */
2492 void testFramework() {
2493 const string root(m_testDir);
2494 const string content("baz:line\n"
2496 "caz/subdir2/sub:# comment\n"
2497 "caz/subdir2/sub:# foo = bar\n"
2498 "caz/subdir2/sub:# empty = \n"
2499 "caz/subdir2/sub:# another comment\n"
2504 const string filtered("baz:line\n"
2506 "caz/subdir2/sub:# foo = bar\n"
2507 "caz/subdir2/sub:# empty = \n"
2511 createFiles(root, content);
2512 string res = scanFiles(root);
2513 CPPUNIT_ASSERT_EQUAL_DIFF(filtered, res);
2516 void removeRandomUUID(string &buffer) {
2517 string uuidstr = "deviceId = syncevolution-";
2518 size_t uuid = buffer.find(uuidstr);
2519 CPPUNIT_ASSERT(uuid != buffer.npos);
2520 size_t end = buffer.find("\n", uuid + uuidstr.size());
2521 CPPUNIT_ASSERT(end != buffer.npos);
2522 buffer.replace(uuid, end - uuid, "deviceId = fixed-devid");
2525 string filterRandomUUID(const string &buffer) {
2526 string copy = buffer;
2527 removeRandomUUID(copy);
2531 /** create new configurations */
2532 void testSetupScheduleWorld() { doSetupScheduleWorld(false); }
2533 void doSetupScheduleWorld(bool shared) {
2535 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
2536 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2537 ScopedEnvChange home("HOME", m_testDir);
2540 root += "/syncevolution/default";
2543 peer = root + "/peers/scheduleworld";
2550 TestCmdline cmdline("--configure",
2551 "--sync-property", "proxyHost = proxy",
2556 string res = scanFiles(root);
2557 removeRandomUUID(res);
2558 string expected = ScheduleWorldConfig();
2559 sortConfig(expected);
2560 boost::replace_first(expected,
2562 "proxyHost = proxy");
2563 boost::replace_all(expected,
2566 boost::replace_first(expected,
2567 "addressbook/config.ini:sync = disabled",
2568 "addressbook/config.ini:sync = two-way");
2569 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2574 TestCmdline cmdline("--configure",
2575 "--sync-property", "deviceID = fixed-devid",
2579 string res = scanFiles(root);
2580 string expected = ScheduleWorldConfig();
2581 sortConfig(expected);
2582 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2586 void expectTooOld() {
2587 bool caught = false;
2589 SyncConfig config("scheduleworld");
2590 } catch (const StatusException &ex) {
2592 if (ex.syncMLStatus() != STATUS_RELEASE_TOO_OLD) {
2595 CPPUNIT_ASSERT_EQUAL(StringPrintf("SyncEvolution %s is too old to read configuration 'scheduleworld', please upgrade SyncEvolution.", VERSION),
2599 CPPUNIT_ASSERT(caught);
2602 void testFutureConfig() {
2603 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2604 ScopedEnvChange home("HOME", m_testDir);
2606 doSetupScheduleWorld(false);
2607 // bump min/cur version to something not supported, then
2608 // try to read => should fail
2609 IniFileConfigNode root(m_testDir, "/syncevolution/.internal.ini", false);
2610 IniFileConfigNode context(m_testDir + "/syncevolution/default", ".internal.ini", false);
2611 IniFileConfigNode peer(m_testDir + "/syncevolution/default/peers/scheduleworld", ".internal.ini", false);
2612 root.setProperty("rootMinVersion", StringPrintf("%d", CONFIG_ROOT_MIN_VERSION + 1));
2613 root.setProperty("rootCurVersion", StringPrintf("%d", CONFIG_ROOT_CUR_VERSION + 1));
2615 context.setProperty("contextMinVersion", StringPrintf("%d", CONFIG_CONTEXT_MIN_VERSION + 1));
2616 context.setProperty("contextCurVersion", StringPrintf("%d", CONFIG_CONTEXT_CUR_VERSION + 1));
2618 peer.setProperty("peerMinVersion", StringPrintf("%d", CONFIG_PEER_MIN_VERSION + 1));
2619 peer.setProperty("peerCurVersion", StringPrintf("%d", CONFIG_PEER_CUR_VERSION + 1));
2624 root.setProperty("rootMinVersion", StringPrintf("%d", CONFIG_ROOT_MIN_VERSION));
2628 context.setProperty("contextMinVersion", StringPrintf("%d", CONFIG_CONTEXT_MIN_VERSION));
2633 peer.setProperty("peerMinVersion", StringPrintf("%d", CONFIG_PEER_MIN_VERSION));
2635 SyncConfig config("scheduleworld");
2638 void expectMigration(const std::string &config) {
2639 bool caught = false;
2641 SyncConfig c(config);
2642 c.prepareConfigForWrite();
2643 } catch (const StatusException &ex) {
2645 if (ex.syncMLStatus() != STATUS_MIGRATION_NEEDED) {
2648 CPPUNIT_ASSERT_EQUAL(StringPrintf("Proceeding would modify config '%s' such that the "
2649 "previous SyncEvolution release will not be able to use it. "
2650 "Stopping now. Please explicitly acknowledge this step by "
2651 "running the following command on the command line: "
2652 "syncevolution --migrate '%s'",
2658 CPPUNIT_ASSERT(caught);
2661 void testPeerConfigMigration() {
2662 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2663 ScopedEnvChange home("HOME", m_testDir);
2665 doSetupScheduleWorld(false);
2666 // decrease min/cur version to something no longer supported,
2667 // then try to write => should migrate in release mode and fail otherwise
2668 IniFileConfigNode peer(m_testDir + "/syncevolution/default/peers/scheduleworld", ".internal.ini", false);
2669 peer.setProperty("peerMinVersion", StringPrintf("%d", CONFIG_PEER_CUR_VERSION - 1));
2670 peer.setProperty("peerCurVersion", StringPrintf("%d", CONFIG_PEER_CUR_VERSION - 1));
2673 SyncContext::setStableRelease(false);
2674 expectMigration("scheduleworld");
2676 SyncContext::setStableRelease(true);
2678 SyncConfig config("scheduleworld");
2679 config.prepareConfigForWrite();
2682 TestCmdline cmdline("--print-servers", NULL);
2684 CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n"
2685 " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n"
2686 " scheduleworld.old = CmdlineTest/syncevolution/default/peers/scheduleworld.old\n",
2687 cmdline.m_out.str());
2690 // should be okay now
2691 SyncContext::setStableRelease(false);
2693 SyncConfig config("scheduleworld");
2694 config.prepareConfigForWrite();
2697 // do the same migration with command line
2698 SyncContext::setStableRelease(false);
2699 rm_r(m_testDir + "/syncevolution/default/peers/scheduleworld");
2700 CPPUNIT_ASSERT_EQUAL(0, rename((m_testDir + "/syncevolution/default/peers/scheduleworld.old").c_str(),
2701 (m_testDir + "/syncevolution/default/peers/scheduleworld").c_str()));
2703 TestCmdline cmdline("--migrate", "scheduleworld", NULL);
2707 SyncConfig config("scheduleworld");
2708 config.prepareConfigForWrite();
2711 TestCmdline cmdline("--print-servers", NULL);
2713 CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n"
2714 " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n"
2715 " scheduleworld.old = CmdlineTest/syncevolution/default/peers/scheduleworld.old\n",
2716 cmdline.m_out.str());
2720 void testContextConfigMigration() {
2721 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2722 ScopedEnvChange home("HOME", m_testDir);
2724 doSetupScheduleWorld(false);
2725 // decrease min/cur version to something no longer supported,
2726 // then try to write => should migrate in release mode and fail otherwise
2727 IniFileConfigNode context(m_testDir + "/syncevolution/default", ".internal.ini", false);
2728 context.setProperty("contextMinVersion", StringPrintf("%d", CONFIG_CONTEXT_CUR_VERSION - 1));
2729 context.setProperty("contextCurVersion", StringPrintf("%d", CONFIG_CONTEXT_CUR_VERSION - 1));
2732 SyncContext::setStableRelease(false);
2733 expectMigration("@default");
2735 SyncContext::setStableRelease(true);
2737 SyncConfig config("@default");
2738 config.prepareConfigForWrite();
2741 TestCmdline cmdline("--print-servers", NULL);
2743 CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n"
2744 " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n"
2745 " scheduleworld.old@default.old = CmdlineTest/syncevolution/default.old/peers/scheduleworld.old\n",
2746 cmdline.m_out.str());
2749 // should be okay now
2750 SyncContext::setStableRelease(false);
2752 SyncConfig config("@default");
2753 config.prepareConfigForWrite();
2756 // do the same migration with command line
2757 SyncContext::setStableRelease(false);
2758 rm_r(m_testDir + "/syncevolution/default");
2759 CPPUNIT_ASSERT_EQUAL(0, rename((m_testDir + "/syncevolution/default.old/peers/scheduleworld.old").c_str(),
2760 (m_testDir + "/syncevolution/default.old/peers/scheduleworld").c_str()));
2761 CPPUNIT_ASSERT_EQUAL(0, rename((m_testDir + "/syncevolution/default.old").c_str(),
2762 (m_testDir + "/syncevolution/default").c_str()));
2764 TestCmdline cmdline("--migrate", "@default", NULL);
2768 SyncConfig config("@default");
2769 config.prepareConfigForWrite();
2772 TestCmdline cmdline("--print-servers", NULL);
2774 CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n"
2775 " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n"
2776 " scheduleworld.old@default.old = CmdlineTest/syncevolution/default.old/peers/scheduleworld.old\n",
2777 cmdline.m_out.str());
2782 void testSetupDefault() {
2784 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
2785 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2786 ScopedEnvChange home("HOME", m_testDir);
2789 root += "/syncevolution/default";
2790 TestCmdline cmdline("--configure",
2791 "--template", "default",
2792 "--sync-property", "deviceID = fixed-devid",
2793 "some-other-server",
2796 string res = scanFiles(root, "some-other-server");
2797 string expected = DefaultConfig();
2798 sortConfig(expected);
2799 boost::replace_all(expected, "/syncevolution/", "/some-other-server/");
2800 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2803 void testSetupRenamed() {
2805 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
2806 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2807 ScopedEnvChange home("HOME", m_testDir);
2810 root += "/syncevolution/default";
2811 TestCmdline cmdline("--configure",
2812 "--template", "scheduleworld",
2813 "--sync-property", "deviceID = fixed-devid",
2817 string res = scanFiles(root, "scheduleworld2");
2818 string expected = ScheduleWorldConfig();
2819 sortConfig(expected);
2820 boost::replace_all(expected, "/scheduleworld/", "/scheduleworld2/");
2821 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2824 void testSetupFunambol() { doSetupFunambol(false); }
2825 void doSetupFunambol(bool shared) {
2827 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
2828 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2829 ScopedEnvChange home("HOME", m_testDir);
2832 root += "/syncevolution/default";
2835 peer = root + "/peers/funambol";
2841 const char * const argv_fixed[] = {
2843 "--sync-property", "deviceID = fixed-devid",
2844 // templates are case-insensitive
2847 }, * const argv_shared[] = {
2852 TestCmdline cmdline(shared ? argv_shared : argv_fixed);
2854 string res = scanFiles(root, "funambol");
2855 string expected = FunambolConfig();
2856 sortConfig(expected);
2857 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2860 void testSetupSynthesis() { doSetupSynthesis(false); }
2861 void doSetupSynthesis(bool shared) {
2863 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
2864 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2865 ScopedEnvChange home("HOME", m_testDir);
2868 root += "/syncevolution/default";
2871 peer = root + "/peers/synthesis";
2876 const char * const argv_fixed[] = {
2878 "--sync-property", "deviceID = fixed-devid",
2881 }, * const argv_shared[] = {
2886 TestCmdline cmdline(shared ? argv_shared : argv_fixed);
2888 string res = scanFiles(root, "synthesis");
2889 string expected = SynthesisConfig();
2890 sortConfig(expected);
2891 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2894 void testTemplate() {
2895 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
2896 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2897 ScopedEnvChange home("HOME", m_testDir);
2899 TestCmdline failure("--template", NULL);
2901 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
2902 CPPUNIT_ASSERT_NO_THROW(failure.expectUsageError("[ERROR] missing parameter for '--template'\n"));
2904 TestCmdline help("--template", "? ", NULL);
2906 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates (servers):\n"
2907 " template name = template description\n"
2908 " eGroupware = http://www.egroupware.org\n"
2909 " Funambol = http://my.funambol.com\n"
2910 " Google_Calendar = event sync via CalDAV, use for the 'target-config@google-calendar' config\n"
2911 " Google_Contacts = contact sync via SyncML, see http://www.google.com/support/mobile/bin/topic.py?topic=22181\n"
2912 " Goosync = http://www.goosync.com/\n"
2913 " Memotoo = http://www.memotoo.com\n"
2914 " Mobical = https://www.everdroid.com\n"
2915 " Oracle = http://www.oracle.com/technology/products/beehive/index.html\n"
2916 " Ovi = http://www.ovi.com\n"
2917 " ScheduleWorld = server no longer in operation\n"
2918 " SyncEvolution = http://www.syncevolution.org\n"
2919 " Synthesis = http://www.synthesis.ch\n"
2920 " WebDAV = contact and event sync using WebDAV, use for the 'target-config@<server>' config\n"
2921 " Yahoo = contact and event sync using WebDAV, use for the 'target-config@yahoo' config\n",
2923 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
2926 void testMatchTemplate() {
2927 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "testcases/templates");
2928 ScopedEnvChange xdg("XDG_CONFIG_HOME", "/dev/null");
2930 TestCmdline help1("--template", "?nokia 7210c", NULL);
2932 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates (clients):\n"
2933 " template name = template description matching score in percent (100% = exact match)\n"
2934 " Nokia_7210c = Template for Nokia S40 series Phone 100%\n"
2935 " SyncEvolution_Client = SyncEvolution server side template 40%\n",
2937 CPPUNIT_ASSERT_EQUAL_DIFF("", help1.m_err.str());
2938 TestCmdline help2("--template", "?nokia", NULL);
2940 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates (clients):\n"
2941 " template name = template description matching score in percent (100% = exact match)\n"
2942 " Nokia_7210c = Template for Nokia S40 series Phone 100%\n"
2943 " SyncEvolution_Client = SyncEvolution server side template 40%\n",
2945 CPPUNIT_ASSERT_EQUAL_DIFF("", help2.m_err.str());
2946 TestCmdline help3("--template", "?7210c", NULL);
2948 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates (clients):\n"
2949 " template name = template description matching score in percent (100% = exact match)\n"
2950 " Nokia_7210c = Template for Nokia S40 series Phone 60%\n"
2951 " SyncEvolution_Client = SyncEvolution server side template 20%\n",
2953 CPPUNIT_ASSERT_EQUAL_DIFF("", help3.m_err.str());
2954 TestCmdline help4("--template", "?syncevolution client", NULL);
2956 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates (clients):\n"
2957 " template name = template description matching score in percent (100% = exact match)\n"
2958 " SyncEvolution_Client = SyncEvolution server side template 100%\n"
2959 " Nokia_7210c = Template for Nokia S40 series Phone 40%\n",
2961 CPPUNIT_ASSERT_EQUAL_DIFF("", help4.m_err.str());
2964 void testPrintServers() {
2965 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
2966 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2967 ScopedEnvChange home("HOME", m_testDir);
2969 doSetupScheduleWorld(false);
2970 doSetupSynthesis(true);
2971 doSetupFunambol(true);
2973 TestCmdline cmdline("--print-servers", NULL);
2975 CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n"
2976 " funambol = CmdlineTest/syncevolution/default/peers/funambol\n"
2977 " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n"
2978 " synthesis = CmdlineTest/syncevolution/default/peers/synthesis\n",
2979 cmdline.m_out.str());
2980 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2983 void testPrintConfig() {
2984 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
2985 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2986 ScopedEnvChange home("HOME", m_testDir);
2988 testSetupFunambol();
2991 TestCmdline failure("--print-config", NULL);
2992 CPPUNIT_ASSERT(failure.m_cmdline->parse());
2993 CPPUNIT_ASSERT(!failure.m_cmdline->run());
2994 CPPUNIT_ASSERT_NO_THROW(failure.expectUsageError("[ERROR] --print-config requires either a --template or a server name.\n"));
2998 TestCmdline failure("--print-config", "foo", NULL);
2999 CPPUNIT_ASSERT(failure.m_cmdline->parse());
3000 CPPUNIT_ASSERT(!failure.m_cmdline->run());
3001 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
3002 CPPUNIT_ASSERT_EQUAL(string("[ERROR] Server 'foo' has not been configured yet.\n"),
3003 failure.m_err.str());
3007 TestCmdline failure("--print-config", "--template", "foo", NULL);
3008 CPPUNIT_ASSERT(failure.m_cmdline->parse());
3009 CPPUNIT_ASSERT(!failure.m_cmdline->run());
3010 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
3011 CPPUNIT_ASSERT_EQUAL(string("[ERROR] No configuration template for 'foo' available.\n"),
3012 failure.m_err.str());
3016 TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL);
3018 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3019 string actual = cmdline.m_out.str();
3020 // deviceId must be the one from Funambol
3021 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
3022 string filtered = injectValues(filterConfig(actual));
3023 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
3025 // there should have been comments
3026 CPPUNIT_ASSERT(actual.size() > filtered.size());
3030 TestCmdline cmdline("--print-config", "--template", "scheduleworld@nosuchcontext", NULL);
3032 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3033 string actual = cmdline.m_out.str();
3034 // deviceId must *not* be the one from Funambol because of the new context
3035 CPPUNIT_ASSERT(!boost::contains(actual, "deviceId = fixed-devid"));
3039 TestCmdline cmdline("--print-config", "--template", "Default", NULL);
3041 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3042 string actual = injectValues(filterConfig(cmdline.m_out.str()));
3043 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
3044 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(DefaultConfig())),
3049 TestCmdline cmdline("--print-config", "funambol", NULL);
3051 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3052 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())),
3053 injectValues(filterConfig(cmdline.m_out.str())));
3057 // override context and template properties
3058 TestCmdline cmdline("--print-config", "--template", "scheduleworld",
3060 "database=Personal",
3061 "--source-property", "sync=disabled",
3064 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3065 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
3066 boost::replace_first(expected,
3067 "syncURL = http://sync.scheduleworld.com/funambol/ds",
3069 boost::replace_all(expected,
3071 "database = Personal");
3072 boost::replace_all(expected,
3075 string actual = injectValues(filterConfig(cmdline.m_out.str()));
3076 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
3077 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
3082 // override context and template properties, using legacy property name
3083 TestCmdline cmdline("--print-config", "--template", "scheduleworld",
3084 "--sync-property", "syncURL=foo",
3085 "--source-property", "evolutionsource=Personal",
3086 "--source-property", "sync=disabled",
3089 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3090 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
3091 boost::replace_first(expected,
3092 "syncURL = http://sync.scheduleworld.com/funambol/ds",
3094 boost::replace_all(expected,
3096 "database = Personal");
3097 boost::replace_all(expected,
3100 string actual = injectValues(filterConfig(cmdline.m_out.str()));
3101 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
3102 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
3107 TestCmdline cmdline("--print-config", "--quiet",
3108 "--template", "scheduleworld",
3112 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3113 string actual = cmdline.m_out.str();
3114 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
3115 CPPUNIT_ASSERT_EQUAL_DIFF(internalToIni(ScheduleWorldConfig()),
3116 injectValues(filterConfig(actual)));
3120 // change shared source properties, then check template again
3121 TestCmdline cmdline("--configure",
3122 "--source-property", "database=Personal",
3126 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3129 TestCmdline cmdline("--print-config", "--quiet",
3130 "--template", "scheduleworld",
3134 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3135 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
3136 // from modified Funambol config
3137 boost::replace_all(expected,
3139 "database = Personal");
3140 string actual = injectValues(filterConfig(cmdline.m_out.str()));
3141 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
3142 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
3147 // print config => must not use settings from default context
3148 TestCmdline cmdline("--print-config", "--template", "scheduleworld@nosuchcontext", NULL);
3150 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3151 // source settings *not* from modified Funambol config
3152 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
3153 string actual = injectValues(filterConfig(cmdline.m_out.str()));
3154 CPPUNIT_ASSERT(!boost::contains(actual, "deviceId = fixed-devid"));
3155 removeRandomUUID(actual);
3156 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
3161 // create config => again, must not use settings from default context
3162 TestCmdline cmdline("--configure", "--template", "scheduleworld", "other@other", NULL);
3164 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3167 TestCmdline cmdline("--print-config", "other@other", NULL);
3169 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3170 // source settings *not* from modified Funambol config
3171 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
3172 string actual = injectValues(filterConfig(cmdline.m_out.str()));
3173 CPPUNIT_ASSERT(!boost::contains(actual, "deviceId = fixed-devid"));
3174 removeRandomUUID(actual);
3175 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
3180 void testPrintFileTemplates() {
3181 // use local copy of templates in build dir (no need to install)
3182 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "./templates");
3183 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
3184 ScopedEnvChange home("HOME", m_testDir);
3186 doPrintFileTemplates();
3189 void testPrintFileTemplatesConfig() {
3190 // simulate reading templates from user's XDG HOME
3191 symlink("../templates", (m_testDir + "/syncevolution-templates").c_str());
3192 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
3193 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
3194 ScopedEnvChange home("HOME", m_testDir);
3196 doPrintFileTemplates();
3199 void doPrintFileTemplates() {
3200 // Compare only the properties which are really set.
3202 // note that "backend" will be take from the @default context if one
3203 // exists, so run this before setting up Funambol below
3205 TestCmdline cmdline("--print-config", "--template", "google calendar", NULL);
3207 CPPUNIT_ASSERT_EQUAL_DIFF(googlecaldav,
3208 removeComments(filterRandomUUID(filterConfig(cmdline.m_out.str()))));
3212 TestCmdline cmdline("--print-config", "--template", "yahoo", NULL);
3214 CPPUNIT_ASSERT_EQUAL_DIFF(yahoo,
3215 removeComments(filterRandomUUID(filterConfig(cmdline.m_out.str()))));
3218 testSetupFunambol();
3221 TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL);
3223 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3224 string actual = cmdline.m_out.str();
3225 // deviceId must be the one from Funambol
3226 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
3227 string filtered = injectValues(filterConfig(actual));
3228 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
3230 // there should have been comments
3231 CPPUNIT_ASSERT(actual.size() > filtered.size());
3235 TestCmdline cmdline("--print-config", "funambol", NULL);
3237 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3238 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())),
3239 injectValues(filterConfig(cmdline.m_out.str())));
3243 void testAddSource() {
3245 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
3246 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
3247 ScopedEnvChange home("HOME", m_testDir);
3249 testSetupScheduleWorld();
3252 root += "/syncevolution/default";
3255 TestCmdline cmdline("--configure",
3256 "--source-property", "uri = dummy",
3261 string res = scanFiles(root);
3262 string expected = ScheduleWorldConfig();
3264 "peers/scheduleworld/sources/xyz/.internal.ini:# adminData = \n"
3265 "peers/scheduleworld/sources/xyz/.internal.ini:# synthesisID = 0\n"
3266 "peers/scheduleworld/sources/xyz/config.ini:# sync = disabled\n"
3267 "peers/scheduleworld/sources/xyz/config.ini:uri = dummy\n"
3268 "peers/scheduleworld/sources/xyz/config.ini:# syncFormat = \n"
3269 "peers/scheduleworld/sources/xyz/config.ini:# forceSyncFormat = 0\n"
3270 "sources/xyz/config.ini:# backend = select backend\n"
3271 "sources/xyz/config.ini:# database = \n"
3272 "sources/xyz/config.ini:# databaseFormat = \n"
3273 "sources/xyz/config.ini:# databaseUser = \n"
3274 "sources/xyz/config.ini:# databasePassword = ";
3275 sortConfig(expected);
3276 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
3281 TestCmdline failure("--sync", NULL);
3282 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
3283 CPPUNIT_ASSERT_NO_THROW(failure.expectUsageError("[ERROR] missing parameter for '--sync'\n"));
3285 TestCmdline failure2("--sync", "foo", NULL);
3286 CPPUNIT_ASSERT(!failure2.m_cmdline->parse());
3287 CPPUNIT_ASSERT_EQUAL_DIFF("", failure2.m_out.str());
3288 CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] '--sync foo': not one of the valid values (two-way, slow, refresh-from-local, refresh-from-remote = refresh, one-way-from-local, one-way-from-remote = one-way, refresh-from-client = refresh-client, refresh-from-server = refresh-server, one-way-from-client = one-way-client, one-way-from-server = one-way-server, disabled = none)\n", failure2.m_err.str());
3290 TestCmdline failure3("--sync=foo", NULL);
3291 CPPUNIT_ASSERT(!failure3.m_cmdline->parse());
3292 CPPUNIT_ASSERT_EQUAL_DIFF("", failure3.m_out.str());
3293 CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] '--sync=foo': not one of the valid values (two-way, slow, refresh-from-local, refresh-from-remote = refresh, one-way-from-local, one-way-from-remote = one-way, refresh-from-client = refresh-client, refresh-from-server = refresh-server, one-way-from-client = one-way-client, one-way-from-server = one-way-server, disabled = none)\n", failure3.m_err.str());
3295 TestCmdline help("--sync", " ?", NULL);
3297 CPPUNIT_ASSERT_EQUAL_DIFF("--sync\n"
3298 " Requests a certain synchronization mode when initiating a sync:\n"
3301 " only send/receive changes since last sync\n"
3303 " exchange all items\n"
3304 " refresh-from-remote\n"
3305 " discard all local items and replace with\n"
3306 " the items on the peer\n"
3307 " refresh-from-local\n"
3308 " discard all items on the peer and replace\n"
3309 " with the local items\n"
3310 " one-way-from-remote\n"
3311 " transmit changes from peer\n"
3312 " one-way-from-local\n"
3313 " transmit local changes\n"
3314 " disabled (or none)\n"
3315 " synchronization disabled\n"
3317 " refresh/one-way-from-server/client are also supported. Their use is\n"
3318 " discouraged because the direction of the data transfer depends\n"
3319 " on the role of the local side (can be server or client), which is\n"
3320 " not always obvious.\n"
3322 " When accepting a sync session in a SyncML server (HTTP server), only\n"
3323 " sources with sync != disabled are made available to the client,\n"
3324 " which chooses the final sync mode based on its own configuration.\n"
3325 " When accepting a sync session in a SyncML client (local sync with\n"
3326 " the server contacting SyncEvolution on a device), the sync mode\n"
3327 " specified in the client is typically overriden by the server.\n",
3329 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
3331 TestCmdline filter("--sync", "refresh-from-server", NULL);
3332 CPPUNIT_ASSERT(filter.m_cmdline->parse());
3333 CPPUNIT_ASSERT(!filter.m_cmdline->run());
3334 CPPUNIT_ASSERT_NO_THROW(filter.expectUsageError("[ERROR] No configuration name specified.\n"));
3335 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh-from-server",
3336 string(filter.m_cmdline->m_props[""].m_sourceProps[""]));
3337 CPPUNIT_ASSERT_EQUAL_DIFF("", string(filter.m_cmdline->m_props[""].m_syncProps));
3339 TestCmdline filter2("--source-property", "sync=refresh", NULL);
3340 CPPUNIT_ASSERT(filter2.m_cmdline->parse());
3341 CPPUNIT_ASSERT(!filter2.m_cmdline->run());
3342 CPPUNIT_ASSERT_NO_THROW(filter2.expectUsageError("[ERROR] No configuration name specified.\n"));
3343 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh",
3344 string(filter2.m_cmdline->m_props[""].m_sourceProps[""]));
3345 CPPUNIT_ASSERT_EQUAL_DIFF("",
3346 string(filter2.m_cmdline->m_props[""].m_syncProps));
3348 TestCmdline filter3("--source-property", "xyz=1", NULL);
3349 CPPUNIT_ASSERT(!filter3.m_cmdline->parse());
3350 CPPUNIT_ASSERT_EQUAL(string(""), filter3.m_out.str());
3351 CPPUNIT_ASSERT_EQUAL(string("[ERROR] '--source-property xyz=1': no such property\n"), filter3.m_err.str());
3353 TestCmdline filter4("xyz=1", NULL);
3354 CPPUNIT_ASSERT(!filter4.m_cmdline->parse());
3355 CPPUNIT_ASSERT_NO_THROW(filter4.expectUsageError("[ERROR] unrecognized property in 'xyz=1'\n"));
3357 TestCmdline filter5("=1", NULL);
3358 CPPUNIT_ASSERT(!filter5.m_cmdline->parse());
3359 CPPUNIT_ASSERT_NO_THROW(filter5.expectUsageError("[ERROR] a property name must be given in '=1'\n"));
3362 void testKeyring() {
3363 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
3364 ScopedEnvChange home("HOME", m_testDir);
3368 TestCmdline cmdline(NULL, NULL);
3369 boost::shared_ptr<SyncContext> context = cmdline.parse();
3370 CPPUNIT_ASSERT(context);
3371 InitStateTri keyring = context->getKeyring();
3372 CPPUNIT_ASSERT_EQUAL(false, keyring.wasSet());
3373 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue());
3376 TestCmdline cmdline("--keyring", NULL);
3377 boost::shared_ptr<SyncContext> context = cmdline.parse();
3378 CPPUNIT_ASSERT(context);
3379 InitStateTri keyring = context->getKeyring();
3380 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3381 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue());
3384 TestCmdline cmdline("--sync-property", "keyring=True", NULL);
3385 boost::shared_ptr<SyncContext> context = cmdline.parse();
3386 CPPUNIT_ASSERT(context);
3387 InitStateTri keyring = context->getKeyring();
3388 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3389 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue());
3392 TestCmdline cmdline("keyring=True", NULL);
3393 boost::shared_ptr<SyncContext> context = cmdline.parse();
3394 CPPUNIT_ASSERT(context);
3395 InitStateTri keyring = context->getKeyring();
3396 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3397 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue());
3400 TestCmdline cmdline("--keyring=true", NULL);
3401 boost::shared_ptr<SyncContext> context = cmdline.parse();
3402 CPPUNIT_ASSERT(context);
3403 InitStateTri keyring = context->getKeyring();
3404 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3405 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue());
3408 TestCmdline cmdline("--keyring=1", NULL);
3409 boost::shared_ptr<SyncContext> context = cmdline.parse();
3410 CPPUNIT_ASSERT(context);
3411 InitStateTri keyring = context->getKeyring();
3412 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3413 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue());
3416 TestCmdline cmdline("--keyring=Yes", NULL);
3417 boost::shared_ptr<SyncContext> context = cmdline.parse();
3418 CPPUNIT_ASSERT(context);
3419 InitStateTri keyring = context->getKeyring();
3420 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3421 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue());
3424 TestCmdline cmdline("--keyring=false", NULL);
3425 boost::shared_ptr<SyncContext> context = cmdline.parse();
3426 CPPUNIT_ASSERT(context);
3427 InitStateTri keyring = context->getKeyring();
3428 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3429 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_FALSE, keyring.getValue());
3432 TestCmdline cmdline("--keyring=0", NULL);
3433 boost::shared_ptr<SyncContext> context = cmdline.parse();
3434 CPPUNIT_ASSERT(context);
3435 InitStateTri keyring = context->getKeyring();
3436 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3437 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_FALSE, keyring.getValue());
3440 TestCmdline cmdline("--keyring=NO", NULL);
3441 boost::shared_ptr<SyncContext> context = cmdline.parse();
3442 CPPUNIT_ASSERT(context);
3443 InitStateTri keyring = context->getKeyring();
3444 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3445 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_FALSE, keyring.getValue());
3448 TestCmdline cmdline("--keyring=GNOME", NULL);
3449 boost::shared_ptr<SyncContext> context = cmdline.parse();
3450 CPPUNIT_ASSERT(context);
3451 InitStateTri keyring = context->getKeyring();
3452 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3453 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_STRING, keyring.getValue());
3454 CPPUNIT_ASSERT_EQUAL(std::string("GNOME"), keyring.get());
3457 // Broken command line: treated like a sync, but config doesn't exist.
3459 TestCmdline cmdline("keyring=KDE", "@foobar", NULL);
3460 cmdline.doit(false);
3461 CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str());
3462 CPPUNIT_ASSERT_EQUAL(std::string("[INFO] Configuration \"@foobar\" does not refer to a sync peer.\n[ERROR] Cannot proceed with sync without a configuration."), cmdline.m_err.str());
3465 TestCmdline cmdline("keyring=KDE", "nosuchpeer@foobar", NULL);
3466 cmdline.doit(false);
3467 CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str());
3468 CPPUNIT_ASSERT_EQUAL(std::string("[INFO] Configuration \"nosuchpeer@foobar\" does not exist.\n[ERROR] Cannot proceed with sync without a configuration."), cmdline.m_err.str());
3471 // empty config prop
3473 TestCmdline cmdline("--configure", "@default", NULL);
3477 // Try broken command line again.
3479 TestCmdline cmdline("keyring=KDE", "@foobar", NULL);
3480 cmdline.doit(false);
3481 CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str());
3482 CPPUNIT_ASSERT_EQUAL(std::string("[INFO] Configuration \"@foobar\" does not refer to a sync peer.\n[ERROR] Cannot proceed with sync without a configuration."), cmdline.m_err.str());
3485 TestCmdline cmdline("keyring=KDE", "nosuchpeer@foobar", NULL);
3486 cmdline.doit(false);
3487 CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str());
3488 CPPUNIT_ASSERT_EQUAL(std::string("[INFO] Configuration \"nosuchpeer@foobar\" does not exist.\n[ERROR] Cannot proceed with sync without a configuration."), cmdline.m_err.str());
3492 TestCmdline cmdline("@foobar", NULL);
3493 boost::shared_ptr<SyncContext> context = cmdline.parse();
3494 CPPUNIT_ASSERT(context);
3495 InitStateTri keyring = context->getKeyring();
3496 CPPUNIT_ASSERT_EQUAL(false, keyring.wasSet());
3497 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue());
3500 // now set the value permanently
3502 TestCmdline cmdline("--keyring", "--configure", "@default", NULL);
3504 boost::shared_ptr<SyncContext> context = cmdline.parse();
3505 CPPUNIT_ASSERT(context);
3506 InitStateTri keyring = context->getKeyring();
3507 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3508 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue());
3511 TestCmdline cmdline("--keyring=KDE", "--configure", "@default", NULL);
3513 boost::shared_ptr<SyncContext> context = cmdline.parse();
3514 CPPUNIT_ASSERT(context);
3515 InitStateTri keyring = context->getKeyring();
3516 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3517 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_STRING, keyring.getValue());
3518 CPPUNIT_ASSERT_EQUAL(std::string("KDE"), keyring.get());
3521 // create by setting keyring in @default, then update;
3522 // @default not strictly needed
3525 TestCmdline cmdline("keyring=KDE", "--configure", "@default", NULL);
3527 boost::shared_ptr<SyncContext> context = cmdline.parse();
3528 CPPUNIT_ASSERT(context);
3529 InitStateTri keyring = context->getKeyring();
3530 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3531 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_STRING, keyring.getValue());
3534 TestCmdline cmdline("keyring=yes", "--configure", "@default", NULL);
3536 boost::shared_ptr<SyncContext> context = cmdline.parse();
3537 CPPUNIT_ASSERT(context);
3538 InitStateTri keyring = context->getKeyring();
3539 CPPUNIT_ASSERT_EQUAL(true, keyring.wasSet());
3540 CPPUNIT_ASSERT_EQUAL(InitStateTri::VALUE_TRUE, keyring.getValue());
3543 // allow sync operation although --keyring was set
3545 TestCmdline cmdline("keyring=GNOME", "foobar@default", NULL);
3546 cmdline.doit(false);
3547 CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str());
3548 CPPUNIT_ASSERT_EQUAL(std::string("[INFO] Configuration \"foobar@default\" does not exist.\n[ERROR] Cannot proceed with sync without a configuration."), cmdline.m_err.str());
3551 // catch invalid "keyring" value
3553 TestCmdline cmdline("--configure",
3556 "syncURL=http://no.such.server",
3557 "keyring=no-such-keyring",
3558 "foobar@default", NULL);
3559 cmdline.doit(false);
3560 CPPUNIT_ASSERT_EQUAL(std::string(""), cmdline.m_out.str());
3561 CPPUNIT_ASSERT_EQUAL(std::string("[ERROR] Unsupported value for the \"keyring\" property, no such keyring found: no-such-keyring"),
3562 cmdline.m_err.str());
3568 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
3569 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
3570 ScopedEnvChange home("HOME", m_testDir);
3572 // configure Yahoo under a different name, with explicit template selection
3574 TestCmdline cmdline("--configure",
3575 "--template", "yahoo",
3576 "target-config@my-yahoo",
3581 TestCmdline cmdline("--print-config", "target-config@my-yahoo", NULL);
3583 CPPUNIT_ASSERT_EQUAL_DIFF(yahoo,
3584 removeComments(filterRandomUUID(filterConfig(cmdline.m_out.str()))));
3587 // configure Google Calendar with template derived from config name
3589 TestCmdline cmdline("--configure",
3590 "target-config@google-calendar",
3595 TestCmdline cmdline("--print-config", "target-config@google-calendar", NULL);
3597 CPPUNIT_ASSERT_EQUAL_DIFF(googlecaldav,
3598 removeComments(filterRandomUUID(filterConfig(cmdline.m_out.str()))));
3601 // test "template not found" error cases
3603 TestCmdline cmdline("--configure",
3604 "--template", "yahooxyz",
3605 "target-config@my-yahoo-xyz",
3607 CPPUNIT_ASSERT(cmdline.m_cmdline->parse());
3608 CPPUNIT_ASSERT(!cmdline.m_cmdline->run());
3609 static const char error[] = "[ERROR] No configuration template for 'yahooxyz' available.\n"
3611 "[INFO] Available configuration templates (clients and servers):\n";
3612 std::string out = cmdline.m_out.str();
3613 std::string err = cmdline.m_err.str();
3614 std::string all = cmdline.m_all.str();
3615 CPPUNIT_ASSERT(boost::starts_with(err, error));
3616 CPPUNIT_ASSERT(boost::ends_with(err, "\n"));
3617 CPPUNIT_ASSERT(!boost::ends_with(err, "\n\n"));
3618 CPPUNIT_ASSERT_EQUAL(string(""), out);
3619 CPPUNIT_ASSERT_EQUAL(all, err);
3622 TestCmdline cmdline("--configure",
3623 "target-config@foobar",
3625 CPPUNIT_ASSERT(cmdline.m_cmdline->parse());
3626 CPPUNIT_ASSERT(!cmdline.m_cmdline->run());
3627 static const char error[] = "[ERROR] No configuration template for 'foobar' available.\n"
3628 "[INFO] Use '--template none' and/or specify relevant properties on the command line to create a configuration without a template. Need values for: syncURL\n"
3630 "[INFO] Available configuration templates (clients and servers):\n";
3631 std::string out = cmdline.m_out.str();
3632 std::string err = cmdline.m_err.str();
3633 std::string all = cmdline.m_all.str();
3634 CPPUNIT_ASSERT(boost::starts_with(err, error));
3635 CPPUNIT_ASSERT(boost::ends_with(err, "\n"));
3636 CPPUNIT_ASSERT(!boost::ends_with(err, "\n\n"));
3637 CPPUNIT_ASSERT_EQUAL(string(""), out);
3638 CPPUNIT_ASSERT_EQUAL(err, all);
3643 void testConfigure() {
3644 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
3645 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
3646 ScopedEnvChange home("HOME", m_testDir);
3648 testSetupScheduleWorld();
3649 string expected = doConfigure(ScheduleWorldConfig(), "sources/addressbook/config.ini:");
3652 // updating "type" for peer is mapped to updating "backend",
3653 // "databaseFormat", "syncFormat", "forceSyncFormat"
3654 TestCmdline cmdline("--configure",
3655 "--source-property", "addressbook/type=file:text/vcard:3.0",
3659 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3660 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
3661 boost::replace_first(expected,
3662 "backend = addressbook",
3664 boost::replace_first(expected,
3665 "# databaseFormat = ",
3666 "databaseFormat = text/vcard");
3667 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
3668 filterConfig(printConfig("scheduleworld")));
3669 string shared = filterConfig(printConfig("@default"));
3670 CPPUNIT_ASSERT(shared.find("backend = file") != shared.npos);
3671 CPPUNIT_ASSERT(shared.find("databaseFormat = text/vcard") != shared.npos);
3675 // updating type for context must not affect peer
3676 TestCmdline cmdline("--configure",
3677 "--source-property", "type=file:text/x-vcard:2.1",
3678 "@default", "addressbook",
3681 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3682 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
3683 boost::replace_first(expected,
3684 "databaseFormat = text/vcard",
3685 "databaseFormat = text/x-vcard");
3686 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
3687 filterConfig(printConfig("scheduleworld")));
3688 string shared = filterConfig(printConfig("@default"));
3689 CPPUNIT_ASSERT(shared.find("backend = file") != shared.npos);
3690 CPPUNIT_ASSERT(shared.find("databaseFormat = text/x-vcard") != shared.npos);
3693 string syncProperties("syncURL (no default, unshared, required)\n"
3695 "username (no default, unshared)\n"
3697 "password (no default, unshared)\n"
3699 "logdir (no default, shared)\n"
3701 "loglevel (0, unshared)\n"
3703 "notifyLevel (3, unshared)\n"
3705 "printChanges (TRUE, unshared)\n"
3707 "dumpData (TRUE, unshared)\n"
3709 "maxlogdirs (10, shared)\n"
3711 "autoSync (0, unshared)\n"
3713 "autoSyncInterval (30M, unshared)\n"
3715 "autoSyncDelay (5M, unshared)\n"
3717 "preventSlowSync (TRUE, unshared)\n"
3719 "useProxy (FALSE, unshared)\n"
3721 "proxyHost (no default, unshared)\n"
3723 "proxyUsername (no default, unshared)\n"
3725 "proxyPassword (no default, unshared)\n"
3727 "clientAuthType (md5, unshared)\n"
3729 "RetryDuration (5M, unshared)\n"
3731 "RetryInterval (2M, unshared)\n"
3733 "remoteIdentifier (no default, unshared)\n"
3735 "PeerIsClient (FALSE, unshared)\n"
3737 "SyncMLVersion (no default, unshared)\n"
3739 "PeerName (no default, unshared)\n"
3741 "deviceId (no default, shared)\n"
3743 "remoteDeviceId (no default, unshared)\n"
3745 "enableWBXML (TRUE, unshared)\n"
3747 "maxMsgSize (150000, unshared), maxObjSize (4000000, unshared)\n"
3749 "SSLServerCertificates (" SYNCEVOLUTION_SSL_SERVER_CERTIFICATES ", unshared)\n"
3751 "SSLVerifyServer (TRUE, unshared)\n"
3753 "SSLVerifyHost (TRUE, unshared)\n"
3755 "WebURL (no default, unshared)\n"
3757 "IconURI (no default, unshared)\n"
3759 "ConsumerReady (FALSE, unshared)\n"
3761 "peerType (no default, unshared)\n"
3763 "defaultPeer (no default, global)\n"
3765 "keyring (yes, global)\n");
3767 string sourceProperties("sync (disabled, unshared, required)\n"
3769 "uri (no default, unshared)\n"
3771 "backend (select backend, shared)\n"
3773 "syncFormat (no default, unshared)\n"
3775 "forceSyncFormat (FALSE, unshared)\n"
3777 "database = evolutionsource (no default, shared)\n"
3779 "databaseFormat (no default, shared)\n"
3781 "databaseUser = evolutionuser (no default, shared), databasePassword = evolutionpassword (no default, shared)\n");
3784 TestCmdline cmdline("--sync-property", "?",
3787 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3788 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties,
3789 filterIndented(cmdline.m_out.str()));
3793 TestCmdline cmdline("--source-property", "?",
3796 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3797 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties,
3798 filterIndented(cmdline.m_out.str()));
3802 TestCmdline cmdline("--source-property", "?",
3803 "--sync-property", "?",
3806 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3807 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties + syncProperties,
3808 filterIndented(cmdline.m_out.str()));
3812 TestCmdline cmdline("--sync-property", "?",
3813 "--source-property", "?",
3816 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3817 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties + sourceProperties,
3818 filterIndented(cmdline.m_out.str()));
3822 TestCmdline cmdline("--source-property", "sync=?",
3825 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3826 CPPUNIT_ASSERT_EQUAL_DIFF("'--source-property sync=?'\n",
3827 filterIndented(cmdline.m_out.str()));
3831 TestCmdline cmdline("sync=?",
3834 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3835 CPPUNIT_ASSERT_EQUAL_DIFF("'sync=?'\n",
3836 filterIndented(cmdline.m_out.str()));
3840 TestCmdline cmdline("syncURL=?",
3843 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3844 CPPUNIT_ASSERT_EQUAL_DIFF("'syncURL=?'\n",
3845 filterIndented(cmdline.m_out.str()));
3850 * Test semantic of config creation (instead of updating) with and without
3851 * templates. See BMC #14805.
3853 void testConfigureTemplates() {
3854 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
3855 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
3856 ScopedEnvChange home("HOME", m_testDir);
3860 // catch possible typos like "sheduleworld"
3861 TestCmdline failure("--configure", "foo", NULL);
3862 CPPUNIT_ASSERT(failure.m_cmdline->parse());
3863 CPPUNIT_ASSERT(!failure.m_cmdline->run());
3864 static const char error[] = "[ERROR] No configuration template for 'foo@default' available.\n"
3865 "[INFO] Use '--template none' and/or specify relevant properties on the command line to create a configuration without a template. Need values for: syncURL\n"
3867 "[INFO] Available configuration templates (clients and servers):\n";
3868 std::string out = failure.m_out.str();
3869 std::string err = failure.m_err.str();
3870 std::string all = failure.m_all.str();
3871 CPPUNIT_ASSERT(boost::starts_with(err, error));
3872 CPPUNIT_ASSERT(boost::ends_with(err, "\n"));
3873 CPPUNIT_ASSERT(!boost::ends_with(err, "\n\n"));
3874 CPPUNIT_ASSERT_EQUAL(string(""), out);
3875 CPPUNIT_ASSERT_EQUAL(all, err);
3880 // catch possible typos like "sheduleworld" when
3881 // enough properties are specified to continue without
3883 TestCmdline failure("--configure", "syncURL=http://foo.com", "--template", "foo", "bar", NULL);
3884 CPPUNIT_ASSERT(failure.m_cmdline->parse());
3885 CPPUNIT_ASSERT(!failure.m_cmdline->run());
3887 static const char error[] = "[ERROR] No configuration template for 'foo' available.\n"
3888 "[INFO] All relevant properties seem to be set, omit the --template parameter to proceed.\n"
3890 "[INFO] Available configuration templates (clients and servers):\n";
3891 std::string out = failure.m_out.str();
3892 std::string err = failure.m_err.str();
3893 std::string all = failure.m_all.str();
3894 CPPUNIT_ASSERT(boost::starts_with(err, error));
3895 CPPUNIT_ASSERT(boost::ends_with(err, "\n"));
3896 CPPUNIT_ASSERT(!boost::ends_with(err, "\n\n"));
3897 CPPUNIT_ASSERT_EQUAL(string(""), out);
3898 CPPUNIT_ASSERT_EQUAL(all, err);
3902 StringPrintf("syncevolution/.internal.ini:rootMinVersion = %d\n"
3903 "syncevolution/.internal.ini:rootCurVersion = %d\n"
3904 "syncevolution/default/.internal.ini:contextMinVersion = %d\n"
3905 "syncevolution/default/.internal.ini:contextCurVersion = %d\n"
3906 "syncevolution/default/config.ini:deviceId = fixed-devid\n"
3907 "syncevolution/default/peers/foo/.internal.ini:peerMinVersion = %d\n"
3908 "syncevolution/default/peers/foo/.internal.ini:peerCurVersion = %d\n",
3909 CONFIG_ROOT_MIN_VERSION, CONFIG_ROOT_CUR_VERSION,
3910 CONFIG_CONTEXT_MIN_VERSION, CONFIG_CONTEXT_CUR_VERSION,
3911 CONFIG_PEER_MIN_VERSION, CONFIG_PEER_CUR_VERSION);
3914 "syncevolution/default/peers/foo/config.ini:syncURL = local://@bar\n";
3916 string configsource =
3917 "syncevolution/default/peers/foo/sources/eds_event/config.ini:sync = two-way\n"
3918 "syncevolution/default/sources/eds_event/config.ini:backend = calendar\n";
3922 // allow user to proceed if they wish: should result in no sources configured
3923 TestCmdline failure("--configure", "--template", "none", "foo", NULL);
3924 CPPUNIT_ASSERT(failure.m_cmdline->parse());
3925 bool success = failure.m_cmdline->run();
3926 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
3927 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_err.str());
3928 CPPUNIT_ASSERT(success);
3929 string res = scanFiles(m_testDir);
3930 removeRandomUUID(res);
3931 CPPUNIT_ASSERT_EQUAL_DIFF(fooconfig, filterFiles(res));
3936 // allow user to proceed if they wish: should result in no sources configured,
3937 // even if general source properties are specified
3938 TestCmdline failure("--configure", "--template", "none", "backend=calendar", "foo", NULL);
3939 bool success = failure.m_cmdline->parse() && failure.m_cmdline->run();
3940 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
3941 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_err.str());
3942 CPPUNIT_ASSERT(success);
3943 string res = scanFiles(m_testDir);
3944 removeRandomUUID(res);
3945 CPPUNIT_ASSERT_EQUAL_DIFF(fooconfig, filterFiles(res));
3950 // allow user to proceed if they wish: should result in no sources configured,
3951 // even if specific source properties are specified
3952 TestCmdline failure("--configure", "--template", "none", "eds_event/backend=calendar", "foo", NULL);
3953 bool success = failure.m_cmdline->parse() && failure.m_cmdline->run();
3954 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
3955 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_err.str());
3956 CPPUNIT_ASSERT(success);
3957 string res = scanFiles(m_testDir);
3958 removeRandomUUID(res);
3959 CPPUNIT_ASSERT_EQUAL_DIFF(fooconfig, filterFiles(res));
3964 // allow user to proceed if they wish and possible: here eds_event is not usable
3965 TestCmdline failure("--configure", "--template", "none", "foo", "eds_event", NULL);
3966 CPPUNIT_ASSERT(failure.m_cmdline->parse());
3967 bool caught = false;
3969 CPPUNIT_ASSERT(failure.m_cmdline->run());
3970 } catch (const StatusException &ex) {
3971 if (!strcmp(ex.what(), "eds_event: no backend available")) {
3977 CPPUNIT_ASSERT(caught);
3982 // allow user to proceed if they wish and possible: here eds_event is not configurable
3983 TestCmdline failure("--configure", "syncURL=local://@bar", "foo", "eds_event", NULL);
3984 CPPUNIT_ASSERT(failure.m_cmdline->parse());
3985 bool caught = false;
3987 CPPUNIT_ASSERT(failure.m_cmdline->run());
3988 } catch (const StatusException &ex) {
3989 if (!strcmp(ex.what(), "no such source(s): eds_event")) {
3995 CPPUNIT_ASSERT(caught);
4000 // allow user to proceed if they wish and possible: here eds_event is not configurable (wrong context)
4001 TestCmdline failure("--configure", "syncURL=local://@bar", "eds_event/backend@xyz=calendar", "foo", "eds_event", NULL);
4002 CPPUNIT_ASSERT(failure.m_cmdline->parse());
4003 bool caught = false;
4005 CPPUNIT_ASSERT(failure.m_cmdline->run());
4006 } catch (const StatusException &ex) {
4007 if (!strcmp(ex.what(), "no such source(s): eds_event")) {
4013 CPPUNIT_ASSERT(caught);
4018 // allow user to proceed if they wish: configure exactly the specified sources
4019 TestCmdline failure("--configure", "--template", "none", "backend=calendar", "foo", "eds_event", NULL);
4020 CPPUNIT_ASSERT(failure.m_cmdline->parse());
4021 CPPUNIT_ASSERT(failure.m_cmdline->run());
4022 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
4023 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_err.str());
4025 string res = scanFiles(m_testDir);
4026 removeRandomUUID(res);
4027 CPPUNIT_ASSERT_EQUAL_DIFF(fooconfig + configsource, filterFiles(res));
4032 // allow user to proceed if they provide enough information: should result in no sources configured
4033 TestCmdline failure("--configure", "syncURL=local://@bar", "foo", NULL);
4034 CPPUNIT_ASSERT(failure.m_cmdline->parse());
4035 CPPUNIT_ASSERT(failure.m_cmdline->run());
4036 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
4037 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_err.str());
4038 string res = scanFiles(m_testDir);
4039 removeRandomUUID(res);
4040 CPPUNIT_ASSERT_EQUAL_DIFF(fooconfig + syncurl, filterFiles(res));
4045 // allow user to proceed if they provide enough information;
4046 // source created because listed and usable
4047 TestCmdline failure("--configure", "syncURL=local://@bar", "backend=calendar", "foo", "eds_event", NULL);
4048 CPPUNIT_ASSERT(failure.m_cmdline->parse());
4049 CPPUNIT_ASSERT(failure.m_cmdline->run());
4050 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
4051 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_err.str());
4052 string res = scanFiles(m_testDir);
4053 removeRandomUUID(res);
4054 CPPUNIT_ASSERT_EQUAL_DIFF(fooconfig + syncurl + configsource, filterFiles(res));
4059 // allow user to proceed if they provide enough information;
4060 // source created because listed and usable
4061 TestCmdline failure("--configure", "syncURL=local://@bar", "eds_event/backend@default=calendar", "foo", "eds_event", NULL);
4062 CPPUNIT_ASSERT(failure.m_cmdline->parse());
4063 CPPUNIT_ASSERT(failure.m_cmdline->run());
4064 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
4065 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_err.str());
4066 string res = scanFiles(m_testDir);
4067 removeRandomUUID(res);
4068 CPPUNIT_ASSERT_EQUAL_DIFF(fooconfig + syncurl + configsource, filterFiles(res));
4073 void testConfigureSources() {
4074 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
4075 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
4076 ScopedEnvChange home("HOME", m_testDir);
4078 // create from scratch with only addressbook configured
4080 TestCmdline cmdline("--configure",
4081 "--source-property", "database = file://tmp/test",
4082 "--source-property", "type = file:text/x-vcard",
4088 string root = m_testDir;
4089 root += "/syncevolution/foobar";
4090 string res = scanFiles(root);
4091 removeRandomUUID(res);
4093 StringPrintf(".internal.ini:contextMinVersion = %d\n"
4094 ".internal.ini:contextCurVersion = %d\n"
4095 "config.ini:# logdir = \n"
4096 "config.ini:# maxlogdirs = 10\n"
4097 "config.ini:deviceId = fixed-devid\n"
4098 "sources/addressbook/config.ini:backend = file\n"
4099 "sources/addressbook/config.ini:database = file://tmp/test\n"
4100 "sources/addressbook/config.ini:databaseFormat = text/x-vcard\n"
4101 "sources/addressbook/config.ini:# databaseUser = \n"
4102 "sources/addressbook/config.ini:# databasePassword = \n",
4103 CONFIG_CONTEXT_MIN_VERSION,
4104 CONFIG_CONTEXT_CUR_VERSION);
4105 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
4109 TestCmdline cmdline("--configure",
4110 "--source-property", "database@foobar = file://tmp/test2",
4111 "--source-property", "backend = calendar",
4117 res = scanFiles(root);
4118 removeRandomUUID(res);
4120 "sources/calendar/config.ini:backend = calendar\n"
4121 "sources/calendar/config.ini:database = file://tmp/test2\n"
4122 "sources/calendar/config.ini:# databaseFormat = \n"
4123 "sources/calendar/config.ini:# databaseUser = \n"
4124 "sources/calendar/config.ini:# databasePassword = \n";
4125 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
4127 // add ScheduleWorld peer: must reuse existing backend settings
4129 TestCmdline cmdline("--configure",
4130 "scheduleworld@foobar",
4134 res = scanFiles(root);
4135 removeRandomUUID(res);
4136 expected = ScheduleWorldConfig();
4137 boost::replace_all(expected,
4138 "addressbook/config.ini:backend = addressbook",
4139 "addressbook/config.ini:backend = file");
4140 boost::replace_all(expected,
4141 "addressbook/config.ini:# database = ",
4142 "addressbook/config.ini:database = file://tmp/test");
4143 boost::replace_all(expected,
4144 "addressbook/config.ini:# databaseFormat = ",
4145 "addressbook/config.ini:databaseFormat = text/x-vcard");
4146 boost::replace_all(expected,
4147 "calendar/config.ini:# database = ",
4148 "calendar/config.ini:database = file://tmp/test2");
4149 sortConfig(expected);
4150 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
4152 // disable all sources except for addressbook
4154 TestCmdline cmdline("--configure",
4155 "--source-property", "addressbook/sync=two-way",
4156 "--source-property", "sync=none",
4157 "scheduleworld@foobar",
4161 res = scanFiles(root);
4162 removeRandomUUID(res);
4163 boost::replace_all(expected, "sync = two-way", "sync = disabled");
4164 boost::replace_first(expected, "sync = disabled", "sync = two-way");
4165 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
4167 // override type in template while creating from scratch
4169 TestCmdline cmdline("--configure",
4170 "--template", "SyncEvolution",
4171 "--source-property", "addressbook/type=file:text/vcard:3.0",
4172 "--source-property", "calendar/type=file:text/calendar:2.0",
4177 string syncevoroot = m_testDir + "/syncevolution/syncevo";
4178 res = scanFiles(syncevoroot + "/sources/addressbook");
4179 CPPUNIT_ASSERT(res.find("backend = file\n") != res.npos);
4180 CPPUNIT_ASSERT(res.find("databaseFormat = text/vcard\n") != res.npos);
4181 res = scanFiles(syncevoroot + "/sources/calendar");
4182 CPPUNIT_ASSERT(res.find("backend = file\n") != res.npos);
4183 CPPUNIT_ASSERT(res.find("databaseFormat = text/calendar\n") != res.npos);
4186 void testOldConfigure() {
4187 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
4188 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
4189 ScopedEnvChange home("HOME", m_testDir);
4191 string oldConfig = OldScheduleWorldConfig();
4192 InitList<string> props = InitList<string>("serverNonce") +
4202 "contextMinVersion" +
4203 "contextCurVersion" +
4208 BOOST_FOREACH(string &prop, props) {
4209 boost::replace_all(oldConfig,
4211 prop + " = internal value");
4215 createFiles(m_testDir + "/.sync4j/evolution/scheduleworld", oldConfig);
4217 // Cannot read/and write old format anymore.
4218 SyncContext::setStableRelease(false);
4219 expectMigration("scheduleworld");
4221 // Migrate explicitly.
4223 TestCmdline cmdline("--migrate", "scheduleworld", NULL);
4227 // now test with new format
4228 string expected = ScheduleWorldConfig();
4229 boost::replace_first(expected, "# ConsumerReady = 0", "ConsumerReady = 1");
4230 boost::replace_first(expected, "# database = ", "database = xyz");
4231 boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
4232 boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
4233 // migrating "type" sets forceSyncFormat if not the default,
4234 // and databaseFormat (if format was part of type, as for addressbook)
4235 boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
4236 doConfigure(expected, "sources/addressbook/config.ini:");
4239 string doConfigure(const string &SWConfig, const string &addressbookPrefix) {
4243 TestCmdline cmdline("--configure",
4244 "--source-property", "sync = disabled",
4248 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4249 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4250 expected = filterConfig(internalToIni(SWConfig));
4251 boost::replace_all(expected,
4254 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
4255 filterConfig(printConfig("scheduleworld")));
4259 TestCmdline cmdline("--configure",
4260 "--source-property", "sync = one-way-from-server",
4265 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4266 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4267 expected = SWConfig;
4268 boost::replace_all(expected,
4271 boost::replace_first(expected,
4272 addressbookPrefix + "sync = disabled",
4273 addressbookPrefix + "sync = one-way-from-server");
4274 expected = filterConfig(internalToIni(expected));
4275 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
4276 filterConfig(printConfig("scheduleworld")));
4280 TestCmdline cmdline("--configure",
4281 "--sync", "two-way",
4282 "-z", "database=source",
4283 // note priority of suffix: most specific wins
4284 "--sync-property", "maxlogdirs@scheduleworld@default=20",
4285 "--sync-property", "maxlogdirs@default=10",
4286 "--sync-property", "maxlogdirs=5",
4287 "-y", "LOGDIR@default=logdir",
4291 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4292 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4293 boost::replace_all(expected,
4294 "sync = one-way-from-server",
4296 boost::replace_all(expected,
4299 boost::replace_all(expected,
4301 "database = source");
4302 boost::replace_all(expected,
4304 "database = source");
4305 boost::replace_all(expected,
4306 "# maxlogdirs = 10",
4308 boost::replace_all(expected,
4311 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
4312 filterConfig(printConfig("scheduleworld")));
4318 void testMigrate() {
4319 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
4320 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
4321 ScopedEnvChange home("HOME", m_testDir);
4323 string oldRoot = m_testDir + "/.sync4j/evolution/scheduleworld";
4324 string newRoot = m_testDir + "/syncevolution/default";
4326 string oldConfig = OldScheduleWorldConfig();
4329 // migrate old config
4330 createFiles(oldRoot, oldConfig);
4331 string createdConfig = scanFiles(oldRoot);
4332 TestCmdline cmdline("--migrate",
4336 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4337 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4339 string migratedConfig = scanFiles(newRoot);
4340 string expected = ScheduleWorldConfig();
4341 sortConfig(expected);
4342 // migrating SyncEvolution < 1.2 configs sets
4343 // ConsumerReady, to keep config visible in the updated
4345 boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1");
4346 boost::replace_first(expected, "# database = ", "database = xyz");
4347 boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
4348 boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
4349 // migrating "type" sets forceSyncFormat if different from the "false" default
4350 // and databaseFormat (if format was part of type, as for addressbook)
4351 boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
4352 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
4353 string renamedConfig = scanFiles(oldRoot + ".old");
4354 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
4358 // rewrite existing config with obsolete properties
4359 // => these properties should get removed
4361 // There is one limitation: shared nodes are not rewritten.
4362 // This is acceptable.
4363 createFiles(newRoot + "/peers/scheduleworld",
4364 "config.ini:# obsolete comment\n"
4365 "config.ini:obsoleteprop = foo\n",
4367 string createdConfig = scanFiles(newRoot, "scheduleworld");
4369 TestCmdline cmdline("--migrate",
4373 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4374 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4376 string migratedConfig = scanFiles(newRoot, "scheduleworld");
4377 string expected = ScheduleWorldConfig();
4378 sortConfig(expected);
4379 boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1");
4380 boost::replace_first(expected, "# database = ", "database = xyz");
4381 boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
4382 boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
4383 boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
4384 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
4385 string renamedConfig = scanFiles(newRoot, "scheduleworld.old.1");
4386 boost::replace_first(createdConfig, "ConsumerReady = 1", "ConsumerReady = 0");
4387 boost::replace_all(createdConfig, "/scheduleworld/", "/scheduleworld.old.1/");
4388 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
4392 // migrate old config with changes and .synthesis directory, a second time
4393 createFiles(oldRoot, oldConfig);
4394 createFiles(oldRoot,
4395 ".synthesis/dummy-file.bfi:dummy = foobar\n"
4396 "spds/sources/addressbook/changes/config.txt:foo = bar\n"
4397 "spds/sources/addressbook/changes/config.txt:foo2 = bar2\n",
4399 string createdConfig = scanFiles(oldRoot);
4401 TestCmdline cmdline("--migrate",
4405 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4406 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4408 string migratedConfig = scanFiles(newRoot);
4409 string expected = ScheduleWorldConfig();
4410 sortConfig(expected);
4411 boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1");
4412 boost::replace_first(expected, "# database = ", "database = xyz");
4413 boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
4414 boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
4415 boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
4416 boost::replace_first(expected,
4417 "peers/scheduleworld/sources/addressbook/config.ini",
4418 "peers/scheduleworld/sources/addressbook/.other.ini:foo = bar\n"
4419 "peers/scheduleworld/sources/addressbook/.other.ini:foo2 = bar2\n"
4420 "peers/scheduleworld/sources/addressbook/config.ini");
4421 boost::replace_first(expected,
4422 "peers/scheduleworld/config.ini",
4423 "peers/scheduleworld/.synthesis/dummy-file.bfi:dummy = foobar\n"
4424 "peers/scheduleworld/config.ini");
4425 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
4426 string renamedConfig = scanFiles(oldRoot + ".old.1");
4427 boost::replace_first(createdConfig, "ConsumerReady = 1", "ConsumerReady = 0");
4428 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
4432 string otherRoot = m_testDir + "/syncevolution/other";
4435 // migrate old config into non-default context
4436 createFiles(oldRoot, oldConfig);
4437 string createdConfig = scanFiles(oldRoot);
4439 TestCmdline cmdline("--migrate",
4440 "scheduleworld@other",
4443 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4444 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4447 string migratedConfig = scanFiles(otherRoot);
4448 string expected = ScheduleWorldConfig();
4449 sortConfig(expected);
4450 boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1");
4451 boost::replace_first(expected, "# database = ", "database = xyz");
4452 boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
4453 boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
4454 boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
4455 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
4456 string renamedConfig = scanFiles(oldRoot + ".old");
4457 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
4459 // migrate the migrated config again inside the "other" context,
4460 // with no "default" context which might interfere with the tests
4462 // ConsumerReady was set as part of previous migration,
4463 // must be removed during migration to hide the migrated
4464 // config from average users.
4467 TestCmdline cmdline("--migrate",
4468 "scheduleworld@other",
4471 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4472 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4474 migratedConfig = scanFiles(otherRoot, "scheduleworld");
4475 expected = ScheduleWorldConfig();
4476 sortConfig(expected);
4477 boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1");
4478 boost::replace_first(expected, "# database = ", "database = xyz");
4479 boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
4480 boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
4481 boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
4482 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
4483 renamedConfig = scanFiles(otherRoot, "scheduleworld.old.3");
4484 boost::replace_all(expected, "/scheduleworld/", "/scheduleworld.old.3/");
4485 boost::replace_all(expected, "ConsumerReady = 1", "ConsumerReady = 0");
4486 CPPUNIT_ASSERT_EQUAL_DIFF(expected, renamedConfig);
4488 // migrate once more, this time without the explicit context in
4489 // the config name => must not change the context, need second .old dir
4491 TestCmdline cmdline("--migrate",
4495 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4496 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4498 migratedConfig = scanFiles(otherRoot, "scheduleworld");
4499 boost::replace_all(expected, "/scheduleworld.old.3/", "/scheduleworld/");
4500 boost::replace_all(expected, "ConsumerReady = 0", "ConsumerReady = 1");
4501 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
4502 renamedConfig = scanFiles(otherRoot, "scheduleworld.old.4");
4503 boost::replace_all(expected, "/scheduleworld/", "/scheduleworld.old.4/");
4504 boost::replace_all(expected, "ConsumerReady = 1", "ConsumerReady = 0");
4505 CPPUNIT_ASSERT_EQUAL_DIFF(expected, renamedConfig);
4507 // remove ConsumerReady: must be remain unset when migrating
4508 // hidden SyncEvolution >= 1.2 configs
4510 TestCmdline cmdline("--configure",
4511 "--sync-property", "ConsumerReady=0",
4515 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4516 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4519 // migrate once more => keep ConsumerReady unset
4521 TestCmdline cmdline("--migrate",
4525 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4526 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4528 migratedConfig = scanFiles(otherRoot, "scheduleworld");
4529 boost::replace_all(expected, "/scheduleworld.old.4/", "/scheduleworld/");
4530 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
4531 renamedConfig = scanFiles(otherRoot, "scheduleworld.old.5");
4532 boost::replace_all(expected, "/scheduleworld/", "/scheduleworld.old.5/");
4533 CPPUNIT_ASSERT_EQUAL_DIFF(expected, renamedConfig);
4537 void testMigrateContext()
4539 // Migrate context containing a peer. Must also migrate peer.
4540 // Covers special case of inconsistent "type".
4542 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
4543 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
4544 ScopedEnvChange home("HOME", m_testDir);
4546 string root = m_testDir + "/syncevolution/default";
4549 "config.ini:logDir = none\n"
4550 "peers/scheduleworld/config.ini:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
4551 "peers/scheduleworld/config.ini:# username = \n"
4552 "peers/scheduleworld/config.ini:# password = \n"
4554 "peers/scheduleworld/sources/addressbook/config.ini:sync = two-way\n"
4555 "peers/scheduleworld/sources/addressbook/config.ini:uri = card3\n"
4556 "peers/scheduleworld/sources/addressbook/config.ini:type = addressbook:text/vcard\n" // correct!
4557 "sources/addressbook/config.ini:type = calendar\n" // wrong!
4559 "peers/funambol/config.ini:syncURL = http://sync.funambol.com/funambol/ds\n"
4560 "peers/funambol/config.ini:# username = \n"
4561 "peers/funambol/config.ini:# password = \n"
4563 "peers/funambol/sources/calendar/config.ini:sync = refresh-from-server\n"
4564 "peers/funambol/sources/calendar/config.ini:uri = cal\n"
4565 "peers/funambol/sources/calendar/config.ini:type = calendar\n" // correct!
4566 "peers/funambol/sources/addressbook/config.ini:# sync = disabled\n"
4567 "peers/funambol/sources/addressbook/config.ini:type = file\n" // not used for context because source disabled
4568 "sources/calendar/config.ini:type = memos\n" // wrong!
4570 "peers/memotoo/config.ini:syncURL = http://sync.memotoo.com/memotoo/ds\n"
4571 "peers/memotoo/config.ini:# username = \n"
4572 "peers/memotoo/config.ini:# password = \n"
4574 "peers/memotoo/sources/memo/config.ini:sync = refresh-from-client\n"
4575 "peers/memotoo/sources/memo/config.ini:uri = cal\n"
4576 "peers/memotoo/sources/memo/config.ini:type = memo:text/plain\n" // correct!
4577 "sources/memo/config.ini:type = todo\n" // wrong!
4581 createFiles(root, oldConfig);
4582 TestCmdline cmdline("--migrate",
4583 "memo/backend=file", // override memo "backend" during migration
4587 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4588 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4590 string migratedConfig = scanFiles(root);
4591 CPPUNIT_ASSERT(migratedConfig.find("peers/scheduleworld/") != migratedConfig.npos);
4592 CPPUNIT_ASSERT(migratedConfig.find("sources/addressbook/config.ini:backend = addressbook") != migratedConfig.npos);
4593 CPPUNIT_ASSERT(migratedConfig.find("sources/addressbook/config.ini:databaseFormat = text/vcard") != migratedConfig.npos);
4594 CPPUNIT_ASSERT(migratedConfig.find("peers/scheduleworld/sources/addressbook/config.ini:syncFormat = text/vcard") != migratedConfig.npos);
4595 CPPUNIT_ASSERT(migratedConfig.find("peers/scheduleworld/sources/addressbook/config.ini:sync = two-way") != migratedConfig.npos);
4596 CPPUNIT_ASSERT(migratedConfig.find("peers/scheduleworld/sources/calendar/config.ini:# sync = disabled") != migratedConfig.npos);
4597 CPPUNIT_ASSERT(migratedConfig.find("peers/scheduleworld/sources/memo/config.ini:# sync = disabled") != migratedConfig.npos);
4598 CPPUNIT_ASSERT(migratedConfig.find("sources/calendar/config.ini:backend = calendar") != migratedConfig.npos);
4599 CPPUNIT_ASSERT(migratedConfig.find("sources/calendar/config.ini:# databaseFormat = ") != migratedConfig.npos);
4600 CPPUNIT_ASSERT(migratedConfig.find("peers/funambol/sources/calendar/config.ini:# syncFormat = ") != migratedConfig.npos);
4601 CPPUNIT_ASSERT(migratedConfig.find("peers/funambol/sources/addressbook/config.ini:# sync = disabled") != migratedConfig.npos);
4602 CPPUNIT_ASSERT(migratedConfig.find("peers/funambol/sources/calendar/config.ini:sync = refresh-from-server") != migratedConfig.npos);
4603 CPPUNIT_ASSERT(migratedConfig.find("peers/funambol/sources/memo/config.ini:# sync = disabled") != migratedConfig.npos);
4604 CPPUNIT_ASSERT(migratedConfig.find("sources/memo/config.ini:backend = file") != migratedConfig.npos);
4605 CPPUNIT_ASSERT(migratedConfig.find("sources/memo/config.ini:databaseFormat = text/plain") != migratedConfig.npos);
4606 CPPUNIT_ASSERT(migratedConfig.find("peers/memotoo/sources/memo/config.ini:syncFormat = text/plain") != migratedConfig.npos);
4607 CPPUNIT_ASSERT(migratedConfig.find("peers/memotoo/sources/addressbook/config.ini:# sync = disabled") != migratedConfig.npos);
4608 CPPUNIT_ASSERT(migratedConfig.find("peers/memotoo/sources/calendar/config.ini:# sync = disabled") != migratedConfig.npos);
4609 CPPUNIT_ASSERT(migratedConfig.find("peers/memotoo/sources/memo/config.ini:sync = refresh-from-client") != migratedConfig.npos);
4613 void testMigrateAutoSync() {
4614 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
4615 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
4616 ScopedEnvChange home("HOME", m_testDir);
4618 string oldRoot = m_testDir + "/.sync4j/evolution/scheduleworld";
4619 string newRoot = m_testDir + "/syncevolution/default";
4621 string oldConfig = "spds/syncml/config.txt:autoSync = 1\n";
4622 oldConfig += OldScheduleWorldConfig();
4625 // migrate old config
4626 createFiles(oldRoot, oldConfig);
4627 string createdConfig = scanFiles(oldRoot);
4628 TestCmdline cmdline("--migrate",
4632 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4633 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4635 string migratedConfig = scanFiles(newRoot);
4636 string expected = ScheduleWorldConfig();
4637 boost::replace_first(expected, "# autoSync = 0", "autoSync = 1");
4638 sortConfig(expected);
4639 // migrating SyncEvolution < 1.2 configs sets
4640 // ConsumerReady, to keep config visible in the updated
4642 boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1");
4643 boost::replace_first(expected, "# database = ", "database = xyz");
4644 boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
4645 boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
4646 // migrating "type" sets forceSyncFormat if not already the default,
4647 // and databaseFormat (if format was part of type, as for addressbook)
4648 boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
4649 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
4650 string renamedConfig = scanFiles(oldRoot + ".old");
4651 // autoSync must have been unset
4652 boost::replace_first(createdConfig, ":autoSync = 1", ":autoSync = 0");
4653 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
4657 // rewrite existing config with autoSync set
4658 string createdConfig = scanFiles(newRoot, "scheduleworld");
4660 TestCmdline cmdline("--migrate",
4664 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4665 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4667 string migratedConfig = scanFiles(newRoot, "scheduleworld");
4668 string expected = ScheduleWorldConfig();
4669 boost::replace_first(expected, "# autoSync = 0", "autoSync = 1");
4670 sortConfig(expected);
4671 boost::replace_all(expected, "# ConsumerReady = 0", "ConsumerReady = 1");
4672 boost::replace_first(expected, "# database = ", "database = xyz");
4673 boost::replace_first(expected, "# databaseUser = ", "databaseUser = foo");
4674 boost::replace_first(expected, "# databasePassword = ", "databasePassword = bar");
4675 boost::replace_first(expected, "# databaseFormat = ", "databaseFormat = text/vcard");
4676 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
4677 string renamedConfig = scanFiles(newRoot, "scheduleworld.old.1");
4678 // autoSync must have been unset
4679 boost::replace_first(createdConfig, ":autoSync = 1", ":autoSync = 0");
4680 // the scheduleworld config was consumer ready, the migrated one isn't
4681 boost::replace_all(createdConfig, "ConsumerReady = 1", "ConsumerReady = 0");
4682 boost::replace_all(createdConfig, "/scheduleworld/", "/scheduleworld.old.1/");
4683 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
4687 void testItemOperations() {
4688 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
4689 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
4690 ScopedEnvChange home("HOME", m_testDir);
4693 // "foo" not configured
4694 TestCmdline cmdline("--print-items",
4698 cmdline.doit(false);
4699 CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] bar: backend not supported or not correctly configured (backend=select backend databaseFormat= syncFormat=)\nconfiguration 'foo' does not exist\nsource 'bar' does not exist\nbackend property not set", cmdline.m_err.str());
4700 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4704 // "foo" not configured, no source named
4705 TestCmdline cmdline("--print-items",
4708 cmdline.doit(false);
4709 CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] backend not supported or not correctly configured (backend=select backend databaseFormat= syncFormat=)\nconfiguration 'foo' does not exist\nno source selected\nbackend property not set", cmdline.m_err.str());
4710 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4714 // nothing known about source
4715 TestCmdline cmdline("--print-items",
4717 cmdline.doit(false);
4718 CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] backend not supported or not correctly configured (backend=select backend databaseFormat= syncFormat=)\nno source selected\nbackend property not set", cmdline.m_err.str());
4719 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4724 TestCmdline cmdline("--configure",
4730 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4731 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4735 // "foo" now configured, still no source
4736 TestCmdline cmdline("--print-items",
4739 cmdline.doit(false);
4740 CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] backend not supported or not correctly configured (backend=select backend databaseFormat= syncFormat=)\nno source selected\nbackend property not set", cmdline.m_err.str());
4741 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4745 // foo configured, but "bar" is not
4746 TestCmdline cmdline("--print-items",
4750 cmdline.doit(false);
4751 CPPUNIT_ASSERT_EQUAL_DIFF("[ERROR] bar: backend not supported or not correctly configured (backend=select backend databaseFormat= syncFormat=)\nsource 'bar' does not exist\nbackend property not set", cmdline.m_err.str());
4752 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4756 // add "bar" source, using file backend
4757 TestCmdline cmdline("--configure",
4759 ("database=file://" + m_testDir + "/addressbook").c_str(),
4760 "databaseFormat=text/vcard",
4765 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4766 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4771 TestCmdline cmdline("--print-items",
4776 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4777 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
4780 static const std::string john =
4795 std::string file1 = "1:" + john, file2 = "2:" + joan;
4796 boost::replace_all(file1, "\n", "\n1:");
4797 file1.resize(file1.size() - 2);
4798 boost::replace_all(file2, "\n", "\n2:");
4799 file2.resize(file2.size() - 2);
4800 createFiles(m_testDir + "/addressbook", file1 + file2);
4802 TestCmdline cmdline("--print-items",
4807 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4808 CPPUNIT_ASSERT_EQUAL_DIFF("1\n2\n", cmdline.m_out.str());
4812 // alternatively just specify enough parameters,
4813 // without the foo bar config part
4814 TestCmdline cmdline("--print-items",
4816 ("database=file://" + m_testDir + "/addressbook").c_str(),
4817 "databaseFormat=text/vcard",
4820 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4821 CPPUNIT_ASSERT_EQUAL_DIFF("1\n2\n", cmdline.m_out.str());
4826 TestCmdline cmdline("--export", "-",
4828 ("database=file://" + m_testDir + "/addressbook").c_str(),
4829 "databaseFormat=text/vcard",
4832 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4833 CPPUNIT_ASSERT_EQUAL_DIFF(john + "\n" + joan, cmdline.m_out.str());
4837 // export all via config
4838 TestCmdline cmdline("--export", "-",
4842 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4843 CPPUNIT_ASSERT_EQUAL_DIFF(john + "\n" + joan, cmdline.m_out.str());
4848 TestCmdline cmdline("--export", "-",
4850 ("database=file://" + m_testDir + "/addressbook").c_str(),
4851 "databaseFormat=text/vcard",
4855 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4856 CPPUNIT_ASSERT_EQUAL_DIFF(john, cmdline.m_out.str());
4860 // export one via config
4861 TestCmdline cmdline("--export", "-",
4865 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
4866 CPPUNIT_ASSERT_EQUAL_DIFF(john, cmdline.m_out.str());
4869 // TODO: check configuration of just the source as @foo bar without peer
4872 // check error message for missing config name
4873 TestCmdline cmdline((const char *)NULL);
4874 cmdline.doit(false);
4875 CPPUNIT_ASSERT_NO_THROW(cmdline.expectUsageError("[ERROR] No configuration name specified.\n"));
4879 // check error message for missing config name, version II
4880 TestCmdline cmdline("--run",
4882 cmdline.doit(false);
4883 CPPUNIT_ASSERT_NO_THROW(cmdline.expectUsageError("[ERROR] No configuration name specified.\n"));
4887 const string m_testDir;
4892 * vararg constructor with NULL termination,
4893 * out and error stream into stringstream members
4895 class TestCmdline : private LoggerBase {
4899 m_argv.reset(new const char *[m_argvstr.size() + 1]);
4900 m_argv[0] = "client-test";
4901 for (size_t index = 0;
4902 index < m_argvstr.size();
4904 m_argv[index + 1] = m_argvstr[index].c_str();
4907 m_cmdline.set(new KeyringSyncCmdline(m_argvstr.size() + 1, m_argv.get()), "cmdline");
4911 TestCmdline(const char *arg, ...) {
4913 va_start (argList, arg);
4914 for (const char *curr = arg;
4916 curr = va_arg(argList, const char *)) {
4917 m_argvstr.push_back(curr);
4923 TestCmdline(const char * const argv[]) {
4924 for (int i = 0; argv[i]; i++) {
4925 m_argvstr.push_back(argv[i]);
4934 boost::shared_ptr<SyncContext> parse()
4936 if (!m_cmdline->parse()) {
4937 return boost::shared_ptr<SyncContext>();
4939 boost::shared_ptr<SyncContext> context(new SyncContext(m_cmdline->m_server));
4940 context->setConfigFilter(true, "", m_cmdline->m_props.createSyncFilter(m_cmdline->m_server));
4944 void doit(bool expectSuccess = true) {
4945 bool success = false;
4948 // emulates syncevolution.cpp exception handling
4950 success = m_cmdline->parse() &&
4952 } catch (const std::exception &ex) {
4953 m_err << "[ERROR] " << ex.what();
4955 std::string explanation;
4956 Exception::handle(explanation);
4957 m_err << "[ERROR] " << explanation;
4959 if (expectSuccess && m_err.str().size()) {
4960 m_out << endl << m_err.str();
4962 CPPUNIT_ASSERT_MESSAGE(m_out.str(), success == expectSuccess);
4965 /** verify that Cmdline::usage() produced a short usage info followed by a specific error message */
4966 void expectUsageError(const std::string &error)
4968 // expect short usage info as normal output
4969 std::string out = m_out.str();
4970 std::string err = m_err.str();
4971 std::string all = m_all.str();
4972 CPPUNIT_ASSERT(boost::starts_with(out, "List databases:\n"));
4973 CPPUNIT_ASSERT(out.find("\nOptions:\n") == std::string::npos);
4974 CPPUNIT_ASSERT(boost::ends_with(out,
4976 " syncevolution --delete-items [--] <config> <source> (<luid> ... | '*')\n\n"));
4977 // exact error message
4978 CPPUNIT_ASSERT_EQUAL(error, err);
4981 CPPUNIT_ASSERT_EQUAL_DIFF(out + err, all);
4984 // separate streams for normal messages and error messages
4985 ostringstream m_out, m_err;
4986 // combined stream with all messages
4987 ostringstream m_all;
4989 cxxptr<Cmdline> m_cmdline;
4992 vector<string> m_argvstr;
4993 boost::scoped_array<const char *> m_argv;
4995 /** capture output produced while test ran */
4996 void messagev(Level level,
5000 const char *function,
5004 if (level <= INFO) {
5005 ostringstream &out = level != SHOW ? m_err : m_out;
5006 std::string str = StringPrintfV(format, args);
5007 if (level != SHOW) {
5008 out << "[" << levelToStr(level) << "] ";
5009 m_all << "[" << levelToStr(level) << "] ";
5013 if (!boost::ends_with(str, "\n")) {
5019 virtual bool isProcessSafe() const { return false; }
5022 string DefaultConfig() {
5023 string config = ScheduleWorldConfig();
5024 boost::replace_first(config,
5025 "syncURL = http://sync.scheduleworld.com/funambol/ds",
5026 "syncURL = http://yourserver:port");
5027 boost::replace_first(config, "http://www.scheduleworld.com", "http://www.syncevolution.org");
5028 boost::replace_all(config, "ScheduleWorld", "SyncEvolution");
5029 boost::replace_all(config, "scheduleworld", "syncevolution");
5030 boost::replace_first(config, "PeerName = SyncEvolution", "# PeerName = ");
5031 boost::replace_first(config, "# ConsumerReady = 0", "ConsumerReady = 1");
5032 boost::replace_first(config, "uri = card3", "uri = addressbook");
5033 boost::replace_first(config, "uri = cal2", "uri = calendar");
5034 boost::replace_first(config, "uri = task2", "uri = todo");
5035 boost::replace_first(config, "uri = note", "uri = memo");
5036 boost::replace_first(config, "syncFormat = text/vcard", "# syncFormat = ");
5040 string ScheduleWorldConfig(int contextMinVersion = CONFIG_CONTEXT_MIN_VERSION,
5041 int contextCurVersion = CONFIG_CONTEXT_CUR_VERSION,
5042 int peerMinVersion = CONFIG_PEER_MIN_VERSION,
5043 int peerCurVersion = CONFIG_PEER_CUR_VERSION) {
5044 // properties sorted by the order in which they are defined
5045 // in the sync and sync source property registry
5047 StringPrintf("peers/scheduleworld/.internal.ini:peerMinVersion = %d\n"
5048 "peers/scheduleworld/.internal.ini:peerCurVersion = %d\n"
5049 "peers/scheduleworld/.internal.ini:# HashCode = 0\n"
5050 "peers/scheduleworld/.internal.ini:# ConfigDate = \n"
5051 "peers/scheduleworld/.internal.ini:# lastNonce = \n"
5052 "peers/scheduleworld/.internal.ini:# deviceData = \n"
5053 "peers/scheduleworld/.internal.ini:# webDAVCredentialsOkay = 0\n"
5054 "peers/scheduleworld/config.ini:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
5055 "peers/scheduleworld/config.ini:# username = \n"
5056 "peers/scheduleworld/config.ini:# password = \n"
5057 ".internal.ini:contextMinVersion = %d\n"
5058 ".internal.ini:contextCurVersion = %d\n"
5059 "config.ini:# logdir = \n"
5060 "peers/scheduleworld/config.ini:# loglevel = 0\n"
5061 "peers/scheduleworld/config.ini:# notifyLevel = 3\n"
5062 "peers/scheduleworld/config.ini:# printChanges = 1\n"
5063 "peers/scheduleworld/config.ini:# dumpData = 1\n"
5064 "config.ini:# maxlogdirs = 10\n"
5065 "peers/scheduleworld/config.ini:# autoSync = 0\n"
5066 "peers/scheduleworld/config.ini:# autoSyncInterval = 30M\n"
5067 "peers/scheduleworld/config.ini:# autoSyncDelay = 5M\n"
5068 "peers/scheduleworld/config.ini:# preventSlowSync = 1\n"
5069 "peers/scheduleworld/config.ini:# useProxy = 0\n"
5070 "peers/scheduleworld/config.ini:# proxyHost = \n"
5071 "peers/scheduleworld/config.ini:# proxyUsername = \n"
5072 "peers/scheduleworld/config.ini:# proxyPassword = \n"
5073 "peers/scheduleworld/config.ini:# clientAuthType = md5\n"
5074 "peers/scheduleworld/config.ini:# RetryDuration = 5M\n"
5075 "peers/scheduleworld/config.ini:# RetryInterval = 2M\n"
5076 "peers/scheduleworld/config.ini:# remoteIdentifier = \n"
5077 "peers/scheduleworld/config.ini:# PeerIsClient = 0\n"
5078 "peers/scheduleworld/config.ini:# SyncMLVersion = \n"
5079 "peers/scheduleworld/config.ini:PeerName = ScheduleWorld\n"
5080 "config.ini:deviceId = fixed-devid\n" /* this is not the default! */
5081 "peers/scheduleworld/config.ini:# remoteDeviceId = \n"
5082 "peers/scheduleworld/config.ini:# enableWBXML = 1\n"
5083 "peers/scheduleworld/config.ini:# maxMsgSize = 150000\n"
5084 "peers/scheduleworld/config.ini:# maxObjSize = 4000000\n"
5085 "peers/scheduleworld/config.ini:# SSLServerCertificates = \n"
5086 "peers/scheduleworld/config.ini:# SSLVerifyServer = 1\n"
5087 "peers/scheduleworld/config.ini:# SSLVerifyHost = 1\n"
5088 "peers/scheduleworld/config.ini:WebURL = http://www.scheduleworld.com\n"
5089 "peers/scheduleworld/config.ini:IconURI = image://themedimage/icons/services/scheduleworld\n"
5090 "peers/scheduleworld/config.ini:# ConsumerReady = 0\n"
5091 "peers/scheduleworld/config.ini:# peerType = \n"
5093 "peers/scheduleworld/sources/addressbook/.internal.ini:# adminData = \n"
5094 "peers/scheduleworld/sources/addressbook/.internal.ini:# synthesisID = 0\n"
5095 "peers/scheduleworld/sources/addressbook/config.ini:sync = two-way\n"
5096 "peers/scheduleworld/sources/addressbook/config.ini:uri = card3\n"
5097 "sources/addressbook/config.ini:backend = addressbook\n"
5098 "peers/scheduleworld/sources/addressbook/config.ini:syncFormat = text/vcard\n"
5099 "peers/scheduleworld/sources/addressbook/config.ini:# forceSyncFormat = 0\n"
5100 "sources/addressbook/config.ini:# database = \n"
5101 "sources/addressbook/config.ini:# databaseFormat = \n"
5102 "sources/addressbook/config.ini:# databaseUser = \n"
5103 "sources/addressbook/config.ini:# databasePassword = \n"
5105 "peers/scheduleworld/sources/calendar/.internal.ini:# adminData = \n"
5106 "peers/scheduleworld/sources/calendar/.internal.ini:# synthesisID = 0\n"
5107 "peers/scheduleworld/sources/calendar/config.ini:sync = two-way\n"
5108 "peers/scheduleworld/sources/calendar/config.ini:uri = cal2\n"
5109 "sources/calendar/config.ini:backend = calendar\n"
5110 "peers/scheduleworld/sources/calendar/config.ini:# syncFormat = \n"
5111 "peers/scheduleworld/sources/calendar/config.ini:# forceSyncFormat = 0\n"
5112 "sources/calendar/config.ini:# database = \n"
5113 "sources/calendar/config.ini:# databaseFormat = \n"
5114 "sources/calendar/config.ini:# databaseUser = \n"
5115 "sources/calendar/config.ini:# databasePassword = \n"
5117 "peers/scheduleworld/sources/memo/.internal.ini:# adminData = \n"
5118 "peers/scheduleworld/sources/memo/.internal.ini:# synthesisID = 0\n"
5119 "peers/scheduleworld/sources/memo/config.ini:sync = two-way\n"
5120 "peers/scheduleworld/sources/memo/config.ini:uri = note\n"
5121 "sources/memo/config.ini:backend = memo\n"
5122 "peers/scheduleworld/sources/memo/config.ini:# syncFormat = \n"
5123 "peers/scheduleworld/sources/memo/config.ini:# forceSyncFormat = 0\n"
5124 "sources/memo/config.ini:# database = \n"
5125 "sources/memo/config.ini:# databaseFormat = \n"
5126 "sources/memo/config.ini:# databaseUser = \n"
5127 "sources/memo/config.ini:# databasePassword = \n"
5129 "peers/scheduleworld/sources/todo/.internal.ini:# adminData = \n"
5130 "peers/scheduleworld/sources/todo/.internal.ini:# synthesisID = 0\n"
5131 "peers/scheduleworld/sources/todo/config.ini:sync = two-way\n"
5132 "peers/scheduleworld/sources/todo/config.ini:uri = task2\n"
5133 "sources/todo/config.ini:backend = todo\n"
5134 "peers/scheduleworld/sources/todo/config.ini:# syncFormat = \n"
5135 "peers/scheduleworld/sources/todo/config.ini:# forceSyncFormat = 0\n"
5136 "sources/todo/config.ini:# database = \n"
5137 "sources/todo/config.ini:# databaseFormat = \n"
5138 "sources/todo/config.ini:# databaseUser = \n"
5139 "sources/todo/config.ini:# databasePassword = ",
5140 peerMinVersion, peerCurVersion,
5141 contextMinVersion, contextCurVersion);
5142 #ifdef ENABLE_LIBSOUP
5143 // path to SSL certificates has to be set only for libsoup
5144 boost::replace_first(config,
5145 "SSLServerCertificates = ",
5146 "SSLServerCertificates = /etc/ssl/certs/ca-certificates.crt:/etc/pki/tls/certs/ca-bundle.crt:/usr/share/ssl/certs/ca-bundle.crt");
5150 // Currently we don't have an icon for ScheduleWorld. If we
5151 // had (MB #2062) one, then this code would ensure that the
5152 // reference config also has the right path for it.
5153 const char *templateDir = getenv("SYNCEVOLUTION_TEMPLATE_DIR");
5155 templateDir = TEMPLATE_DIR;
5159 if (isDir(string(templateDir) + "/ScheduleWorld")) {
5160 boost::replace_all(config,
5162 string("IconURI = file://") + templateDir + "/ScheduleWorld/icon.png");
5168 string OldScheduleWorldConfig() {
5171 "spds/syncml/config.txt:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
5172 "spds/syncml/config.txt:# username = \n"
5173 "spds/syncml/config.txt:# password = \n"
5174 "spds/syncml/config.txt:# logdir = \n"
5175 "spds/syncml/config.txt:# loglevel = 0\n"
5176 "spds/syncml/config.txt:# notifyLevel = 3\n"
5177 "spds/syncml/config.txt:# printChanges = 1\n"
5178 "spds/syncml/config.txt:# dumpData = 1\n"
5179 "spds/syncml/config.txt:# maxlogdirs = 10\n"
5180 "spds/syncml/config.txt:# autoSync = 0\n"
5181 "spds/syncml/config.txt:# autoSyncInterval = 30M\n"
5182 "spds/syncml/config.txt:# autoSyncDelay = 5M\n"
5183 "spds/syncml/config.txt:# preventSlowSync = 1\n"
5184 "spds/syncml/config.txt:# useProxy = 0\n"
5185 "spds/syncml/config.txt:# proxyHost = \n"
5186 "spds/syncml/config.txt:# proxyUsername = \n"
5187 "spds/syncml/config.txt:# proxyPassword = \n"
5188 "spds/syncml/config.txt:# clientAuthType = md5\n"
5189 "spds/syncml/config.txt:# RetryDuration = 5M\n"
5190 "spds/syncml/config.txt:# RetryInterval = 2M\n"
5191 "spds/syncml/config.txt:# remoteIdentifier = \n"
5192 "spds/syncml/config.txt:# PeerIsClient = 0\n"
5193 "spds/syncml/config.txt:# SyncMLVersion = \n"
5194 "spds/syncml/config.txt:PeerName = ScheduleWorld\n"
5195 "spds/syncml/config.txt:deviceId = fixed-devid\n" /* this is not the default! */
5196 "spds/syncml/config.txt:# remoteDeviceId = \n"
5197 "spds/syncml/config.txt:# enableWBXML = 1\n"
5198 "spds/syncml/config.txt:# maxMsgSize = 150000\n"
5199 "spds/syncml/config.txt:# maxObjSize = 4000000\n"
5200 #ifdef ENABLE_LIBSOUP
5201 // path to SSL certificates is only set for libsoup
5202 "spds/syncml/config.txt:# SSLServerCertificates = /etc/ssl/certs/ca-certificates.crt:/etc/pki/tls/certs/ca-bundle.crt:/usr/share/ssl/certs/ca-bundle.crt\n"
5205 "spds/syncml/config.txt:# SSLServerCertificates = \n"
5207 "spds/syncml/config.txt:# SSLVerifyServer = 1\n"
5208 "spds/syncml/config.txt:# SSLVerifyHost = 1\n"
5209 "spds/syncml/config.txt:WebURL = http://www.scheduleworld.com\n"
5210 "spds/syncml/config.txt:IconURI = image://themedimage/icons/services/scheduleworld\n"
5211 "spds/syncml/config.txt:# ConsumerReady = 0\n"
5212 "spds/sources/addressbook/config.txt:sync = two-way\n"
5213 "spds/sources/addressbook/config.txt:type = addressbook:text/vcard\n"
5214 "spds/sources/addressbook/config.txt:evolutionsource = xyz\n"
5215 "spds/sources/addressbook/config.txt:uri = card3\n"
5216 "spds/sources/addressbook/config.txt:evolutionuser = foo\n"
5217 "spds/sources/addressbook/config.txt:evolutionpassword = bar\n"
5218 "spds/sources/calendar/config.txt:sync = two-way\n"
5219 "spds/sources/calendar/config.txt:type = calendar\n"
5220 "spds/sources/calendar/config.txt:# database = \n"
5221 "spds/sources/calendar/config.txt:uri = cal2\n"
5222 "spds/sources/calendar/config.txt:# evolutionuser = \n"
5223 "spds/sources/calendar/config.txt:# evolutionpassword = \n"
5224 "spds/sources/memo/config.txt:sync = two-way\n"
5225 "spds/sources/memo/config.txt:type = memo\n"
5226 "spds/sources/memo/config.txt:# database = \n"
5227 "spds/sources/memo/config.txt:uri = note\n"
5228 "spds/sources/memo/config.txt:# evolutionuser = \n"
5229 "spds/sources/memo/config.txt:# evolutionpassword = \n"
5230 "spds/sources/todo/config.txt:sync = two-way\n"
5231 "spds/sources/todo/config.txt:type = todo\n"
5232 "spds/sources/todo/config.txt:# database = \n"
5233 "spds/sources/todo/config.txt:uri = task2\n"
5234 "spds/sources/todo/config.txt:# evolutionuser = \n"
5235 "spds/sources/todo/config.txt:# evolutionpassword = \n";
5239 string FunambolConfig() {
5240 string config = ScheduleWorldConfig();
5241 boost::replace_all(config, "/scheduleworld/", "/funambol/");
5242 boost::replace_all(config, "PeerName = ScheduleWorld", "PeerName = Funambol");
5244 boost::replace_first(config,
5245 "syncURL = http://sync.scheduleworld.com/funambol/ds",
5246 "syncURL = http://my.funambol.com/sync");
5248 boost::replace_first(config,
5249 "WebURL = http://www.scheduleworld.com",
5250 "WebURL = http://my.funambol.com");
5252 boost::replace_first(config,
5253 "IconURI = image://themedimage/icons/services/scheduleworld",
5254 "IconURI = image://themedimage/icons/services/funambol");
5256 boost::replace_first(config,
5257 "# ConsumerReady = 0",
5258 "ConsumerReady = 1");
5260 boost::replace_first(config,
5261 "# enableWBXML = 1",
5264 boost::replace_first(config,
5265 "# RetryInterval = 2M",
5266 "RetryInterval = 0");
5268 boost::replace_first(config,
5269 "addressbook/config.ini:uri = card3",
5270 "addressbook/config.ini:uri = card");
5271 boost::replace_all(config,
5272 "addressbook/config.ini:syncFormat = text/vcard",
5273 "addressbook/config.ini:# syncFormat = ");
5275 boost::replace_first(config,
5276 "calendar/config.ini:uri = cal2",
5277 "calendar/config.ini:uri = event");
5278 boost::replace_all(config,
5279 "calendar/config.ini:# syncFormat = ",
5280 "calendar/config.ini:syncFormat = text/calendar");
5281 boost::replace_all(config,
5282 "calendar/config.ini:# forceSyncFormat = 0",
5283 "calendar/config.ini:forceSyncFormat = 1");
5285 boost::replace_first(config,
5286 "todo/config.ini:uri = task2",
5287 "todo/config.ini:uri = task");
5288 boost::replace_all(config,
5289 "todo/config.ini:# syncFormat = ",
5290 "todo/config.ini:syncFormat = text/calendar");
5291 boost::replace_all(config,
5292 "todo/config.ini:# forceSyncFormat = 0",
5293 "todo/config.ini:forceSyncFormat = 1");
5298 string SynthesisConfig() {
5299 string config = ScheduleWorldConfig();
5300 boost::replace_all(config, "/scheduleworld/", "/synthesis/");
5301 boost::replace_all(config, "PeerName = ScheduleWorld", "PeerName = Synthesis");
5303 boost::replace_first(config,
5304 "syncURL = http://sync.scheduleworld.com/funambol/ds",
5305 "syncURL = http://www.synthesis.ch/sync");
5307 boost::replace_first(config,
5308 "WebURL = http://www.scheduleworld.com",
5309 "WebURL = http://www.synthesis.ch");
5311 boost::replace_first(config,
5312 "IconURI = image://themedimage/icons/services/scheduleworld",
5313 "IconURI = image://themedimage/icons/services/synthesis");
5315 boost::replace_first(config,
5316 "addressbook/config.ini:uri = card3",
5317 "addressbook/config.ini:uri = contacts");
5318 boost::replace_all(config,
5319 "addressbook/config.ini:syncFormat = text/vcard",
5320 "addressbook/config.ini:# syncFormat = ");
5322 boost::replace_first(config,
5323 "calendar/config.ini:uri = cal2",
5324 "calendar/config.ini:uri = events");
5325 boost::replace_first(config,
5326 "calendar/config.ini:sync = two-way",
5327 "calendar/config.ini:sync = disabled");
5329 boost::replace_first(config,
5330 "memo/config.ini:uri = note",
5331 "memo/config.ini:uri = notes");
5333 boost::replace_first(config,
5334 "todo/config.ini:uri = task2",
5335 "todo/config.ini:uri = tasks");
5336 boost::replace_first(config,
5337 "todo/config.ini:sync = two-way",
5338 "todo/config.ini:sync = disabled");
5343 /** create directory hierarchy, overwriting previous content */
5344 void createFiles(const string &root, const string &content, bool append = false) {
5353 out.exceptions(ios_base::badbit|ios_base::failbit);
5354 while (start < content.size()) {
5355 size_t delim = content.find(':', start);
5356 size_t end = content.find('\n', start);
5357 if (delim == content.npos ||
5358 end == content.npos) {
5359 // invalid content ?!
5362 string newname = content.substr(start, delim - start);
5363 string line = content.substr(delim + 1, end - delim - 1);
5364 if (newname != outname) {
5365 if (out.is_open()) {
5368 string fullpath = root + "/" + newname;
5369 size_t fileoff = fullpath.rfind('/');
5370 mkdir_p(fullpath.substr(0, fileoff));
5371 out.open(fullpath.c_str(),
5372 append ? (ios_base::out|ios_base::ate|ios_base::app) : (ios_base::out|ios_base::trunc));
5375 out << line << endl;
5380 /** turn directory hierarchy into string
5382 * @param root root path in file system
5383 * @param peer if non-empty, then ignore all <root>/peers/<foo> directories
5384 * where <foo> != peer
5385 * @param onlyProps ignore lines which are comments
5387 string scanFiles(const string &root, const string &peer = "", bool onlyProps = true) {
5390 scanFiles(root, "", peer, out, onlyProps);
5394 void scanFiles(const string &root, const string &dir, const string &peer, ostringstream &out, bool onlyProps) {
5395 string newroot = root;
5398 ReadDir readDir(newroot);
5399 sort(readDir.begin(), readDir.end());
5401 BOOST_FOREACH(const string &entry, readDir) {
5402 if (isDir(newroot + "/" + entry)) {
5403 if (boost::ends_with(newroot, "/peers") &&
5406 // skip different peer directory
5409 scanFiles(root, dir + (dir.empty() ? "" : "/") + entry, peer, out, onlyProps);
5413 in.exceptions(ios_base::badbit /* failbit must not trigger exception because is set when reaching eof ?! */);
5414 in.open((newroot + "/" + entry).c_str());
5418 if ((line.size() || !in.eof()) &&
5420 (boost::starts_with(line, "# ") ?
5421 isPropAssignment(line.substr(2)) :
5426 out << entry << ":";
5427 out << line << '\n';
5434 string printConfig(const string &server) {
5435 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "templates");
5436 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
5437 ScopedEnvChange home("HOME", m_testDir);
5439 TestCmdline cmdline("--print-config", server.c_str(), NULL);
5441 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
5442 return cmdline.m_out.str();
5446 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(CmdlineTest);
5448 #endif // ENABLE_UNIT_TESTS