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>
40 #include <boost/shared_ptr.hpp>
41 #include <boost/algorithm/string/join.hpp>
42 #include <boost/algorithm/string.hpp>
43 #include <boost/foreach.hpp>
45 #include <syncevo/declarations.h>
48 Cmdline::Cmdline(int argc, const char * const * argv, ostream &out, ostream &err) :
53 m_validSyncProps(SyncConfig::getRegistry()),
54 m_validSourceProps(SyncSourceConfig::getRegistry())
60 while (opt < m_argc) {
61 if (m_argv[opt][0] != '-') {
64 if (boost::iequals(m_argv[opt], "--sync") ||
65 boost::iequals(m_argv[opt], "-s")) {
68 string cmdopt(m_argv[opt - 1]);
69 if (!parseProp(m_validSourceProps, m_sourceProps,
70 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt],
71 SyncSourceConfig::m_sourcePropSync.getName().c_str())) {
75 // disable requirement to add --run explicitly in order to
76 // be compatible with traditional command lines
78 } else if(boost::iequals(m_argv[opt], "--sync-property") ||
79 boost::iequals(m_argv[opt], "-y")) {
81 if (!parseProp(m_validSyncProps, m_syncProps,
82 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
85 } else if(boost::iequals(m_argv[opt], "--source-property") ||
86 boost::iequals(m_argv[opt], "-z")) {
88 if (!parseProp(m_validSourceProps, m_sourceProps,
89 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
92 }else if(boost::iequals(m_argv[opt], "--template") ||
93 boost::iequals(m_argv[opt], "-l")) {
96 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
99 m_template = m_argv[opt];
101 string temp = boost::trim_copy (m_template);
102 if (temp.find ("?") == 0){
103 m_printTemplates = true;
105 m_template = temp.substr (1);
107 } else if(boost::iequals(m_argv[opt], "--print-servers") ||
108 boost::iequals(m_argv[opt], "--print-peers") ||
109 boost::iequals(m_argv[opt], "--print-configs")) {
110 m_printServers = true;
111 } else if(boost::iequals(m_argv[opt], "--print-config") ||
112 boost::iequals(m_argv[opt], "-p")) {
113 m_printConfig = true;
114 } else if(boost::iequals(m_argv[opt], "--print-sessions")) {
115 m_printSessions = true;
116 } else if(boost::iequals(m_argv[opt], "--configure") ||
117 boost::iequals(m_argv[opt], "-c")) {
119 } else if(boost::iequals(m_argv[opt], "--remove")) {
121 } else if(boost::iequals(m_argv[opt], "--run") ||
122 boost::iequals(m_argv[opt], "-r")) {
124 } else if(boost::iequals(m_argv[opt], "--restore")) {
127 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
130 m_restore = m_argv[opt];
131 if (m_restore.empty()) {
132 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
135 if (!isDir(m_restore)) {
136 usage(true, string("parameter '") + m_restore + "' for " + cmdOpt(m_argv[opt - 1]) + " must be log directory");
139 } else if(boost::iequals(m_argv[opt], "--before")) {
141 } else if(boost::iequals(m_argv[opt], "--after")) {
143 } else if(boost::iequals(m_argv[opt], "--dry-run")) {
145 } else if(boost::iequals(m_argv[opt], "--migrate")) {
147 } else if(boost::iequals(m_argv[opt], "--status") ||
148 boost::iequals(m_argv[opt], "-t")) {
150 } else if(boost::iequals(m_argv[opt], "--quiet") ||
151 boost::iequals(m_argv[opt], "-q")) {
153 } else if(boost::iequals(m_argv[opt], "--help") ||
154 boost::iequals(m_argv[opt], "-h")) {
156 } else if(boost::iequals(m_argv[opt], "--version")) {
158 } else if(boost::iequals(m_argv[opt], "--keyring")||
159 boost::iequals(m_argv[opt], "-k")) {
162 usage(false, string(m_argv[opt]) + ": unknown parameter");
169 m_server = m_argv[opt++];
170 while (opt < m_argc) {
171 m_sources.insert(m_argv[opt++]);
178 bool Cmdline::run() {
179 // --dry-run is only supported by some operations.
180 // Be very strict about it and make sure it is off in all
181 // potentially harmful operations, otherwise users might
182 // expect it to have an effect when it doesn't.
186 } else if (m_version) {
187 printf("SyncEvolution %s\n", VERSION);
188 printf("%s", EDSAbiWrapperInfo());
189 printf("%s", SyncSource::backendsInfo().c_str());
190 } else if (m_printServers || boost::trim_copy(m_server) == "?") {
191 dumpConfigs("Configured servers:",
192 SyncConfig::getConfigs());
193 } else if (m_printTemplates) {
194 SyncConfig::DeviceList devices;
195 if (m_template.empty()){
196 dumpConfigTemplates("Available configuration templates:",
197 SyncConfig::getPeerTemplates(devices), false);
199 //limiting at templates for syncml clients only.
200 devices.push_back (SyncConfig::DeviceList::value_type (m_template, SyncConfig::MATCH_FOR_SERVER_MODE));
201 dumpConfigTemplates("Available configuration templates:",
202 SyncConfig::matchPeerTemplates(devices), true);
204 } else if (m_dontrun) {
205 // user asked for information
206 } else if (m_argc == 1) {
207 // no parameters: list databases and short usage
208 const SourceRegistry ®istry(SyncSource::getSourceRegistry());
209 boost::shared_ptr<FilterConfigNode> sharedNode(new VolatileConfigNode());
210 boost::shared_ptr<FilterConfigNode> configNode(new VolatileConfigNode());
211 boost::shared_ptr<FilterConfigNode> hiddenNode(new VolatileConfigNode());
212 boost::shared_ptr<FilterConfigNode> trackingNode(new VolatileConfigNode());
213 boost::shared_ptr<FilterConfigNode> serverNode(new VolatileConfigNode());
214 SyncSourceNodes nodes(sharedNode, configNode, hiddenNode, trackingNode, serverNode);
215 SyncSourceParams params("list", nodes);
217 BOOST_FOREACH(const RegisterSyncSource *source, registry) {
218 BOOST_FOREACH(const Values::value_type &alias, source->m_typeValues) {
219 if (!alias.empty() && source->m_enabled) {
220 configNode->setProperty("type", *alias.begin());
221 auto_ptr<SyncSource> source(SyncSource::createSource(params, false));
222 if (source.get() != NULL) {
223 listSources(*source, boost::join(alias, " = "));
231 } else if (m_printConfig) {
232 boost::shared_ptr<SyncConfig> config;
233 ConfigProps syncFilter;
234 SourceFilters_t sourceFilters;
236 if (m_template.empty()) {
237 if (m_server.empty()) {
238 m_err << "ERROR: --print-config requires either a --template or a server name." << endl;
241 config.reset(new SyncConfig(m_server));
242 if (!config->exists()) {
243 m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
247 syncFilter = m_syncProps;
248 sourceFilters[""] = m_sourceProps;
250 string peer, context;
251 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_template), peer, context);
253 config = SyncConfig::createPeerTemplate(peer);
255 m_err << "ERROR: no configuration template for '" << m_template << "' available." << endl;
259 getFilters(context, syncFilter, sourceFilters);
262 if (m_sources.empty() ||
263 m_sources.find("main") != m_sources.end()) {
264 boost::shared_ptr<FilterConfigNode> syncProps(config->getProperties());
265 syncProps->setFilter(syncFilter);
266 dumpProperties(*syncProps, config->getRegistry(), false);
269 list<string> sources = config->getSyncSources();
271 BOOST_FOREACH(const string &name, sources) {
272 if (m_sources.empty() ||
273 m_sources.find(name) != m_sources.end()) {
274 m_out << endl << "[" << name << "]" << endl;
275 SyncSourceNodes nodes = config->getSyncSourceNodes(name);
276 boost::shared_ptr<FilterConfigNode> sourceProps = nodes.getProperties();
277 SourceFilters_t::const_iterator it = sourceFilters.find(name);
278 if (it != sourceFilters.end()) {
279 sourceProps->setFilter(it->second);
281 sourceProps->setFilter(sourceFilters[""]);
283 dumpProperties(*sourceProps, SyncSourceConfig::getRegistry(),
284 name != *(--sources.end()));
287 } else if (m_server == "" && m_argc > 1) {
288 // Options given, but no server - not sure what the user wanted?!
289 usage(true, "server name missing");
291 } else if (m_configure || m_migrate) {
293 SyncContext::throwError("--dry-run not supported for configuration changes");
296 #ifndef USE_GNOME_KEYRING
297 m_err << "Error: this syncevolution binary was compiled without support for storing "
298 "passwords in a keyring. Either store passwords in your configuration "
299 "files or enter them interactively on each program run." << endl;
304 bool fromScratch = false;
305 string peer, context;
306 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_server), peer, context);
308 // Both config changes and migration are implemented as copying from
309 // another config (template resp. old one). Migration also moves
311 boost::shared_ptr<SyncConfig> from;
313 string oldContext = context;
314 from.reset(new SyncConfig(m_server));
315 if (!from->exists()) {
316 // for migration into a different context, search for config without context
318 from.reset(new SyncConfig(peer));
319 if (!from->exists()) {
320 m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
326 string oldRoot = from->getRootPath();
330 ostringstream newsuffix;
333 newsuffix << "." << counter;
335 suffix = newsuffix.str();
336 newname = oldRoot + suffix;
337 if (!rename(oldRoot.c_str(),
340 } else if (errno != EEXIST && errno != ENOTEMPTY) {
341 m_err << "ERROR: renaming " << oldRoot << " to " <<
342 newname << ": " << strerror(errno) << endl;
348 from.reset(new SyncConfig(peer + suffix +
349 (oldContext.empty() ? "" : "@") +
352 from.reset(new SyncConfig(m_server));
353 if (!from->exists()) {
354 // creating from scratch, look for template
356 string configTemplate = m_template.empty() ? m_server : m_template;
357 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(configTemplate), peer, context);
358 from = SyncConfig::createPeerTemplate(peer);
360 m_err << "ERROR: no configuration template for '" << configTemplate << "' available." << endl;
361 dumpConfigTemplates("Available configuration templates:",
362 SyncConfig::getPeerTemplates(SyncConfig::DeviceList()));
366 // Templates no longer contain these strings, because
367 // GUIs would have to localize them. For configs created
368 // via the command line, the extra hint that these
369 // properties need to be set is useful, so set these
370 // strings here. They'll get copied into the new
371 // config only if no other value was given on the
373 if (!from->getUsername()[0]) {
374 from->setUsername("your SyncML server account name");
376 if (!from->getPassword()[0]) {
377 from->setPassword("your SyncML server password");
382 // Apply config changes on-the-fly. Regardless what we do
383 // (changing an existing config, migrating, creating from
384 // a template), existing shared properties in the desired
385 // context must be preserved unless explicitly overwritten.
386 // Therefore read those, update with command line properties,
387 // then set as filter.
388 ConfigProps syncFilter;
389 SourceFilters_t sourceFilters;
390 getFilters(context, syncFilter, sourceFilters);
391 from->setConfigFilter(true, "", syncFilter);
392 BOOST_FOREACH(const SourceFilters_t::value_type &entry, sourceFilters) {
393 from->setConfigFilter(false, entry.first, entry.second);
396 // write into the requested configuration, creating it if necessary
397 boost::shared_ptr<SyncContext> to(createSyncClient());
398 to->copy(*from, !fromScratch && !m_sources.empty() ? &m_sources : NULL);
400 // Sources are active now according to the server default.
401 // Disable all sources not selected by user (if any selected)
402 // and those which have no database.
404 list<string> configuredSources = to->getSyncSources();
405 set<string> sources = m_sources;
407 BOOST_FOREACH(const string &source, configuredSources) {
408 boost::shared_ptr<PersistentSyncSourceConfig> sourceConfig(to->getSyncSourceConfig(source));
410 set<string>::iterator entry = sources.find(source);
411 bool selected = entry != sources.end();
413 if (!m_sources.empty() &&
415 disable = "not selected";
417 if (entry != sources.end()) {
418 // The command line parameter matched a valid source.
419 // All entries left afterwards must have been typos.
420 sources.erase(entry);
423 // check whether the sync source works
424 SyncSourceParams params("list", to->getSyncSourceNodes(source));
425 auto_ptr<SyncSource> syncSource(SyncSource::createSource(params, false));
426 if (syncSource.get() == NULL) {
427 disable = "no backend available";
430 SyncSource::Databases databases = syncSource->getDatabases();
431 if (databases.empty()) {
432 disable = "no database to synchronize";
435 disable = "backend failed";
440 if (!disable.empty()) {
441 // abort if the user explicitly asked for the sync source
442 // and it cannot be enabled, otherwise disable it silently
444 SyncContext::throwError(source + ": " + disable);
446 sourceConfig->setSync("disabled");
447 } else if (selected) {
448 // user absolutely wants it: enable even if off by default
449 FilterConfigNode::ConfigFilter::const_iterator sync =
450 m_sourceProps.find(SyncSourceConfig::m_sourcePropSync.getName());
451 sourceConfig->setSync(sync == m_sourceProps.end() ? "two-way" : sync->second);
455 if (!sources.empty()) {
456 SyncContext::throwError(string("no such source(s): ") + boost::join(sources, " "));
459 // give a change to do something before flushing configs to files
462 // done, now write it
465 // also copy .synthesis dir?
467 string fromDir, toDir;
468 fromDir = from->getRootPath() + "/.synthesis";
469 toDir = to->getRootPath() + "/.synthesis";
470 if (isDir(fromDir)) {
471 cp_r(fromDir, toDir);
474 } else if (m_remove) {
476 SyncContext::throwError("--dry-run not supported for removing configurations");
479 // extra sanity check
480 if (!m_sources.empty() ||
481 !m_syncProps.empty() ||
482 !m_sourceProps.empty()) {
483 usage(true, "too many parameters for --remove");
486 boost::shared_ptr<SyncConfig> config;
487 config.reset(new SyncConfig(m_server));
488 if (!config->exists()) {
489 SyncContext::throwError(string("no such configuration: ") + m_server);
495 std::set<std::string> unmatchedSources;
496 boost::shared_ptr<SyncContext> context;
497 context.reset(createSyncClient());
498 context->setQuiet(m_quiet);
499 context->setDryRun(m_dryrun);
500 context->setConfigFilter(true, "", m_syncProps);
501 if (m_sources.empty()) {
502 if (m_sourceProps.empty()) {
503 // empty source list, empty source filter => run with
504 // existing configuration without filtering it
506 // Special semantic of 'no source selected': apply
507 // filter only to sources which are
508 // *active*. Configuration of inactive sources is left
509 // unchanged. This way we don't activate sync sources
510 // accidentally when the sync mode is modified
512 BOOST_FOREACH(const std::string &source,
513 context->getSyncSources()) {
514 boost::shared_ptr<PersistentSyncSourceConfig> source_config =
515 context->getSyncSourceConfig(source);
516 if (strcmp(source_config->getSync(), "disabled")) {
517 context->setConfigFilter(false, source, m_sourceProps);
522 // apply (possibly empty) source filter to selected sources
523 BOOST_FOREACH(const std::string &source,
525 boost::shared_ptr<PersistentSyncSourceConfig> source_config =
526 context->getSyncSourceConfig(source);
527 if (!source_config || !source_config->exists()) {
528 // invalid source name in m_sources, remember and
530 unmatchedSources.insert(source);
531 } else if (m_sourceProps.find(SyncSourceConfig::m_sourcePropSync.getName()) ==
532 m_sourceProps.end()) {
533 // Sync mode is not set, must override the
534 // "sync=disabled" set below with the original
535 // sync mode for the source or (if that is also
536 // "disabled") with "two-way". The latter is part
537 // of the command line semantic that listing a
538 // source activates it.
539 FilterConfigNode::ConfigFilter filter = m_sourceProps;
540 string sync = source_config->getSync();
541 filter[SyncSourceConfig::m_sourcePropSync.getName()] =
542 sync == "disabled" ? "two-way" : sync;
543 context->setConfigFilter(false, source, filter);
545 // sync mode is set, can use m_sourceProps
546 // directly to apply it
547 context->setConfigFilter(false, source, m_sourceProps);
551 // temporarily disable the rest
552 FilterConfigNode::ConfigFilter disabled;
553 disabled[SyncSourceConfig::m_sourcePropSync.getName()] = "disabled";
554 context->setConfigFilter(false, "", disabled);
557 // check whether there were any sources specified which do not exist
558 if (unmatchedSources.size()) {
559 context->throwError(string("no such source(s): ") + boost::join(unmatchedSources, " "));
564 } else if (m_printSessions) {
566 context->getSessions(dirs);
568 BOOST_FOREACH(const string &dir, dirs) {
571 } else if(!m_quiet) {
577 context->readSessionInfo(dir, report);
581 } else if (!m_restore.empty()) {
582 // sanity checks: either --after or --before must be given, sources must be selected
583 if ((!m_after && !m_before) ||
584 (m_after && m_before)) {
585 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)");
588 if (m_sources.empty()) {
589 usage(false, "Sources must be selected explicitly for --restore to prevent accidental restore.");
592 context->restore(m_restore,
594 SyncContext::DATABASE_AFTER_SYNC :
595 SyncContext::DATABASE_BEFORE_SYNC);
598 SyncContext::throwError("--dry-run not supported for running a synchronization");
601 // safety catch: if props are given, then --run
604 (m_syncProps.size() || m_sourceProps.size())) {
605 usage(false, "Properties specified, but neither '--configure' nor '--run' - what did you want?");
609 return (context->sync() == STATUS_OK);
616 string Cmdline::cmdOpt(const char *opt, const char *param)
628 bool Cmdline::parseProp(const ConfigPropertyRegistry &validProps,
629 FilterConfigNode::ConfigFilter &props,
632 const char *propname)
635 usage(true, string("missing parameter for ") + cmdOpt(opt, param));
637 } else if (boost::trim_copy(string(param)) == "?") {
640 return listPropValues(validProps, propname, opt);
642 return listProperties(validProps, opt);
651 const char *equal = strchr(param, '=');
653 usage(true, string("the '=<value>' part is missing in: ") + cmdOpt(opt, param));
656 propstr.assign(param, equal - param);
657 paramstr.assign(equal + 1);
660 boost::trim(propstr);
661 boost::trim_left(paramstr);
663 if (boost::trim_copy(paramstr) == "?") {
665 return listPropValues(validProps, propstr, cmdOpt(opt, param));
667 const ConfigProperty *prop = validProps.find(propstr);
669 m_err << "ERROR: " << cmdOpt(opt, param) << ": no such property" << endl;
673 if (!prop->checkValue(paramstr, error)) {
674 m_err << "ERROR: " << cmdOpt(opt, param) << ": " << error << endl;
677 props[propstr] = paramstr;
685 bool Cmdline::listPropValues(const ConfigPropertyRegistry &validProps,
686 const string &propName,
689 const ConfigProperty *prop = validProps.find(propName);
691 m_err << "ERROR: "<< opt << ": no such property" << endl;
694 m_out << opt << endl;
695 string comment = prop->getComment();
698 list<string> commentLines;
699 ConfigProperty::splitComment(comment, commentLines);
700 BOOST_FOREACH(const string &line, commentLines) {
701 m_out << " " << line << endl;
704 m_out << " no documentation available" << endl;
710 bool Cmdline::listProperties(const ConfigPropertyRegistry &validProps,
713 // The first of several related properties has a comment.
714 // Remember that comment and print it as late as possible,
715 // that way related properties preceed their comment.
717 BOOST_FOREACH(const ConfigProperty *prop, validProps) {
718 if (!prop->isHidden()) {
719 string newComment = prop->getComment();
721 if (newComment != "") {
722 if (!comment.empty()) {
723 dumpComment(m_out, " ", comment);
726 comment = newComment;
728 m_out << prop->getName() << ":" << endl;
731 dumpComment(m_out, " ", comment);
735 void Cmdline::getFilters(const string &context,
736 ConfigProps &syncFilter,
737 map<string, ConfigProps> &sourceFilters)
739 // Read from context. If it does not exist, we simply set no properties
740 // as filter. Previously there was a check for existance, but that was
741 // flawed because it ignored the global property "defaultPeer".
742 boost::shared_ptr<SyncConfig> shared(new SyncConfig(string("@") + context));
743 shared->getProperties()->readProperties(syncFilter);
744 BOOST_FOREACH(StringPair entry, m_syncProps) {
745 syncFilter[entry.first] = entry.second;
748 BOOST_FOREACH(std::string source, shared->getSyncSources()) {
749 SyncSourceNodes nodes = shared->getSyncSourceNodes(source, "");
750 ConfigProps &props = sourceFilters[source];
751 nodes.getProperties()->readProperties(props);
753 // Special case "type" property: the value in the context
754 // is not preserved. Every new peer must ensure that
755 // its own value is compatible (= same backend) with
759 BOOST_FOREACH(StringPair entry, m_sourceProps) {
760 props[entry.first] = entry.second;
763 sourceFilters[""] = m_sourceProps;
766 void Cmdline::listSources(SyncSource &syncSource, const string &header)
768 m_out << header << ":\n";
769 SyncSource::Databases databases = syncSource.getDatabases();
771 BOOST_FOREACH(const SyncSource::Database &database, databases) {
772 m_out << " " << database.m_name << " (" << database.m_uri << ")";
773 if (database.m_isDefault) {
774 m_out << " <default>";
780 void Cmdline::dumpConfigs(const string &preamble,
781 const SyncConfig::ConfigList &servers)
783 m_out << preamble << endl;
784 BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server,servers) {
785 m_out << " " << server.first << " = " << server.second <<endl;
787 if (!servers.size()) {
788 m_out << " none" << endl;
792 void Cmdline::dumpConfigTemplates(const string &preamble,
793 const SyncConfig::TemplateList &templates,
796 m_out << preamble << endl;
797 BOOST_FOREACH(const SyncConfig::TemplateList::value_type server,templates) {
798 m_out << " " << server->m_name << " = " << server->m_description;
800 m_out << " " << server->m_rank;
804 if (!templates.size()) {
805 m_out << " none" << endl;
809 void Cmdline::dumpProperties(const ConfigNode &configuredProps,
810 const ConfigPropertyRegistry &allProps,
813 list<string> perPeer, perContext, global;
815 BOOST_FOREACH(const ConfigProperty *prop, allProps) {
816 if (prop->isHidden()) {
820 string comment = prop->getComment();
821 if (!comment.empty()) {
823 dumpComment(m_out, "# ", comment);
827 prop->getProperty(configuredProps, &isDefault);
831 m_out << prop->getName() << " = " << prop->getProperty(configuredProps) << endl;
833 list<string> *type = NULL;
834 switch (prop->getSharing()) {
835 case ConfigProperty::GLOBAL_SHARING:
838 case ConfigProperty::SOURCE_SET_SHARING:
841 case ConfigProperty::NO_SHARING:
846 type->push_back(prop->getName());
850 if (!m_quiet && !hideLegend) {
851 if (!perPeer.empty() ||
852 !perContext.empty() ||
856 if (!perPeer.empty()) {
857 m_out << "# per-peer (unshared) properties: " << boost::join(perPeer, ", ") << endl;
859 if (!perContext.empty()) {
860 m_out << "# shared by peers in same context: " << boost::join(perContext, ", ") << endl;
862 if (!global.empty()) {
863 m_out << "# global properties: " << boost::join(global, ", ") << endl;
868 void Cmdline::dumpComment(ostream &stream,
869 const string &prefix,
870 const string &comment)
872 list<string> commentLines;
873 ConfigProperty::splitComment(comment, commentLines);
874 BOOST_FOREACH(const string &line, commentLines) {
875 stream << prefix << line << endl;
879 void Cmdline::usage(bool full, const string &error, const string ¶m)
881 ostream &out(error.empty() ? m_out : m_err);
883 out << "Show available sources:" << endl;
884 out << " " << m_argv[0] << endl;
885 out << "Show information about configuration(s) and sync sessions:" << endl;
886 out << " " << m_argv[0] << " --print-servers|--print-configs|--print-peers" << endl;
887 out << " " << m_argv[0] << " --print-config [--quiet] <config> [main|<source ...]" << endl;
888 out << " " << m_argv[0] << " --print-sessions [--quiet] <config>" << endl;
889 out << "Show information about SyncEvolution:" << endl;
890 out << " " << m_argv[0] << " --help|-h" << endl;
891 out << " " << m_argv[0] << " --version" << endl;
892 out << "Run a synchronization:" << endl;
893 out << " " << m_argv[0] << " <config> [<source> ...]" << endl;
894 out << " " << m_argv[0] << " --run <options for run> <config> [<source> ...]" << endl;
895 out << "Restore data from the automatic backups:" << endl;
896 out << " " << m_argv[0] << " --restore <session directory> --before|--after [--dry-run] <config> <source> ..." << endl;
897 out << "Remove a configuration:" << endl;
898 out << " " << m_argv[0] << " --remove <config>" << endl;
899 out << "Modify configuration:" << endl;
900 out << " " << m_argv[0] << " --configure <options for configuration> <config> [<source> ...]" << endl;
901 out << " " << m_argv[0] << " --migrate <config>" << endl;
904 "Options:" << endl <<
905 "--sync|-s <mode>" << endl <<
906 "--sync|-s ?" << endl <<
907 " Temporarily synchronize the active sources in that mode. Useful" << endl <<
908 " for a \"refresh-from-server\" or \"refresh-from-client\" sync which" << endl <<
909 " clears all data at one end and copies all items from the other." << endl <<
911 "--print-servers|--print-configs|--print-peers" << endl <<
912 " Prints the names of all configured peers to stdout." << endl <<
914 "--print-config|-p" << endl <<
915 " Prints the complete configuration for the selected peer" << endl <<
916 " to stdout, including up-to-date comments for all properties. The" << endl <<
917 " format is the normal .ini format with source configurations in" << endl <<
918 " different sections introduced with [<source>] lines. Can be combined" << endl <<
919 " with --sync-property and --source-property to modify the configuration" << endl <<
920 " on-the-fly. When one or more sources are listed after the <config>" << endl <<
921 " name on the command line, then only the configs of those sources are" << endl <<
922 " printed. Using --quiet suppresses the comments for each property." << endl <<
923 " When setting a --template, then the reference configuration for" << endl <<
924 " that peer is printed instead of an existing configuration." << endl <<
926 "--print-sessions" << endl <<
927 " Prints a list of all previous log directories. Unless --quiet is used, each" << endl <<
928 " file name is followed by the original sync report." << endl <<
930 "--configure|-c" << endl <<
931 " Modify the configuration files for the selected peer. If no such" << endl <<
932 " configuration exists, then a new one is created using one of the" << endl <<
933 " template configurations (see --template option). When creating" << endl <<
934 " a new configuration only the active sources will be set to active" << endl <<
935 " in the new configuration, i.e. \"syncevolution -c scheduleworld addressbook\"" << endl <<
936 " followed by \"syncevolution scheduleworld\" will only synchronize the" << endl <<
937 " address book. The other sources are created in a disabled state." << endl <<
938 " When modifying an existing configuration and sources are specified," << endl <<
939 " then the source properties of only those sources are modified." << endl <<
941 "--migrate" << endl <<
942 " In older SyncEvolution releases a different layout of configuration files" << endl <<
943 " was used. Using --migrate will automatically migrate to the new" << endl <<
944 " layout and rename the <config> into <config>.old to prevent accidental use" << endl <<
945 " of the old configuration. WARNING: old SyncEvolution releases cannot" << endl <<
946 " use the new configuration!" << endl <<
948 " The switch can also be used to migrate a configuration in the current" << endl <<
949 " configuration directory: this preserves all property values, discards" << endl <<
950 " obsolete properties and sets all comments exactly as if the configuration" << endl <<
951 " had been created from scratch. WARNING: custom comments in the" << endl <<
952 " configuration are not preserved." << endl <<
954 "--restore" << endl <<
955 " Restores the data of the selected sources to the state from before or after the" << endl <<
956 " selected synchronization. The synchronization is selected via its log directory" << endl <<
957 " (see --print-sessions). Other directories can also be given as long as" << endl <<
958 " they contain database dumps in the format created by SyncEvolution." << endl <<
959 " The output includes information about the changes made during the" << endl <<
960 " restore, both in terms of item changes and content changes (which is" << endl <<
961 " not always the same, see manual for details). This output can be suppressed" << endl <<
962 " with --quiet." << endl <<
963 " In combination with --dry-run, the changes to local data are only simulated." << endl <<
964 " This can be used to check that --restore will not remove valuable information." << endl <<
966 "--remove" << endl <<
967 " Deletes the configuration. If the <config> refers to a specific" << endl <<
968 " peer, only that peer's configuration is removed. If it refers to" << endl <<
969 " a context, that context and all peers inside it are removed." << endl <<
970 " Note that there is no confirmation question. Neither local data" << endl <<
971 " referenced by the configuration nor the content of log dirs are" << endl <<
972 " deleted." << endl <<
974 "--sync-property|-y <property>=<value>" << endl <<
975 "--sync-property|-y ?" << endl <<
976 "--sync-property|-y <property>=?" << endl <<
977 " Overrides a source-independent configuration property for the" << endl <<
978 " current synchronization run or permanently when --configure is used" << endl <<
979 " to update the configuration. Can be used multiple times. Specifying" << endl <<
980 " an unused property will trigger an error message." << endl <<
982 " When using the configuration layout introduced with 1.0, some of the" << endl <<
983 " sync properties are shared between peers, for example the directory" << endl <<
984 " where sessions are logged. Permanently changing such a shared" << endl <<
985 " property for one peer will automatically update the property for all" << endl <<
986 " other peers in the same context because the property is stored in a" << endl <<
987 " shared config file." << endl <<
989 "--source-property|-z <property>=<value>" << endl <<
990 "--source-property|-z ?" << endl <<
991 "--source-property|-z <property>=?" << endl <<
992 " Same as --sync-property, but applies to the configuration of all active" << endl <<
993 " sources. \"--sync <mode>\" is a shortcut for \"--source-property sync=<mode>\"." << endl <<
995 "--template|-l <peer name>|default|?|?<device>" << endl <<
996 " Can be used to select from one of the built-in default configurations" << endl <<
997 " for known SyncML peers. Defaults to the <config> name, so --template" << endl <<
998 " only has to be specified when creating multiple different configurations" << endl <<
999 " for the same peer, or when using a template that is named differently" << endl <<
1000 " than the peer. \"default\" is an alias for \"scheduleworld\" and can be" << endl <<
1001 " used as the starting point for servers which do not have a built-in" << endl <<
1002 " template." << endl <<
1004 " Each template contains a pseudo-random device ID. Therefore setting the" << endl <<
1005 " \"deviceId\" sync property is only necessary when manually recreating a" << endl <<
1006 " configuration or when a more descriptive name is desired." << endl <<
1008 " The available templates for different known SyncML servers are listed when" << endl <<
1009 " using a single question mark instead of template name. When using the" << endl <<
1010 " ?<device> format, a fuzzy search for a template that might be" << endl <<
1011 " suitable for talking to such a device is done. The matching works best" << endl <<
1012 " when using <device> = <Manufacturer>_<Model>. If you don't know the" << endl <<
1013 " manufacturer, you can just keep it as empty. The output in this mode" << endl <<
1014 " gives the template name followed by a short description and a rating how well" << endl <<
1015 " the template matches the device (higher is better)." << endl <<
1017 "--status|-t" << endl <<
1018 " The changes made to local data since the last synchronization are" << endl <<
1019 " shown without starting a new one. This can be used to see in advance" << endl <<
1020 " whether the local data needs to be synchronized with the peer." << endl <<
1022 "--quiet|-q" << endl <<
1023 " Suppresses most of the normal output during a synchronization. The" << endl <<
1024 " log file still contains all the information." << endl <<
1026 "--keyring|-k" << endl <<
1027 " Save or retrieve passwords from the GNOME keyring when modifying the" << endl <<
1028 " configuration or running a synchronization. Note that using this option" << endl <<
1029 " applies to *all* passwords in a configuration, so setting a single" << endl <<
1030 " password as follows moves the other passwords into the keyring, if" << endl <<
1031 " they were not stored there already:" << endl <<
1032 " --keyring --configure --sync-property proxyPassword=foo" << endl <<
1034 " When passwords were stored in the keyring, their value is set to '-'" << endl <<
1035 " in the configuration. This means that when running a synchronization" << endl <<
1036 " without the --keyring argument, the password has to be entered" << endl <<
1037 " interactively. The --print-config output always shows '-' instead of" << endl <<
1038 " retrieving the password from the keyring." << endl <<
1040 "--help|-h" << endl <<
1041 " Prints usage information." << endl <<
1043 "--version" << endl <<
1044 " Prints the SyncEvolution version." << endl;
1048 out << endl << "ERROR: " << error << endl;
1051 out << "INFO: use '" << param << (param[param.size() - 1] == '=' ? "" : " ") <<
1052 "?' to get a list of valid parameters" << endl;
1056 SyncContext* Cmdline::createSyncClient() {
1057 return new SyncContext(m_server, true);
1060 #ifdef ENABLE_UNIT_TESTS
1062 /** simple line-by-line diff */
1063 static string diffStrings(const string &lhs, const string &rhs)
1067 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1068 string_split_iterator lit =
1069 boost::make_split_iterator(lhs, boost::first_finder("\n", boost::is_iequal()));
1070 string_split_iterator rit =
1071 boost::make_split_iterator(rhs, boost::first_finder("\n", boost::is_iequal()));
1072 while (lit != string_split_iterator() &&
1073 rit != string_split_iterator()) {
1075 res << "< " << *lit << endl;
1076 res << "> " << *rit << endl;
1082 while (lit != string_split_iterator()) {
1083 res << "< " << *lit << endl;
1087 while (rit != string_split_iterator()) {
1088 res << "> " << *rit << endl;
1095 # define CPPUNIT_ASSERT_EQUAL_DIFF( expected, actual ) \
1097 string expected_ = (expected); \
1098 string actual_ = (actual); \
1099 if (expected_ != actual_) { \
1100 CPPUNIT_NS::Message cpputMsg_(string("expected:\n") + \
1102 cpputMsg_.addDetail(string("actual:\n") + \
1104 cpputMsg_.addDetail(string("diff:\n") + \
1105 diffStrings(expected_, actual_)); \
1106 CPPUNIT_NS::Asserter::fail( cpputMsg_, \
1107 CPPUNIT_SOURCELINE() ); \
1111 // returns last line, including trailing line break, empty if input is empty
1112 static string lastLine(const string &buffer)
1114 if (buffer.size() < 2) {
1118 size_t line = buffer.rfind("\n", buffer.size() - 2);
1119 if (line == buffer.npos) {
1123 return buffer.substr(line + 1);
1127 static bool isPropAssignment(const string &buffer) {
1129 while (start < buffer.size() &&
1130 !isspace(buffer[start])) {
1133 if (start + 3 <= buffer.size() &&
1134 buffer.substr(start, 3) == " = ") {
1141 // remove pure comment lines from buffer,
1142 // also empty lines,
1143 // also defaultPeer (because reference properties do not include global props)
1144 static string filterConfig(const string &buffer)
1148 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1149 for (string_split_iterator it =
1150 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
1151 it != string_split_iterator();
1153 string line = boost::copy_range<string>(*it);
1154 if (!line.empty() &&
1155 line.find("defaultPeer =") == line.npos &&
1156 (!boost::starts_with(line, "# ") ||
1157 isPropAssignment(line.substr(2)))) {
1158 res << line << endl;
1165 static string injectValues(const string &buffer)
1167 string res = buffer;
1169 // username/password not set in templates, only in configs created via
1171 boost::replace_first(res,
1173 "username = your SyncML server account name");
1174 boost::replace_first(res,
1176 "password = your SyncML server password");
1180 // remove lines indented with spaces
1181 static string filterIndented(const string &buffer)
1186 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1187 for (string_split_iterator it =
1188 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
1189 it != string_split_iterator();
1191 if (!boost::starts_with(*it, " ")) {
1204 // sort lines by file, preserving order inside each line
1205 static void sortConfig(string &config)
1207 // file name, line number, property
1208 typedef pair<string, pair<int, string> > line_t;
1209 vector<line_t> lines;
1210 typedef boost::split_iterator<string::iterator> string_split_iterator;
1212 for (string_split_iterator it =
1213 boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal()));
1214 it != string_split_iterator();
1216 string line(it->begin(), it->end());
1221 size_t colon = line.find(':');
1222 string prefix = line.substr(0, colon);
1223 lines.push_back(make_pair(prefix, make_pair(linenr, line.substr(colon))));
1226 // stable sort because of line number
1227 sort(lines.begin(), lines.end());
1229 size_t len = config.size();
1231 config.reserve(len);
1232 BOOST_FOREACH(const line_t &line, lines) {
1233 config += line.first;
1234 config += line.second.second;
1239 // convert the internal config dump to .ini style (--print-config)
1240 static string internalToIni(const string &config)
1245 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1246 for (string_split_iterator it =
1247 boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal()));
1248 it != string_split_iterator();
1250 string line(it->begin(), it->end());
1255 size_t colon = line.find(':');
1256 string prefix = line.substr(0, colon);
1258 // internal values are not part of the --print-config output
1259 if (boost::contains(prefix, ".internal.ini") ||
1260 boost::contains(line, "= internal value")) {
1264 // --print-config also doesn't duplicate the "type" property
1265 // => remove the shared property
1266 if (boost::contains(line, ":type = ") &&
1267 boost::starts_with(line, "sources/")) {
1271 // sources/<name>/config.ini or
1272 // spds/sources/<name>/config.ini
1273 size_t endslash = prefix.rfind('/');
1274 if (endslash != line.npos && endslash > 1) {
1275 size_t slash = prefix.rfind('/', endslash - 1);
1276 if (slash != line.npos) {
1277 string newsource = prefix.substr(slash + 1, endslash - slash - 1);
1278 if (newsource != section &&
1279 prefix.find("/sources/") != prefix.npos &&
1280 newsource != "syncml") {
1281 res << "[" << newsource << "]" << endl;
1282 section = newsource;
1286 string assignment = line.substr(colon + 1);
1287 // substitude aliases with generic values
1288 boost::replace_first(assignment, "= F", "= 0");
1289 boost::replace_first(assignment, "= T", "= 1");
1290 boost::replace_first(assignment, "= syncml:auth-md5", "= md5");
1291 boost::replace_first(assignment, "= syncml:auth-basix", "= basic");
1292 res << assignment << endl;
1300 * Testing is based on a text representation of a directory
1301 * hierarchy where each line is of the format
1302 * <file path>:<line in file>
1304 * The order of files is alphabetical, of lines in the file as
1305 * in the file. Lines in the file without line break cannot
1308 * The root of the hierarchy is not part of the representation
1311 class CmdlineTest : public CppUnit::TestFixture {
1312 CPPUNIT_TEST_SUITE(CmdlineTest);
1313 CPPUNIT_TEST(testFramework);
1314 CPPUNIT_TEST(testSetupScheduleWorld);
1315 CPPUNIT_TEST(testSetupDefault);
1316 CPPUNIT_TEST(testSetupRenamed);
1317 CPPUNIT_TEST(testSetupFunambol);
1318 CPPUNIT_TEST(testSetupSynthesis);
1319 CPPUNIT_TEST(testPrintServers);
1320 CPPUNIT_TEST(testPrintConfig);
1321 CPPUNIT_TEST(testPrintFileTemplates);
1322 CPPUNIT_TEST(testTemplate);
1323 CPPUNIT_TEST(testMatchTemplate);
1324 CPPUNIT_TEST(testAddSource);
1325 CPPUNIT_TEST(testSync);
1326 CPPUNIT_TEST(testConfigure);
1327 CPPUNIT_TEST(testOldConfigure);
1328 CPPUNIT_TEST(testListSources);
1329 CPPUNIT_TEST(testMigrate);
1330 CPPUNIT_TEST_SUITE_END();
1334 m_testDir("CmdlineTest"),
1335 // properties sorted by the order in which they are defined
1336 // in the sync and sync source property registry
1337 m_scheduleWorldConfig("peers/scheduleworld/.internal.ini:# HashCode = 0\n"
1338 "peers/scheduleworld/.internal.ini:# ConfigDate = \n"
1339 "peers/scheduleworld/.internal.ini:# lastNonce = \n"
1340 "peers/scheduleworld/.internal.ini:# deviceData = \n"
1341 "peers/scheduleworld/config.ini:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
1342 "peers/scheduleworld/config.ini:username = your SyncML server account name\n"
1343 "peers/scheduleworld/config.ini:password = your SyncML server password\n"
1344 "config.ini:# logdir = \n"
1345 "peers/scheduleworld/config.ini:# loglevel = 0\n"
1346 "peers/scheduleworld/config.ini:# printChanges = 1\n"
1347 "config.ini:# maxlogdirs = 10\n"
1348 "peers/scheduleworld/config.ini:# preventSlowSync = 0\n"
1349 "peers/scheduleworld/config.ini:# useProxy = 0\n"
1350 "peers/scheduleworld/config.ini:# proxyHost = \n"
1351 "peers/scheduleworld/config.ini:# proxyUsername = \n"
1352 "peers/scheduleworld/config.ini:# proxyPassword = \n"
1353 "peers/scheduleworld/config.ini:# clientAuthType = md5\n"
1354 "peers/scheduleworld/config.ini:# RetryDuration = 300\n"
1355 "peers/scheduleworld/config.ini:# RetryInterval = 60\n"
1356 "peers/scheduleworld/config.ini:# remoteIdentifier = \n"
1357 "peers/scheduleworld/config.ini:# PeerIsClient = 0\n"
1358 "peers/scheduleworld/config.ini:# PeerName = \n"
1359 "config.ini:deviceId = fixed-devid\n" /* this is not the default! */
1360 "peers/scheduleworld/config.ini:# remoteDeviceId = \n"
1361 "peers/scheduleworld/config.ini:# enableWBXML = 1\n"
1362 "peers/scheduleworld/config.ini:# maxMsgSize = 20000\n"
1363 "peers/scheduleworld/config.ini:# maxObjSize = 4000000\n"
1364 "peers/scheduleworld/config.ini:# enableCompression = 0\n"
1365 "peers/scheduleworld/config.ini:# SSLServerCertificates = \n"
1366 "peers/scheduleworld/config.ini:# SSLVerifyServer = 1\n"
1367 "peers/scheduleworld/config.ini:# SSLVerifyHost = 1\n"
1368 "peers/scheduleworld/config.ini:WebURL = http://www.scheduleworld.com\n"
1369 "peers/scheduleworld/config.ini:# IconURI = \n"
1370 "peers/scheduleworld/config.ini:ConsumerReady = 1\n"
1372 "peers/scheduleworld/sources/addressbook/.internal.ini:# adminData = \n"
1373 "peers/scheduleworld/sources/addressbook/config.ini:sync = two-way\n"
1374 "sources/addressbook/config.ini:type = addressbook:text/vcard\n"
1375 "peers/scheduleworld/sources/addressbook/config.ini:type = addressbook:text/vcard\n"
1376 "sources/addressbook/config.ini:# evolutionsource = \n"
1377 "peers/scheduleworld/sources/addressbook/config.ini:uri = card3\n"
1378 "sources/addressbook/config.ini:# evolutionuser = \n"
1379 "sources/addressbook/config.ini:# evolutionpassword = \n"
1381 "peers/scheduleworld/sources/calendar/.internal.ini:# adminData = \n"
1382 "peers/scheduleworld/sources/calendar/config.ini:sync = two-way\n"
1383 "sources/calendar/config.ini:type = calendar\n"
1384 "peers/scheduleworld/sources/calendar/config.ini:type = calendar\n"
1385 "sources/calendar/config.ini:# evolutionsource = \n"
1386 "peers/scheduleworld/sources/calendar/config.ini:uri = cal2\n"
1387 "sources/calendar/config.ini:# evolutionuser = \n"
1388 "sources/calendar/config.ini:# evolutionpassword = \n"
1390 "peers/scheduleworld/sources/memo/.internal.ini:# adminData = \n"
1391 "peers/scheduleworld/sources/memo/config.ini:sync = two-way\n"
1392 "sources/memo/config.ini:type = memo\n"
1393 "peers/scheduleworld/sources/memo/config.ini:type = memo\n"
1394 "sources/memo/config.ini:# evolutionsource = \n"
1395 "peers/scheduleworld/sources/memo/config.ini:uri = note\n"
1396 "sources/memo/config.ini:# evolutionuser = \n"
1397 "sources/memo/config.ini:# evolutionpassword = \n"
1399 "peers/scheduleworld/sources/todo/.internal.ini:# adminData = \n"
1400 "peers/scheduleworld/sources/todo/config.ini:sync = two-way\n"
1401 "sources/todo/config.ini:type = todo\n"
1402 "peers/scheduleworld/sources/todo/config.ini:type = todo\n"
1403 "sources/todo/config.ini:# evolutionsource = \n"
1404 "peers/scheduleworld/sources/todo/config.ini:uri = task2\n"
1405 "sources/todo/config.ini:# evolutionuser = \n"
1406 "sources/todo/config.ini:# evolutionpassword = ")
1408 #ifdef ENABLE_LIBSOUP
1409 // path to SSL certificates has to be set only for libsoup
1410 boost::replace_first(m_scheduleWorldConfig,
1411 "SSLServerCertificates = ",
1412 "SSLServerCertificates = /etc/ssl/certs/ca-certificates.crt:/etc/pki/tls/certs/ca-bundle.crt:/usr/share/ssl/certs/ca-bundle.crt");
1418 /** verify that createFiles/scanFiles themselves work */
1419 void testFramework() {
1420 const string root(m_testDir);
1421 const string content("baz:line\n"
1423 "caz/subdir2/sub:# comment\n"
1424 "caz/subdir2/sub:# foo = bar\n"
1425 "caz/subdir2/sub:# empty = \n"
1426 "caz/subdir2/sub:# another comment\n"
1431 const string filtered("baz:line\n"
1433 "caz/subdir2/sub:# foo = bar\n"
1434 "caz/subdir2/sub:# empty = \n"
1438 createFiles(root, content);
1439 string res = scanFiles(root);
1440 CPPUNIT_ASSERT_EQUAL_DIFF(filtered, res);
1443 void removeRandomUUID(string &buffer) {
1444 string uuidstr = "deviceId = sc-pim-";
1445 size_t uuid = buffer.find(uuidstr);
1446 CPPUNIT_ASSERT(uuid != buffer.npos);
1447 size_t end = buffer.find("\n", uuid + uuidstr.size());
1448 CPPUNIT_ASSERT(end != buffer.npos);
1449 buffer.replace(uuid, end - uuid, "deviceId = fixed-devid");
1452 /** create new configurations */
1453 void testSetupScheduleWorld() { doSetupScheduleWorld(false); }
1454 void doSetupScheduleWorld(bool shared) {
1456 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1457 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1458 ScopedEnvChange home("HOME", m_testDir);
1461 root += "/syncevolution/default";
1464 peer = root + "/peers/scheduleworld";
1471 TestCmdline cmdline("--configure",
1472 "--sync-property", "proxyHost = proxy",
1477 string res = scanFiles(root);
1478 removeRandomUUID(res);
1479 string expected = ScheduleWorldConfig();
1480 sortConfig(expected);
1481 boost::replace_first(expected,
1483 "proxyHost = proxy");
1484 boost::replace_all(expected,
1487 boost::replace_first(expected,
1488 "addressbook/config.ini:sync = disabled",
1489 "addressbook/config.ini:sync = two-way");
1490 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1495 TestCmdline cmdline("--configure",
1496 "--sync-property", "deviceID = fixed-devid",
1500 string res = scanFiles(root);
1501 string expected = ScheduleWorldConfig();
1502 sortConfig(expected);
1503 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1507 void testSetupDefault() {
1509 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1510 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1511 ScopedEnvChange home("HOME", m_testDir);
1514 root += "/syncevolution/default";
1516 TestCmdline cmdline("--configure",
1517 "--template", "default",
1518 "--sync-property", "deviceID = fixed-devid",
1519 "some-other-server",
1522 string res = scanFiles(root, "some-other-server");
1523 string expected = ScheduleWorldConfig();
1524 sortConfig(expected);
1525 boost::replace_all(expected, "/scheduleworld/", "/some-other-server/");
1526 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1528 void testSetupRenamed() {
1530 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1531 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1532 ScopedEnvChange home("HOME", m_testDir);
1535 root += "/syncevolution/default";
1537 TestCmdline cmdline("--configure",
1538 "--template", "scheduleworld",
1539 "--sync-property", "deviceID = fixed-devid",
1543 string res = scanFiles(root, "scheduleworld2");
1544 string expected = ScheduleWorldConfig();
1545 sortConfig(expected);
1546 boost::replace_all(expected, "/scheduleworld/", "/scheduleworld2/");
1547 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1550 void testSetupFunambol() { doSetupFunambol(false); }
1551 void doSetupFunambol(bool shared) {
1553 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1554 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1555 ScopedEnvChange home("HOME", m_testDir);
1558 root += "/syncevolution/default";
1561 peer = root + "/peers/funambol";
1567 const char * const argv_fixed[] = {
1569 "--sync-property", "deviceID = fixed-devid",
1570 // templates are case-insensitive
1573 }, * const argv_shared[] = {
1578 TestCmdline cmdline(shared ? argv_shared : argv_fixed);
1580 string res = scanFiles(root, "funambol");
1581 string expected = FunambolConfig();
1582 sortConfig(expected);
1583 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1586 void testSetupSynthesis() { doSetupSynthesis(false); }
1587 void doSetupSynthesis(bool shared) {
1589 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1590 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1591 ScopedEnvChange home("HOME", m_testDir);
1594 root += "/syncevolution/default";
1597 peer = root + "/peers/synthesis";
1602 const char * const argv_fixed[] = {
1604 "--sync-property", "deviceID = fixed-devid",
1607 }, * const argv_shared[] = {
1612 TestCmdline cmdline(shared ? argv_shared : argv_fixed);
1614 string res = scanFiles(root, "synthesis");
1615 string expected = SynthesisConfig();
1616 sortConfig(expected);
1617 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1620 void testTemplate() {
1621 TestCmdline failure("--template", NULL);
1622 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
1623 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1624 CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--template'\n"), lastLine(failure.m_err.str()));
1626 TestCmdline help("--template", "? ", NULL);
1628 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
1629 " Funambol = http://my.funambol.com\n"
1630 " Google = http://m.google.com/sync\n"
1631 " Goosync = http://www.goosync.com/\n"
1632 " Memotoo = http://www.memotoo.com\n"
1633 " Mobical = http://www.mobical.net\n"
1634 " Oracle = http://www.oracle.com/technology/products/beehive/index.html\n"
1635 " ScheduleWorld = http://www.scheduleworld.com\n"
1636 " SyncEvolution = http://www.syncevolution.org\n"
1637 " Synthesis = http://www.synthesis.ch\n"
1638 " ZYB = http://www.zyb.com\n",
1640 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
1643 void testMatchTemplate() {
1644 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "testcases/templates");
1646 TestCmdline help1("--template", "?nokia_7210c", NULL);
1648 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
1649 " Nokia_7210c = Template for Nokia 7210c phone and as default template for all Nokia phones 5\n"
1650 " SyncEvolutionClient = SyncEvolution server side template 2\n"
1651 " ServerDefault = server side default template 1\n",
1653 CPPUNIT_ASSERT_EQUAL_DIFF("", help1.m_err.str());
1654 TestCmdline help2("--template", "?nokia", NULL);
1656 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
1657 " Nokia_7210c = Template for Nokia 7210c phone and as default template for all Nokia phones 5\n"
1658 " SyncEvolutionClient = SyncEvolution server side template 2\n"
1659 " ServerDefault = server side default template 1\n",
1661 CPPUNIT_ASSERT_EQUAL_DIFF("", help2.m_err.str());
1662 TestCmdline help3("--template", "?7210c", NULL);
1664 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
1665 " Nokia_7210c = Template for Nokia 7210c phone and as default template for all Nokia phones 3\n"
1666 " ServerDefault = server side default template 1\n"
1667 " SyncEvolutionClient = SyncEvolution server side template 1\n",
1669 CPPUNIT_ASSERT_EQUAL_DIFF("", help3.m_err.str());
1670 TestCmdline help4("--template", "?syncevolution", NULL);
1672 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
1673 " SyncEvolutionClient = SyncEvolution server side template 5\n"
1674 " Nokia_7210c = Template for Nokia 7210c phone and as default template for all Nokia phones 2\n"
1675 " ServerDefault = server side default template 2\n",
1677 CPPUNIT_ASSERT_EQUAL_DIFF("", help4.m_err.str());
1680 void testPrintServers() {
1681 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1682 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1683 ScopedEnvChange home("HOME", m_testDir);
1686 doSetupScheduleWorld(false);
1687 doSetupSynthesis(true);
1688 doSetupFunambol(true);
1690 TestCmdline cmdline("--print-servers", NULL);
1692 CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n"
1693 " funambol = CmdlineTest/syncevolution/default/peers/funambol\n"
1694 " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n"
1695 " synthesis = CmdlineTest/syncevolution/default/peers/synthesis\n",
1696 cmdline.m_out.str());
1697 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1700 void testPrintConfig() {
1701 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1702 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1703 ScopedEnvChange home("HOME", m_testDir);
1706 testSetupFunambol();
1709 TestCmdline failure("--print-config", NULL);
1710 CPPUNIT_ASSERT(failure.m_cmdline->parse());
1711 CPPUNIT_ASSERT(!failure.m_cmdline->run());
1712 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1713 CPPUNIT_ASSERT_EQUAL(string("ERROR: --print-config requires either a --template or a server name.\n"),
1714 lastLine(failure.m_err.str()));
1718 TestCmdline failure("--print-config", "foo", NULL);
1719 CPPUNIT_ASSERT(failure.m_cmdline->parse());
1720 CPPUNIT_ASSERT(!failure.m_cmdline->run());
1721 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1722 CPPUNIT_ASSERT_EQUAL(string("ERROR: server 'foo' has not been configured yet.\n"),
1723 lastLine(failure.m_err.str()));
1727 TestCmdline failure("--print-config", "--template", "foo", NULL);
1728 CPPUNIT_ASSERT(failure.m_cmdline->parse());
1729 CPPUNIT_ASSERT(!failure.m_cmdline->run());
1730 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1731 CPPUNIT_ASSERT_EQUAL(string("ERROR: no configuration template for 'foo' available.\n"),
1732 lastLine(failure.m_err.str()));
1736 TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL);
1738 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1739 string actual = cmdline.m_out.str();
1740 // deviceId must be the one from Funambol
1741 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
1742 string filtered = injectValues(filterConfig(actual));
1743 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
1745 // there should have been comments
1746 CPPUNIT_ASSERT(actual.size() > filtered.size());
1750 TestCmdline cmdline("--print-config", "--template", "Default", NULL);
1752 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1753 string actual = injectValues(filterConfig(cmdline.m_out.str()));
1754 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
1755 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
1760 TestCmdline cmdline("--print-config", "funambol", NULL);
1762 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1763 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())),
1764 injectValues(filterConfig(cmdline.m_out.str())));
1768 // override context and template properties
1769 TestCmdline cmdline("--print-config", "--template", "scheduleworld",
1770 "--sync-property", "syncURL=foo",
1771 "--source-property", "evolutionsource=Personal",
1772 "--source-property", "sync=disabled",
1775 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1776 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
1777 boost::replace_first(expected,
1778 "syncURL = http://sync.scheduleworld.com/funambol/ds",
1780 boost::replace_all(expected,
1781 "# evolutionsource = ",
1782 "evolutionsource = Personal");
1783 boost::replace_all(expected,
1786 string actual = injectValues(filterConfig(cmdline.m_out.str()));
1787 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
1788 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
1793 TestCmdline cmdline("--print-config", "--quiet",
1794 "--template", "scheduleworld",
1798 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1799 string actual = cmdline.m_out.str();
1800 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
1801 CPPUNIT_ASSERT_EQUAL_DIFF(internalToIni(ScheduleWorldConfig()),
1802 injectValues(filterConfig(actual)));
1806 // change shared source properties, then check template again
1807 TestCmdline cmdline("--configure",
1808 "--source-property", "evolutionsource=Personal",
1812 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1815 TestCmdline cmdline("--print-config", "--quiet",
1816 "--template", "scheduleworld",
1820 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1821 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
1822 // from modified Funambol config
1823 boost::replace_all(expected,
1824 "# evolutionsource = ",
1825 "evolutionsource = Personal");
1826 string actual = injectValues(filterConfig(cmdline.m_out.str()));
1827 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
1828 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
1833 void testPrintFileTemplates() {
1834 // use local copy of templates in build dir (no need to install)
1835 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "./templates");
1836 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1837 ScopedEnvChange home("HOME", m_testDir);
1840 testSetupFunambol();
1843 TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL);
1845 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1846 string actual = cmdline.m_out.str();
1847 // deviceId must be the one from Funambol
1848 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
1849 string filtered = injectValues(filterConfig(actual));
1850 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
1852 // there should have been comments
1853 CPPUNIT_ASSERT(actual.size() > filtered.size());
1857 TestCmdline cmdline("--print-config", "funambol", NULL);
1859 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1860 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())),
1861 injectValues(filterConfig(cmdline.m_out.str())));
1865 void testAddSource() {
1867 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1868 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1869 ScopedEnvChange home("HOME", m_testDir);
1871 testSetupScheduleWorld();
1874 root += "/syncevolution/default";
1877 TestCmdline cmdline("--configure",
1878 "--source-property", "uri = dummy",
1883 string res = scanFiles(root);
1884 string expected = ScheduleWorldConfig();
1886 "peers/scheduleworld/sources/xyz/.internal.ini:# adminData = \n"
1887 "peers/scheduleworld/sources/xyz/config.ini:# sync = two-way\n"
1888 "peers/scheduleworld/sources/xyz/config.ini:# type = select backend\n"
1889 "peers/scheduleworld/sources/xyz/config.ini:uri = dummy\n"
1890 "sources/xyz/config.ini:# type = select backend\n"
1891 "sources/xyz/config.ini:# evolutionsource = \n"
1892 "sources/xyz/config.ini:# evolutionuser = \n"
1893 "sources/xyz/config.ini:# evolutionpassword = ";
1894 sortConfig(expected);
1895 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1900 TestCmdline failure("--sync", NULL);
1901 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
1902 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1903 CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--sync'\n"), lastLine(failure.m_err.str()));
1905 TestCmdline failure2("--sync", "foo", NULL);
1906 CPPUNIT_ASSERT(!failure2.m_cmdline->parse());
1907 CPPUNIT_ASSERT_EQUAL_DIFF("", failure2.m_out.str());
1908 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()));
1910 TestCmdline help("--sync", " ?", NULL);
1912 CPPUNIT_ASSERT_EQUAL_DIFF("--sync\n"
1913 " requests a certain synchronization mode:\n"
1914 " two-way = only send/receive changes since last sync\n"
1915 " slow = exchange all items\n"
1916 " refresh-from-client = discard all remote items and replace with\n"
1917 " the items on the client\n"
1918 " refresh-from-server = discard all local items and replace with\n"
1919 " the items on the server\n"
1920 " one-way-from-client = transmit changes from client\n"
1921 " one-way-from-server = transmit changes from server\n"
1922 " none (or disabled) = synchronization disabled\n",
1924 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
1926 TestCmdline filter("--sync", "refresh-from-server", NULL);
1927 CPPUNIT_ASSERT(filter.m_cmdline->parse());
1928 CPPUNIT_ASSERT(!filter.m_cmdline->run());
1929 CPPUNIT_ASSERT_EQUAL_DIFF("", filter.m_out.str());
1930 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh-from-server",
1931 string(filter.m_cmdline->m_sourceProps));
1932 CPPUNIT_ASSERT_EQUAL_DIFF("",
1933 string(filter.m_cmdline->m_syncProps));
1935 TestCmdline filter2("--source-property", "sync=refresh", NULL);
1936 CPPUNIT_ASSERT(filter2.m_cmdline->parse());
1937 CPPUNIT_ASSERT(!filter2.m_cmdline->run());
1938 CPPUNIT_ASSERT_EQUAL_DIFF("", filter2.m_out.str());
1939 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh",
1940 string(filter2.m_cmdline->m_sourceProps));
1941 CPPUNIT_ASSERT_EQUAL_DIFF("",
1942 string(filter2.m_cmdline->m_syncProps));
1945 void testConfigure() {
1946 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1947 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1948 ScopedEnvChange home("HOME", m_testDir);
1951 testSetupScheduleWorld();
1952 doConfigure(ScheduleWorldConfig(), "sources/addressbook/config.ini:");
1954 string syncProperties("syncURL:\n"
1968 "preventSlowSync:\n"
1984 "remoteIdentifier:\n"
1999 "enableCompression:\n"
2001 "SSLServerCertificates:\n"
2003 "SSLVerifyServer:\n"
2014 string sourceProperties("sync:\n"
2018 "evolutionsource:\n"
2023 "evolutionpassword:\n");
2026 TestCmdline cmdline("--sync-property", "?",
2029 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2030 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties,
2031 filterIndented(cmdline.m_out.str()));
2035 TestCmdline cmdline("--source-property", "?",
2038 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2039 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties,
2040 filterIndented(cmdline.m_out.str()));
2044 TestCmdline cmdline("--source-property", "?",
2045 "--sync-property", "?",
2048 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2049 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties + syncProperties,
2050 filterIndented(cmdline.m_out.str()));
2054 TestCmdline cmdline("--sync-property", "?",
2055 "--source-property", "?",
2058 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2059 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties + sourceProperties,
2060 filterIndented(cmdline.m_out.str()));
2064 void testOldConfigure() {
2065 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2066 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2067 ScopedEnvChange home("HOME", m_testDir);
2069 string oldConfig = OldScheduleWorldConfig();
2070 InitList<string> props = InitList<string>("serverNonce") +
2079 BOOST_FOREACH(string &prop, props) {
2080 boost::replace_all(oldConfig,
2082 prop + " = internal value");
2086 createFiles(m_testDir + "/.sync4j/evolution/scheduleworld", oldConfig);
2087 doConfigure(oldConfig, "spds/sources/addressbook/config.txt:");
2090 void doConfigure(const string &SWConfig, const string &addressbookPrefix) {
2094 TestCmdline cmdline("--configure",
2095 "--source-property", "sync = disabled",
2099 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2100 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2101 expected = filterConfig(internalToIni(SWConfig));
2102 boost::replace_all(expected,
2105 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2106 filterConfig(printConfig("scheduleworld")));
2110 TestCmdline cmdline("--configure",
2111 "--source-property", "sync = one-way-from-server",
2116 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2117 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2118 expected = SWConfig;
2119 boost::replace_all(expected,
2122 boost::replace_first(expected,
2123 addressbookPrefix + "sync = disabled",
2124 addressbookPrefix + "sync = one-way-from-server");
2125 expected = filterConfig(internalToIni(expected));
2126 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2127 filterConfig(printConfig("scheduleworld")));
2131 TestCmdline cmdline("--configure",
2132 "--sync", "two-way",
2133 "-z", "evolutionsource=source",
2134 "--sync-property", "maxlogdirs=20",
2135 "-y", "LOGDIR=logdir",
2139 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2140 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2141 boost::replace_all(expected,
2142 "sync = one-way-from-server",
2144 boost::replace_all(expected,
2147 boost::replace_all(expected,
2148 "# evolutionsource = ",
2149 "evolutionsource = source");
2150 boost::replace_all(expected,
2151 "# maxlogdirs = 10",
2153 boost::replace_all(expected,
2156 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2157 filterConfig(printConfig("scheduleworld")));
2161 void testListSources() {
2162 // pick the varargs constructor; NULL alone is ambiguous
2163 TestCmdline cmdline(NULL, NULL);
2165 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2166 // exact output varies, do not test
2169 void testMigrate() {
2170 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2171 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2172 ScopedEnvChange home("HOME", m_testDir);
2175 string oldRoot = m_testDir + "/.sync4j/evolution/scheduleworld";
2176 string newRoot = m_testDir + "/syncevolution/default";
2178 string oldConfig = OldScheduleWorldConfig();
2181 // migrate old config
2182 createFiles(oldRoot, oldConfig);
2183 string createdConfig = scanFiles(oldRoot);
2184 TestCmdline cmdline("--migrate",
2188 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2189 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2191 string migratedConfig = scanFiles(newRoot);
2192 string expected = ScheduleWorldConfig();
2193 sortConfig(expected);
2194 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2195 string renamedConfig = scanFiles(oldRoot + ".old");
2196 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2200 // rewrite existing config with obsolete properties
2201 // => these properties should get removed
2203 // There is one limitation: shared nodes are not rewritten.
2204 // This is acceptable.
2205 createFiles(newRoot + "/peers/scheduleworld",
2206 "config.ini:# obsolete comment\n"
2207 "config.ini:obsoleteprop = foo\n",
2209 string createdConfig = scanFiles(newRoot, "scheduleworld");
2211 TestCmdline cmdline("--migrate",
2215 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2216 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2218 string migratedConfig = scanFiles(newRoot, "scheduleworld");
2219 string expected = ScheduleWorldConfig();
2220 sortConfig(expected);
2221 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2222 string renamedConfig = scanFiles(newRoot, "scheduleworld.old");
2223 boost::replace_all(createdConfig, "/scheduleworld/", "/scheduleworld.old/");
2224 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2228 // migrate old config with changes and .synthesis directory, a second time
2229 createFiles(oldRoot, oldConfig);
2230 createFiles(oldRoot,
2231 ".synthesis/dummy-file.bfi:dummy = foobar\n"
2232 "spds/sources/addressbook/changes/config.txt:foo = bar\n"
2233 "spds/sources/addressbook/changes/config.txt:foo2 = bar2\n",
2235 string createdConfig = scanFiles(oldRoot);
2237 TestCmdline cmdline("--migrate",
2241 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2242 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2244 string migratedConfig = scanFiles(newRoot);
2245 string expected = m_scheduleWorldConfig;
2246 sortConfig(expected);
2247 boost::replace_first(expected,
2248 "peers/scheduleworld/sources/addressbook/config.ini",
2249 "peers/scheduleworld/sources/addressbook/.other.ini:foo = bar\n"
2250 "peers/scheduleworld/sources/addressbook/.other.ini:foo2 = bar2\n"
2251 "peers/scheduleworld/sources/addressbook/config.ini");
2252 boost::replace_first(expected,
2253 "peers/scheduleworld/config.ini",
2254 "peers/scheduleworld/.synthesis/dummy-file.bfi:dummy = foobar\n"
2255 "peers/scheduleworld/config.ini");
2256 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2257 string renamedConfig = scanFiles(oldRoot + ".old.1");
2258 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2262 string otherRoot = m_testDir + "/syncevolution/other";
2265 // migrate old config into non-default context
2266 createFiles(oldRoot, oldConfig);
2267 string createdConfig = scanFiles(oldRoot);
2269 TestCmdline cmdline("--migrate",
2270 "scheduleworld@other",
2273 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2274 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2277 string migratedConfig = scanFiles(otherRoot);
2278 string expected = ScheduleWorldConfig();
2279 sortConfig(expected);
2280 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2281 string renamedConfig = scanFiles(oldRoot + ".old");
2282 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2284 // migrate the migrated config again inside the "other" context
2286 TestCmdline cmdline("--migrate",
2287 "scheduleworld@other",
2290 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2291 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2293 migratedConfig = scanFiles(otherRoot, "scheduleworld");
2294 expected = ScheduleWorldConfig();
2295 sortConfig(expected);
2296 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2297 renamedConfig = scanFiles(otherRoot, "scheduleworld.old");
2298 boost::replace_all(expected, "/scheduleworld/", "/scheduleworld.old/");
2299 CPPUNIT_ASSERT_EQUAL_DIFF(expected, renamedConfig);
2303 const string m_testDir;
2304 string m_scheduleWorldConfig;
2310 * vararg constructor with NULL termination,
2311 * out and error stream into stringstream members
2315 m_argv.reset(new const char *[m_argvstr.size() + 1]);
2316 m_argv[0] = "client-test";
2317 for (size_t index = 0;
2318 index < m_argvstr.size();
2320 m_argv[index + 1] = m_argvstr[index].c_str();
2323 m_cmdline.set(new Cmdline(m_argvstr.size() + 1, m_argv.get(), m_out, m_err), "cmdline");
2328 TestCmdline(const char *arg, ...) {
2330 va_start (argList, arg);
2331 for (const char *curr = arg;
2333 curr = va_arg(argList, const char *)) {
2334 m_argvstr.push_back(curr);
2340 TestCmdline(const char * const argv[]) {
2341 for (int i = 0; argv[i]; i++) {
2342 m_argvstr.push_back(argv[i]);
2349 success = m_cmdline->parse() &&
2351 if (m_err.str().size()) {
2352 cout << endl << m_err.str();
2354 CPPUNIT_ASSERT(success);
2357 ostringstream m_out, m_err;
2358 cxxptr<Cmdline> m_cmdline;
2361 vector<string> m_argvstr;
2362 boost::scoped_array<const char *> m_argv;
2365 string ScheduleWorldConfig() {
2366 string config = m_scheduleWorldConfig;
2369 // Currently we don't have an icon for ScheduleWorld. If we
2370 // had (MB #2062) one, then this code would ensure that the
2371 // reference config also has the right path for it.
2372 const char *templateDir = getenv("SYNCEVOLUTION_TEMPLATE_DIR");
2374 templateDir = TEMPLATE_DIR;
2378 if (isDir(string(templateDir) + "/ScheduleWorld")) {
2379 boost::replace_all(config,
2381 string("IconURI = file://") + templateDir + "/ScheduleWorld/icon.png");
2387 string OldScheduleWorldConfig() {
2390 "spds/syncml/config.txt:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
2391 "spds/syncml/config.txt:username = your SyncML server account name\n"
2392 "spds/syncml/config.txt:password = your SyncML server password\n"
2393 "spds/syncml/config.txt:# logdir = \n"
2394 "spds/syncml/config.txt:# loglevel = 0\n"
2395 "spds/syncml/config.txt:# printChanges = 1\n"
2396 "spds/syncml/config.txt:# maxlogdirs = 10\n"
2397 "spds/syncml/config.txt:# preventSlowSync = 0\n"
2398 "spds/syncml/config.txt:# useProxy = 0\n"
2399 "spds/syncml/config.txt:# proxyHost = \n"
2400 "spds/syncml/config.txt:# proxyUsername = \n"
2401 "spds/syncml/config.txt:# proxyPassword = \n"
2402 "spds/syncml/config.txt:# clientAuthType = md5\n"
2403 "spds/syncml/config.txt:# RetryDuration = 300\n"
2404 "spds/syncml/config.txt:# RetryInterval = 60\n"
2405 "spds/syncml/config.txt:# remoteIdentifier = \n"
2406 "spds/syncml/config.txt:# PeerIsClient = 0\n"
2407 "spds/syncml/config.txt:# PeerName = \n"
2408 "spds/syncml/config.txt:deviceId = fixed-devid\n" /* this is not the default! */
2409 "spds/syncml/config.txt:# remoteDeviceId = \n"
2410 "spds/syncml/config.txt:# enableWBXML = 1\n"
2411 "spds/syncml/config.txt:# maxMsgSize = 20000\n"
2412 "spds/syncml/config.txt:# maxObjSize = 4000000\n"
2413 "spds/syncml/config.txt:# enableCompression = 0\n"
2414 #ifdef ENABLE_LIBSOUP
2415 // path to SSL certificates is only set for libsoup
2416 "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"
2419 "spds/syncml/config.txt:# SSLServerCertificates = \n"
2421 "spds/syncml/config.txt:# SSLVerifyServer = 1\n"
2422 "spds/syncml/config.txt:# SSLVerifyHost = 1\n"
2423 "spds/syncml/config.txt:WebURL = http://www.scheduleworld.com\n"
2424 "spds/syncml/config.txt:# IconURI = \n"
2425 "spds/syncml/config.txt:ConsumerReady = 1\n"
2426 "spds/sources/addressbook/config.txt:sync = two-way\n"
2427 "spds/sources/addressbook/config.txt:type = addressbook:text/vcard\n"
2428 "spds/sources/addressbook/config.txt:# evolutionsource = \n"
2429 "spds/sources/addressbook/config.txt:uri = card3\n"
2430 "spds/sources/addressbook/config.txt:# evolutionuser = \n"
2431 "spds/sources/addressbook/config.txt:# evolutionpassword = \n"
2432 "spds/sources/calendar/config.txt:sync = two-way\n"
2433 "spds/sources/calendar/config.txt:type = calendar\n"
2434 "spds/sources/calendar/config.txt:# evolutionsource = \n"
2435 "spds/sources/calendar/config.txt:uri = cal2\n"
2436 "spds/sources/calendar/config.txt:# evolutionuser = \n"
2437 "spds/sources/calendar/config.txt:# evolutionpassword = \n"
2438 "spds/sources/memo/config.txt:sync = two-way\n"
2439 "spds/sources/memo/config.txt:type = memo\n"
2440 "spds/sources/memo/config.txt:# evolutionsource = \n"
2441 "spds/sources/memo/config.txt:uri = note\n"
2442 "spds/sources/memo/config.txt:# evolutionuser = \n"
2443 "spds/sources/memo/config.txt:# evolutionpassword = \n"
2444 "spds/sources/todo/config.txt:sync = two-way\n"
2445 "spds/sources/todo/config.txt:type = todo\n"
2446 "spds/sources/todo/config.txt:# evolutionsource = \n"
2447 "spds/sources/todo/config.txt:uri = task2\n"
2448 "spds/sources/todo/config.txt:# evolutionuser = \n"
2449 "spds/sources/todo/config.txt:# evolutionpassword = \n";
2453 string FunambolConfig() {
2454 string config = m_scheduleWorldConfig;
2455 boost::replace_all(config, "/scheduleworld/", "/funambol/");
2457 boost::replace_first(config,
2458 "syncURL = http://sync.scheduleworld.com/funambol/ds",
2459 "syncURL = http://my.funambol.com/sync");
2461 boost::replace_first(config,
2462 "WebURL = http://www.scheduleworld.com",
2463 "WebURL = http://my.funambol.com");
2465 boost::replace_first(config,
2466 "# enableWBXML = 1",
2469 boost::replace_first(config,
2470 "addressbook/config.ini:uri = card3",
2471 "addressbook/config.ini:uri = card");
2472 boost::replace_all(config,
2473 "addressbook/config.ini:type = addressbook:text/vcard",
2474 "addressbook/config.ini:type = addressbook");
2476 boost::replace_first(config,
2477 "calendar/config.ini:uri = cal2",
2478 "calendar/config.ini:uri = event");
2479 boost::replace_all(config,
2480 "calendar/config.ini:type = calendar",
2481 "calendar/config.ini:type = calendar:text/calendar!");
2483 boost::replace_first(config,
2484 "todo/config.ini:uri = task2",
2485 "todo/config.ini:uri = task");
2486 boost::replace_all(config,
2487 "todo/config.ini:type = todo",
2488 "todo/config.ini:type = todo:text/calendar!");
2493 string SynthesisConfig() {
2494 string config = m_scheduleWorldConfig;
2495 boost::replace_all(config, "/scheduleworld/", "/synthesis/");
2497 boost::replace_first(config,
2498 "syncURL = http://sync.scheduleworld.com/funambol/ds",
2499 "syncURL = http://www.synthesis.ch/sync");
2501 boost::replace_first(config,
2502 "WebURL = http://www.scheduleworld.com",
2503 "WebURL = http://www.synthesis.ch");
2505 boost::replace_first(config,
2506 "ConsumerReady = 1",
2507 "# ConsumerReady = 0");
2509 boost::replace_first(config,
2510 "addressbook/config.ini:uri = card3",
2511 "addressbook/config.ini:uri = contacts");
2512 boost::replace_all(config,
2513 "addressbook/config.ini:type = addressbook:text/vcard",
2514 "addressbook/config.ini:type = addressbook");
2516 boost::replace_first(config,
2517 "calendar/config.ini:uri = cal2",
2518 "calendar/config.ini:uri = events");
2519 boost::replace_first(config,
2520 "calendar/config.ini:sync = two-way",
2521 "calendar/config.ini:sync = disabled");
2523 boost::replace_first(config,
2524 "memo/config.ini:uri = note",
2525 "memo/config.ini:uri = notes");
2527 boost::replace_first(config,
2528 "todo/config.ini:uri = task2",
2529 "todo/config.ini:uri = tasks");
2530 boost::replace_first(config,
2531 "todo/config.ini:sync = two-way",
2532 "todo/config.ini:sync = disabled");
2537 /** temporarily set env variable, restore old value on destruction */
2538 class ScopedEnvChange {
2540 ScopedEnvChange(const string &var, const string &value) :
2543 const char *oldval = getenv(var.c_str());
2548 m_oldvalset = false;
2550 setenv(var.c_str(), value.c_str(), 1);
2555 setenv(m_var.c_str(), m_oldval.c_str(), 1);
2557 unsetenv(m_var.c_str());
2561 string m_var, m_oldval;
2566 /** create directory hierarchy, overwriting previous content */
2567 void createFiles(const string &root, const string &content, bool append = false) {
2576 out.exceptions(ios_base::badbit|ios_base::failbit);
2577 while (start < content.size()) {
2578 size_t delim = content.find(':', start);
2579 size_t end = content.find('\n', start);
2580 if (delim == content.npos ||
2581 end == content.npos) {
2582 // invalid content ?!
2585 string newname = content.substr(start, delim - start);
2586 string line = content.substr(delim + 1, end - delim - 1);
2587 if (newname != outname) {
2588 if (out.is_open()) {
2591 string fullpath = root + "/" + newname;
2592 size_t fileoff = fullpath.rfind('/');
2593 mkdir_p(fullpath.substr(0, fileoff));
2594 out.open(fullpath.c_str(),
2595 append ? ios_base::out : (ios_base::out|ios_base::trunc));
2598 out << line << endl;
2603 /** turn directory hierarchy into string
2605 * @param root root path in file system
2606 * @param peer if non-empty, then ignore all <root>/peers/<foo> directories
2607 * where <foo> != peer
2608 * @param onlyProps ignore lines which are comments
2610 string scanFiles(const string &root, const string &peer = "", bool onlyProps = true) {
2613 scanFiles(root, "", peer, out, onlyProps);
2617 void scanFiles(const string &root, const string &dir, const string &peer, ostringstream &out, bool onlyProps) {
2618 string newroot = root;
2621 ReadDir readDir(newroot);
2622 sort(readDir.begin(), readDir.end());
2624 BOOST_FOREACH(const string &entry, readDir) {
2625 if (isDir(newroot + "/" + entry)) {
2626 if (boost::ends_with(newroot, "/peers") &&
2629 // skip different peer directory
2632 scanFiles(root, dir + (dir.empty() ? "" : "/") + entry, peer, out, onlyProps);
2636 in.exceptions(ios_base::badbit /* failbit must not trigger exception because is set when reaching eof ?! */);
2637 in.open((newroot + "/" + entry).c_str());
2641 if ((line.size() || !in.eof()) &&
2643 (boost::starts_with(line, "# ") ?
2644 isPropAssignment(line.substr(2)) :
2649 out << entry << ":";
2650 out << line << '\n';
2657 string printConfig(const string &server) {
2658 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2659 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2660 ScopedEnvChange home("HOME", m_testDir);
2662 TestCmdline cmdline("--print-config", server.c_str(), NULL);
2664 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2665 return cmdline.m_out.str();
2669 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(CmdlineTest);
2671 #endif // ENABLE_UNIT_TESTS