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/FilterConfigNode.h>
23 #include <syncevo/VolatileConfigNode.h>
24 #include <syncevo/SyncSource.h>
25 #include <syncevo/SyncContext.h>
26 #include <syncevo/util.h>
41 #include <boost/shared_ptr.hpp>
42 #include <boost/algorithm/string/join.hpp>
43 #include <boost/algorithm/string.hpp>
44 #include <boost/foreach.hpp>
46 #include <syncevo/declarations.h>
49 Cmdline::Cmdline(int argc, const char * const * argv, ostream &out, ostream &err) :
54 m_validSyncProps(SyncConfig::getRegistry()),
55 m_validSourceProps(SyncSourceConfig::getRegistry())
58 Cmdline::Cmdline(const vector<string> &args, ostream &out, ostream &err) :
62 m_validSyncProps(SyncConfig::getRegistry()),
63 m_validSourceProps(SyncSourceConfig::getRegistry())
66 m_argvArray.reset(new const char *[args.size()]);
67 for(int i = 0; i < m_argc; i++) {
68 m_argvArray[i] = m_args[i].c_str();
70 m_argv = m_argvArray.get();
75 vector<string> parsed;
79 bool Cmdline::parse(vector<string> &parsed)
83 parsed.push_back(m_argv[0]);
88 while (opt < m_argc) {
89 parsed.push_back(m_argv[opt]);
90 if (m_argv[opt][0] != '-') {
93 if (boost::iequals(m_argv[opt], "--sync") ||
94 boost::iequals(m_argv[opt], "-s")) {
97 string cmdopt(m_argv[opt - 1]);
98 if (!parseProp(m_validSourceProps, m_sourceProps,
99 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt],
100 SyncSourceConfig::m_sourcePropSync.getName().c_str())) {
103 parsed.push_back(m_argv[opt]);
105 // disable requirement to add --run explicitly in order to
106 // be compatible with traditional command lines
108 } else if(boost::iequals(m_argv[opt], "--sync-property") ||
109 boost::iequals(m_argv[opt], "-y")) {
111 if (!parseProp(m_validSyncProps, m_syncProps,
112 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
115 parsed.push_back(m_argv[opt]);
116 } else if(boost::iequals(m_argv[opt], "--source-property") ||
117 boost::iequals(m_argv[opt], "-z")) {
119 if (!parseProp(m_validSourceProps, m_sourceProps,
120 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
123 parsed.push_back(m_argv[opt]);
124 }else if(boost::iequals(m_argv[opt], "--template") ||
125 boost::iequals(m_argv[opt], "-l")) {
128 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
131 parsed.push_back(m_argv[opt]);
132 m_template = m_argv[opt];
134 string temp = boost::trim_copy (m_template);
135 if (temp.find ("?") == 0){
136 m_printTemplates = true;
138 m_template = temp.substr (1);
140 } else if(boost::iequals(m_argv[opt], "--print-servers") ||
141 boost::iequals(m_argv[opt], "--print-peers") ||
142 boost::iequals(m_argv[opt], "--print-configs")) {
143 m_printServers = true;
144 } else if(boost::iequals(m_argv[opt], "--print-config") ||
145 boost::iequals(m_argv[opt], "-p")) {
146 m_printConfig = true;
147 } else if(boost::iequals(m_argv[opt], "--print-sessions")) {
148 m_printSessions = true;
149 } else if(boost::iequals(m_argv[opt], "--configure") ||
150 boost::iequals(m_argv[opt], "-c")) {
152 } else if(boost::iequals(m_argv[opt], "--remove")) {
154 } else if(boost::iequals(m_argv[opt], "--run") ||
155 boost::iequals(m_argv[opt], "-r")) {
157 } else if(boost::iequals(m_argv[opt], "--restore")) {
160 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
163 m_restore = m_argv[opt];
164 if (m_restore.empty()) {
165 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
168 //if can't convert it successfully, it's an invalid path
169 if (!relToAbs(m_restore)) {
170 usage(true, string("parameter '") + m_restore + "' for " + cmdOpt(m_argv[opt - 1]) + " must be log directory");
173 parsed.push_back(m_restore);
174 } else if(boost::iequals(m_argv[opt], "--before")) {
176 } else if(boost::iequals(m_argv[opt], "--after")) {
178 } else if(boost::iequals(m_argv[opt], "--dry-run")) {
180 } else if(boost::iequals(m_argv[opt], "--migrate")) {
182 } else if(boost::iequals(m_argv[opt], "--status") ||
183 boost::iequals(m_argv[opt], "-t")) {
185 } else if(boost::iequals(m_argv[opt], "--quiet") ||
186 boost::iequals(m_argv[opt], "-q")) {
188 } else if(boost::iequals(m_argv[opt], "--help") ||
189 boost::iequals(m_argv[opt], "-h")) {
191 } else if(boost::iequals(m_argv[opt], "--version")) {
193 } else if (parseBool(opt, "--keyring", "-k", true, m_keyring, ok)) {
197 } else if (parseBool(opt, "--daemon", NULL, true, m_useDaemon, ok)) {
201 } else if(boost::iequals(m_argv[opt], "--monitor")||
202 boost::iequals(m_argv[opt], "-m")) {
205 usage(false, string(m_argv[opt]) + ": unknown parameter");
212 m_server = m_argv[opt++];
213 while (opt < m_argc) {
214 parsed.push_back(m_argv[opt]);
215 m_sources.insert(m_argv[opt++]);
222 bool Cmdline::parseBool(int opt, const char *longName, const char *shortName,
223 bool def, Bool &value,
226 string option = m_argv[opt];
228 size_t pos = option.find('=');
229 if (pos != option.npos) {
230 param = option.substr(pos + 1);
233 if ((longName && boost::iequals(option, longName)) ||
234 (shortName && boost::iequals(option, shortName))) {
238 } else if (boost::iequals(param, "t") ||
239 boost::iequals(param, "1") ||
240 boost::iequals(param, "true") ||
241 boost::iequals(param, "yes")) {
243 } else if (boost::iequals(param, "f") ||
244 boost::iequals(param, "0") ||
245 boost::iequals(param, "false") ||
246 boost::iequals(param, "no")) {
249 usage(true, string("parameter in '") + m_argv[opt] + "' must be 1/t/true/yes or 0/f/false/no");
255 // keep searching for match
260 bool Cmdline::isSync()
262 //make sure command line arguments really try to run sync
263 if(m_usage || m_version) {
265 } else if(m_printServers || boost::trim_copy(m_server) == "?") {
267 } else if(m_printTemplates || m_dontrun) {
269 } else if(m_argc == 1 || (m_useDaemon.wasSet() && m_argc == 2)) {
271 } else if(m_printConfig || m_remove) {
273 } else if (m_server == "" && m_argc > 1) {
275 } else if(m_configure || m_migrate) {
277 } else if(m_status || m_printSessions) {
279 } else if(!m_restore.empty()) {
281 } else if(m_dryrun) {
283 } else if(!m_run && (m_syncProps.size() || m_sourceProps.size())) {
289 bool Cmdline::dontRun() const
291 // this mimics the if() checks in run()
292 if (m_usage || m_version ||
293 m_printServers || boost::trim_copy(m_server) == "?" ||
301 bool Cmdline::run() {
302 // --dry-run is only supported by some operations.
303 // Be very strict about it and make sure it is off in all
304 // potentially harmful operations, otherwise users might
305 // expect it to have an effect when it doesn't.
309 } else if (m_version) {
310 printf("SyncEvolution %s\n", VERSION);
311 printf("%s", EDSAbiWrapperInfo());
312 printf("%s", SyncSource::backendsInfo().c_str());
313 } else if (m_printServers || boost::trim_copy(m_server) == "?") {
314 dumpConfigs("Configured servers:",
315 SyncConfig::getConfigs());
316 } else if (m_printTemplates) {
317 SyncConfig::DeviceList devices;
318 if (m_template.empty()){
319 dumpConfigTemplates("Available configuration templates:",
320 SyncConfig::getPeerTemplates(devices), false);
322 //limiting at templates for syncml clients only.
323 devices.push_back (SyncConfig::DeviceDescription("", m_template, SyncConfig::MATCH_FOR_SERVER_MODE));
324 dumpConfigTemplates("Available configuration templates:",
325 SyncConfig::matchPeerTemplates(devices), true);
327 } else if (m_dontrun) {
328 // user asked for information
329 } else if (m_argc == 1 || (m_useDaemon.wasSet() && m_argc == 2)) {
330 // no parameters: list databases and short usage
331 const SourceRegistry ®istry(SyncSource::getSourceRegistry());
332 boost::shared_ptr<FilterConfigNode> sharedNode(new VolatileConfigNode());
333 boost::shared_ptr<FilterConfigNode> configNode(new VolatileConfigNode());
334 boost::shared_ptr<FilterConfigNode> hiddenNode(new VolatileConfigNode());
335 boost::shared_ptr<FilterConfigNode> trackingNode(new VolatileConfigNode());
336 boost::shared_ptr<FilterConfigNode> serverNode(new VolatileConfigNode());
337 SyncSourceNodes nodes(true, sharedNode, configNode, hiddenNode, trackingNode, serverNode, "");
338 SyncSourceParams params("list", nodes);
340 BOOST_FOREACH(const RegisterSyncSource *source, registry) {
341 BOOST_FOREACH(const Values::value_type &alias, source->m_typeValues) {
342 if (!alias.empty() && source->m_enabled) {
343 configNode->setProperty("type", *alias.begin());
344 auto_ptr<SyncSource> source(SyncSource::createSource(params, false));
345 if (source.get() != NULL) {
346 listSources(*source, boost::join(alias, " = "));
354 } else if (m_printConfig) {
355 boost::shared_ptr<SyncConfig> config;
356 ConfigProps syncFilter;
357 SourceFilters_t sourceFilters;
359 if (m_template.empty()) {
360 if (m_server.empty()) {
361 m_err << "ERROR: --print-config requires either a --template or a server name." << endl;
364 config.reset(new SyncConfig(m_server));
365 if (!config->exists()) {
366 m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
370 syncFilter = m_syncProps;
371 sourceFilters[""] = m_sourceProps;
373 string peer, context;
374 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_template), peer, context);
376 config = SyncConfig::createPeerTemplate(peer);
378 m_err << "ERROR: no configuration template for '" << m_template << "' available." << endl;
382 getFilters(context, syncFilter, sourceFilters);
385 // determine whether we dump a peer or a context
386 int flags = DUMP_PROPS_NORMAL;
387 string peer, context;
388 SyncConfig::splitConfigString(config->getConfigName(), peer, context);
390 flags |= HIDE_PER_PEER;
394 if (m_sources.empty() ||
395 m_sources.find("main") != m_sources.end()) {
396 boost::shared_ptr<FilterConfigNode> syncProps(config->getProperties());
397 syncProps->setFilter(syncFilter);
398 dumpProperties(*syncProps, config->getRegistry(), flags);
401 list<string> sources = config->getSyncSources();
403 BOOST_FOREACH(const string &name, sources) {
404 if (m_sources.empty() ||
405 m_sources.find(name) != m_sources.end()) {
406 m_out << endl << "[" << name << "]" << endl;
407 SyncSourceNodes nodes = config->getSyncSourceNodes(name);
408 boost::shared_ptr<FilterConfigNode> sourceProps = nodes.getProperties();
409 SourceFilters_t::const_iterator it = sourceFilters.find(name);
410 if (it != sourceFilters.end()) {
411 sourceProps->setFilter(it->second);
413 sourceProps->setFilter(sourceFilters[""]);
415 dumpProperties(*sourceProps, SyncSourceConfig::getRegistry(),
416 flags | ((name != *(--sources.end())) ? HIDE_LEGEND : DUMP_PROPS_NORMAL));
419 } else if (m_server == "" && m_argc > 1) {
420 // Options given, but no server - not sure what the user wanted?!
421 usage(true, "server name missing");
423 } else if (m_configure || m_migrate) {
425 SyncContext::throwError("--dry-run not supported for configuration changes");
428 #ifndef USE_GNOME_KEYRING
429 m_err << "Error: this syncevolution binary was compiled without support for storing "
430 "passwords in a keyring. Either store passwords in your configuration "
431 "files or enter them interactively on each program run." << endl;
436 bool fromScratch = false;
437 string peer, context;
438 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_server), peer, context);
443 // Both config changes and migration are implemented as copying from
444 // another config (template resp. old one). Migration also moves
446 boost::shared_ptr<SyncConfig> from;
448 string oldContext = context;
449 from.reset(new SyncConfig(m_server));
450 if (!from->exists()) {
451 // for migration into a different context, search for config without context
453 from.reset(new SyncConfig(peer));
454 if (!from->exists()) {
455 m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
461 string oldRoot = from->getRootPath();
465 ostringstream newsuffix;
468 newsuffix << "." << counter;
470 suffix = newsuffix.str();
471 newname = oldRoot + suffix;
472 if (!rename(oldRoot.c_str(),
475 } else if (errno != EEXIST && errno != ENOTEMPTY) {
476 m_err << "ERROR: renaming " << oldRoot << " to " <<
477 newname << ": " << strerror(errno) << endl;
483 from.reset(new SyncConfig(peer + suffix +
484 (oldContext.empty() ? "" : "@") +
487 from.reset(new SyncConfig(m_server));
488 if (!from->exists()) {
489 // creating from scratch, look for template
491 string configTemplate;
492 if (m_template.empty()) {
493 // template is the peer name
494 configTemplate = m_server;
495 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(configTemplate), peer, context);
497 // Template is specified explicitly. It must not contain a context,
498 // because the context comes from the config name.
499 configTemplate = m_template;
500 if (SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(configTemplate), peer, context)) {
501 m_err << "ERROR: template " << configTemplate << " must not specify a context." << endl;
505 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_server), tmp, context);
507 from = SyncConfig::createPeerTemplate(peer);
509 m_err << "ERROR: no configuration template for '" << configTemplate << "' available." << endl;
510 dumpConfigTemplates("Available configuration templates:",
511 SyncConfig::getPeerTemplates(SyncConfig::DeviceList()));
515 if (!from->getPeerIsClient()) {
516 // Templates no longer contain these strings, because
517 // GUIs would have to localize them. For configs created
518 // via the command line, the extra hint that these
519 // properties need to be set is useful, so set these
520 // strings here. They'll get copied into the new
521 // config only if no other value was given on the
523 if (!from->getUsername()[0]) {
524 from->setUsername("your SyncML server account name");
526 if (!from->getPassword()[0]) {
527 from->setPassword("your SyncML server password");
530 // uncomment SyncURL, so that it can be shown by
532 if (from->getSyncURL().size() == 0) {
533 from->setSyncURL ("input your peer address here");
539 // Apply config changes on-the-fly. Regardless what we do
540 // (changing an existing config, migrating, creating from
541 // a template), existing shared properties in the desired
542 // context must be preserved unless explicitly overwritten.
543 // Therefore read those, update with command line properties,
544 // then set as filter.
545 ConfigProps syncFilter;
546 SourceFilters_t sourceFilters;
547 getFilters(context, syncFilter, sourceFilters);
548 from->setConfigFilter(true, "", syncFilter);
549 BOOST_FOREACH(const SourceFilters_t::value_type &entry, sourceFilters) {
550 from->setConfigFilter(false, entry.first, entry.second);
553 // write into the requested configuration, creating it if necessary
554 boost::shared_ptr<SyncContext> to(createSyncClient());
555 to->copy(*from, !fromScratch && !m_sources.empty() ? &m_sources : NULL);
557 // Sources are active now according to the server default.
558 // Disable all sources not selected by user (if any selected)
559 // and those which have no database.
561 list<string> configuredSources = to->getSyncSources();
562 set<string> sources = m_sources;
564 BOOST_FOREACH(const string &source, configuredSources) {
565 boost::shared_ptr<PersistentSyncSourceConfig> sourceConfig(to->getSyncSourceConfig(source));
567 set<string>::iterator entry = sources.find(source);
568 bool selected = entry != sources.end();
570 if (!m_sources.empty() &&
572 disable = "not selected";
574 if (entry != sources.end()) {
575 // The command line parameter matched a valid source.
576 // All entries left afterwards must have been typos.
577 sources.erase(entry);
580 // check whether the sync source works
581 SyncSourceParams params("list", to->getSyncSourceNodes(source));
582 auto_ptr<SyncSource> syncSource(SyncSource::createSource(params, false, to.get()));
583 if (syncSource.get() == NULL) {
584 disable = "no backend available";
587 SyncSource::Databases databases = syncSource->getDatabases();
588 if (databases.empty()) {
589 disable = "no database to synchronize";
592 disable = "backend failed";
597 if (!disable.empty()) {
598 // abort if the user explicitly asked for the sync source
599 // and it cannot be enabled, otherwise disable it silently
601 SyncContext::throwError(source + ": " + disable);
603 sourceConfig->setSync("disabled");
604 } else if (selected) {
605 // user absolutely wants it: enable even if off by default
606 FilterConfigNode::ConfigFilter::const_iterator sync =
607 m_sourceProps.find(SyncSourceConfig::m_sourcePropSync.getName());
608 sourceConfig->setSync(sync == m_sourceProps.end() ? "two-way" : sync->second);
612 if (!sources.empty()) {
613 SyncContext::throwError(string("no such source(s): ") + boost::join(sources, " "));
616 // give a change to do something before flushing configs to files
619 // done, now write it
622 // also copy .synthesis dir?
624 string fromDir, toDir;
625 fromDir = from->getRootPath() + "/.synthesis";
626 toDir = to->getRootPath() + "/.synthesis";
627 if (isDir(fromDir)) {
628 cp_r(fromDir, toDir);
631 } else if (m_remove) {
633 SyncContext::throwError("--dry-run not supported for removing configurations");
636 // extra sanity check
637 if (!m_sources.empty() ||
638 !m_syncProps.empty() ||
639 !m_sourceProps.empty()) {
640 usage(true, "too many parameters for --remove");
643 boost::shared_ptr<SyncConfig> config;
644 config.reset(new SyncConfig(m_server));
645 if (!config->exists()) {
646 SyncContext::throwError(string("no such configuration: ") + m_server);
652 std::set<std::string> unmatchedSources;
653 boost::shared_ptr<SyncContext> context;
654 context.reset(createSyncClient());
655 context->setQuiet(m_quiet);
656 context->setDryRun(m_dryrun);
657 context->setConfigFilter(true, "", m_syncProps);
658 context->setOutput(&m_out);
659 if (m_sources.empty()) {
660 if (m_sourceProps.empty()) {
661 // empty source list, empty source filter => run with
662 // existing configuration without filtering it
664 // Special semantic of 'no source selected': apply
665 // filter only to sources which are
666 // *active*. Configuration of inactive sources is left
667 // unchanged. This way we don't activate sync sources
668 // accidentally when the sync mode is modified
670 BOOST_FOREACH(const std::string &source,
671 context->getSyncSources()) {
672 boost::shared_ptr<PersistentSyncSourceConfig> source_config =
673 context->getSyncSourceConfig(source);
674 if (strcmp(source_config->getSync(), "disabled")) {
675 context->setConfigFilter(false, source, m_sourceProps);
680 // apply (possibly empty) source filter to selected sources
681 BOOST_FOREACH(const std::string &source,
683 boost::shared_ptr<PersistentSyncSourceConfig> source_config =
684 context->getSyncSourceConfig(source);
685 if (!source_config || !source_config->exists()) {
686 // invalid source name in m_sources, remember and
688 unmatchedSources.insert(source);
689 } else if (m_sourceProps.find(SyncSourceConfig::m_sourcePropSync.getName()) ==
690 m_sourceProps.end()) {
691 // Sync mode is not set, must override the
692 // "sync=disabled" set below with the original
693 // sync mode for the source or (if that is also
694 // "disabled") with "two-way". The latter is part
695 // of the command line semantic that listing a
696 // source activates it.
697 FilterConfigNode::ConfigFilter filter = m_sourceProps;
698 string sync = source_config->getSync();
699 filter[SyncSourceConfig::m_sourcePropSync.getName()] =
700 sync == "disabled" ? "two-way" : sync;
701 context->setConfigFilter(false, source, filter);
703 // sync mode is set, can use m_sourceProps
704 // directly to apply it
705 context->setConfigFilter(false, source, m_sourceProps);
709 // temporarily disable the rest
710 FilterConfigNode::ConfigFilter disabled;
711 disabled[SyncSourceConfig::m_sourcePropSync.getName()] = "disabled";
712 context->setConfigFilter(false, "", disabled);
715 // check whether there were any sources specified which do not exist
716 if (unmatchedSources.size()) {
717 context->throwError(string("no such source(s): ") + boost::join(unmatchedSources, " "));
722 } else if (m_printSessions) {
724 context->getSessions(dirs);
726 BOOST_FOREACH(const string &dir, dirs) {
729 } else if(!m_quiet) {
732 m_out << dir << endl;
735 context->readSessionInfo(dir, report);
739 } else if (!m_restore.empty()) {
740 // sanity checks: either --after or --before must be given, sources must be selected
741 if ((!m_after && !m_before) ||
742 (m_after && m_before)) {
743 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)");
746 if (m_sources.empty()) {
747 usage(false, "Sources must be selected explicitly for --restore to prevent accidental restore.");
750 context->restore(m_restore,
752 SyncContext::DATABASE_AFTER_SYNC :
753 SyncContext::DATABASE_BEFORE_SYNC);
756 SyncContext::throwError("--dry-run not supported for running a synchronization");
759 // safety catch: if props are given, then --run
762 (m_syncProps.size() || m_sourceProps.size())) {
763 usage(false, "Properties specified, but neither '--configure' nor '--run' - what did you want?");
767 return (context->sync() == STATUS_OK);
774 string Cmdline::cmdOpt(const char *opt, const char *param)
786 bool Cmdline::parseProp(const ConfigPropertyRegistry &validProps,
787 FilterConfigNode::ConfigFilter &props,
790 const char *propname)
793 usage(true, string("missing parameter for ") + cmdOpt(opt, param));
795 } else if (boost::trim_copy(string(param)) == "?") {
798 return listPropValues(validProps, propname, opt);
800 return listProperties(validProps, opt);
809 const char *equal = strchr(param, '=');
811 usage(true, string("the '=<value>' part is missing in: ") + cmdOpt(opt, param));
814 propstr.assign(param, equal - param);
815 paramstr.assign(equal + 1);
818 boost::trim(propstr);
819 boost::trim_left(paramstr);
821 if (boost::trim_copy(paramstr) == "?") {
823 return listPropValues(validProps, propstr, cmdOpt(opt, param));
825 const ConfigProperty *prop = validProps.find(propstr);
827 m_err << "ERROR: " << cmdOpt(opt, param) << ": no such property" << endl;
831 if (!prop->checkValue(paramstr, error)) {
832 m_err << "ERROR: " << cmdOpt(opt, param) << ": " << error << endl;
835 props[propstr] = paramstr;
843 bool Cmdline::listPropValues(const ConfigPropertyRegistry &validProps,
844 const string &propName,
847 const ConfigProperty *prop = validProps.find(propName);
849 m_err << "ERROR: "<< opt << ": no such property" << endl;
852 m_out << opt << endl;
853 string comment = prop->getComment();
856 list<string> commentLines;
857 ConfigProperty::splitComment(comment, commentLines);
858 BOOST_FOREACH(const string &line, commentLines) {
859 m_out << " " << line << endl;
862 m_out << " no documentation available" << endl;
868 bool Cmdline::listProperties(const ConfigPropertyRegistry &validProps,
871 // The first of several related properties has a comment.
872 // Remember that comment and print it as late as possible,
873 // that way related properties preceed their comment.
875 BOOST_FOREACH(const ConfigProperty *prop, validProps) {
876 if (!prop->isHidden()) {
877 string newComment = prop->getComment();
879 if (newComment != "") {
880 if (!comment.empty()) {
881 dumpComment(m_out, " ", comment);
884 comment = newComment;
886 m_out << prop->getName() << ":" << endl;
889 dumpComment(m_out, " ", comment);
893 void Cmdline::getFilters(const string &context,
894 ConfigProps &syncFilter,
895 map<string, ConfigProps> &sourceFilters)
897 // Read from context. If it does not exist, we simply set no properties
898 // as filter. Previously there was a check for existance, but that was
899 // flawed because it ignored the global property "defaultPeer".
900 boost::shared_ptr<SyncConfig> shared(new SyncConfig(string("@") + context));
901 shared->getProperties()->readProperties(syncFilter);
902 BOOST_FOREACH(StringPair entry, m_syncProps) {
903 syncFilter[entry.first] = entry.second;
906 BOOST_FOREACH(std::string source, shared->getSyncSources()) {
907 SyncSourceNodes nodes = shared->getSyncSourceNodes(source, "");
908 ConfigProps &props = sourceFilters[source];
909 nodes.getProperties()->readProperties(props);
911 // Special case "type" property: the value in the context
912 // is not preserved. Every new peer must ensure that
913 // its own value is compatible (= same backend) with
917 BOOST_FOREACH(StringPair entry, m_sourceProps) {
918 props[entry.first] = entry.second;
921 sourceFilters[""] = m_sourceProps;
924 static void findPeerProps(FilterConfigNode::ConfigFilter &filter,
925 ConfigPropertyRegistry ®istry,
926 list<string> &peerProps)
928 BOOST_FOREACH(StringPair entry, filter) {
929 const ConfigProperty *prop = registry.find(entry.first);
931 prop->getSharing() == ConfigProperty::NO_SHARING &&
932 !(prop->getFlags() & ConfigProperty::SHARED_AND_UNSHARED)) {
933 peerProps.push_back(entry.first);
938 void Cmdline::checkForPeerProps()
940 list<string> peerProps;
942 findPeerProps(m_syncProps, SyncConfig::getRegistry(), peerProps);
943 findPeerProps(m_sourceProps, SyncSourceConfig::getRegistry(), peerProps);
944 if (!peerProps.empty()) {
945 SyncContext::throwError(string("per-peer (unshared) properties not allowed: ") +
946 boost::join(peerProps, ", "));
950 void Cmdline::listSources(SyncSource &syncSource, const string &header)
952 m_out << header << ":\n";
953 SyncSource::Databases databases = syncSource.getDatabases();
955 BOOST_FOREACH(const SyncSource::Database &database, databases) {
956 m_out << " " << database.m_name << " (" << database.m_uri << ")";
957 if (database.m_isDefault) {
958 m_out << " <default>";
964 void Cmdline::dumpConfigs(const string &preamble,
965 const SyncConfig::ConfigList &servers)
967 m_out << preamble << endl;
968 BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server,servers) {
969 m_out << " " << server.first << " = " << server.second <<endl;
971 if (!servers.size()) {
972 m_out << " none" << endl;
976 void Cmdline::dumpConfigTemplates(const string &preamble,
977 const SyncConfig::TemplateList &templates,
980 m_out << preamble << endl;
981 m_out << " " << "template name" << " = " << "template description";
983 m_out << " " << "matching score in percent (100% = exact match)";
987 BOOST_FOREACH(const SyncConfig::TemplateList::value_type server,templates) {
988 m_out << " " << server->m_templateId << " = " << server->m_description;
990 m_out << " " << server->m_rank *20 << "%";
994 if (!templates.size()) {
995 m_out << " none" << endl;
999 void Cmdline::dumpProperties(const ConfigNode &configuredProps,
1000 const ConfigPropertyRegistry &allProps,
1003 list<string> perPeer, perContext, global;
1005 BOOST_FOREACH(const ConfigProperty *prop, allProps) {
1006 if (prop->isHidden() ||
1007 ((flags & HIDE_PER_PEER) &&
1008 prop->getSharing() == ConfigProperty::NO_SHARING &&
1009 !(prop->getFlags() & ConfigProperty::SHARED_AND_UNSHARED))) {
1013 string comment = prop->getComment();
1014 if (!comment.empty()) {
1016 dumpComment(m_out, "# ", comment);
1020 prop->getProperty(configuredProps, &isDefault);
1024 m_out << prop->getName() << " = " << prop->getProperty(configuredProps) << endl;
1026 list<string> *type = NULL;
1027 switch (prop->getSharing()) {
1028 case ConfigProperty::GLOBAL_SHARING:
1031 case ConfigProperty::SOURCE_SET_SHARING:
1034 case ConfigProperty::NO_SHARING:
1039 type->push_back(prop->getName());
1043 if (!m_quiet && !(flags & HIDE_LEGEND)) {
1044 if (!perPeer.empty() ||
1045 !perContext.empty() ||
1049 if (!perPeer.empty()) {
1050 m_out << "# per-peer (unshared) properties: " << boost::join(perPeer, ", ") << endl;
1052 if (!perContext.empty()) {
1053 m_out << "# shared by peers in same context: " << boost::join(perContext, ", ") << endl;
1055 if (!global.empty()) {
1056 m_out << "# global properties: " << boost::join(global, ", ") << endl;
1061 void Cmdline::dumpComment(ostream &stream,
1062 const string &prefix,
1063 const string &comment)
1065 list<string> commentLines;
1066 ConfigProperty::splitComment(comment, commentLines);
1067 BOOST_FOREACH(const string &line, commentLines) {
1068 stream << prefix << line << endl;
1072 void Cmdline::usage(bool full, const string &error, const string ¶m)
1074 ostream &out(error.empty() ? m_out : m_err);
1076 out << "Show available sources:" << endl;
1077 out << " " << m_argv[0] << endl;
1078 out << "Show information about configuration(s) and sync sessions:" << endl;
1079 out << " " << m_argv[0] << " --print-servers|--print-configs|--print-peers" << endl;
1080 out << " " << m_argv[0] << " --print-config [--quiet] <config> [main|<source ...]" << endl;
1081 out << " " << m_argv[0] << " --print-sessions [--quiet] <config>" << endl;
1082 out << "Show information about SyncEvolution:" << endl;
1083 out << " " << m_argv[0] << " --help|-h" << endl;
1084 out << " " << m_argv[0] << " --version" << endl;
1085 out << "Run a synchronization:" << endl;
1086 out << " " << m_argv[0] << " <config> [<source> ...]" << endl;
1087 out << " " << m_argv[0] << " --run <options for run> <config> [<source> ...]" << endl;
1088 out << "Restore data from the automatic backups:" << endl;
1089 out << " " << m_argv[0] << " --restore <session directory> --before|--after [--dry-run] <config> <source> ..." << endl;
1090 out << "Remove a configuration:" << endl;
1091 out << " " << m_argv[0] << " --remove <config>" << endl;
1092 out << "Modify configuration:" << endl;
1093 out << " " << m_argv[0] << " --configure <options for configuration> <config> [<source> ...]" << endl;
1094 out << " " << m_argv[0] << " --migrate <config>" << endl;
1097 "Options:" << endl <<
1098 "--sync|-s <mode>" << endl <<
1099 "--sync|-s ?" << endl <<
1100 " Temporarily synchronize the active sources in that mode. Useful" << endl <<
1101 " for a \"refresh-from-server\" or \"refresh-from-client\" sync which" << endl <<
1102 " clears all data at one end and copies all items from the other." << endl <<
1104 "--print-servers|--print-configs|--print-peers" << endl <<
1105 " Prints the names of all configured peers to stdout." << endl <<
1107 "--print-config|-p" << endl <<
1108 " Prints the complete configuration for the selected peer" << endl <<
1109 " to stdout, including up-to-date comments for all properties. The" << endl <<
1110 " format is the normal .ini format with source configurations in" << endl <<
1111 " different sections introduced with [<source>] lines. Can be combined" << endl <<
1112 " with --sync-property and --source-property to modify the configuration" << endl <<
1113 " on-the-fly. When one or more sources are listed after the <config>" << endl <<
1114 " name on the command line, then only the configs of those sources are" << endl <<
1115 " printed. Using --quiet suppresses the comments for each property." << endl <<
1116 " When setting a --template, then the reference configuration for" << endl <<
1117 " that peer is printed instead of an existing configuration." << endl <<
1119 "--print-sessions" << endl <<
1120 " Prints a list of all previous log directories. Unless --quiet is used, each" << endl <<
1121 " file name is followed by the original sync report." << endl <<
1123 "--configure|-c" << endl <<
1124 " Modify the configuration files for the selected peer. If no such" << endl <<
1125 " configuration exists, then a new one is created using one of the" << endl <<
1126 " template configurations (see --template option). When creating" << endl <<
1127 " a new configuration only the active sources will be set to active" << endl <<
1128 " in the new configuration, i.e. \"syncevolution -c scheduleworld addressbook\"" << endl <<
1129 " followed by \"syncevolution scheduleworld\" will only synchronize the" << endl <<
1130 " address book. The other sources are created in a disabled state." << endl <<
1131 " When modifying an existing configuration and sources are specified," << endl <<
1132 " then the source properties of only those sources are modified." << endl <<
1134 "--migrate" << endl <<
1135 " In older SyncEvolution releases a different layout of configuration files" << endl <<
1136 " was used. Using --migrate will automatically migrate to the new" << endl <<
1137 " layout and rename the <config> into <config>.old to prevent accidental use" << endl <<
1138 " of the old configuration. WARNING: old SyncEvolution releases cannot" << endl <<
1139 " use the new configuration!" << endl <<
1141 " The switch can also be used to migrate a configuration in the current" << endl <<
1142 " configuration directory: this preserves all property values, discards" << endl <<
1143 " obsolete properties and sets all comments exactly as if the configuration" << endl <<
1144 " had been created from scratch. WARNING: custom comments in the" << endl <<
1145 " configuration are not preserved." << endl <<
1147 "--restore" << endl <<
1148 " Restores the data of the selected sources to the state from before or after the" << endl <<
1149 " selected synchronization. The synchronization is selected via its log directory" << endl <<
1150 " (see --print-sessions). Other directories can also be given as long as" << endl <<
1151 " they contain database dumps in the format created by SyncEvolution." << endl <<
1152 " The output includes information about the changes made during the" << endl <<
1153 " restore, both in terms of item changes and content changes (which is" << endl <<
1154 " not always the same, see manual for details). This output can be suppressed" << endl <<
1155 " with --quiet." << endl <<
1156 " In combination with --dry-run, the changes to local data are only simulated." << endl <<
1157 " This can be used to check that --restore will not remove valuable information." << endl <<
1159 "--remove" << endl <<
1160 " Deletes the configuration. If the <config> refers to a specific" << endl <<
1161 " peer, only that peer's configuration is removed. If it refers to" << endl <<
1162 " a context, that context and all peers inside it are removed." << endl <<
1163 " Note that there is no confirmation question. Neither local data" << endl <<
1164 " referenced by the configuration nor the content of log dirs are" << endl <<
1165 " deleted." << endl <<
1167 "--sync-property|-y <property>=<value>" << endl <<
1168 "--sync-property|-y ?" << endl <<
1169 "--sync-property|-y <property>=?" << endl <<
1170 " Overrides a source-independent configuration property for the" << endl <<
1171 " current synchronization run or permanently when --configure is used" << endl <<
1172 " to update the configuration. Can be used multiple times. Specifying" << endl <<
1173 " an unused property will trigger an error message." << endl <<
1175 " When using the configuration layout introduced with 1.0, some of the" << endl <<
1176 " sync properties are shared between peers, for example the directory" << endl <<
1177 " where sessions are logged. Permanently changing such a shared" << endl <<
1178 " property for one peer will automatically update the property for all" << endl <<
1179 " other peers in the same context because the property is stored in a" << endl <<
1180 " shared config file." << endl <<
1182 "--source-property|-z <property>=<value>" << endl <<
1183 "--source-property|-z ?" << endl <<
1184 "--source-property|-z <property>=?" << endl <<
1185 " Same as --sync-property, but applies to the configuration of all active" << endl <<
1186 " sources. \"--sync <mode>\" is a shortcut for \"--source-property sync=<mode>\"." << endl <<
1188 "--template|-l <peer name>|default|?|?<device>" << endl <<
1189 " Can be used to select from one of the built-in default configurations" << endl <<
1190 " for known SyncML peers. Defaults to the <config> name, so --template" << endl <<
1191 " only has to be specified when creating multiple different configurations" << endl <<
1192 " for the same peer, or when using a template that is named differently" << endl <<
1193 " than the peer. \"default\" is an alias for \"scheduleworld\" and can be" << endl <<
1194 " used as the starting point for servers which do not have a built-in" << endl <<
1195 " template." << endl <<
1197 " Each template contains a pseudo-random device ID. Therefore setting the" << endl <<
1198 " \"deviceId\" sync property is only necessary when manually recreating a" << endl <<
1199 " configuration or when a more descriptive name is desired." << endl <<
1201 " The available templates for different known SyncML servers are listed when" << endl <<
1202 " using a single question mark instead of template name. When using the" << endl <<
1203 " ?<device> format, a fuzzy search for a template that might be" << endl <<
1204 " suitable for talking to such a device is done. The matching works best" << endl <<
1205 " when using <device> = <Manufacturer>_<Model>. If you don't know the" << endl <<
1206 " manufacturer, you can just keep it as empty. The output in this mode" << endl <<
1207 " gives the template name followed by a short description and a rating how well" << endl <<
1208 " the template matches the device (higher is better)." << endl <<
1210 "--status|-t" << endl <<
1211 " The changes made to local data since the last synchronization are" << endl <<
1212 " shown without starting a new one. This can be used to see in advance" << endl <<
1213 " whether the local data needs to be synchronized with the peer." << endl <<
1215 " When used without configuration name, it shows the status of the background" << endl <<
1216 " sync daemon or an error if no such daemon exists." << endl <<
1218 "--quiet|-q" << endl <<
1219 " Suppresses most of the normal output during a synchronization. The" << endl <<
1220 " log file still contains all the information." << endl <<
1222 "--keyring|-k[=yes/no/...]" << endl <<
1223 " Save or retrieve passwords from the GNOME keyring when modifying the" << endl <<
1224 " configuration or running a synchronization. Note that using this option" << endl <<
1225 " applies to *all* passwords in a configuration, so setting a single" << endl <<
1226 " password as follows moves the other passwords into the keyring, if" << endl <<
1227 " they were not stored there already:" << endl <<
1228 " --keyring --configure --sync-property proxyPassword=foo" << endl <<
1230 " When passwords were stored in the keyring, their value is set to '-'" << endl <<
1231 " in the configuration. This means that when running a synchronization" << endl <<
1232 " without the --keyring argument, the password has to be entered" << endl <<
1233 " interactively. The --print-config output always shows '-' instead of" << endl <<
1234 " retrieving the password from the keyring." << endl <<
1236 "--daemon[=yes/no/...]" << endl <<
1237 " Run operations in cooperation with the background sync daemon;" << endl <<
1238 " enabled by default if it is installed." << endl <<
1240 "--help|-h" << endl <<
1241 " Prints usage information." << endl <<
1243 "--version" << endl <<
1244 " Prints the SyncEvolution version." << endl;
1248 out << endl << "ERROR: " << error << endl;
1251 out << "INFO: use '" << param << (param[param.size() - 1] == '=' ? "" : " ") <<
1252 "?' to get a list of valid parameters" << endl;
1256 SyncContext* Cmdline::createSyncClient() {
1257 return new SyncContext(m_server, true);
1260 #ifdef ENABLE_UNIT_TESTS
1262 /** simple line-by-line diff */
1263 static string diffStrings(const string &lhs, const string &rhs)
1267 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1268 string_split_iterator lit =
1269 boost::make_split_iterator(lhs, boost::first_finder("\n", boost::is_iequal()));
1270 string_split_iterator rit =
1271 boost::make_split_iterator(rhs, boost::first_finder("\n", boost::is_iequal()));
1272 while (lit != string_split_iterator() &&
1273 rit != string_split_iterator()) {
1275 res << "< " << *lit << endl;
1276 res << "> " << *rit << endl;
1282 while (lit != string_split_iterator()) {
1283 res << "< " << *lit << endl;
1287 while (rit != string_split_iterator()) {
1288 res << "> " << *rit << endl;
1295 # define CPPUNIT_ASSERT_EQUAL_DIFF( expected, actual ) \
1297 string expected_ = (expected); \
1298 string actual_ = (actual); \
1299 if (expected_ != actual_) { \
1300 CPPUNIT_NS::Message cpputMsg_(string("expected:\n") + \
1302 cpputMsg_.addDetail(string("actual:\n") + \
1304 cpputMsg_.addDetail(string("diff:\n") + \
1305 diffStrings(expected_, actual_)); \
1306 CPPUNIT_NS::Asserter::fail( cpputMsg_, \
1307 CPPUNIT_SOURCELINE() ); \
1311 // returns last line, including trailing line break, empty if input is empty
1312 static string lastLine(const string &buffer)
1314 if (buffer.size() < 2) {
1318 size_t line = buffer.rfind("\n", buffer.size() - 2);
1319 if (line == buffer.npos) {
1323 return buffer.substr(line + 1);
1327 static bool isPropAssignment(const string &buffer) {
1329 while (start < buffer.size() &&
1330 !isspace(buffer[start])) {
1333 if (start + 3 <= buffer.size() &&
1334 buffer.substr(start, 3) == " = ") {
1341 // remove pure comment lines from buffer,
1342 // also empty lines,
1343 // also defaultPeer (because reference properties do not include global props)
1344 static string filterConfig(const string &buffer)
1348 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1349 for (string_split_iterator it =
1350 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
1351 it != string_split_iterator();
1353 string line = boost::copy_range<string>(*it);
1354 if (!line.empty() &&
1355 line.find("defaultPeer =") == line.npos &&
1356 (!boost::starts_with(line, "# ") ||
1357 isPropAssignment(line.substr(2)))) {
1358 res << line << endl;
1365 static string injectValues(const string &buffer)
1367 string res = buffer;
1369 // username/password not set in templates, only in configs created via
1371 boost::replace_first(res,
1373 "username = your SyncML server account name");
1374 boost::replace_first(res,
1376 "password = your SyncML server password");
1380 // remove lines indented with spaces
1381 static string filterIndented(const string &buffer)
1386 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1387 for (string_split_iterator it =
1388 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
1389 it != string_split_iterator();
1391 if (!boost::starts_with(*it, " ")) {
1404 // sort lines by file, preserving order inside each line
1405 static void sortConfig(string &config)
1407 // file name, line number, property
1408 typedef pair<string, pair<int, string> > line_t;
1409 vector<line_t> lines;
1410 typedef boost::split_iterator<string::iterator> string_split_iterator;
1412 for (string_split_iterator it =
1413 boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal()));
1414 it != string_split_iterator();
1416 string line(it->begin(), it->end());
1421 size_t colon = line.find(':');
1422 string prefix = line.substr(0, colon);
1423 lines.push_back(make_pair(prefix, make_pair(linenr, line.substr(colon))));
1426 // stable sort because of line number
1427 sort(lines.begin(), lines.end());
1429 size_t len = config.size();
1431 config.reserve(len);
1432 BOOST_FOREACH(const line_t &line, lines) {
1433 config += line.first;
1434 config += line.second.second;
1439 // convert the internal config dump to .ini style (--print-config)
1440 static string internalToIni(const string &config)
1445 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1446 for (string_split_iterator it =
1447 boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal()));
1448 it != string_split_iterator();
1450 string line(it->begin(), it->end());
1455 size_t colon = line.find(':');
1456 string prefix = line.substr(0, colon);
1458 // internal values are not part of the --print-config output
1459 if (boost::contains(prefix, ".internal.ini") ||
1460 boost::contains(line, "= internal value")) {
1464 // --print-config also doesn't duplicate the "type" property
1465 // => remove the shared property
1466 if (boost::contains(line, ":type = ") &&
1467 boost::starts_with(line, "sources/")) {
1471 // sources/<name>/config.ini or
1472 // spds/sources/<name>/config.ini
1473 size_t endslash = prefix.rfind('/');
1474 if (endslash != line.npos && endslash > 1) {
1475 size_t slash = prefix.rfind('/', endslash - 1);
1476 if (slash != line.npos) {
1477 string newsource = prefix.substr(slash + 1, endslash - slash - 1);
1478 if (newsource != section &&
1479 prefix.find("/sources/") != prefix.npos &&
1480 newsource != "syncml") {
1481 res << "[" << newsource << "]" << endl;
1482 section = newsource;
1486 string assignment = line.substr(colon + 1);
1487 // substitude aliases with generic values
1488 boost::replace_first(assignment, "= F", "= 0");
1489 boost::replace_first(assignment, "= T", "= 1");
1490 boost::replace_first(assignment, "= syncml:auth-md5", "= md5");
1491 boost::replace_first(assignment, "= syncml:auth-basix", "= basic");
1492 res << assignment << endl;
1500 * Testing is based on a text representation of a directory
1501 * hierarchy where each line is of the format
1502 * <file path>:<line in file>
1504 * The order of files is alphabetical, of lines in the file as
1505 * in the file. Lines in the file without line break cannot
1508 * The root of the hierarchy is not part of the representation
1511 class CmdlineTest : public CppUnit::TestFixture {
1512 CPPUNIT_TEST_SUITE(CmdlineTest);
1513 CPPUNIT_TEST(testFramework);
1514 CPPUNIT_TEST(testSetupScheduleWorld);
1515 CPPUNIT_TEST(testSetupDefault);
1516 CPPUNIT_TEST(testSetupRenamed);
1517 CPPUNIT_TEST(testSetupFunambol);
1518 CPPUNIT_TEST(testSetupSynthesis);
1519 CPPUNIT_TEST(testPrintServers);
1520 CPPUNIT_TEST(testPrintConfig);
1521 CPPUNIT_TEST(testPrintFileTemplates);
1522 CPPUNIT_TEST(testTemplate);
1523 CPPUNIT_TEST(testMatchTemplate);
1524 CPPUNIT_TEST(testAddSource);
1525 CPPUNIT_TEST(testSync);
1526 CPPUNIT_TEST(testConfigure);
1527 CPPUNIT_TEST(testOldConfigure);
1528 CPPUNIT_TEST(testListSources);
1529 CPPUNIT_TEST(testMigrate);
1530 CPPUNIT_TEST_SUITE_END();
1534 m_testDir("CmdlineTest"),
1535 // properties sorted by the order in which they are defined
1536 // in the sync and sync source property registry
1537 m_scheduleWorldConfig("peers/scheduleworld/.internal.ini:# HashCode = 0\n"
1538 "peers/scheduleworld/.internal.ini:# ConfigDate = \n"
1539 "peers/scheduleworld/.internal.ini:# lastNonce = \n"
1540 "peers/scheduleworld/.internal.ini:# deviceData = \n"
1541 "peers/scheduleworld/config.ini:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
1542 "peers/scheduleworld/config.ini:username = your SyncML server account name\n"
1543 "peers/scheduleworld/config.ini:password = your SyncML server password\n"
1544 "config.ini:# logdir = \n"
1545 "peers/scheduleworld/config.ini:# loglevel = 0\n"
1546 "peers/scheduleworld/config.ini:# printChanges = 1\n"
1547 "config.ini:# maxlogdirs = 10\n"
1548 "peers/scheduleworld/config.ini:# autoSync = 0\n"
1549 "peers/scheduleworld/config.ini:# autoSyncInterval = 30M\n"
1550 "peers/scheduleworld/config.ini:# autoSyncDelay = 5M\n"
1551 "peers/scheduleworld/config.ini:# preventSlowSync = 1\n"
1552 "peers/scheduleworld/config.ini:# useProxy = 0\n"
1553 "peers/scheduleworld/config.ini:# proxyHost = \n"
1554 "peers/scheduleworld/config.ini:# proxyUsername = \n"
1555 "peers/scheduleworld/config.ini:# proxyPassword = \n"
1556 "peers/scheduleworld/config.ini:# clientAuthType = md5\n"
1557 "peers/scheduleworld/config.ini:# RetryDuration = 5M\n"
1558 "peers/scheduleworld/config.ini:# RetryInterval = 2M\n"
1559 "peers/scheduleworld/config.ini:# remoteIdentifier = \n"
1560 "peers/scheduleworld/config.ini:# PeerIsClient = 0\n"
1561 "peers/scheduleworld/config.ini:# SyncMLVersion = \n"
1562 "peers/scheduleworld/config.ini:# PeerName = \n"
1563 "config.ini:deviceId = fixed-devid\n" /* this is not the default! */
1564 "peers/scheduleworld/config.ini:# remoteDeviceId = \n"
1565 "peers/scheduleworld/config.ini:# enableWBXML = 1\n"
1566 "peers/scheduleworld/config.ini:# maxMsgSize = 150000\n"
1567 "peers/scheduleworld/config.ini:# maxObjSize = 4000000\n"
1568 "peers/scheduleworld/config.ini:# enableCompression = 0\n"
1569 "peers/scheduleworld/config.ini:# SSLServerCertificates = \n"
1570 "peers/scheduleworld/config.ini:# SSLVerifyServer = 1\n"
1571 "peers/scheduleworld/config.ini:# SSLVerifyHost = 1\n"
1572 "peers/scheduleworld/config.ini:WebURL = http://www.scheduleworld.com\n"
1573 "peers/scheduleworld/config.ini:# IconURI = \n"
1574 "peers/scheduleworld/config.ini:ConsumerReady = 1\n"
1576 "peers/scheduleworld/sources/addressbook/.internal.ini:# adminData = \n"
1577 "peers/scheduleworld/sources/addressbook/.internal.ini:# synthesisID = 0\n"
1578 "peers/scheduleworld/sources/addressbook/config.ini:sync = two-way\n"
1579 "sources/addressbook/config.ini:type = addressbook:text/vcard\n"
1580 "peers/scheduleworld/sources/addressbook/config.ini:type = addressbook:text/vcard\n"
1581 "sources/addressbook/config.ini:# evolutionsource = \n"
1582 "peers/scheduleworld/sources/addressbook/config.ini:uri = card3\n"
1583 "sources/addressbook/config.ini:# evolutionuser = \n"
1584 "sources/addressbook/config.ini:# evolutionpassword = \n"
1586 "peers/scheduleworld/sources/calendar/.internal.ini:# adminData = \n"
1587 "peers/scheduleworld/sources/calendar/.internal.ini:# synthesisID = 0\n"
1588 "peers/scheduleworld/sources/calendar/config.ini:sync = two-way\n"
1589 "sources/calendar/config.ini:type = calendar\n"
1590 "peers/scheduleworld/sources/calendar/config.ini:type = calendar\n"
1591 "sources/calendar/config.ini:# evolutionsource = \n"
1592 "peers/scheduleworld/sources/calendar/config.ini:uri = cal2\n"
1593 "sources/calendar/config.ini:# evolutionuser = \n"
1594 "sources/calendar/config.ini:# evolutionpassword = \n"
1596 "peers/scheduleworld/sources/memo/.internal.ini:# adminData = \n"
1597 "peers/scheduleworld/sources/memo/.internal.ini:# synthesisID = 0\n"
1598 "peers/scheduleworld/sources/memo/config.ini:sync = two-way\n"
1599 "sources/memo/config.ini:type = memo\n"
1600 "peers/scheduleworld/sources/memo/config.ini:type = memo\n"
1601 "sources/memo/config.ini:# evolutionsource = \n"
1602 "peers/scheduleworld/sources/memo/config.ini:uri = note\n"
1603 "sources/memo/config.ini:# evolutionuser = \n"
1604 "sources/memo/config.ini:# evolutionpassword = \n"
1606 "peers/scheduleworld/sources/todo/.internal.ini:# adminData = \n"
1607 "peers/scheduleworld/sources/todo/.internal.ini:# synthesisID = 0\n"
1608 "peers/scheduleworld/sources/todo/config.ini:sync = two-way\n"
1609 "sources/todo/config.ini:type = todo\n"
1610 "peers/scheduleworld/sources/todo/config.ini:type = todo\n"
1611 "sources/todo/config.ini:# evolutionsource = \n"
1612 "peers/scheduleworld/sources/todo/config.ini:uri = task2\n"
1613 "sources/todo/config.ini:# evolutionuser = \n"
1614 "sources/todo/config.ini:# evolutionpassword = ")
1616 #ifdef ENABLE_LIBSOUP
1617 // path to SSL certificates has to be set only for libsoup
1618 boost::replace_first(m_scheduleWorldConfig,
1619 "SSLServerCertificates = ",
1620 "SSLServerCertificates = /etc/ssl/certs/ca-certificates.crt:/etc/pki/tls/certs/ca-bundle.crt:/usr/share/ssl/certs/ca-bundle.crt");
1626 /** verify that createFiles/scanFiles themselves work */
1627 void testFramework() {
1628 const string root(m_testDir);
1629 const string content("baz:line\n"
1631 "caz/subdir2/sub:# comment\n"
1632 "caz/subdir2/sub:# foo = bar\n"
1633 "caz/subdir2/sub:# empty = \n"
1634 "caz/subdir2/sub:# another comment\n"
1639 const string filtered("baz:line\n"
1641 "caz/subdir2/sub:# foo = bar\n"
1642 "caz/subdir2/sub:# empty = \n"
1646 createFiles(root, content);
1647 string res = scanFiles(root);
1648 CPPUNIT_ASSERT_EQUAL_DIFF(filtered, res);
1651 void removeRandomUUID(string &buffer) {
1652 string uuidstr = "deviceId = syncevolution-";
1653 size_t uuid = buffer.find(uuidstr);
1654 CPPUNIT_ASSERT(uuid != buffer.npos);
1655 size_t end = buffer.find("\n", uuid + uuidstr.size());
1656 CPPUNIT_ASSERT(end != buffer.npos);
1657 buffer.replace(uuid, end - uuid, "deviceId = fixed-devid");
1660 /** create new configurations */
1661 void testSetupScheduleWorld() { doSetupScheduleWorld(false); }
1662 void doSetupScheduleWorld(bool shared) {
1664 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1665 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1666 ScopedEnvChange home("HOME", m_testDir);
1669 root += "/syncevolution/default";
1672 peer = root + "/peers/scheduleworld";
1679 TestCmdline cmdline("--configure",
1680 "--sync-property", "proxyHost = proxy",
1685 string res = scanFiles(root);
1686 removeRandomUUID(res);
1687 string expected = ScheduleWorldConfig();
1688 sortConfig(expected);
1689 boost::replace_first(expected,
1691 "proxyHost = proxy");
1692 boost::replace_all(expected,
1695 boost::replace_first(expected,
1696 "addressbook/config.ini:sync = disabled",
1697 "addressbook/config.ini:sync = two-way");
1698 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1703 TestCmdline cmdline("--configure",
1704 "--sync-property", "deviceID = fixed-devid",
1708 string res = scanFiles(root);
1709 string expected = ScheduleWorldConfig();
1710 sortConfig(expected);
1711 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1715 void testSetupDefault() {
1717 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1718 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1719 ScopedEnvChange home("HOME", m_testDir);
1722 root += "/syncevolution/default";
1724 TestCmdline cmdline("--configure",
1725 "--template", "default",
1726 "--sync-property", "deviceID = fixed-devid",
1727 "some-other-server",
1730 string res = scanFiles(root, "some-other-server");
1731 string expected = ScheduleWorldConfig();
1732 sortConfig(expected);
1733 boost::replace_all(expected, "/scheduleworld/", "/some-other-server/");
1734 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1736 void testSetupRenamed() {
1738 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1739 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1740 ScopedEnvChange home("HOME", m_testDir);
1743 root += "/syncevolution/default";
1745 TestCmdline cmdline("--configure",
1746 "--template", "scheduleworld",
1747 "--sync-property", "deviceID = fixed-devid",
1751 string res = scanFiles(root, "scheduleworld2");
1752 string expected = ScheduleWorldConfig();
1753 sortConfig(expected);
1754 boost::replace_all(expected, "/scheduleworld/", "/scheduleworld2/");
1755 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1758 void testSetupFunambol() { doSetupFunambol(false); }
1759 void doSetupFunambol(bool shared) {
1761 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1762 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1763 ScopedEnvChange home("HOME", m_testDir);
1766 root += "/syncevolution/default";
1769 peer = root + "/peers/funambol";
1775 const char * const argv_fixed[] = {
1777 "--sync-property", "deviceID = fixed-devid",
1778 // templates are case-insensitive
1781 }, * const argv_shared[] = {
1786 TestCmdline cmdline(shared ? argv_shared : argv_fixed);
1788 string res = scanFiles(root, "funambol");
1789 string expected = FunambolConfig();
1790 sortConfig(expected);
1791 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1794 void testSetupSynthesis() { doSetupSynthesis(false); }
1795 void doSetupSynthesis(bool shared) {
1797 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1798 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1799 ScopedEnvChange home("HOME", m_testDir);
1802 root += "/syncevolution/default";
1805 peer = root + "/peers/synthesis";
1810 const char * const argv_fixed[] = {
1812 "--sync-property", "deviceID = fixed-devid",
1815 }, * const argv_shared[] = {
1820 TestCmdline cmdline(shared ? argv_shared : argv_fixed);
1822 string res = scanFiles(root, "synthesis");
1823 string expected = SynthesisConfig();
1824 sortConfig(expected);
1825 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1828 void testTemplate() {
1829 TestCmdline failure("--template", NULL);
1830 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
1831 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1832 CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--template'\n"), lastLine(failure.m_err.str()));
1834 TestCmdline help("--template", "? ", NULL);
1836 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
1837 " template name = template description\n"
1838 " Funambol = http://my.funambol.com\n"
1839 " Google = http://m.google.com/sync\n"
1840 " Goosync = http://www.goosync.com/\n"
1841 " Memotoo = http://www.memotoo.com\n"
1842 " Mobical = http://www.mobical.net\n"
1843 " Oracle = http://www.oracle.com/technology/products/beehive/index.html\n"
1844 " Ovi = http://www.ovi.com\n"
1845 " ScheduleWorld = http://www.scheduleworld.com\n"
1846 " SyncEvolution = http://www.syncevolution.org\n"
1847 " Synthesis = http://www.synthesis.ch\n"
1848 " ZYB = http://www.zyb.com\n",
1850 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
1853 void testMatchTemplate() {
1854 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "testcases/templates");
1856 TestCmdline help1("--template", "?nokia 7210c", NULL);
1858 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
1859 " template name = template description matching score in percent (100% = exact match)\n"
1860 " Nokia 7210c = Template for Nokia S40 series Phone 100%\n"
1861 " SyncEvolution Client = SyncEvolution server side template 40%\n",
1863 CPPUNIT_ASSERT_EQUAL_DIFF("", help1.m_err.str());
1864 TestCmdline help2("--template", "?nokia", NULL);
1866 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
1867 " template name = template description matching score in percent (100% = exact match)\n"
1868 " Nokia 7210c = Template for Nokia S40 series Phone 100%\n"
1869 " SyncEvolution Client = SyncEvolution server side template 40%\n",
1871 CPPUNIT_ASSERT_EQUAL_DIFF("", help2.m_err.str());
1872 TestCmdline help3("--template", "?7210c", NULL);
1874 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
1875 " template name = template description matching score in percent (100% = exact match)\n"
1876 " Nokia 7210c = Template for Nokia S40 series Phone 60%\n"
1877 " SyncEvolution Client = SyncEvolution server side template 20%\n",
1879 CPPUNIT_ASSERT_EQUAL_DIFF("", help3.m_err.str());
1880 TestCmdline help4("--template", "?syncevolution client", NULL);
1882 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
1883 " template name = template description matching score in percent (100% = exact match)\n"
1884 " SyncEvolution Client = SyncEvolution server side template 100%\n"
1885 " Nokia 7210c = Template for Nokia S40 series Phone 40%\n",
1887 CPPUNIT_ASSERT_EQUAL_DIFF("", help4.m_err.str());
1890 void testPrintServers() {
1891 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1892 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1893 ScopedEnvChange home("HOME", m_testDir);
1896 doSetupScheduleWorld(false);
1897 doSetupSynthesis(true);
1898 doSetupFunambol(true);
1900 TestCmdline cmdline("--print-servers", NULL);
1902 CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n"
1903 " funambol = CmdlineTest/syncevolution/default/peers/funambol\n"
1904 " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n"
1905 " synthesis = CmdlineTest/syncevolution/default/peers/synthesis\n",
1906 cmdline.m_out.str());
1907 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1910 void testPrintConfig() {
1911 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1912 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1913 ScopedEnvChange home("HOME", m_testDir);
1916 testSetupFunambol();
1919 TestCmdline failure("--print-config", NULL);
1920 CPPUNIT_ASSERT(failure.m_cmdline->parse());
1921 CPPUNIT_ASSERT(!failure.m_cmdline->run());
1922 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1923 CPPUNIT_ASSERT_EQUAL(string("ERROR: --print-config requires either a --template or a server name.\n"),
1924 lastLine(failure.m_err.str()));
1928 TestCmdline failure("--print-config", "foo", NULL);
1929 CPPUNIT_ASSERT(failure.m_cmdline->parse());
1930 CPPUNIT_ASSERT(!failure.m_cmdline->run());
1931 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1932 CPPUNIT_ASSERT_EQUAL(string("ERROR: server 'foo' has not been configured yet.\n"),
1933 lastLine(failure.m_err.str()));
1937 TestCmdline failure("--print-config", "--template", "foo", NULL);
1938 CPPUNIT_ASSERT(failure.m_cmdline->parse());
1939 CPPUNIT_ASSERT(!failure.m_cmdline->run());
1940 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1941 CPPUNIT_ASSERT_EQUAL(string("ERROR: no configuration template for 'foo' available.\n"),
1942 lastLine(failure.m_err.str()));
1946 TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL);
1948 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1949 string actual = cmdline.m_out.str();
1950 // deviceId must be the one from Funambol
1951 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
1952 string filtered = injectValues(filterConfig(actual));
1953 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
1955 // there should have been comments
1956 CPPUNIT_ASSERT(actual.size() > filtered.size());
1960 TestCmdline cmdline("--print-config", "--template", "scheduleworld@nosuchcontext", NULL);
1962 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1963 string actual = cmdline.m_out.str();
1964 // deviceId must *not* be the one from Funambol because of the new context
1965 CPPUNIT_ASSERT(!boost::contains(actual, "deviceId = fixed-devid"));
1969 TestCmdline cmdline("--print-config", "--template", "Default", NULL);
1971 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1972 string actual = injectValues(filterConfig(cmdline.m_out.str()));
1973 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
1974 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
1979 TestCmdline cmdline("--print-config", "funambol", NULL);
1981 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1982 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())),
1983 injectValues(filterConfig(cmdline.m_out.str())));
1987 // override context and template properties
1988 TestCmdline cmdline("--print-config", "--template", "scheduleworld",
1989 "--sync-property", "syncURL=foo",
1990 "--source-property", "evolutionsource=Personal",
1991 "--source-property", "sync=disabled",
1994 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1995 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
1996 boost::replace_first(expected,
1997 "syncURL = http://sync.scheduleworld.com/funambol/ds",
1999 boost::replace_all(expected,
2000 "# evolutionsource = ",
2001 "evolutionsource = Personal");
2002 boost::replace_all(expected,
2005 string actual = injectValues(filterConfig(cmdline.m_out.str()));
2006 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
2007 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2012 TestCmdline cmdline("--print-config", "--quiet",
2013 "--template", "scheduleworld",
2017 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2018 string actual = cmdline.m_out.str();
2019 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
2020 CPPUNIT_ASSERT_EQUAL_DIFF(internalToIni(ScheduleWorldConfig()),
2021 injectValues(filterConfig(actual)));
2025 // change shared source properties, then check template again
2026 TestCmdline cmdline("--configure",
2027 "--source-property", "evolutionsource=Personal",
2031 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2034 TestCmdline cmdline("--print-config", "--quiet",
2035 "--template", "scheduleworld",
2039 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2040 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
2041 // from modified Funambol config
2042 boost::replace_all(expected,
2043 "# evolutionsource = ",
2044 "evolutionsource = Personal");
2045 string actual = injectValues(filterConfig(cmdline.m_out.str()));
2046 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
2047 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2052 // print config => must not use settings from default context
2053 TestCmdline cmdline("--print-config", "--template", "scheduleworld@nosuchcontext", NULL);
2055 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2056 // source settings *not* from modified Funambol config
2057 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
2058 string actual = injectValues(filterConfig(cmdline.m_out.str()));
2059 CPPUNIT_ASSERT(!boost::contains(actual, "deviceId = fixed-devid"));
2060 removeRandomUUID(actual);
2061 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2066 // create config => again, must not use settings from default context
2067 TestCmdline cmdline("--configure", "--template", "scheduleworld", "other@other", NULL);
2069 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2072 TestCmdline cmdline("--print-config", "other@other", NULL);
2074 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2075 // source settings *not* from modified Funambol config
2076 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
2077 string actual = injectValues(filterConfig(cmdline.m_out.str()));
2078 CPPUNIT_ASSERT(!boost::contains(actual, "deviceId = fixed-devid"));
2079 removeRandomUUID(actual);
2080 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2085 void testPrintFileTemplates() {
2086 // use local copy of templates in build dir (no need to install)
2087 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "./templates");
2088 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2089 ScopedEnvChange home("HOME", m_testDir);
2092 testSetupFunambol();
2095 TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL);
2097 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2098 string actual = cmdline.m_out.str();
2099 // deviceId must be the one from Funambol
2100 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
2101 string filtered = injectValues(filterConfig(actual));
2102 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
2104 // there should have been comments
2105 CPPUNIT_ASSERT(actual.size() > filtered.size());
2109 TestCmdline cmdline("--print-config", "funambol", NULL);
2111 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2112 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())),
2113 injectValues(filterConfig(cmdline.m_out.str())));
2117 void testAddSource() {
2119 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2120 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2121 ScopedEnvChange home("HOME", m_testDir);
2123 testSetupScheduleWorld();
2126 root += "/syncevolution/default";
2129 TestCmdline cmdline("--configure",
2130 "--source-property", "uri = dummy",
2135 string res = scanFiles(root);
2136 string expected = ScheduleWorldConfig();
2138 "peers/scheduleworld/sources/xyz/.internal.ini:# adminData = \n"
2139 "peers/scheduleworld/sources/xyz/.internal.ini:# synthesisID = 0\n"
2140 "peers/scheduleworld/sources/xyz/config.ini:# sync = disabled\n"
2141 "peers/scheduleworld/sources/xyz/config.ini:# type = select backend\n"
2142 "peers/scheduleworld/sources/xyz/config.ini:uri = dummy\n"
2143 "sources/xyz/config.ini:# type = select backend\n"
2144 "sources/xyz/config.ini:# evolutionsource = \n"
2145 "sources/xyz/config.ini:# evolutionuser = \n"
2146 "sources/xyz/config.ini:# evolutionpassword = ";
2147 sortConfig(expected);
2148 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2153 TestCmdline failure("--sync", NULL);
2154 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
2155 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
2156 CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--sync'\n"), lastLine(failure.m_err.str()));
2158 TestCmdline failure2("--sync", "foo", NULL);
2159 CPPUNIT_ASSERT(!failure2.m_cmdline->parse());
2160 CPPUNIT_ASSERT_EQUAL_DIFF("", failure2.m_out.str());
2161 CPPUNIT_ASSERT_EQUAL(string("ERROR: '--sync foo': not one of the valid values (two-way, slow, refresh-from-client = refresh-client, refresh-from-server = refresh-server = refresh, one-way-from-client = one-way-client, one-way-from-server = one-way-server = one-way, disabled = none)\n"), lastLine(failure2.m_err.str()));
2163 TestCmdline help("--sync", " ?", NULL);
2165 CPPUNIT_ASSERT_EQUAL_DIFF("--sync\n"
2166 " requests a certain synchronization mode:\n"
2167 " two-way = only send/receive changes since last sync\n"
2168 " slow = exchange all items\n"
2169 " refresh-from-client = discard all remote items and replace with\n"
2170 " the items on the client\n"
2171 " refresh-from-server = discard all local items and replace with\n"
2172 " the items on the server\n"
2173 " one-way-from-client = transmit changes from client\n"
2174 " one-way-from-server = transmit changes from server\n"
2175 " none (or disabled) = synchronization disabled\n",
2177 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
2179 TestCmdline filter("--sync", "refresh-from-server", NULL);
2180 CPPUNIT_ASSERT(filter.m_cmdline->parse());
2181 CPPUNIT_ASSERT(!filter.m_cmdline->run());
2182 CPPUNIT_ASSERT_EQUAL_DIFF("", filter.m_out.str());
2183 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh-from-server",
2184 string(filter.m_cmdline->m_sourceProps));
2185 CPPUNIT_ASSERT_EQUAL_DIFF("",
2186 string(filter.m_cmdline->m_syncProps));
2188 TestCmdline filter2("--source-property", "sync=refresh", NULL);
2189 CPPUNIT_ASSERT(filter2.m_cmdline->parse());
2190 CPPUNIT_ASSERT(!filter2.m_cmdline->run());
2191 CPPUNIT_ASSERT_EQUAL_DIFF("", filter2.m_out.str());
2192 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh",
2193 string(filter2.m_cmdline->m_sourceProps));
2194 CPPUNIT_ASSERT_EQUAL_DIFF("",
2195 string(filter2.m_cmdline->m_syncProps));
2198 void testConfigure() {
2199 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2200 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2201 ScopedEnvChange home("HOME", m_testDir);
2204 testSetupScheduleWorld();
2205 string expected = doConfigure(ScheduleWorldConfig(), "sources/addressbook/config.ini:");
2208 // updating type for peer must also update type for context
2209 TestCmdline cmdline("--configure",
2210 "--source-property", "type=file:text/vcard:3.0",
2211 "scheduleworld", "addressbook",
2214 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2215 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2216 boost::replace_all(expected,
2217 "type = addressbook:text/vcard",
2218 "type = file:text/vcard:3.0");
2219 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2220 filterConfig(printConfig("scheduleworld")));
2221 string shared = filterConfig(printConfig("@default"));
2222 CPPUNIT_ASSERT(shared.find("type = file:text/vcard:3.0") != shared.npos);
2226 // updating type for context must not affect peer
2227 TestCmdline cmdline("--configure",
2228 "--source-property", "type=file:text/vcard:2.1",
2229 "@default", "addressbook",
2232 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2233 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2234 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2235 filterConfig(printConfig("scheduleworld")));
2236 string shared = filterConfig(printConfig("@default"));
2237 CPPUNIT_ASSERT(shared.find("type = file:text/vcard:2.1") != shared.npos);
2240 string syncProperties("syncURL:\n"
2256 "autoSyncInterval:\n"
2260 "preventSlowSync:\n"
2276 "remoteIdentifier:\n"
2293 "enableCompression:\n"
2295 "SSLServerCertificates:\n"
2297 "SSLVerifyServer:\n"
2308 string sourceProperties("sync:\n"
2312 "evolutionsource:\n"
2317 "evolutionpassword:\n");
2320 TestCmdline cmdline("--sync-property", "?",
2323 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2324 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties,
2325 filterIndented(cmdline.m_out.str()));
2329 TestCmdline cmdline("--source-property", "?",
2332 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2333 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties,
2334 filterIndented(cmdline.m_out.str()));
2338 TestCmdline cmdline("--source-property", "?",
2339 "--sync-property", "?",
2342 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2343 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties + syncProperties,
2344 filterIndented(cmdline.m_out.str()));
2348 TestCmdline cmdline("--sync-property", "?",
2349 "--source-property", "?",
2352 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2353 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties + sourceProperties,
2354 filterIndented(cmdline.m_out.str()));
2358 void testOldConfigure() {
2359 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2360 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2361 ScopedEnvChange home("HOME", m_testDir);
2363 string oldConfig = OldScheduleWorldConfig();
2364 InitList<string> props = InitList<string>("serverNonce") +
2374 BOOST_FOREACH(string &prop, props) {
2375 boost::replace_all(oldConfig,
2377 prop + " = internal value");
2381 createFiles(m_testDir + "/.sync4j/evolution/scheduleworld", oldConfig);
2382 doConfigure(oldConfig, "spds/sources/addressbook/config.txt:");
2385 string doConfigure(const string &SWConfig, const string &addressbookPrefix) {
2389 TestCmdline cmdline("--configure",
2390 "--source-property", "sync = disabled",
2394 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2395 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2396 expected = filterConfig(internalToIni(SWConfig));
2397 boost::replace_all(expected,
2400 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2401 filterConfig(printConfig("scheduleworld")));
2405 TestCmdline cmdline("--configure",
2406 "--source-property", "sync = one-way-from-server",
2411 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2412 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2413 expected = SWConfig;
2414 boost::replace_all(expected,
2417 boost::replace_first(expected,
2418 addressbookPrefix + "sync = disabled",
2419 addressbookPrefix + "sync = one-way-from-server");
2420 expected = filterConfig(internalToIni(expected));
2421 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2422 filterConfig(printConfig("scheduleworld")));
2426 TestCmdline cmdline("--configure",
2427 "--sync", "two-way",
2428 "-z", "evolutionsource=source",
2429 "--sync-property", "maxlogdirs=20",
2430 "-y", "LOGDIR=logdir",
2434 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2435 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2436 boost::replace_all(expected,
2437 "sync = one-way-from-server",
2439 boost::replace_all(expected,
2442 boost::replace_all(expected,
2443 "# evolutionsource = ",
2444 "evolutionsource = source");
2445 boost::replace_all(expected,
2446 "# maxlogdirs = 10",
2448 boost::replace_all(expected,
2451 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2452 filterConfig(printConfig("scheduleworld")));
2458 void testListSources() {
2459 // pick the varargs constructor; NULL alone is ambiguous
2460 TestCmdline cmdline(NULL, NULL);
2462 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2463 // exact output varies, do not test
2466 void testMigrate() {
2467 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2468 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2469 ScopedEnvChange home("HOME", m_testDir);
2472 string oldRoot = m_testDir + "/.sync4j/evolution/scheduleworld";
2473 string newRoot = m_testDir + "/syncevolution/default";
2475 string oldConfig = OldScheduleWorldConfig();
2478 // migrate old config
2479 createFiles(oldRoot, oldConfig);
2480 string createdConfig = scanFiles(oldRoot);
2481 TestCmdline cmdline("--migrate",
2485 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2486 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2488 string migratedConfig = scanFiles(newRoot);
2489 string expected = ScheduleWorldConfig();
2490 sortConfig(expected);
2491 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2492 string renamedConfig = scanFiles(oldRoot + ".old");
2493 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2497 // rewrite existing config with obsolete properties
2498 // => these properties should get removed
2500 // There is one limitation: shared nodes are not rewritten.
2501 // This is acceptable.
2502 createFiles(newRoot + "/peers/scheduleworld",
2503 "config.ini:# obsolete comment\n"
2504 "config.ini:obsoleteprop = foo\n",
2506 string createdConfig = scanFiles(newRoot, "scheduleworld");
2508 TestCmdline cmdline("--migrate",
2512 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2513 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2515 string migratedConfig = scanFiles(newRoot, "scheduleworld");
2516 string expected = ScheduleWorldConfig();
2517 sortConfig(expected);
2518 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2519 string renamedConfig = scanFiles(newRoot, "scheduleworld.old");
2520 boost::replace_all(createdConfig, "/scheduleworld/", "/scheduleworld.old/");
2521 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2525 // migrate old config with changes and .synthesis directory, a second time
2526 createFiles(oldRoot, oldConfig);
2527 createFiles(oldRoot,
2528 ".synthesis/dummy-file.bfi:dummy = foobar\n"
2529 "spds/sources/addressbook/changes/config.txt:foo = bar\n"
2530 "spds/sources/addressbook/changes/config.txt:foo2 = bar2\n",
2532 string createdConfig = scanFiles(oldRoot);
2534 TestCmdline cmdline("--migrate",
2538 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2539 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2541 string migratedConfig = scanFiles(newRoot);
2542 string expected = m_scheduleWorldConfig;
2543 sortConfig(expected);
2544 boost::replace_first(expected,
2545 "peers/scheduleworld/sources/addressbook/config.ini",
2546 "peers/scheduleworld/sources/addressbook/.other.ini:foo = bar\n"
2547 "peers/scheduleworld/sources/addressbook/.other.ini:foo2 = bar2\n"
2548 "peers/scheduleworld/sources/addressbook/config.ini");
2549 boost::replace_first(expected,
2550 "peers/scheduleworld/config.ini",
2551 "peers/scheduleworld/.synthesis/dummy-file.bfi:dummy = foobar\n"
2552 "peers/scheduleworld/config.ini");
2553 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2554 string renamedConfig = scanFiles(oldRoot + ".old.1");
2555 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2559 string otherRoot = m_testDir + "/syncevolution/other";
2562 // migrate old config into non-default context
2563 createFiles(oldRoot, oldConfig);
2564 string createdConfig = scanFiles(oldRoot);
2566 TestCmdline cmdline("--migrate",
2567 "scheduleworld@other",
2570 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2571 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2574 string migratedConfig = scanFiles(otherRoot);
2575 string expected = ScheduleWorldConfig();
2576 sortConfig(expected);
2577 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2578 string renamedConfig = scanFiles(oldRoot + ".old");
2579 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2581 // migrate the migrated config again inside the "other" context
2583 TestCmdline cmdline("--migrate",
2584 "scheduleworld@other",
2587 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2588 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2590 migratedConfig = scanFiles(otherRoot, "scheduleworld");
2591 expected = ScheduleWorldConfig();
2592 sortConfig(expected);
2593 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2594 renamedConfig = scanFiles(otherRoot, "scheduleworld.old");
2595 boost::replace_all(expected, "/scheduleworld/", "/scheduleworld.old/");
2596 CPPUNIT_ASSERT_EQUAL_DIFF(expected, renamedConfig);
2600 const string m_testDir;
2601 string m_scheduleWorldConfig;
2607 * vararg constructor with NULL termination,
2608 * out and error stream into stringstream members
2612 m_argv.reset(new const char *[m_argvstr.size() + 1]);
2613 m_argv[0] = "client-test";
2614 for (size_t index = 0;
2615 index < m_argvstr.size();
2617 m_argv[index + 1] = m_argvstr[index].c_str();
2620 m_cmdline.set(new Cmdline(m_argvstr.size() + 1, m_argv.get(), m_out, m_err), "cmdline");
2625 TestCmdline(const char *arg, ...) {
2627 va_start (argList, arg);
2628 for (const char *curr = arg;
2630 curr = va_arg(argList, const char *)) {
2631 m_argvstr.push_back(curr);
2637 TestCmdline(const char * const argv[]) {
2638 for (int i = 0; argv[i]; i++) {
2639 m_argvstr.push_back(argv[i]);
2646 success = m_cmdline->parse() &&
2648 if (m_err.str().size()) {
2649 m_out << endl << m_err.str();
2651 CPPUNIT_ASSERT(success);
2654 ostringstream m_out, m_err;
2655 cxxptr<Cmdline> m_cmdline;
2658 vector<string> m_argvstr;
2659 boost::scoped_array<const char *> m_argv;
2662 string ScheduleWorldConfig() {
2663 string config = m_scheduleWorldConfig;
2666 // Currently we don't have an icon for ScheduleWorld. If we
2667 // had (MB #2062) one, then this code would ensure that the
2668 // reference config also has the right path for it.
2669 const char *templateDir = getenv("SYNCEVOLUTION_TEMPLATE_DIR");
2671 templateDir = TEMPLATE_DIR;
2675 if (isDir(string(templateDir) + "/ScheduleWorld")) {
2676 boost::replace_all(config,
2678 string("IconURI = file://") + templateDir + "/ScheduleWorld/icon.png");
2684 string OldScheduleWorldConfig() {
2687 "spds/syncml/config.txt:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
2688 "spds/syncml/config.txt:username = your SyncML server account name\n"
2689 "spds/syncml/config.txt:password = your SyncML server password\n"
2690 "spds/syncml/config.txt:# logdir = \n"
2691 "spds/syncml/config.txt:# loglevel = 0\n"
2692 "spds/syncml/config.txt:# printChanges = 1\n"
2693 "spds/syncml/config.txt:# maxlogdirs = 10\n"
2694 "spds/syncml/config.txt:# autoSync = 0\n"
2695 "spds/syncml/config.txt:# autoSyncInterval = 30M\n"
2696 "spds/syncml/config.txt:# autoSyncDelay = 5M\n"
2697 "spds/syncml/config.txt:# preventSlowSync = 1\n"
2698 "spds/syncml/config.txt:# useProxy = 0\n"
2699 "spds/syncml/config.txt:# proxyHost = \n"
2700 "spds/syncml/config.txt:# proxyUsername = \n"
2701 "spds/syncml/config.txt:# proxyPassword = \n"
2702 "spds/syncml/config.txt:# clientAuthType = md5\n"
2703 "spds/syncml/config.txt:# RetryDuration = 5M\n"
2704 "spds/syncml/config.txt:# RetryInterval = 2M\n"
2705 "spds/syncml/config.txt:# remoteIdentifier = \n"
2706 "spds/syncml/config.txt:# PeerIsClient = 0\n"
2707 "spds/syncml/config.txt:# SyncMLVersion = \n"
2708 "spds/syncml/config.txt:# PeerName = \n"
2709 "spds/syncml/config.txt:deviceId = fixed-devid\n" /* this is not the default! */
2710 "spds/syncml/config.txt:# remoteDeviceId = \n"
2711 "spds/syncml/config.txt:# enableWBXML = 1\n"
2712 "spds/syncml/config.txt:# maxMsgSize = 150000\n"
2713 "spds/syncml/config.txt:# maxObjSize = 4000000\n"
2714 "spds/syncml/config.txt:# enableCompression = 0\n"
2715 #ifdef ENABLE_LIBSOUP
2716 // path to SSL certificates is only set for libsoup
2717 "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"
2720 "spds/syncml/config.txt:# SSLServerCertificates = \n"
2722 "spds/syncml/config.txt:# SSLVerifyServer = 1\n"
2723 "spds/syncml/config.txt:# SSLVerifyHost = 1\n"
2724 "spds/syncml/config.txt:WebURL = http://www.scheduleworld.com\n"
2725 "spds/syncml/config.txt:# IconURI = \n"
2726 "spds/syncml/config.txt:ConsumerReady = 1\n"
2727 "spds/sources/addressbook/config.txt:sync = two-way\n"
2728 "spds/sources/addressbook/config.txt:type = addressbook:text/vcard\n"
2729 "spds/sources/addressbook/config.txt:# evolutionsource = \n"
2730 "spds/sources/addressbook/config.txt:uri = card3\n"
2731 "spds/sources/addressbook/config.txt:# evolutionuser = \n"
2732 "spds/sources/addressbook/config.txt:# evolutionpassword = \n"
2733 "spds/sources/calendar/config.txt:sync = two-way\n"
2734 "spds/sources/calendar/config.txt:type = calendar\n"
2735 "spds/sources/calendar/config.txt:# evolutionsource = \n"
2736 "spds/sources/calendar/config.txt:uri = cal2\n"
2737 "spds/sources/calendar/config.txt:# evolutionuser = \n"
2738 "spds/sources/calendar/config.txt:# evolutionpassword = \n"
2739 "spds/sources/memo/config.txt:sync = two-way\n"
2740 "spds/sources/memo/config.txt:type = memo\n"
2741 "spds/sources/memo/config.txt:# evolutionsource = \n"
2742 "spds/sources/memo/config.txt:uri = note\n"
2743 "spds/sources/memo/config.txt:# evolutionuser = \n"
2744 "spds/sources/memo/config.txt:# evolutionpassword = \n"
2745 "spds/sources/todo/config.txt:sync = two-way\n"
2746 "spds/sources/todo/config.txt:type = todo\n"
2747 "spds/sources/todo/config.txt:# evolutionsource = \n"
2748 "spds/sources/todo/config.txt:uri = task2\n"
2749 "spds/sources/todo/config.txt:# evolutionuser = \n"
2750 "spds/sources/todo/config.txt:# evolutionpassword = \n";
2754 string FunambolConfig() {
2755 string config = m_scheduleWorldConfig;
2756 boost::replace_all(config, "/scheduleworld/", "/funambol/");
2758 boost::replace_first(config,
2759 "syncURL = http://sync.scheduleworld.com/funambol/ds",
2760 "syncURL = http://my.funambol.com/sync");
2762 boost::replace_first(config,
2763 "WebURL = http://www.scheduleworld.com",
2764 "WebURL = http://my.funambol.com");
2766 boost::replace_first(config,
2767 "# enableWBXML = 1",
2770 boost::replace_first(config,
2771 "# RetryInterval = 2M",
2772 "RetryInterval = 0");
2774 boost::replace_first(config,
2775 "addressbook/config.ini:uri = card3",
2776 "addressbook/config.ini:uri = card");
2777 boost::replace_all(config,
2778 "addressbook/config.ini:type = addressbook:text/vcard",
2779 "addressbook/config.ini:type = addressbook");
2781 boost::replace_first(config,
2782 "calendar/config.ini:uri = cal2",
2783 "calendar/config.ini:uri = event");
2784 boost::replace_all(config,
2785 "calendar/config.ini:type = calendar",
2786 "calendar/config.ini:type = calendar:text/calendar!");
2788 boost::replace_first(config,
2789 "todo/config.ini:uri = task2",
2790 "todo/config.ini:uri = task");
2791 boost::replace_all(config,
2792 "todo/config.ini:type = todo",
2793 "todo/config.ini:type = todo:text/calendar!");
2798 string SynthesisConfig() {
2799 string config = m_scheduleWorldConfig;
2800 boost::replace_all(config, "/scheduleworld/", "/synthesis/");
2802 boost::replace_first(config,
2803 "syncURL = http://sync.scheduleworld.com/funambol/ds",
2804 "syncURL = http://www.synthesis.ch/sync");
2806 boost::replace_first(config,
2807 "WebURL = http://www.scheduleworld.com",
2808 "WebURL = http://www.synthesis.ch");
2810 boost::replace_first(config,
2811 "ConsumerReady = 1",
2812 "# ConsumerReady = 0");
2814 boost::replace_first(config,
2815 "addressbook/config.ini:uri = card3",
2816 "addressbook/config.ini:uri = contacts");
2817 boost::replace_all(config,
2818 "addressbook/config.ini:type = addressbook:text/vcard",
2819 "addressbook/config.ini:type = addressbook");
2821 boost::replace_first(config,
2822 "calendar/config.ini:uri = cal2",
2823 "calendar/config.ini:uri = events");
2824 boost::replace_first(config,
2825 "calendar/config.ini:sync = two-way",
2826 "calendar/config.ini:sync = disabled");
2828 boost::replace_first(config,
2829 "memo/config.ini:uri = note",
2830 "memo/config.ini:uri = notes");
2832 boost::replace_first(config,
2833 "todo/config.ini:uri = task2",
2834 "todo/config.ini:uri = tasks");
2835 boost::replace_first(config,
2836 "todo/config.ini:sync = two-way",
2837 "todo/config.ini:sync = disabled");
2842 /** create directory hierarchy, overwriting previous content */
2843 void createFiles(const string &root, const string &content, bool append = false) {
2852 out.exceptions(ios_base::badbit|ios_base::failbit);
2853 while (start < content.size()) {
2854 size_t delim = content.find(':', start);
2855 size_t end = content.find('\n', start);
2856 if (delim == content.npos ||
2857 end == content.npos) {
2858 // invalid content ?!
2861 string newname = content.substr(start, delim - start);
2862 string line = content.substr(delim + 1, end - delim - 1);
2863 if (newname != outname) {
2864 if (out.is_open()) {
2867 string fullpath = root + "/" + newname;
2868 size_t fileoff = fullpath.rfind('/');
2869 mkdir_p(fullpath.substr(0, fileoff));
2870 out.open(fullpath.c_str(),
2871 append ? ios_base::out : (ios_base::out|ios_base::trunc));
2874 out << line << endl;
2879 /** turn directory hierarchy into string
2881 * @param root root path in file system
2882 * @param peer if non-empty, then ignore all <root>/peers/<foo> directories
2883 * where <foo> != peer
2884 * @param onlyProps ignore lines which are comments
2886 string scanFiles(const string &root, const string &peer = "", bool onlyProps = true) {
2889 scanFiles(root, "", peer, out, onlyProps);
2893 void scanFiles(const string &root, const string &dir, const string &peer, ostringstream &out, bool onlyProps) {
2894 string newroot = root;
2897 ReadDir readDir(newroot);
2898 sort(readDir.begin(), readDir.end());
2900 BOOST_FOREACH(const string &entry, readDir) {
2901 if (isDir(newroot + "/" + entry)) {
2902 if (boost::ends_with(newroot, "/peers") &&
2905 // skip different peer directory
2908 scanFiles(root, dir + (dir.empty() ? "" : "/") + entry, peer, out, onlyProps);
2912 in.exceptions(ios_base::badbit /* failbit must not trigger exception because is set when reaching eof ?! */);
2913 in.open((newroot + "/" + entry).c_str());
2917 if ((line.size() || !in.eof()) &&
2919 (boost::starts_with(line, "# ") ?
2920 isPropAssignment(line.substr(2)) :
2925 out << entry << ":";
2926 out << line << '\n';
2933 string printConfig(const string &server) {
2934 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2935 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2936 ScopedEnvChange home("HOME", m_testDir);
2938 TestCmdline cmdline("--print-config", server.c_str(), NULL);
2940 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2941 return cmdline.m_out.str();
2945 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(CmdlineTest);
2947 #endif // ENABLE_UNIT_TESTS