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>
42 #include <boost/shared_ptr.hpp>
43 #include <boost/algorithm/string/join.hpp>
44 #include <boost/algorithm/string/split.hpp>
45 #include <boost/algorithm/string.hpp>
46 #include <boost/foreach.hpp>
47 #include <boost/range.hpp>
49 #include <syncevo/declarations.h>
52 // synopsis and options char strings
53 #include "CmdlineHelp.c"
55 Cmdline::Cmdline(int argc, const char * const * argv, ostream &out, ostream &err) :
60 m_validSyncProps(SyncConfig::getRegistry()),
61 m_validSourceProps(SyncSourceConfig::getRegistry())
64 Cmdline::Cmdline(const vector<string> &args, ostream &out, ostream &err) :
68 m_validSyncProps(SyncConfig::getRegistry()),
69 m_validSourceProps(SyncSourceConfig::getRegistry())
72 m_argvArray.reset(new const char *[args.size()]);
73 for(int i = 0; i < m_argc; i++) {
74 m_argvArray[i] = m_args[i].c_str();
76 m_argv = m_argvArray.get();
81 vector<string> parsed;
85 bool Cmdline::parse(vector<string> &parsed)
89 parsed.push_back(m_argv[0]);
93 // All command line options which ask for a specific operation,
94 // like --restore, --print-config, ... Used to detect conflicting
96 vector<string> operations;
100 while (opt < m_argc) {
101 parsed.push_back(m_argv[opt]);
102 if (m_argv[opt][0] != '-') {
105 if (boost::iequals(m_argv[opt], "--sync") ||
106 boost::iequals(m_argv[opt], "-s")) {
109 string cmdopt(m_argv[opt - 1]);
110 if (!parseProp(m_validSourceProps, m_sourceProps,
111 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt],
112 SyncSourceConfig::m_sourcePropSync.getName().c_str())) {
115 parsed.push_back(m_argv[opt]);
117 // disable requirement to add --run explicitly in order to
118 // be compatible with traditional command lines
120 } else if(boost::iequals(m_argv[opt], "--sync-property") ||
121 boost::iequals(m_argv[opt], "-y")) {
123 if (!parseProp(m_validSyncProps, m_syncProps,
124 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
127 parsed.push_back(m_argv[opt]);
128 } else if(boost::iequals(m_argv[opt], "--source-property") ||
129 boost::iequals(m_argv[opt], "-z")) {
131 if (!parseProp(m_validSourceProps, m_sourceProps,
132 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
135 parsed.push_back(m_argv[opt]);
136 }else if(boost::iequals(m_argv[opt], "--template") ||
137 boost::iequals(m_argv[opt], "-l")) {
140 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
143 parsed.push_back(m_argv[opt]);
144 m_template = m_argv[opt];
146 string temp = boost::trim_copy (m_template);
147 if (temp.find ("?") == 0){
148 m_printTemplates = true;
150 m_template = temp.substr (1);
152 } else if(boost::iequals(m_argv[opt], "--print-servers") ||
153 boost::iequals(m_argv[opt], "--print-peers") ||
154 boost::iequals(m_argv[opt], "--print-configs")) {
155 operations.push_back(m_argv[opt]);
156 m_printServers = true;
157 } else if(boost::iequals(m_argv[opt], "--print-config") ||
158 boost::iequals(m_argv[opt], "-p")) {
159 operations.push_back(m_argv[opt]);
160 m_printConfig = true;
161 } else if(boost::iequals(m_argv[opt], "--print-sessions")) {
162 operations.push_back(m_argv[opt]);
163 m_printSessions = true;
164 } else if(boost::iequals(m_argv[opt], "--configure") ||
165 boost::iequals(m_argv[opt], "-c")) {
166 operations.push_back(m_argv[opt]);
168 } else if(boost::iequals(m_argv[opt], "--remove")) {
169 operations.push_back(m_argv[opt]);
171 } else if(boost::iequals(m_argv[opt], "--run") ||
172 boost::iequals(m_argv[opt], "-r")) {
173 operations.push_back(m_argv[opt]);
175 } else if(boost::iequals(m_argv[opt], "--restore")) {
176 operations.push_back(m_argv[opt]);
179 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
182 m_restore = m_argv[opt];
183 if (m_restore.empty()) {
184 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
187 //if can't convert it successfully, it's an invalid path
188 if (!relToAbs(m_restore)) {
189 usage(true, string("parameter '") + m_restore + "' for " + cmdOpt(m_argv[opt - 1]) + " must be log directory");
192 parsed.push_back(m_restore);
193 } else if(boost::iequals(m_argv[opt], "--before")) {
195 } else if(boost::iequals(m_argv[opt], "--after")) {
197 } else if (boost::iequals(m_argv[opt], "--print-items")) {
198 operations.push_back(m_argv[opt]);
199 m_printItems = m_accessItems = true;
200 } else if ((boost::iequals(m_argv[opt], "--export") && (m_export = true)) ||
201 (boost::iequals(m_argv[opt], "--import") && (m_import = true)) ||
202 (boost::iequals(m_argv[opt], "--update") && (m_update = true))) {
203 operations.push_back(m_argv[opt]);
204 m_accessItems = true;
206 if (opt >= m_argc || !m_argv[opt][0]) {
207 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
210 m_itemPath = m_argv[opt];
211 if (m_itemPath != "-") {
213 splitPath(m_itemPath, dir, file);
217 if (!relToAbs(dir)) {
218 SyncContext::throwError(dir, errno);
220 m_itemPath = dir + "/" + file;
222 parsed.push_back(m_itemPath);
223 } else if (boost::iequals(m_argv[opt], "--delimiter")) {
226 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
229 m_delimiter = m_argv[opt];
230 parsed.push_back(m_delimiter);
231 } else if (boost::iequals(m_argv[opt], "--delete-items")) {
232 operations.push_back(m_argv[opt]);
233 m_deleteItems = m_accessItems = true;
234 } else if(boost::iequals(m_argv[opt], "--dry-run")) {
236 } else if(boost::iequals(m_argv[opt], "--migrate")) {
237 operations.push_back(m_argv[opt]);
239 } else if(boost::iequals(m_argv[opt], "--status") ||
240 boost::iequals(m_argv[opt], "-t")) {
241 operations.push_back(m_argv[opt]);
243 } else if(boost::iequals(m_argv[opt], "--quiet") ||
244 boost::iequals(m_argv[opt], "-q")) {
246 } else if(boost::iequals(m_argv[opt], "--help") ||
247 boost::iequals(m_argv[opt], "-h")) {
249 } else if(boost::iequals(m_argv[opt], "--version")) {
250 operations.push_back(m_argv[opt]);
252 } else if (parseBool(opt, "--keyring", "-k", true, m_keyring, ok)) {
256 } else if (parseBool(opt, "--daemon", NULL, true, m_useDaemon, ok)) {
260 } else if(boost::iequals(m_argv[opt], "--monitor")||
261 boost::iequals(m_argv[opt], "-m")) {
262 operations.push_back(m_argv[opt]);
265 usage(false, string(m_argv[opt]) + ": unknown parameter");
272 m_server = m_argv[opt++];
273 while (opt < m_argc) {
274 parsed.push_back(m_argv[opt]);
275 if (m_sources.empty() ||
277 m_sources.insert(m_argv[opt++]);
279 // first additional parameter was source, rest are luids
280 m_luids.push_back(CmdlineLUID::toLUID(m_argv[opt++]));
285 // check whether we have conflicting operations requested by user
286 if (operations.size() > 1) {
287 usage(false, boost::join(operations, " ") + ": mutually exclusive operations");
291 // common sanity checking for item listing/import/export/update
293 if (m_server.empty()) {
294 usage(false, operations[0] + ": needs configuration name");
297 if (m_sources.size() == 0) {
298 usage(false, operations[0] + ": needs source name");
301 if ((m_import || m_update) && m_dryrun) {
302 usage(false, operations[0] + ": --dry-run not supported");
310 bool Cmdline::parseBool(int opt, const char *longName, const char *shortName,
311 bool def, Bool &value,
314 string option = m_argv[opt];
316 size_t pos = option.find('=');
317 if (pos != option.npos) {
318 param = option.substr(pos + 1);
321 if ((longName && boost::iequals(option, longName)) ||
322 (shortName && boost::iequals(option, shortName))) {
326 } else if (boost::iequals(param, "t") ||
327 boost::iequals(param, "1") ||
328 boost::iequals(param, "true") ||
329 boost::iequals(param, "yes")) {
331 } else if (boost::iequals(param, "f") ||
332 boost::iequals(param, "0") ||
333 boost::iequals(param, "false") ||
334 boost::iequals(param, "no")) {
337 usage(true, string("parameter in '") + m_argv[opt] + "' must be 1/t/true/yes or 0/f/false/no");
343 // keep searching for match
348 bool Cmdline::isSync()
350 // make sure command line arguments really try to run sync
351 if (m_usage || m_version ||
352 m_printServers || boost::trim_copy(m_server) == "?" ||
353 m_printTemplates || m_dontrun ||
354 m_argc == 1 || (m_useDaemon.wasSet() && m_argc == 2) ||
355 m_printConfig || m_remove ||
356 (m_server == "" && m_argc > 1) ||
357 m_configure || m_migrate ||
358 m_status || m_printSessions ||
359 !m_restore.empty() ||
362 (!m_run && (m_syncProps.size() || m_sourceProps.size()))) {
369 bool Cmdline::dontRun() const
371 // this mimics the if() checks in run()
372 if (m_usage || m_version ||
373 m_printServers || boost::trim_copy(m_server) == "?" ||
382 * Finds first instance of delimiter string in other string. In
383 * addition, it treats "\n\n" in a special way: that delimiter also
386 class FindDelimiter {
387 const string m_delimiter;
389 FindDelimiter(const string &delimiter) :
390 m_delimiter(delimiter)
392 boost::iterator_range<string::iterator> operator()(string::iterator begin,
393 string::iterator end)
395 if (m_delimiter == "\n\n") {
396 // match both "\n\n" and "\n\r\n"
397 while (end - begin >= 2) {
398 if (*begin == '\n') {
399 if (*(begin + 1) == '\n') {
400 return boost::iterator_range<string::iterator>(begin, begin + 2);
401 } else if (end - begin >= 3 &&
402 *(begin + 1) == '\r' &&
403 *(begin + 2) == '\n') {
404 return boost::iterator_range<string::iterator>(begin, begin + 3);
409 return boost::iterator_range<string::iterator>(end, end);
411 boost::sub_range<string> range(begin, end);
412 return boost::find_first(range, m_delimiter);
417 bool Cmdline::run() {
418 // --dry-run is only supported by some operations.
419 // Be very strict about it and make sure it is off in all
420 // potentially harmful operations, otherwise users might
421 // expect it to have an effect when it doesn't.
425 } else if (m_version) {
426 printf("SyncEvolution %s\n", VERSION);
427 printf("%s", EDSAbiWrapperInfo());
428 printf("%s", SyncSource::backendsInfo().c_str());
429 } else if (m_printServers || boost::trim_copy(m_server) == "?") {
430 dumpConfigs("Configured servers:",
431 SyncConfig::getConfigs());
432 } else if (m_printTemplates) {
433 SyncConfig::DeviceList devices;
434 if (m_template.empty()){
435 dumpConfigTemplates("Available configuration templates:",
436 SyncConfig::getPeerTemplates(devices), false);
438 //limiting at templates for syncml clients only.
439 devices.push_back (SyncConfig::DeviceDescription("", m_template, SyncConfig::MATCH_FOR_SERVER_MODE));
440 dumpConfigTemplates("Available configuration templates:",
441 SyncConfig::matchPeerTemplates(devices), true);
443 } else if (m_dontrun) {
444 // user asked for information
445 } else if (m_argc == 1 || (m_useDaemon.wasSet() && m_argc == 2)) {
446 // no parameters: list databases and short usage
447 const SourceRegistry ®istry(SyncSource::getSourceRegistry());
448 boost::shared_ptr<FilterConfigNode> sharedNode(new VolatileConfigNode());
449 boost::shared_ptr<FilterConfigNode> configNode(new VolatileConfigNode());
450 boost::shared_ptr<FilterConfigNode> hiddenNode(new VolatileConfigNode());
451 boost::shared_ptr<FilterConfigNode> trackingNode(new VolatileConfigNode());
452 boost::shared_ptr<FilterConfigNode> serverNode(new VolatileConfigNode());
453 SyncSourceNodes nodes(true, sharedNode, configNode, hiddenNode, trackingNode, serverNode, "");
454 SyncSourceParams params("list", nodes);
456 BOOST_FOREACH(const RegisterSyncSource *source, registry) {
457 BOOST_FOREACH(const Values::value_type &alias, source->m_typeValues) {
458 if (!alias.empty() && source->m_enabled) {
459 configNode->setProperty("type", *alias.begin());
460 auto_ptr<SyncSource> source(SyncSource::createSource(params, false));
461 if (source.get() != NULL) {
462 listSources(*source, boost::join(alias, " = "));
470 } else if (m_printConfig) {
471 boost::shared_ptr<SyncConfig> config;
472 ConfigProps syncFilter;
473 SourceFilters_t sourceFilters;
475 if (m_template.empty()) {
476 if (m_server.empty()) {
477 m_err << "ERROR: --print-config requires either a --template or a server name." << endl;
480 config.reset(new SyncConfig(m_server));
481 if (!config->exists()) {
482 m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
486 syncFilter = m_syncProps;
487 sourceFilters[""] = m_sourceProps;
489 string peer, context;
490 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_template), peer, context);
492 config = SyncConfig::createPeerTemplate(peer);
494 m_err << "ERROR: no configuration template for '" << m_template << "' available." << endl;
498 getFilters(context, syncFilter, sourceFilters);
501 // determine whether we dump a peer or a context
502 int flags = DUMP_PROPS_NORMAL;
503 string peer, context;
504 SyncConfig::splitConfigString(config->getConfigName(), peer, context);
506 flags |= HIDE_PER_PEER;
510 if (m_sources.empty() ||
511 m_sources.find("main") != m_sources.end()) {
512 boost::shared_ptr<FilterConfigNode> syncProps(config->getProperties());
513 syncProps->setFilter(syncFilter);
514 dumpProperties(*syncProps, config->getRegistry(), flags);
517 list<string> sources = config->getSyncSources();
519 BOOST_FOREACH(const string &name, sources) {
520 if (m_sources.empty() ||
521 m_sources.find(name) != m_sources.end()) {
522 m_out << endl << "[" << name << "]" << endl;
523 SyncSourceNodes nodes = config->getSyncSourceNodes(name);
524 boost::shared_ptr<FilterConfigNode> sourceProps = nodes.getProperties();
525 SourceFilters_t::const_iterator it = sourceFilters.find(name);
526 if (it != sourceFilters.end()) {
527 sourceProps->setFilter(it->second);
529 sourceProps->setFilter(sourceFilters[""]);
531 dumpProperties(*sourceProps, SyncSourceConfig::getRegistry(),
532 flags | ((name != *(--sources.end())) ? HIDE_LEGEND : DUMP_PROPS_NORMAL));
535 } else if (m_server == "" && m_argc > 1) {
536 // Options given, but no server - not sure what the user wanted?!
537 usage(true, "server name missing");
539 } else if (m_configure || m_migrate) {
541 SyncContext::throwError("--dry-run not supported for configuration changes");
544 #ifndef USE_GNOME_KEYRING
545 m_err << "Error: this syncevolution binary was compiled without support for storing "
546 "passwords in a keyring. Either store passwords in your configuration "
547 "files or enter them interactively on each program run." << endl;
552 bool fromScratch = false;
553 string peer, context;
554 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_server), peer, context);
559 // True if the target configuration is a context like @default
560 // or @foobar. Relevant in several places in the following
562 bool configureContext;
564 string peer, context;
565 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_server), peer, context);
566 configureContext = peer.empty();
570 // Both config changes and migration are implemented as copying from
571 // another config (template resp. old one). Migration also moves
573 boost::shared_ptr<SyncConfig> from;
575 string oldContext = context;
576 from.reset(new SyncConfig(m_server));
577 if (!from->exists()) {
578 // for migration into a different context, search for config without context
580 from.reset(new SyncConfig(peer));
581 if (!from->exists()) {
582 m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
588 string oldRoot = from->getRootPath();
592 ostringstream newsuffix;
595 newsuffix << "." << counter;
597 suffix = newsuffix.str();
598 newname = oldRoot + suffix;
599 if (!rename(oldRoot.c_str(),
602 } else if (errno != EEXIST && errno != ENOTEMPTY) {
603 m_err << "ERROR: renaming " << oldRoot << " to " <<
604 newname << ": " << strerror(errno) << endl;
610 from.reset(new SyncConfig(peer + suffix +
611 (oldContext.empty() ? "" : "@") +
614 from.reset(new SyncConfig(m_server));
615 if (!from->exists()) {
616 // creating from scratch, look for template
618 string configTemplate;
619 if (m_template.empty()) {
620 // template is the peer name
621 configTemplate = m_server;
622 if (configureContext) {
623 // configuring a context, template doesn't matter =>
624 // use default "SyncEvolution" template
626 peer = "SyncEvolution";
629 // Template is specified explicitly. It must not contain a context,
630 // because the context comes from the config name.
631 configTemplate = m_template;
632 if (SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(configTemplate), peer, context)) {
633 m_err << "ERROR: template " << configTemplate << " must not specify a context." << endl;
637 SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_server), tmp, context);
639 from = SyncConfig::createPeerTemplate(peer);
641 m_err << "ERROR: no configuration template for '" << configTemplate << "' available." << endl;
642 dumpConfigTemplates("Available configuration templates:",
643 SyncConfig::getPeerTemplates(SyncConfig::DeviceList()));
647 if (!from->getPeerIsClient()) {
648 // Templates no longer contain these strings, because
649 // GUIs would have to localize them. For configs created
650 // via the command line, the extra hint that these
651 // properties need to be set is useful, so set these
652 // strings here. They'll get copied into the new
653 // config only if no other value was given on the
655 if (!from->getUsername()[0]) {
656 from->setUsername("your SyncML server account name");
658 if (!from->getPassword()[0]) {
659 from->setPassword("your SyncML server password");
662 // uncomment SyncURL, so that it can be shown by
664 if (from->getSyncURL().size() == 0) {
665 from->setSyncURL ("input your peer address here");
671 // Apply config changes on-the-fly. Regardless what we do
672 // (changing an existing config, migrating, creating from
673 // a template), existing shared properties in the desired
674 // context must be preserved unless explicitly overwritten.
675 // Therefore read those, update with command line properties,
676 // then set as filter.
677 ConfigProps syncFilter;
678 SourceFilters_t sourceFilters;
679 getFilters(context, syncFilter, sourceFilters);
680 from->setConfigFilter(true, "", syncFilter);
681 BOOST_FOREACH(const SourceFilters_t::value_type &entry, sourceFilters) {
682 from->setConfigFilter(false, entry.first, entry.second);
685 // Write into the requested configuration, creating it if necessary.
687 // Which sources are configured is determined as follows:
688 // - all sources in the template by default, except when
689 // - sources are listed explicitly, and either
690 // - updating an existing config or
691 // - configuring a context.
693 // This implies that when configuring a peer from scratch, all
694 // sources in the template will be created, with command line
695 // source properties applied to all of them. This might not be
696 // what we want, but because this is how we have done it
697 // traditionally, I keep this behavior for now.
698 set<string> *sources = NULL;
699 if (!m_sources.empty() &&
700 (!fromScratch || configureContext)) {
701 sources = &m_sources;
703 boost::shared_ptr<SyncContext> to(createSyncClient());
704 to->copy(*from, sources);
706 // Sources are active now according to the server default.
707 // Disable all sources not selected by user (if any selected)
708 // and those which have no database.
710 list<string> configuredSources = to->getSyncSources();
711 set<string> sources = m_sources;
713 BOOST_FOREACH(const string &source, configuredSources) {
714 boost::shared_ptr<PersistentSyncSourceConfig> sourceConfig(to->getSyncSourceConfig(source));
716 set<string>::iterator entry = sources.find(source);
717 bool selected = entry != sources.end();
719 if (!m_sources.empty() &&
721 disable = "not selected";
723 if (entry != sources.end()) {
724 // The command line parameter matched a valid source.
725 // All entries left afterwards must have been typos.
726 sources.erase(entry);
729 // check whether the sync source works
730 SyncSourceParams params("list", to->getSyncSourceNodes(source));
731 auto_ptr<SyncSource> syncSource(SyncSource::createSource(params, false, to.get()));
732 if (syncSource.get() == NULL) {
733 disable = "no backend available";
736 SyncSource::Databases databases = syncSource->getDatabases();
737 if (databases.empty()) {
738 disable = "no database to synchronize";
741 disable = "backend failed";
746 // Do sanity checking of source (can it be enabled?),
747 // but only set the sync mode if configuring a peer.
748 // A context-only config doesn't have the "sync"
751 if (!disable.empty()) {
752 // abort if the user explicitly asked for the sync source
753 // and it cannot be enabled, otherwise disable it silently
755 SyncContext::throwError(source + ": " + disable);
757 syncMode = "disabled";
758 } else if (selected) {
759 // user absolutely wants it: enable even if off by default
760 FilterConfigNode::ConfigFilter::const_iterator sync =
761 m_sourceProps.find(SyncSourceConfig::m_sourcePropSync.getName());
762 syncMode = sync == m_sourceProps.end() ? "two-way" : sync->second;
764 if (!syncMode.empty() &&
766 sourceConfig->setSync(syncMode);
770 if (!sources.empty()) {
771 SyncContext::throwError(string("no such source(s): ") + boost::join(sources, " "));
774 // give a change to do something before flushing configs to files
777 // done, now write it
778 m_configModified = true;
781 // also copy .synthesis dir?
783 string fromDir, toDir;
784 fromDir = from->getRootPath() + "/.synthesis";
785 toDir = to->getRootPath() + "/.synthesis";
786 if (isDir(fromDir)) {
787 cp_r(fromDir, toDir);
790 } else if (m_remove) {
792 SyncContext::throwError("--dry-run not supported for removing configurations");
795 // extra sanity check
796 if (!m_sources.empty() ||
797 !m_syncProps.empty() ||
798 !m_sourceProps.empty()) {
799 usage(true, "too many parameters for --remove");
802 boost::shared_ptr<SyncConfig> config;
803 config.reset(new SyncConfig(m_server));
804 if (!config->exists()) {
805 SyncContext::throwError(string("no such configuration: ") + m_server);
808 m_configModified = true;
811 } else if (m_accessItems) {
812 // need access to specific source
813 boost::shared_ptr<SyncContext> context;
814 context.reset(createSyncClient());
815 context->setOutput(&m_out);
818 context->setConfigFilter(true, "", m_syncProps);
819 context->setConfigFilter(false, "", m_sourceProps);
821 string sourceName = *m_sources.begin();
822 SyncSourceNodes sourceNodes = context->getSyncSourceNodesNoTracking(sourceName);
823 SyncSourceParams params(sourceName, sourceNodes);
824 cxxptr<SyncSource> source(SyncSource::createSource(params, true));
826 sysync::TSyError err;
827 #define CHECK_ERROR(_op) if (err) { SE_THROW_EXCEPTION_STATUS(StatusException, string(source->getName()) + ": " + (_op), SyncMLStatus(err)); }
830 const SyncSource::Operations &ops = source->getOperations();
832 SyncSourceLogging *logging = dynamic_cast<SyncSourceLogging *>(source.get());
833 if (!ops.m_startDataRead ||
834 !ops.m_readNextItem) {
835 source->throwError("reading items not supported");
837 err = ops.m_startDataRead("", "");
838 CHECK_ERROR("reading items");
840 readLUIDs(source, luids);
841 BOOST_FOREACH(string &luid, luids) {
844 description = logging->getDescription(luid);
845 if (!description.empty()) {
846 description.insert(0, ": ");
849 m_out << CmdlineLUID::fromLUID(luid) << description << std::endl;
851 } else if (m_deleteItems) {
852 if (!ops.m_deleteItem) {
853 source->throwError("deleting items not supported");
856 bool deleteAll = std::find(m_luids.begin(), m_luids.end(), "*") != m_luids.end();
857 err = ops.m_startDataRead("", "");
858 CHECK_ERROR("reading items");
860 readLUIDs(source, luids);
864 if (ops.m_endDataRead) {
865 err = ops.m_endDataRead();
866 CHECK_ERROR("stop reading items");
868 if (ops.m_startDataWrite) {
869 err = ops.m_startDataWrite();
870 CHECK_ERROR("writing items");
872 BOOST_FOREACH(const string &luid, luids) {
873 sysync::ItemIDType id;
874 id.item = (char *)luid.c_str();
875 err = ops.m_deleteItem(&id);
876 CHECK_ERROR("deleting item");
879 err = ops.m_endDataWrite(true, &token);
883 CHECK_ERROR("stop writing items");
885 SyncSourceRaw *raw = dynamic_cast<SyncSourceRaw *>(source.get());
887 source->throwError("reading/writing items directly not supported");
889 if (m_import || m_update) {
890 err = ops.m_startDataRead("", "");
891 CHECK_ERROR("reading items");
892 if (ops.m_endDataRead) {
893 err = ops.m_endDataRead();
894 CHECK_ERROR("stop reading items");
896 if (ops.m_startDataWrite) {
897 err = ops.m_startDataWrite();
898 CHECK_ERROR("writing items");
901 cxxptr<ifstream> inFile;
902 if (m_itemPath =="-" ||
903 !isDir(m_itemPath)) {
906 if (m_itemPath == "-") {
907 context->readStdin(content);
908 } else if (!ReadFile(m_itemPath, content)) {
909 SyncContext::throwError(m_itemPath, errno);
911 if (m_delimiter == "none") {
913 if (m_luids.size() != 1) {
914 SyncContext::throwError("need exactly one LUID parameter");
916 luid = *m_luids.begin();
920 << insertItem(raw, luid, content).getEncoded()
923 typedef boost::split_iterator<string::iterator> string_split_iterator;
925 FindDelimiter finder(m_delimiter);
927 // when updating, check number of luids in advance
929 unsigned long total = 0;
930 for (string_split_iterator it =
931 boost::make_split_iterator(content, finder);
932 it != string_split_iterator();
936 if (total != m_luids.size()) {
937 SyncContext::throwError(StringPrintf("%lu items != %lu luids, must match => aborting",
938 total, (unsigned long)m_luids.size()));
941 list<string>::const_iterator luidit = m_luids.begin();
942 for (string_split_iterator it =
943 boost::make_split_iterator(content, finder);
944 it != string_split_iterator();
946 m_out << "#" << count << ": ";
949 if (luidit == m_luids.end()) {
951 SyncContext::throwError("internal error, not enough luids");
956 m_out << insertItem(raw,
958 string(it->begin(), it->end())).getEncoded()
964 ReadDir dir(m_itemPath);
966 BOOST_FOREACH(const string &entry, dir) {
968 string path = m_itemPath + "/" + entry;
969 m_out << count << ": " << entry << ": ";
970 if (!ReadFile(path, content)) {
971 SyncContext::throwError(path, errno);
973 m_out << insertItem(raw, "", content).getEncoded() << endl;
977 err = ops.m_endDataWrite(true, &token);
981 CHECK_ERROR("stop writing items");
982 } else if (m_export) {
983 err = ops.m_startDataRead("", "");
984 CHECK_ERROR("reading items");
987 cxxptr<ofstream> outFile;
988 if (m_itemPath == "-") {
990 } else if(!isDir(m_itemPath)) {
991 outFile.set(new ofstream(m_itemPath.c_str()));
994 if (m_luids.empty()) {
995 readLUIDs(source, m_luids);
997 bool haveItem = false; // have written one item
998 bool haveNewline = false; // that item had a newline at the end
1000 BOOST_FOREACH(const string &luid, m_luids) {
1002 raw->readItemRaw(luid, item);
1004 // write into directory
1005 string fullPath = m_itemPath + "/" + luid;
1006 ofstream file((m_itemPath + "/" + luid).c_str());
1010 SyncContext::throwError(fullPath, errno);
1014 if (m_delimiter.size() > 1 &&
1016 m_delimiter[0] == '\n') {
1017 // already wrote initial newline, skip it
1018 *out << m_delimiter.substr(1);
1020 *out << m_delimiter;
1024 haveNewline = !item.empty() && item[item.size() - 1] == '\n';
1029 // ensure that we start following output on new line
1030 if (m_itemPath == "-" && haveItem && !haveNewline) {
1037 if (outFile->bad()) {
1038 SyncContext::throwError(m_itemPath, errno);
1045 std::set<std::string> unmatchedSources;
1046 boost::shared_ptr<SyncContext> context;
1047 context.reset(createSyncClient());
1048 context->setQuiet(m_quiet);
1049 context->setDryRun(m_dryrun);
1050 context->setConfigFilter(true, "", m_syncProps);
1051 context->setOutput(&m_out);
1052 if (m_sources.empty()) {
1053 if (m_sourceProps.empty()) {
1054 // empty source list, empty source filter => run with
1055 // existing configuration without filtering it
1057 // Special semantic of 'no source selected': apply
1058 // filter only to sources which are
1059 // *active*. Configuration of inactive sources is left
1060 // unchanged. This way we don't activate sync sources
1061 // accidentally when the sync mode is modified
1063 BOOST_FOREACH(const std::string &source,
1064 context->getSyncSources()) {
1065 boost::shared_ptr<PersistentSyncSourceConfig> source_config =
1066 context->getSyncSourceConfig(source);
1067 if (strcmp(source_config->getSync(), "disabled")) {
1068 context->setConfigFilter(false, source, m_sourceProps);
1073 // apply (possibly empty) source filter to selected sources
1074 BOOST_FOREACH(const std::string &source,
1076 boost::shared_ptr<PersistentSyncSourceConfig> source_config =
1077 context->getSyncSourceConfig(source);
1078 if (!source_config || !source_config->exists()) {
1079 // invalid source name in m_sources, remember and
1080 // report this below
1081 unmatchedSources.insert(source);
1082 } else if (m_sourceProps.find(SyncSourceConfig::m_sourcePropSync.getName()) ==
1083 m_sourceProps.end()) {
1084 // Sync mode is not set, must override the
1085 // "sync=disabled" set below with the original
1086 // sync mode for the source or (if that is also
1087 // "disabled") with "two-way". The latter is part
1088 // of the command line semantic that listing a
1089 // source activates it.
1090 FilterConfigNode::ConfigFilter filter = m_sourceProps;
1091 string sync = source_config->getSync();
1092 filter[SyncSourceConfig::m_sourcePropSync.getName()] =
1093 sync == "disabled" ? "two-way" : sync;
1094 context->setConfigFilter(false, source, filter);
1096 // sync mode is set, can use m_sourceProps
1097 // directly to apply it
1098 context->setConfigFilter(false, source, m_sourceProps);
1102 // temporarily disable the rest
1103 FilterConfigNode::ConfigFilter disabled;
1104 disabled[SyncSourceConfig::m_sourcePropSync.getName()] = "disabled";
1105 context->setConfigFilter(false, "", disabled);
1108 // check whether there were any sources specified which do not exist
1109 if (unmatchedSources.size()) {
1110 context->throwError(string("no such source(s): ") + boost::join(unmatchedSources, " "));
1115 } else if (m_printSessions) {
1116 vector<string> dirs;
1117 context->getSessions(dirs);
1119 BOOST_FOREACH(const string &dir, dirs) {
1122 } else if(!m_quiet) {
1125 m_out << dir << endl;
1128 context->readSessionInfo(dir, report);
1132 } else if (!m_restore.empty()) {
1133 // sanity checks: either --after or --before must be given, sources must be selected
1134 if ((!m_after && !m_before) ||
1135 (m_after && m_before)) {
1136 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)");
1139 if (m_sources.empty()) {
1140 usage(false, "Sources must be selected explicitly for --restore to prevent accidental restore.");
1143 context->restore(m_restore,
1145 SyncContext::DATABASE_AFTER_SYNC :
1146 SyncContext::DATABASE_BEFORE_SYNC);
1149 SyncContext::throwError("--dry-run not supported for running a synchronization");
1152 // safety catch: if props are given, then --run
1155 (m_syncProps.size() || m_sourceProps.size())) {
1156 usage(false, "Properties specified, but neither '--configure' nor '--run' - what did you want?");
1160 return (context->sync() == STATUS_OK);
1167 void Cmdline::readLUIDs(SyncSource *source, list<string> &luids)
1169 const SyncSource::Operations &ops = source->getOperations();
1170 sysync::ItemIDType id;
1171 sysync::sInt32 status;
1172 sysync::TSyError err = ops.m_readNextItem(&id, &status, true);
1173 CHECK_ERROR("next item");
1174 while (status != sysync::ReadNextItem_EOF) {
1175 luids.push_back(id.item);
1176 err = ops.m_readNextItem(&id, &status, false);
1177 CHECK_ERROR("next item");
1181 CmdlineLUID Cmdline::insertItem(SyncSourceRaw *source, const string &luid, const string &data)
1183 SyncSourceRaw::InsertItemResult res = source->insertItemRaw(luid, data);
1185 cluid.setLUID(res.m_luid);
1189 string Cmdline::cmdOpt(const char *opt, const char *param)
1201 bool Cmdline::parseProp(const ConfigPropertyRegistry &validProps,
1202 FilterConfigNode::ConfigFilter &props,
1205 const char *propname)
1208 usage(true, string("missing parameter for ") + cmdOpt(opt, param));
1210 } else if (boost::trim_copy(string(param)) == "?") {
1213 return listPropValues(validProps, propname, opt);
1215 return listProperties(validProps, opt);
1224 const char *equal = strchr(param, '=');
1226 usage(true, string("the '=<value>' part is missing in: ") + cmdOpt(opt, param));
1229 propstr.assign(param, equal - param);
1230 paramstr.assign(equal + 1);
1233 boost::trim(propstr);
1234 boost::trim_left(paramstr);
1236 if (boost::trim_copy(paramstr) == "?") {
1238 return listPropValues(validProps, propstr, cmdOpt(opt, param));
1240 const ConfigProperty *prop = validProps.find(propstr);
1242 m_err << "ERROR: " << cmdOpt(opt, param) << ": no such property" << endl;
1246 if (!prop->checkValue(paramstr, error)) {
1247 m_err << "ERROR: " << cmdOpt(opt, param) << ": " << error << endl;
1250 props[propstr] = paramstr;
1258 bool Cmdline::listPropValues(const ConfigPropertyRegistry &validProps,
1259 const string &propName,
1262 const ConfigProperty *prop = validProps.find(propName);
1264 m_err << "ERROR: "<< opt << ": no such property" << endl;
1267 m_out << opt << endl;
1268 string comment = prop->getComment();
1270 if (comment != "") {
1271 list<string> commentLines;
1272 ConfigProperty::splitComment(comment, commentLines);
1273 BOOST_FOREACH(const string &line, commentLines) {
1274 m_out << " " << line << endl;
1277 m_out << " no documentation available" << endl;
1283 bool Cmdline::listProperties(const ConfigPropertyRegistry &validProps,
1286 // The first of several related properties has a comment.
1287 // Remember that comment and print it as late as possible,
1288 // that way related properties preceed their comment.
1290 BOOST_FOREACH(const ConfigProperty *prop, validProps) {
1291 if (!prop->isHidden()) {
1292 string newComment = prop->getComment();
1294 if (newComment != "") {
1295 if (!comment.empty()) {
1296 dumpComment(m_out, " ", comment);
1299 comment = newComment;
1301 m_out << prop->getName() << ":" << endl;
1304 dumpComment(m_out, " ", comment);
1308 void Cmdline::getFilters(const string &context,
1309 ConfigProps &syncFilter,
1310 map<string, ConfigProps> &sourceFilters)
1312 // Read from context. If it does not exist, we simply set no properties
1313 // as filter. Previously there was a check for existance, but that was
1314 // flawed because it ignored the global property "defaultPeer".
1315 boost::shared_ptr<SyncConfig> shared(new SyncConfig(string("@") + context));
1316 shared->getProperties()->readProperties(syncFilter);
1317 BOOST_FOREACH(StringPair entry, m_syncProps) {
1318 syncFilter[entry.first] = entry.second;
1321 BOOST_FOREACH(std::string source, shared->getSyncSources()) {
1322 SyncSourceNodes nodes = shared->getSyncSourceNodes(source, "");
1323 ConfigProps &props = sourceFilters[source];
1324 nodes.getProperties()->readProperties(props);
1326 // Special case "type" property: the value in the context
1327 // is not preserved. Every new peer must ensure that
1328 // its own value is compatible (= same backend) with
1330 props.erase("type");
1332 BOOST_FOREACH(StringPair entry, m_sourceProps) {
1333 props[entry.first] = entry.second;
1336 sourceFilters[""] = m_sourceProps;
1339 static void findPeerProps(FilterConfigNode::ConfigFilter &filter,
1340 ConfigPropertyRegistry ®istry,
1341 list<string> &peerProps)
1343 BOOST_FOREACH(StringPair entry, filter) {
1344 const ConfigProperty *prop = registry.find(entry.first);
1346 prop->getSharing() == ConfigProperty::NO_SHARING &&
1347 !(prop->getFlags() & ConfigProperty::SHARED_AND_UNSHARED)) {
1348 peerProps.push_back(entry.first);
1353 void Cmdline::checkForPeerProps()
1355 list<string> peerProps;
1357 findPeerProps(m_syncProps, SyncConfig::getRegistry(), peerProps);
1358 findPeerProps(m_sourceProps, SyncSourceConfig::getRegistry(), peerProps);
1359 if (!peerProps.empty()) {
1360 SyncContext::throwError(string("per-peer (unshared) properties not allowed: ") +
1361 boost::join(peerProps, ", "));
1365 void Cmdline::listSources(SyncSource &syncSource, const string &header)
1367 m_out << header << ":\n";
1368 SyncSource::Databases databases = syncSource.getDatabases();
1370 BOOST_FOREACH(const SyncSource::Database &database, databases) {
1371 m_out << " " << database.m_name << " (" << database.m_uri << ")";
1372 if (database.m_isDefault) {
1373 m_out << " <default>";
1379 void Cmdline::dumpConfigs(const string &preamble,
1380 const SyncConfig::ConfigList &servers)
1382 m_out << preamble << endl;
1383 BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server,servers) {
1384 m_out << " " << server.first << " = " << server.second <<endl;
1386 if (!servers.size()) {
1387 m_out << " none" << endl;
1391 void Cmdline::dumpConfigTemplates(const string &preamble,
1392 const SyncConfig::TemplateList &templates,
1395 m_out << preamble << endl;
1396 m_out << " " << "template name" << " = " << "template description";
1398 m_out << " " << "matching score in percent (100% = exact match)";
1402 BOOST_FOREACH(const SyncConfig::TemplateList::value_type server,templates) {
1403 m_out << " " << server->m_templateId << " = " << server->m_description;
1405 m_out << " " << server->m_rank *20 << "%";
1409 if (!templates.size()) {
1410 m_out << " none" << endl;
1414 void Cmdline::dumpProperties(const ConfigNode &configuredProps,
1415 const ConfigPropertyRegistry &allProps,
1418 list<string> perPeer, perContext, global;
1420 BOOST_FOREACH(const ConfigProperty *prop, allProps) {
1421 if (prop->isHidden() ||
1422 ((flags & HIDE_PER_PEER) &&
1423 prop->getSharing() == ConfigProperty::NO_SHARING &&
1424 !(prop->getFlags() & ConfigProperty::SHARED_AND_UNSHARED))) {
1428 string comment = prop->getComment();
1429 if (!comment.empty()) {
1431 dumpComment(m_out, "# ", comment);
1435 prop->getProperty(configuredProps, &isDefault);
1439 m_out << prop->getName() << " = " << prop->getProperty(configuredProps) << endl;
1441 list<string> *type = NULL;
1442 switch (prop->getSharing()) {
1443 case ConfigProperty::GLOBAL_SHARING:
1446 case ConfigProperty::SOURCE_SET_SHARING:
1449 case ConfigProperty::NO_SHARING:
1454 type->push_back(prop->getName());
1458 if (!m_quiet && !(flags & HIDE_LEGEND)) {
1459 if (!perPeer.empty() ||
1460 !perContext.empty() ||
1464 if (!perPeer.empty()) {
1465 m_out << "# per-peer (unshared) properties: " << boost::join(perPeer, ", ") << endl;
1467 if (!perContext.empty()) {
1468 m_out << "# shared by peers in same context: " << boost::join(perContext, ", ") << endl;
1470 if (!global.empty()) {
1471 m_out << "# global properties: " << boost::join(global, ", ") << endl;
1476 void Cmdline::dumpComment(ostream &stream,
1477 const string &prefix,
1478 const string &comment)
1480 list<string> commentLines;
1481 ConfigProperty::splitComment(comment, commentLines);
1482 BOOST_FOREACH(const string &line, commentLines) {
1483 stream << prefix << line << endl;
1487 void Cmdline::usage(bool full, const string &error, const string ¶m)
1489 ostream &out(error.empty() ? m_out : m_err);
1494 "Options:" << endl <<
1499 out << endl << "ERROR: " << error << endl;
1502 out << "INFO: use '" << param << (param[param.size() - 1] == '=' ? "" : " ") <<
1503 "?' to get a list of valid parameters" << endl;
1507 SyncContext* Cmdline::createSyncClient() {
1508 return new SyncContext(m_server, true);
1511 #ifdef ENABLE_UNIT_TESTS
1513 /** simple line-by-line diff */
1514 static string diffStrings(const string &lhs, const string &rhs)
1518 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1519 string_split_iterator lit =
1520 boost::make_split_iterator(lhs, boost::first_finder("\n", boost::is_iequal()));
1521 string_split_iterator rit =
1522 boost::make_split_iterator(rhs, boost::first_finder("\n", boost::is_iequal()));
1523 while (lit != string_split_iterator() &&
1524 rit != string_split_iterator()) {
1526 res << "< " << *lit << endl;
1527 res << "> " << *rit << endl;
1533 while (lit != string_split_iterator()) {
1534 res << "< " << *lit << endl;
1538 while (rit != string_split_iterator()) {
1539 res << "> " << *rit << endl;
1546 # define CPPUNIT_ASSERT_EQUAL_DIFF( expected, actual ) \
1548 string expected_ = (expected); \
1549 string actual_ = (actual); \
1550 if (expected_ != actual_) { \
1551 CPPUNIT_NS::Message cpputMsg_(string("expected:\n") + \
1553 cpputMsg_.addDetail(string("actual:\n") + \
1555 cpputMsg_.addDetail(string("diff:\n") + \
1556 diffStrings(expected_, actual_)); \
1557 CPPUNIT_NS::Asserter::fail( cpputMsg_, \
1558 CPPUNIT_SOURCELINE() ); \
1562 // returns last line, including trailing line break, empty if input is empty
1563 static string lastLine(const string &buffer)
1565 if (buffer.size() < 2) {
1569 size_t line = buffer.rfind("\n", buffer.size() - 2);
1570 if (line == buffer.npos) {
1574 return buffer.substr(line + 1);
1578 static bool isPropAssignment(const string &buffer) {
1579 // ignore these comments (occur in type description)
1580 if (boost::starts_with(buffer, "KCalExtended = ") ||
1581 boost::starts_with(buffer, "mkcal = ") ||
1582 boost::starts_with(buffer, "QtContacts = ")) {
1587 while (start < buffer.size() &&
1588 !isspace(buffer[start])) {
1591 if (start + 3 <= buffer.size() &&
1592 buffer.substr(start, 3) == " = ") {
1599 // remove pure comment lines from buffer,
1600 // also empty lines,
1601 // also defaultPeer (because reference properties do not include global props)
1602 static string filterConfig(const string &buffer)
1606 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1607 for (string_split_iterator it =
1608 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
1609 it != string_split_iterator();
1611 string line = boost::copy_range<string>(*it);
1612 if (!line.empty() &&
1613 line.find("defaultPeer =") == line.npos &&
1614 (!boost::starts_with(line, "# ") ||
1615 isPropAssignment(line.substr(2)))) {
1616 res << line << endl;
1623 static string injectValues(const string &buffer)
1625 string res = buffer;
1627 // username/password not set in templates, only in configs created via
1629 boost::replace_first(res,
1631 "username = your SyncML server account name");
1632 boost::replace_first(res,
1634 "password = your SyncML server password");
1638 // remove lines indented with spaces
1639 static string filterIndented(const string &buffer)
1644 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1645 for (string_split_iterator it =
1646 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
1647 it != string_split_iterator();
1649 if (!boost::starts_with(*it, " ")) {
1662 // sort lines by file, preserving order inside each line
1663 static void sortConfig(string &config)
1665 // file name, line number, property
1666 typedef pair<string, pair<int, string> > line_t;
1667 vector<line_t> lines;
1668 typedef boost::split_iterator<string::iterator> string_split_iterator;
1670 for (string_split_iterator it =
1671 boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal()));
1672 it != string_split_iterator();
1674 string line(it->begin(), it->end());
1679 size_t colon = line.find(':');
1680 string prefix = line.substr(0, colon);
1681 lines.push_back(make_pair(prefix, make_pair(linenr, line.substr(colon))));
1684 // stable sort because of line number
1685 sort(lines.begin(), lines.end());
1687 size_t len = config.size();
1689 config.reserve(len);
1690 BOOST_FOREACH(const line_t &line, lines) {
1691 config += line.first;
1692 config += line.second.second;
1697 // convert the internal config dump to .ini style (--print-config)
1698 static string internalToIni(const string &config)
1703 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
1704 for (string_split_iterator it =
1705 boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal()));
1706 it != string_split_iterator();
1708 string line(it->begin(), it->end());
1713 size_t colon = line.find(':');
1714 string prefix = line.substr(0, colon);
1716 // internal values are not part of the --print-config output
1717 if (boost::contains(prefix, ".internal.ini") ||
1718 boost::contains(line, "= internal value")) {
1722 // --print-config also doesn't duplicate the "type" property
1723 // => remove the shared property
1724 if (boost::contains(line, ":type = ") &&
1725 boost::starts_with(line, "sources/")) {
1729 // sources/<name>/config.ini or
1730 // spds/sources/<name>/config.ini
1731 size_t endslash = prefix.rfind('/');
1732 if (endslash != line.npos && endslash > 1) {
1733 size_t slash = prefix.rfind('/', endslash - 1);
1734 if (slash != line.npos) {
1735 string newsource = prefix.substr(slash + 1, endslash - slash - 1);
1736 if (newsource != section &&
1737 prefix.find("/sources/") != prefix.npos &&
1738 newsource != "syncml") {
1739 res << "[" << newsource << "]" << endl;
1740 section = newsource;
1744 string assignment = line.substr(colon + 1);
1745 // substitude aliases with generic values
1746 boost::replace_first(assignment, "= F", "= 0");
1747 boost::replace_first(assignment, "= T", "= 1");
1748 boost::replace_first(assignment, "= syncml:auth-md5", "= md5");
1749 boost::replace_first(assignment, "= syncml:auth-basix", "= basic");
1750 res << assignment << endl;
1758 * Testing is based on a text representation of a directory
1759 * hierarchy where each line is of the format
1760 * <file path>:<line in file>
1762 * The order of files is alphabetical, of lines in the file as
1763 * in the file. Lines in the file without line break cannot
1766 * The root of the hierarchy is not part of the representation
1769 class CmdlineTest : public CppUnit::TestFixture {
1770 CPPUNIT_TEST_SUITE(CmdlineTest);
1771 CPPUNIT_TEST(testFramework);
1772 CPPUNIT_TEST(testSetupScheduleWorld);
1773 CPPUNIT_TEST(testSetupDefault);
1774 CPPUNIT_TEST(testSetupRenamed);
1775 CPPUNIT_TEST(testSetupFunambol);
1776 CPPUNIT_TEST(testSetupSynthesis);
1777 CPPUNIT_TEST(testPrintServers);
1778 CPPUNIT_TEST(testPrintConfig);
1779 CPPUNIT_TEST(testPrintFileTemplates);
1780 CPPUNIT_TEST(testPrintFileTemplatesConfig);
1781 CPPUNIT_TEST(testTemplate);
1782 CPPUNIT_TEST(testMatchTemplate);
1783 CPPUNIT_TEST(testAddSource);
1784 CPPUNIT_TEST(testSync);
1785 CPPUNIT_TEST(testConfigure);
1786 CPPUNIT_TEST(testConfigureSources);
1787 CPPUNIT_TEST(testOldConfigure);
1788 CPPUNIT_TEST(testListSources);
1789 CPPUNIT_TEST(testMigrate);
1790 CPPUNIT_TEST_SUITE_END();
1794 m_testDir("CmdlineTest"),
1795 // properties sorted by the order in which they are defined
1796 // in the sync and sync source property registry
1797 m_scheduleWorldConfig("peers/scheduleworld/.internal.ini:# HashCode = 0\n"
1798 "peers/scheduleworld/.internal.ini:# ConfigDate = \n"
1799 "peers/scheduleworld/.internal.ini:# lastNonce = \n"
1800 "peers/scheduleworld/.internal.ini:# deviceData = \n"
1801 "peers/scheduleworld/config.ini:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
1802 "peers/scheduleworld/config.ini:username = your SyncML server account name\n"
1803 "peers/scheduleworld/config.ini:password = your SyncML server password\n"
1804 "config.ini:# logdir = \n"
1805 "peers/scheduleworld/config.ini:# loglevel = 0\n"
1806 "peers/scheduleworld/config.ini:# printChanges = 1\n"
1807 "config.ini:# maxlogdirs = 10\n"
1808 "peers/scheduleworld/config.ini:# autoSync = 0\n"
1809 "peers/scheduleworld/config.ini:# autoSyncInterval = 30M\n"
1810 "peers/scheduleworld/config.ini:# autoSyncDelay = 5M\n"
1811 "peers/scheduleworld/config.ini:# preventSlowSync = 1\n"
1812 "peers/scheduleworld/config.ini:# useProxy = 0\n"
1813 "peers/scheduleworld/config.ini:# proxyHost = \n"
1814 "peers/scheduleworld/config.ini:# proxyUsername = \n"
1815 "peers/scheduleworld/config.ini:# proxyPassword = \n"
1816 "peers/scheduleworld/config.ini:# clientAuthType = md5\n"
1817 "peers/scheduleworld/config.ini:# RetryDuration = 5M\n"
1818 "peers/scheduleworld/config.ini:# RetryInterval = 2M\n"
1819 "peers/scheduleworld/config.ini:# remoteIdentifier = \n"
1820 "peers/scheduleworld/config.ini:# PeerIsClient = 0\n"
1821 "peers/scheduleworld/config.ini:# SyncMLVersion = \n"
1822 "peers/scheduleworld/config.ini:# PeerName = \n"
1823 "config.ini:deviceId = fixed-devid\n" /* this is not the default! */
1824 "peers/scheduleworld/config.ini:# remoteDeviceId = \n"
1825 "peers/scheduleworld/config.ini:# enableWBXML = 1\n"
1826 "peers/scheduleworld/config.ini:# maxMsgSize = 150000\n"
1827 "peers/scheduleworld/config.ini:# maxObjSize = 4000000\n"
1828 "peers/scheduleworld/config.ini:# enableCompression = 0\n"
1829 "peers/scheduleworld/config.ini:# SSLServerCertificates = \n"
1830 "peers/scheduleworld/config.ini:# SSLVerifyServer = 1\n"
1831 "peers/scheduleworld/config.ini:# SSLVerifyHost = 1\n"
1832 "peers/scheduleworld/config.ini:WebURL = http://www.scheduleworld.com\n"
1833 "peers/scheduleworld/config.ini:# IconURI = \n"
1834 "peers/scheduleworld/config.ini:ConsumerReady = 1\n"
1836 "peers/scheduleworld/sources/addressbook/.internal.ini:# adminData = \n"
1837 "peers/scheduleworld/sources/addressbook/.internal.ini:# synthesisID = 0\n"
1838 "peers/scheduleworld/sources/addressbook/config.ini:sync = two-way\n"
1839 "sources/addressbook/config.ini:type = addressbook:text/vcard\n"
1840 "peers/scheduleworld/sources/addressbook/config.ini:type = addressbook:text/vcard\n"
1841 "sources/addressbook/config.ini:# evolutionsource = \n"
1842 "peers/scheduleworld/sources/addressbook/config.ini:uri = card3\n"
1843 "sources/addressbook/config.ini:# evolutionuser = \n"
1844 "sources/addressbook/config.ini:# evolutionpassword = \n"
1846 "peers/scheduleworld/sources/calendar/.internal.ini:# adminData = \n"
1847 "peers/scheduleworld/sources/calendar/.internal.ini:# synthesisID = 0\n"
1848 "peers/scheduleworld/sources/calendar/config.ini:sync = two-way\n"
1849 "sources/calendar/config.ini:type = calendar\n"
1850 "peers/scheduleworld/sources/calendar/config.ini:type = calendar\n"
1851 "sources/calendar/config.ini:# evolutionsource = \n"
1852 "peers/scheduleworld/sources/calendar/config.ini:uri = cal2\n"
1853 "sources/calendar/config.ini:# evolutionuser = \n"
1854 "sources/calendar/config.ini:# evolutionpassword = \n"
1856 "peers/scheduleworld/sources/memo/.internal.ini:# adminData = \n"
1857 "peers/scheduleworld/sources/memo/.internal.ini:# synthesisID = 0\n"
1858 "peers/scheduleworld/sources/memo/config.ini:sync = two-way\n"
1859 "sources/memo/config.ini:type = memo\n"
1860 "peers/scheduleworld/sources/memo/config.ini:type = memo\n"
1861 "sources/memo/config.ini:# evolutionsource = \n"
1862 "peers/scheduleworld/sources/memo/config.ini:uri = note\n"
1863 "sources/memo/config.ini:# evolutionuser = \n"
1864 "sources/memo/config.ini:# evolutionpassword = \n"
1866 "peers/scheduleworld/sources/todo/.internal.ini:# adminData = \n"
1867 "peers/scheduleworld/sources/todo/.internal.ini:# synthesisID = 0\n"
1868 "peers/scheduleworld/sources/todo/config.ini:sync = two-way\n"
1869 "sources/todo/config.ini:type = todo\n"
1870 "peers/scheduleworld/sources/todo/config.ini:type = todo\n"
1871 "sources/todo/config.ini:# evolutionsource = \n"
1872 "peers/scheduleworld/sources/todo/config.ini:uri = task2\n"
1873 "sources/todo/config.ini:# evolutionuser = \n"
1874 "sources/todo/config.ini:# evolutionpassword = ")
1876 #ifdef ENABLE_LIBSOUP
1877 // path to SSL certificates has to be set only for libsoup
1878 boost::replace_first(m_scheduleWorldConfig,
1879 "SSLServerCertificates = ",
1880 "SSLServerCertificates = /etc/ssl/certs/ca-certificates.crt:/etc/pki/tls/certs/ca-bundle.crt:/usr/share/ssl/certs/ca-bundle.crt");
1886 /** verify that createFiles/scanFiles themselves work */
1887 void testFramework() {
1888 const string root(m_testDir);
1889 const string content("baz:line\n"
1891 "caz/subdir2/sub:# comment\n"
1892 "caz/subdir2/sub:# foo = bar\n"
1893 "caz/subdir2/sub:# empty = \n"
1894 "caz/subdir2/sub:# another comment\n"
1899 const string filtered("baz:line\n"
1901 "caz/subdir2/sub:# foo = bar\n"
1902 "caz/subdir2/sub:# empty = \n"
1906 createFiles(root, content);
1907 string res = scanFiles(root);
1908 CPPUNIT_ASSERT_EQUAL_DIFF(filtered, res);
1911 void removeRandomUUID(string &buffer) {
1912 string uuidstr = "deviceId = syncevolution-";
1913 size_t uuid = buffer.find(uuidstr);
1914 CPPUNIT_ASSERT(uuid != buffer.npos);
1915 size_t end = buffer.find("\n", uuid + uuidstr.size());
1916 CPPUNIT_ASSERT(end != buffer.npos);
1917 buffer.replace(uuid, end - uuid, "deviceId = fixed-devid");
1920 /** create new configurations */
1921 void testSetupScheduleWorld() { doSetupScheduleWorld(false); }
1922 void doSetupScheduleWorld(bool shared) {
1924 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1925 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1926 ScopedEnvChange home("HOME", m_testDir);
1929 root += "/syncevolution/default";
1932 peer = root + "/peers/scheduleworld";
1939 TestCmdline cmdline("--configure",
1940 "--sync-property", "proxyHost = proxy",
1945 string res = scanFiles(root);
1946 removeRandomUUID(res);
1947 string expected = ScheduleWorldConfig();
1948 sortConfig(expected);
1949 boost::replace_first(expected,
1951 "proxyHost = proxy");
1952 boost::replace_all(expected,
1955 boost::replace_first(expected,
1956 "addressbook/config.ini:sync = disabled",
1957 "addressbook/config.ini:sync = two-way");
1958 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1963 TestCmdline cmdline("--configure",
1964 "--sync-property", "deviceID = fixed-devid",
1968 string res = scanFiles(root);
1969 string expected = ScheduleWorldConfig();
1970 sortConfig(expected);
1971 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1975 void testSetupDefault() {
1977 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1978 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1979 ScopedEnvChange home("HOME", m_testDir);
1982 root += "/syncevolution/default";
1984 TestCmdline cmdline("--configure",
1985 "--template", "default",
1986 "--sync-property", "deviceID = fixed-devid",
1987 "some-other-server",
1990 string res = scanFiles(root, "some-other-server");
1991 string expected = ScheduleWorldConfig();
1992 sortConfig(expected);
1993 boost::replace_all(expected, "/scheduleworld/", "/some-other-server/");
1994 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1996 void testSetupRenamed() {
1998 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
1999 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2000 ScopedEnvChange home("HOME", m_testDir);
2003 root += "/syncevolution/default";
2005 TestCmdline cmdline("--configure",
2006 "--template", "scheduleworld",
2007 "--sync-property", "deviceID = fixed-devid",
2011 string res = scanFiles(root, "scheduleworld2");
2012 string expected = ScheduleWorldConfig();
2013 sortConfig(expected);
2014 boost::replace_all(expected, "/scheduleworld/", "/scheduleworld2/");
2015 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2018 void testSetupFunambol() { doSetupFunambol(false); }
2019 void doSetupFunambol(bool shared) {
2021 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2022 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2023 ScopedEnvChange home("HOME", m_testDir);
2026 root += "/syncevolution/default";
2029 peer = root + "/peers/funambol";
2035 const char * const argv_fixed[] = {
2037 "--sync-property", "deviceID = fixed-devid",
2038 // templates are case-insensitive
2041 }, * const argv_shared[] = {
2046 TestCmdline cmdline(shared ? argv_shared : argv_fixed);
2048 string res = scanFiles(root, "funambol");
2049 string expected = FunambolConfig();
2050 sortConfig(expected);
2051 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2054 void testSetupSynthesis() { doSetupSynthesis(false); }
2055 void doSetupSynthesis(bool shared) {
2057 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2058 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2059 ScopedEnvChange home("HOME", m_testDir);
2062 root += "/syncevolution/default";
2065 peer = root + "/peers/synthesis";
2070 const char * const argv_fixed[] = {
2072 "--sync-property", "deviceID = fixed-devid",
2075 }, * const argv_shared[] = {
2080 TestCmdline cmdline(shared ? argv_shared : argv_fixed);
2082 string res = scanFiles(root, "synthesis");
2083 string expected = SynthesisConfig();
2084 sortConfig(expected);
2085 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2088 void testTemplate() {
2089 TestCmdline failure("--template", NULL);
2090 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
2091 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
2092 CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--template'\n"), lastLine(failure.m_err.str()));
2094 TestCmdline help("--template", "? ", NULL);
2096 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
2097 " template name = template description\n"
2098 " Funambol = http://my.funambol.com\n"
2099 " Google = http://m.google.com/sync\n"
2100 " Goosync = http://www.goosync.com/\n"
2101 " Memotoo = http://www.memotoo.com\n"
2102 " Mobical = http://www.mobical.net\n"
2103 " Oracle = http://www.oracle.com/technology/products/beehive/index.html\n"
2104 " Ovi = http://www.ovi.com\n"
2105 " ScheduleWorld = http://www.scheduleworld.com\n"
2106 " SyncEvolution = http://www.syncevolution.org\n"
2107 " Synthesis = http://www.synthesis.ch\n",
2109 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
2112 void testMatchTemplate() {
2113 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "testcases/templates");
2114 ScopedEnvChange xdg("XDG_CONFIG_HOME", "/dev/null");
2116 TestCmdline help1("--template", "?nokia 7210c", NULL);
2118 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
2119 " template name = template description matching score in percent (100% = exact match)\n"
2120 " Nokia 7210c = Template for Nokia S40 series Phone 100%\n"
2121 " SyncEvolution Client = SyncEvolution server side template 40%\n",
2123 CPPUNIT_ASSERT_EQUAL_DIFF("", help1.m_err.str());
2124 TestCmdline help2("--template", "?nokia", NULL);
2126 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
2127 " template name = template description matching score in percent (100% = exact match)\n"
2128 " Nokia 7210c = Template for Nokia S40 series Phone 100%\n"
2129 " SyncEvolution Client = SyncEvolution server side template 40%\n",
2131 CPPUNIT_ASSERT_EQUAL_DIFF("", help2.m_err.str());
2132 TestCmdline help3("--template", "?7210c", NULL);
2134 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
2135 " template name = template description matching score in percent (100% = exact match)\n"
2136 " Nokia 7210c = Template for Nokia S40 series Phone 60%\n"
2137 " SyncEvolution Client = SyncEvolution server side template 20%\n",
2139 CPPUNIT_ASSERT_EQUAL_DIFF("", help3.m_err.str());
2140 TestCmdline help4("--template", "?syncevolution client", NULL);
2142 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
2143 " template name = template description matching score in percent (100% = exact match)\n"
2144 " SyncEvolution Client = SyncEvolution server side template 100%\n"
2145 " Nokia 7210c = Template for Nokia S40 series Phone 40%\n",
2147 CPPUNIT_ASSERT_EQUAL_DIFF("", help4.m_err.str());
2150 void testPrintServers() {
2151 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2152 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2153 ScopedEnvChange home("HOME", m_testDir);
2156 doSetupScheduleWorld(false);
2157 doSetupSynthesis(true);
2158 doSetupFunambol(true);
2160 TestCmdline cmdline("--print-servers", NULL);
2162 CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n"
2163 " funambol = CmdlineTest/syncevolution/default/peers/funambol\n"
2164 " scheduleworld = CmdlineTest/syncevolution/default/peers/scheduleworld\n"
2165 " synthesis = CmdlineTest/syncevolution/default/peers/synthesis\n",
2166 cmdline.m_out.str());
2167 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2170 void testPrintConfig() {
2171 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2172 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2173 ScopedEnvChange home("HOME", m_testDir);
2176 testSetupFunambol();
2179 TestCmdline failure("--print-config", NULL);
2180 CPPUNIT_ASSERT(failure.m_cmdline->parse());
2181 CPPUNIT_ASSERT(!failure.m_cmdline->run());
2182 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
2183 CPPUNIT_ASSERT_EQUAL(string("ERROR: --print-config requires either a --template or a server name.\n"),
2184 lastLine(failure.m_err.str()));
2188 TestCmdline failure("--print-config", "foo", NULL);
2189 CPPUNIT_ASSERT(failure.m_cmdline->parse());
2190 CPPUNIT_ASSERT(!failure.m_cmdline->run());
2191 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
2192 CPPUNIT_ASSERT_EQUAL(string("ERROR: server 'foo' has not been configured yet.\n"),
2193 lastLine(failure.m_err.str()));
2197 TestCmdline failure("--print-config", "--template", "foo", NULL);
2198 CPPUNIT_ASSERT(failure.m_cmdline->parse());
2199 CPPUNIT_ASSERT(!failure.m_cmdline->run());
2200 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
2201 CPPUNIT_ASSERT_EQUAL(string("ERROR: no configuration template for 'foo' available.\n"),
2202 lastLine(failure.m_err.str()));
2206 TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL);
2208 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2209 string actual = cmdline.m_out.str();
2210 // deviceId must be the one from Funambol
2211 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
2212 string filtered = injectValues(filterConfig(actual));
2213 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
2215 // there should have been comments
2216 CPPUNIT_ASSERT(actual.size() > filtered.size());
2220 TestCmdline cmdline("--print-config", "--template", "scheduleworld@nosuchcontext", NULL);
2222 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2223 string actual = cmdline.m_out.str();
2224 // deviceId must *not* be the one from Funambol because of the new context
2225 CPPUNIT_ASSERT(!boost::contains(actual, "deviceId = fixed-devid"));
2229 TestCmdline cmdline("--print-config", "--template", "Default", NULL);
2231 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2232 string actual = injectValues(filterConfig(cmdline.m_out.str()));
2233 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
2234 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
2239 TestCmdline cmdline("--print-config", "funambol", NULL);
2241 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2242 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())),
2243 injectValues(filterConfig(cmdline.m_out.str())));
2247 // override context and template properties
2248 TestCmdline cmdline("--print-config", "--template", "scheduleworld",
2249 "--sync-property", "syncURL=foo",
2250 "--source-property", "evolutionsource=Personal",
2251 "--source-property", "sync=disabled",
2254 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2255 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
2256 boost::replace_first(expected,
2257 "syncURL = http://sync.scheduleworld.com/funambol/ds",
2259 boost::replace_all(expected,
2260 "# evolutionsource = ",
2261 "evolutionsource = Personal");
2262 boost::replace_all(expected,
2265 string actual = injectValues(filterConfig(cmdline.m_out.str()));
2266 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
2267 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2272 TestCmdline cmdline("--print-config", "--quiet",
2273 "--template", "scheduleworld",
2277 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2278 string actual = cmdline.m_out.str();
2279 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
2280 CPPUNIT_ASSERT_EQUAL_DIFF(internalToIni(ScheduleWorldConfig()),
2281 injectValues(filterConfig(actual)));
2285 // change shared source properties, then check template again
2286 TestCmdline cmdline("--configure",
2287 "--source-property", "evolutionsource=Personal",
2291 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2294 TestCmdline cmdline("--print-config", "--quiet",
2295 "--template", "scheduleworld",
2299 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2300 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
2301 // from modified Funambol config
2302 boost::replace_all(expected,
2303 "# evolutionsource = ",
2304 "evolutionsource = Personal");
2305 string actual = injectValues(filterConfig(cmdline.m_out.str()));
2306 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
2307 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2312 // print config => must not use settings from default context
2313 TestCmdline cmdline("--print-config", "--template", "scheduleworld@nosuchcontext", NULL);
2315 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2316 // source settings *not* from modified Funambol config
2317 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
2318 string actual = injectValues(filterConfig(cmdline.m_out.str()));
2319 CPPUNIT_ASSERT(!boost::contains(actual, "deviceId = fixed-devid"));
2320 removeRandomUUID(actual);
2321 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2326 // create config => again, must not use settings from default context
2327 TestCmdline cmdline("--configure", "--template", "scheduleworld", "other@other", NULL);
2329 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2332 TestCmdline cmdline("--print-config", "other@other", NULL);
2334 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2335 // source settings *not* from modified Funambol config
2336 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
2337 string actual = injectValues(filterConfig(cmdline.m_out.str()));
2338 CPPUNIT_ASSERT(!boost::contains(actual, "deviceId = fixed-devid"));
2339 removeRandomUUID(actual);
2340 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2345 void testPrintFileTemplates() {
2347 // use local copy of templates in build dir (no need to install)
2348 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "./templates");
2349 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2350 ScopedEnvChange home("HOME", m_testDir);
2352 doPrintFileTemplates();
2355 void testPrintFileTemplatesConfig() {
2358 symlink("../templates", (m_testDir + "/syncevolution-templates").c_str());
2359 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2360 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2361 ScopedEnvChange home("HOME", m_testDir);
2363 doPrintFileTemplates();
2366 void doPrintFileTemplates() {
2367 testSetupFunambol();
2370 TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL);
2372 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2373 string actual = cmdline.m_out.str();
2374 // deviceId must be the one from Funambol
2375 CPPUNIT_ASSERT(boost::contains(actual, "deviceId = fixed-devid"));
2376 string filtered = injectValues(filterConfig(actual));
2377 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
2379 // there should have been comments
2380 CPPUNIT_ASSERT(actual.size() > filtered.size());
2384 TestCmdline cmdline("--print-config", "funambol", NULL);
2386 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2387 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())),
2388 injectValues(filterConfig(cmdline.m_out.str())));
2392 void testAddSource() {
2394 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2395 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2396 ScopedEnvChange home("HOME", m_testDir);
2398 testSetupScheduleWorld();
2401 root += "/syncevolution/default";
2404 TestCmdline cmdline("--configure",
2405 "--source-property", "uri = dummy",
2410 string res = scanFiles(root);
2411 string expected = ScheduleWorldConfig();
2413 "peers/scheduleworld/sources/xyz/.internal.ini:# adminData = \n"
2414 "peers/scheduleworld/sources/xyz/.internal.ini:# synthesisID = 0\n"
2415 "peers/scheduleworld/sources/xyz/config.ini:# sync = disabled\n"
2416 "peers/scheduleworld/sources/xyz/config.ini:# type = select backend\n"
2417 "peers/scheduleworld/sources/xyz/config.ini:uri = dummy\n"
2418 "sources/xyz/config.ini:# type = select backend\n"
2419 "sources/xyz/config.ini:# evolutionsource = \n"
2420 "sources/xyz/config.ini:# evolutionuser = \n"
2421 "sources/xyz/config.ini:# evolutionpassword = ";
2422 sortConfig(expected);
2423 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2428 TestCmdline failure("--sync", NULL);
2429 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
2430 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
2431 CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--sync'\n"), lastLine(failure.m_err.str()));
2433 TestCmdline failure2("--sync", "foo", NULL);
2434 CPPUNIT_ASSERT(!failure2.m_cmdline->parse());
2435 CPPUNIT_ASSERT_EQUAL_DIFF("", failure2.m_out.str());
2436 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()));
2438 TestCmdline help("--sync", " ?", NULL);
2440 CPPUNIT_ASSERT_EQUAL_DIFF("--sync\n"
2441 " Requests a certain synchronization mode when initiating a sync:\n"
2442 " two-way = only send/receive changes since last sync\n"
2443 " slow = exchange all items\n"
2444 " refresh-from-client = discard all remote items and replace with\n"
2445 " the items on the client\n"
2446 " refresh-from-server = discard all local items and replace with\n"
2447 " the items on the server\n"
2448 " one-way-from-client = transmit changes from client\n"
2449 " one-way-from-server = transmit changes from server\n"
2450 " disabled (or none) = synchronization disabled\n"
2451 " When accepting a sync session in a SyncML server (HTTP server), only\n"
2452 " sources with sync != disabled are made available to the client,\n"
2453 " which chooses the final sync mode based on its own configuration.\n"
2454 " When accepting a sync session in a SyncML client (local sync with\n"
2455 " the server contacting SyncEvolution on a device), the sync mode\n"
2456 " specified in the client is typically overriden by the server.\n",
2458 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
2460 TestCmdline filter("--sync", "refresh-from-server", NULL);
2461 CPPUNIT_ASSERT(filter.m_cmdline->parse());
2462 CPPUNIT_ASSERT(!filter.m_cmdline->run());
2463 CPPUNIT_ASSERT_EQUAL_DIFF("", filter.m_out.str());
2464 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh-from-server",
2465 string(filter.m_cmdline->m_sourceProps));
2466 CPPUNIT_ASSERT_EQUAL_DIFF("",
2467 string(filter.m_cmdline->m_syncProps));
2469 TestCmdline filter2("--source-property", "sync=refresh", NULL);
2470 CPPUNIT_ASSERT(filter2.m_cmdline->parse());
2471 CPPUNIT_ASSERT(!filter2.m_cmdline->run());
2472 CPPUNIT_ASSERT_EQUAL_DIFF("", filter2.m_out.str());
2473 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh",
2474 string(filter2.m_cmdline->m_sourceProps));
2475 CPPUNIT_ASSERT_EQUAL_DIFF("",
2476 string(filter2.m_cmdline->m_syncProps));
2479 void testConfigure() {
2480 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2481 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2482 ScopedEnvChange home("HOME", m_testDir);
2485 testSetupScheduleWorld();
2486 string expected = doConfigure(ScheduleWorldConfig(), "sources/addressbook/config.ini:");
2489 // updating type for peer must also update type for context
2490 TestCmdline cmdline("--configure",
2491 "--source-property", "type=file:text/vcard:3.0",
2492 "scheduleworld", "addressbook",
2495 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2496 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2497 boost::replace_all(expected,
2498 "type = addressbook:text/vcard",
2499 "type = file:text/vcard:3.0");
2500 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2501 filterConfig(printConfig("scheduleworld")));
2502 string shared = filterConfig(printConfig("@default"));
2503 CPPUNIT_ASSERT(shared.find("type = file:text/vcard:3.0") != shared.npos);
2507 // updating type for context must not affect peer
2508 TestCmdline cmdline("--configure",
2509 "--source-property", "type=file:text/vcard:2.1",
2510 "@default", "addressbook",
2513 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2514 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2515 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2516 filterConfig(printConfig("scheduleworld")));
2517 string shared = filterConfig(printConfig("@default"));
2518 CPPUNIT_ASSERT(shared.find("type = file:text/vcard:2.1") != shared.npos);
2521 string syncProperties("syncURL:\n"
2537 "autoSyncInterval:\n"
2541 "preventSlowSync:\n"
2557 "remoteIdentifier:\n"
2574 "enableCompression:\n"
2576 "SSLServerCertificates:\n"
2578 "SSLVerifyServer:\n"
2589 string sourceProperties("sync:\n"
2593 "evolutionsource:\n"
2598 "evolutionpassword:\n");
2601 TestCmdline cmdline("--sync-property", "?",
2604 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2605 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties,
2606 filterIndented(cmdline.m_out.str()));
2610 TestCmdline cmdline("--source-property", "?",
2613 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2614 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties,
2615 filterIndented(cmdline.m_out.str()));
2619 TestCmdline cmdline("--source-property", "?",
2620 "--sync-property", "?",
2623 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2624 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties + syncProperties,
2625 filterIndented(cmdline.m_out.str()));
2629 TestCmdline cmdline("--sync-property", "?",
2630 "--source-property", "?",
2633 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2634 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties + sourceProperties,
2635 filterIndented(cmdline.m_out.str()));
2639 void testConfigureSources() {
2640 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2641 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2642 ScopedEnvChange home("HOME", m_testDir);
2646 // create from scratch with only addressbook configured
2648 TestCmdline cmdline("--configure",
2649 "--source-property", "evolutionsource = file://tmp/test",
2650 "--source-property", "type = file:text/vcard:3.0",
2656 string root = m_testDir;
2657 root += "/syncevolution/foobar";
2658 string res = scanFiles(root);
2659 removeRandomUUID(res);
2661 "config.ini:# logdir = \n"
2662 "config.ini:# maxlogdirs = 10\n"
2663 "config.ini:deviceId = fixed-devid\n"
2664 "sources/addressbook/config.ini:type = file:text/vcard:3.0\n"
2665 "sources/addressbook/config.ini:evolutionsource = file://tmp/test\n"
2666 "sources/addressbook/config.ini:# evolutionuser = \n"
2667 "sources/addressbook/config.ini:# evolutionpassword = \n";
2668 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2672 TestCmdline cmdline("--configure",
2673 "--source-property", "evolutionsource = file://tmp/test2",
2674 "--source-property", "type = calendar",
2680 res = scanFiles(root);
2681 removeRandomUUID(res);
2683 "sources/calendar/config.ini:type = calendar\n"
2684 "sources/calendar/config.ini:evolutionsource = file://tmp/test2\n"
2685 "sources/calendar/config.ini:# evolutionuser = \n"
2686 "sources/calendar/config.ini:# evolutionpassword = \n";
2687 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2689 // add ScheduleWorld peer
2691 TestCmdline cmdline("--configure",
2692 "scheduleworld@foobar",
2696 res = scanFiles(root);
2697 removeRandomUUID(res);
2698 expected = ScheduleWorldConfig();
2699 boost::replace_all(expected,
2700 "peers/scheduleworld/sources/addressbook/config.ini:type = addressbook:text/vcard",
2701 "peers/scheduleworld/sources/addressbook/config.ini:type = file:text/vcard:3.0");
2702 boost::replace_all(expected,
2703 "addressbook/config.ini:# evolutionsource = ",
2704 "addressbook/config.ini:evolutionsource = file://tmp/test");
2705 boost::replace_all(expected,
2706 "calendar/config.ini:# evolutionsource = ",
2707 "calendar/config.ini:evolutionsource = file://tmp/test2");
2708 sortConfig(expected);
2709 // Known problem (BMC #1023): type is reset to what is in the template,
2710 // should be preserved.
2712 // Temporarily fix the "expected" result so
2713 // that we can pass the rest of the test.
2714 boost::replace_all(expected,
2715 "peers/scheduleworld/sources/addressbook/config.ini:type = file:text/vcard:3.0",
2716 "peers/scheduleworld/sources/addressbook/config.ini:type = addressbook:text/vcard");
2717 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
2720 void testOldConfigure() {
2721 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2722 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2723 ScopedEnvChange home("HOME", m_testDir);
2725 string oldConfig = OldScheduleWorldConfig();
2726 InitList<string> props = InitList<string>("serverNonce") +
2736 BOOST_FOREACH(string &prop, props) {
2737 boost::replace_all(oldConfig,
2739 prop + " = internal value");
2743 createFiles(m_testDir + "/.sync4j/evolution/scheduleworld", oldConfig);
2744 doConfigure(oldConfig, "spds/sources/addressbook/config.txt:");
2747 string doConfigure(const string &SWConfig, const string &addressbookPrefix) {
2751 TestCmdline cmdline("--configure",
2752 "--source-property", "sync = disabled",
2756 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2757 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2758 expected = filterConfig(internalToIni(SWConfig));
2759 boost::replace_all(expected,
2762 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2763 filterConfig(printConfig("scheduleworld")));
2767 TestCmdline cmdline("--configure",
2768 "--source-property", "sync = one-way-from-server",
2773 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2774 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2775 expected = SWConfig;
2776 boost::replace_all(expected,
2779 boost::replace_first(expected,
2780 addressbookPrefix + "sync = disabled",
2781 addressbookPrefix + "sync = one-way-from-server");
2782 expected = filterConfig(internalToIni(expected));
2783 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2784 filterConfig(printConfig("scheduleworld")));
2788 TestCmdline cmdline("--configure",
2789 "--sync", "two-way",
2790 "-z", "evolutionsource=source",
2791 "--sync-property", "maxlogdirs=20",
2792 "-y", "LOGDIR=logdir",
2796 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2797 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2798 boost::replace_all(expected,
2799 "sync = one-way-from-server",
2801 boost::replace_all(expected,
2804 boost::replace_all(expected,
2805 "# evolutionsource = ",
2806 "evolutionsource = source");
2807 boost::replace_all(expected,
2808 "# maxlogdirs = 10",
2810 boost::replace_all(expected,
2813 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
2814 filterConfig(printConfig("scheduleworld")));
2820 void testListSources() {
2821 // pick the varargs constructor; NULL alone is ambiguous
2822 TestCmdline cmdline(NULL, NULL);
2824 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2825 // exact output varies, do not test
2828 void testMigrate() {
2829 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
2830 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
2831 ScopedEnvChange home("HOME", m_testDir);
2834 string oldRoot = m_testDir + "/.sync4j/evolution/scheduleworld";
2835 string newRoot = m_testDir + "/syncevolution/default";
2837 string oldConfig = OldScheduleWorldConfig();
2840 // migrate old config
2841 createFiles(oldRoot, oldConfig);
2842 string createdConfig = scanFiles(oldRoot);
2843 TestCmdline cmdline("--migrate",
2847 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2848 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2850 string migratedConfig = scanFiles(newRoot);
2851 string expected = ScheduleWorldConfig();
2852 sortConfig(expected);
2853 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2854 string renamedConfig = scanFiles(oldRoot + ".old");
2855 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2859 // rewrite existing config with obsolete properties
2860 // => these properties should get removed
2862 // There is one limitation: shared nodes are not rewritten.
2863 // This is acceptable.
2864 createFiles(newRoot + "/peers/scheduleworld",
2865 "config.ini:# obsolete comment\n"
2866 "config.ini:obsoleteprop = foo\n",
2868 string createdConfig = scanFiles(newRoot, "scheduleworld");
2870 TestCmdline cmdline("--migrate",
2874 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2875 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2877 string migratedConfig = scanFiles(newRoot, "scheduleworld");
2878 string expected = ScheduleWorldConfig();
2879 sortConfig(expected);
2880 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2881 string renamedConfig = scanFiles(newRoot, "scheduleworld.old");
2882 boost::replace_all(createdConfig, "/scheduleworld/", "/scheduleworld.old/");
2883 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2887 // migrate old config with changes and .synthesis directory, a second time
2888 createFiles(oldRoot, oldConfig);
2889 createFiles(oldRoot,
2890 ".synthesis/dummy-file.bfi:dummy = foobar\n"
2891 "spds/sources/addressbook/changes/config.txt:foo = bar\n"
2892 "spds/sources/addressbook/changes/config.txt:foo2 = bar2\n",
2894 string createdConfig = scanFiles(oldRoot);
2896 TestCmdline cmdline("--migrate",
2900 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2901 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2903 string migratedConfig = scanFiles(newRoot);
2904 string expected = m_scheduleWorldConfig;
2905 sortConfig(expected);
2906 boost::replace_first(expected,
2907 "peers/scheduleworld/sources/addressbook/config.ini",
2908 "peers/scheduleworld/sources/addressbook/.other.ini:foo = bar\n"
2909 "peers/scheduleworld/sources/addressbook/.other.ini:foo2 = bar2\n"
2910 "peers/scheduleworld/sources/addressbook/config.ini");
2911 boost::replace_first(expected,
2912 "peers/scheduleworld/config.ini",
2913 "peers/scheduleworld/.synthesis/dummy-file.bfi:dummy = foobar\n"
2914 "peers/scheduleworld/config.ini");
2915 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2916 string renamedConfig = scanFiles(oldRoot + ".old.1");
2917 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2921 string otherRoot = m_testDir + "/syncevolution/other";
2924 // migrate old config into non-default context
2925 createFiles(oldRoot, oldConfig);
2926 string createdConfig = scanFiles(oldRoot);
2928 TestCmdline cmdline("--migrate",
2929 "scheduleworld@other",
2932 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2933 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2936 string migratedConfig = scanFiles(otherRoot);
2937 string expected = ScheduleWorldConfig();
2938 sortConfig(expected);
2939 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2940 string renamedConfig = scanFiles(oldRoot + ".old");
2941 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
2943 // migrate the migrated config again inside the "other" context
2945 TestCmdline cmdline("--migrate",
2946 "scheduleworld@other",
2949 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
2950 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
2952 migratedConfig = scanFiles(otherRoot, "scheduleworld");
2953 expected = ScheduleWorldConfig();
2954 sortConfig(expected);
2955 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
2956 renamedConfig = scanFiles(otherRoot, "scheduleworld.old");
2957 boost::replace_all(expected, "/scheduleworld/", "/scheduleworld.old/");
2958 CPPUNIT_ASSERT_EQUAL_DIFF(expected, renamedConfig);
2962 const string m_testDir;
2963 string m_scheduleWorldConfig;
2969 * vararg constructor with NULL termination,
2970 * out and error stream into stringstream members
2974 m_argv.reset(new const char *[m_argvstr.size() + 1]);
2975 m_argv[0] = "client-test";
2976 for (size_t index = 0;
2977 index < m_argvstr.size();
2979 m_argv[index + 1] = m_argvstr[index].c_str();
2982 m_cmdline.set(new Cmdline(m_argvstr.size() + 1, m_argv.get(), m_out, m_err), "cmdline");
2987 TestCmdline(const char *arg, ...) {
2989 va_start (argList, arg);
2990 for (const char *curr = arg;
2992 curr = va_arg(argList, const char *)) {
2993 m_argvstr.push_back(curr);
2999 TestCmdline(const char * const argv[]) {
3000 for (int i = 0; argv[i]; i++) {
3001 m_argvstr.push_back(argv[i]);
3008 success = m_cmdline->parse() &&
3010 if (m_err.str().size()) {
3011 m_out << endl << m_err.str();
3013 CPPUNIT_ASSERT(success);
3016 ostringstream m_out, m_err;
3017 cxxptr<Cmdline> m_cmdline;
3020 vector<string> m_argvstr;
3021 boost::scoped_array<const char *> m_argv;
3024 string ScheduleWorldConfig() {
3025 string config = m_scheduleWorldConfig;
3028 // Currently we don't have an icon for ScheduleWorld. If we
3029 // had (MB #2062) one, then this code would ensure that the
3030 // reference config also has the right path for it.
3031 const char *templateDir = getenv("SYNCEVOLUTION_TEMPLATE_DIR");
3033 templateDir = TEMPLATE_DIR;
3037 if (isDir(string(templateDir) + "/ScheduleWorld")) {
3038 boost::replace_all(config,
3040 string("IconURI = file://") + templateDir + "/ScheduleWorld/icon.png");
3046 string OldScheduleWorldConfig() {
3049 "spds/syncml/config.txt:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
3050 "spds/syncml/config.txt:username = your SyncML server account name\n"
3051 "spds/syncml/config.txt:password = your SyncML server password\n"
3052 "spds/syncml/config.txt:# logdir = \n"
3053 "spds/syncml/config.txt:# loglevel = 0\n"
3054 "spds/syncml/config.txt:# printChanges = 1\n"
3055 "spds/syncml/config.txt:# maxlogdirs = 10\n"
3056 "spds/syncml/config.txt:# autoSync = 0\n"
3057 "spds/syncml/config.txt:# autoSyncInterval = 30M\n"
3058 "spds/syncml/config.txt:# autoSyncDelay = 5M\n"
3059 "spds/syncml/config.txt:# preventSlowSync = 1\n"
3060 "spds/syncml/config.txt:# useProxy = 0\n"
3061 "spds/syncml/config.txt:# proxyHost = \n"
3062 "spds/syncml/config.txt:# proxyUsername = \n"
3063 "spds/syncml/config.txt:# proxyPassword = \n"
3064 "spds/syncml/config.txt:# clientAuthType = md5\n"
3065 "spds/syncml/config.txt:# RetryDuration = 5M\n"
3066 "spds/syncml/config.txt:# RetryInterval = 2M\n"
3067 "spds/syncml/config.txt:# remoteIdentifier = \n"
3068 "spds/syncml/config.txt:# PeerIsClient = 0\n"
3069 "spds/syncml/config.txt:# SyncMLVersion = \n"
3070 "spds/syncml/config.txt:# PeerName = \n"
3071 "spds/syncml/config.txt:deviceId = fixed-devid\n" /* this is not the default! */
3072 "spds/syncml/config.txt:# remoteDeviceId = \n"
3073 "spds/syncml/config.txt:# enableWBXML = 1\n"
3074 "spds/syncml/config.txt:# maxMsgSize = 150000\n"
3075 "spds/syncml/config.txt:# maxObjSize = 4000000\n"
3076 "spds/syncml/config.txt:# enableCompression = 0\n"
3077 #ifdef ENABLE_LIBSOUP
3078 // path to SSL certificates is only set for libsoup
3079 "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"
3082 "spds/syncml/config.txt:# SSLServerCertificates = \n"
3084 "spds/syncml/config.txt:# SSLVerifyServer = 1\n"
3085 "spds/syncml/config.txt:# SSLVerifyHost = 1\n"
3086 "spds/syncml/config.txt:WebURL = http://www.scheduleworld.com\n"
3087 "spds/syncml/config.txt:# IconURI = \n"
3088 "spds/syncml/config.txt:ConsumerReady = 1\n"
3089 "spds/sources/addressbook/config.txt:sync = two-way\n"
3090 "spds/sources/addressbook/config.txt:type = addressbook:text/vcard\n"
3091 "spds/sources/addressbook/config.txt:# evolutionsource = \n"
3092 "spds/sources/addressbook/config.txt:uri = card3\n"
3093 "spds/sources/addressbook/config.txt:# evolutionuser = \n"
3094 "spds/sources/addressbook/config.txt:# evolutionpassword = \n"
3095 "spds/sources/calendar/config.txt:sync = two-way\n"
3096 "spds/sources/calendar/config.txt:type = calendar\n"
3097 "spds/sources/calendar/config.txt:# evolutionsource = \n"
3098 "spds/sources/calendar/config.txt:uri = cal2\n"
3099 "spds/sources/calendar/config.txt:# evolutionuser = \n"
3100 "spds/sources/calendar/config.txt:# evolutionpassword = \n"
3101 "spds/sources/memo/config.txt:sync = two-way\n"
3102 "spds/sources/memo/config.txt:type = memo\n"
3103 "spds/sources/memo/config.txt:# evolutionsource = \n"
3104 "spds/sources/memo/config.txt:uri = note\n"
3105 "spds/sources/memo/config.txt:# evolutionuser = \n"
3106 "spds/sources/memo/config.txt:# evolutionpassword = \n"
3107 "spds/sources/todo/config.txt:sync = two-way\n"
3108 "spds/sources/todo/config.txt:type = todo\n"
3109 "spds/sources/todo/config.txt:# evolutionsource = \n"
3110 "spds/sources/todo/config.txt:uri = task2\n"
3111 "spds/sources/todo/config.txt:# evolutionuser = \n"
3112 "spds/sources/todo/config.txt:# evolutionpassword = \n";
3116 string FunambolConfig() {
3117 string config = m_scheduleWorldConfig;
3118 boost::replace_all(config, "/scheduleworld/", "/funambol/");
3120 boost::replace_first(config,
3121 "syncURL = http://sync.scheduleworld.com/funambol/ds",
3122 "syncURL = http://my.funambol.com/sync");
3124 boost::replace_first(config,
3125 "WebURL = http://www.scheduleworld.com",
3126 "WebURL = http://my.funambol.com");
3128 boost::replace_first(config,
3129 "# enableWBXML = 1",
3132 boost::replace_first(config,
3133 "# RetryInterval = 2M",
3134 "RetryInterval = 0");
3136 boost::replace_first(config,
3137 "addressbook/config.ini:uri = card3",
3138 "addressbook/config.ini:uri = card");
3139 boost::replace_all(config,
3140 "addressbook/config.ini:type = addressbook:text/vcard",
3141 "addressbook/config.ini:type = addressbook");
3143 boost::replace_first(config,
3144 "calendar/config.ini:uri = cal2",
3145 "calendar/config.ini:uri = event");
3146 boost::replace_all(config,
3147 "calendar/config.ini:type = calendar",
3148 "calendar/config.ini:type = calendar:text/calendar!");
3150 boost::replace_first(config,
3151 "todo/config.ini:uri = task2",
3152 "todo/config.ini:uri = task");
3153 boost::replace_all(config,
3154 "todo/config.ini:type = todo",
3155 "todo/config.ini:type = todo:text/calendar!");
3160 string SynthesisConfig() {
3161 string config = m_scheduleWorldConfig;
3162 boost::replace_all(config, "/scheduleworld/", "/synthesis/");
3164 boost::replace_first(config,
3165 "syncURL = http://sync.scheduleworld.com/funambol/ds",
3166 "syncURL = http://www.synthesis.ch/sync");
3168 boost::replace_first(config,
3169 "WebURL = http://www.scheduleworld.com",
3170 "WebURL = http://www.synthesis.ch");
3172 boost::replace_first(config,
3173 "ConsumerReady = 1",
3174 "# ConsumerReady = 0");
3176 boost::replace_first(config,
3177 "addressbook/config.ini:uri = card3",
3178 "addressbook/config.ini:uri = contacts");
3179 boost::replace_all(config,
3180 "addressbook/config.ini:type = addressbook:text/vcard",
3181 "addressbook/config.ini:type = addressbook");
3183 boost::replace_first(config,
3184 "calendar/config.ini:uri = cal2",
3185 "calendar/config.ini:uri = events");
3186 boost::replace_first(config,
3187 "calendar/config.ini:sync = two-way",
3188 "calendar/config.ini:sync = disabled");
3190 boost::replace_first(config,
3191 "memo/config.ini:uri = note",
3192 "memo/config.ini:uri = notes");
3194 boost::replace_first(config,
3195 "todo/config.ini:uri = task2",
3196 "todo/config.ini:uri = tasks");
3197 boost::replace_first(config,
3198 "todo/config.ini:sync = two-way",
3199 "todo/config.ini:sync = disabled");
3204 /** create directory hierarchy, overwriting previous content */
3205 void createFiles(const string &root, const string &content, bool append = false) {
3214 out.exceptions(ios_base::badbit|ios_base::failbit);
3215 while (start < content.size()) {
3216 size_t delim = content.find(':', start);
3217 size_t end = content.find('\n', start);
3218 if (delim == content.npos ||
3219 end == content.npos) {
3220 // invalid content ?!
3223 string newname = content.substr(start, delim - start);
3224 string line = content.substr(delim + 1, end - delim - 1);
3225 if (newname != outname) {
3226 if (out.is_open()) {
3229 string fullpath = root + "/" + newname;
3230 size_t fileoff = fullpath.rfind('/');
3231 mkdir_p(fullpath.substr(0, fileoff));
3232 out.open(fullpath.c_str(),
3233 append ? ios_base::out : (ios_base::out|ios_base::trunc));
3236 out << line << endl;
3241 /** turn directory hierarchy into string
3243 * @param root root path in file system
3244 * @param peer if non-empty, then ignore all <root>/peers/<foo> directories
3245 * where <foo> != peer
3246 * @param onlyProps ignore lines which are comments
3248 string scanFiles(const string &root, const string &peer = "", bool onlyProps = true) {
3251 scanFiles(root, "", peer, out, onlyProps);
3255 void scanFiles(const string &root, const string &dir, const string &peer, ostringstream &out, bool onlyProps) {
3256 string newroot = root;
3259 ReadDir readDir(newroot);
3260 sort(readDir.begin(), readDir.end());
3262 BOOST_FOREACH(const string &entry, readDir) {
3263 if (isDir(newroot + "/" + entry)) {
3264 if (boost::ends_with(newroot, "/peers") &&
3267 // skip different peer directory
3270 scanFiles(root, dir + (dir.empty() ? "" : "/") + entry, peer, out, onlyProps);
3274 in.exceptions(ios_base::badbit /* failbit must not trigger exception because is set when reaching eof ?! */);
3275 in.open((newroot + "/" + entry).c_str());
3279 if ((line.size() || !in.eof()) &&
3281 (boost::starts_with(line, "# ") ?
3282 isPropAssignment(line.substr(2)) :
3287 out << entry << ":";
3288 out << line << '\n';
3295 string printConfig(const string &server) {
3296 ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
3297 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
3298 ScopedEnvChange home("HOME", m_testDir);
3300 TestCmdline cmdline("--print-config", server.c_str(), NULL);
3302 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
3303 return cmdline.m_out.str();
3307 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(CmdlineTest);
3309 #endif // ENABLE_UNIT_TESTS