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 "SyncEvolutionCmdline.h"
22 #include "FilterConfigNode.h"
23 #include "VolatileConfigNode.h"
24 #include "EvolutionSyncSource.h"
25 #include "EvolutionSyncClient.h"
26 #include "SyncEvolutionUtil.h"
39 #include <boost/shared_ptr.hpp>
40 #include <boost/algorithm/string/join.hpp>
41 #include <boost/algorithm/string.hpp>
42 #include <boost/foreach.hpp>
44 SyncEvolutionCmdline::SyncEvolutionCmdline(int argc, const char * const * argv, ostream &out, ostream &err) :
49 m_validSyncProps(EvolutionSyncConfig::getRegistry()),
50 m_validSourceProps(EvolutionSyncSourceConfig::getRegistry())
53 bool SyncEvolutionCmdline::parse()
56 while (opt < m_argc) {
57 if (m_argv[opt][0] != '-') {
60 if (boost::iequals(m_argv[opt], "--sync") ||
61 boost::iequals(m_argv[opt], "-s")) {
64 string cmdopt(m_argv[opt - 1]);
65 if (!parseProp(m_validSourceProps, m_sourceProps,
66 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt],
67 EvolutionSyncSourceConfig::m_sourcePropSync.getName().c_str())) {
71 // disable requirement to add --run explicitly in order to
72 // be compatible with traditional command lines
74 } else if(boost::iequals(m_argv[opt], "--sync-property") ||
75 boost::iequals(m_argv[opt], "-y")) {
77 if (!parseProp(m_validSyncProps, m_syncProps,
78 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
81 } else if(boost::iequals(m_argv[opt], "--source-property") ||
82 boost::iequals(m_argv[opt], "-z")) {
84 if (!parseProp(m_validSourceProps, m_sourceProps,
85 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
88 } else if(boost::iequals(m_argv[opt], "--template") ||
89 boost::iequals(m_argv[opt], "-l")) {
92 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
95 m_template = m_argv[opt];
97 if (boost::trim_copy(m_template) == "?") {
98 dumpServers("Available configuration templates:",
99 EvolutionSyncConfig::getServerTemplates());
102 } else if(boost::iequals(m_argv[opt], "--print-servers")) {
103 m_printServers = true;
104 } else if(boost::iequals(m_argv[opt], "--print-config") ||
105 boost::iequals(m_argv[opt], "-p")) {
106 m_printConfig = true;
107 } else if(boost::iequals(m_argv[opt], "--print-sessions")) {
108 m_printSessions = true;
109 } else if(boost::iequals(m_argv[opt], "--configure") ||
110 boost::iequals(m_argv[opt], "-c")) {
112 } else if(boost::iequals(m_argv[opt], "--remove")) {
114 } else if(boost::iequals(m_argv[opt], "--run") ||
115 boost::iequals(m_argv[opt], "-r")) {
117 } else if(boost::iequals(m_argv[opt], "--restore")) {
120 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
123 m_restore = m_argv[opt];
124 if (m_restore.empty()) {
125 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
128 if (!isDir(m_restore)) {
129 usage(true, string("parameter '") + m_restore + "' for " + cmdOpt(m_argv[opt - 1]) + " must be log directory");
132 } else if(boost::iequals(m_argv[opt], "--before")) {
134 } else if(boost::iequals(m_argv[opt], "--after")) {
136 } else if(boost::iequals(m_argv[opt], "--dry-run")) {
138 } else if(boost::iequals(m_argv[opt], "--migrate")) {
140 } else if(boost::iequals(m_argv[opt], "--status") ||
141 boost::iequals(m_argv[opt], "-t")) {
143 } else if(boost::iequals(m_argv[opt], "--quiet") ||
144 boost::iequals(m_argv[opt], "-q")) {
146 } else if(boost::iequals(m_argv[opt], "--help") ||
147 boost::iequals(m_argv[opt], "-h")) {
149 } else if(boost::iequals(m_argv[opt], "--version")) {
152 usage(false, string(m_argv[opt]) + ": unknown parameter");
159 m_server = m_argv[opt++];
160 while (opt < m_argc) {
161 m_sources.insert(m_argv[opt++]);
168 bool SyncEvolutionCmdline::run() {
169 // --dry-run is only supported by some operations.
170 // Be very strict about it and make sure it is off in all
171 // potentially harmful operations, otherwise users might
172 // expect it to have an effect when it doesn't.
176 } else if (m_version) {
177 printf("SyncEvolution %s\n", VERSION);
178 printf("%s", EDSAbiWrapperInfo());
179 } else if (m_printServers || boost::trim_copy(m_server) == "?") {
180 dumpServers("Configured servers:",
181 EvolutionSyncConfig::getServers());
182 } else if (m_dontrun) {
183 // user asked for information
184 } else if (m_argc == 1) {
185 // no parameters: list databases and short usage
186 const SourceRegistry ®istry(EvolutionSyncSource::getSourceRegistry());
187 boost::shared_ptr<FilterConfigNode> configNode(new VolatileConfigNode());
188 boost::shared_ptr<FilterConfigNode> hiddenNode(new VolatileConfigNode());
189 boost::shared_ptr<FilterConfigNode> trackingNode(new VolatileConfigNode());
190 SyncSourceNodes nodes(configNode, hiddenNode, trackingNode);
191 EvolutionSyncSourceParams params("list", nodes, "");
193 BOOST_FOREACH(const RegisterSyncSource *source, registry) {
194 BOOST_FOREACH(const Values::value_type &alias, source->m_typeValues) {
195 if (!alias.empty() && source->m_enabled) {
196 configNode->setProperty("type", *alias.begin());
197 auto_ptr<EvolutionSyncSource> source(EvolutionSyncSource::createSource(params, false));
198 if (source.get() != NULL) {
199 listSources(*source, boost::join(alias, " = "));
207 } else if (m_printConfig) {
208 boost::shared_ptr<EvolutionSyncConfig> config;
210 if (m_template.empty()) {
211 if (m_server.empty()) {
212 m_err << "ERROR: --print-config requires either a --template or a server name." << endl;
215 config.reset(new EvolutionSyncConfig(m_server));
216 if (!config->exists()) {
217 m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
221 config = EvolutionSyncConfig::createServerTemplate(m_template);
223 m_err << "ERROR: no configuration template for '" << m_template << "' available." << endl;
228 if (m_sources.empty() ||
229 m_sources.find("main") != m_sources.end()) {
230 boost::shared_ptr<FilterConfigNode> syncProps(config->getProperties());
231 syncProps->setFilter(m_syncProps);
232 dumpProperties(*syncProps, config->getRegistry());
235 list<string> sources = config->getSyncSources();
237 BOOST_FOREACH(const string &name, sources) {
238 if (m_sources.empty() ||
239 m_sources.find(name) != m_sources.end()) {
240 m_out << endl << "[" << name << "]" << endl;
241 ConstSyncSourceNodes nodes = config->getSyncSourceNodes(name);
242 boost::shared_ptr<FilterConfigNode> sourceProps(new FilterConfigNode(boost::shared_ptr<const ConfigNode>(nodes.m_configNode)));
243 sourceProps->setFilter(m_sourceProps);
244 dumpProperties(*sourceProps, EvolutionSyncSourceConfig::getRegistry());
247 } else if (m_server == "" && m_argc > 1) {
248 // Options given, but no server - not sure what the user wanted?!
249 usage(true, "server name missing");
251 } else if (m_configure || m_migrate) {
253 EvolutionSyncClient::throwError("--dry-run not supported for configuration changes");
256 bool fromScratch = false;
258 // Both config changes and migration are implemented as copying from
259 // another config (template resp. old one). Migration also moves
261 boost::shared_ptr<EvolutionSyncConfig> from;
263 from.reset(new EvolutionSyncConfig(m_server));
264 if (!from->exists()) {
265 m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
270 string oldRoot = from->getRootPath();
274 ostringstream newsuffix;
277 newsuffix << "." << counter;
279 suffix = newsuffix.str();
280 newname = oldRoot + suffix;
281 if (!rename(oldRoot.c_str(),
284 } else if (errno != EEXIST && errno != ENOTEMPTY) {
285 m_err << "ERROR: renaming " << oldRoot << " to " <<
286 newname << ": " << strerror(errno) << endl;
292 from.reset(new EvolutionSyncConfig(m_server + suffix));
294 from.reset(new EvolutionSyncConfig(m_server));
295 if (!from->exists()) {
296 // creating from scratch, look for template
298 string configTemplate = m_template.empty() ? m_server : m_template;
299 from = EvolutionSyncConfig::createServerTemplate(configTemplate);
301 m_err << "ERROR: no configuration template for '" << configTemplate << "' available." << endl;
302 dumpServers("Available configuration templates:",
303 EvolutionSyncConfig::getServerTemplates());
309 // apply config changes on-the-fly
310 from->setConfigFilter(true, m_syncProps);
311 from->setConfigFilter(false, m_sourceProps);
313 // write into the requested configuration, creating it if necessary
314 boost::shared_ptr<EvolutionSyncConfig> to(new EvolutionSyncConfig(m_server));
315 to->copy(*from, !fromScratch && !m_sources.empty() ? &m_sources : NULL);
317 // Sources are active now according to the server default.
318 // Disable all sources not selected by user (if any selected)
319 // and those which have no database.
321 list<string> configuredSources = to->getSyncSources();
322 set<string> sources = m_sources;
324 BOOST_FOREACH(const string &source, configuredSources) {
325 boost::shared_ptr<PersistentEvolutionSyncSourceConfig> sourceConfig(to->getSyncSourceConfig(source));
327 set<string>::iterator entry = sources.find(source);
328 bool selected = entry != sources.end();
330 if (!m_sources.empty() &&
332 disable = "not selected";
334 if (entry != sources.end()) {
335 // The command line parameter matched a valid source.
336 // All entries left afterwards must have been typos.
337 sources.erase(entry);
340 // check whether the sync source works
341 EvolutionSyncSourceParams params("list", to->getSyncSourceNodes(source), "");
342 auto_ptr<EvolutionSyncSource> syncSource(EvolutionSyncSource::createSource(params, false));
343 if (syncSource.get() == NULL) {
344 disable = "no backend available";
347 EvolutionSyncSource::Databases databases = syncSource->getDatabases();
348 if (databases.empty()) {
349 disable = "no database to synchronize";
352 disable = "backend failed";
357 if (!disable.empty()) {
358 // abort if the user explicitly asked for the sync source
359 // and it cannot be enabled, otherwise disable it silently
361 EvolutionSyncClient::throwError(source + ": " + disable);
363 sourceConfig->setSync("disabled");
364 } else if (selected) {
365 // user absolutely wants it: enable even if off by default
366 FilterConfigNode::ConfigFilter::const_iterator sync =
367 m_sourceProps.find(EvolutionSyncSourceConfig::m_sourcePropSync.getName());
368 sourceConfig->setSync(sync == m_sourceProps.end() ? "two-way" : sync->second);
372 if (!sources.empty()) {
373 EvolutionSyncClient::throwError(string("no such source(s): ") + boost::join(sources, " "));
377 // done, now write it
379 } else if (m_remove) {
381 EvolutionSyncClient::throwError("--dry-run not supported for removing configurations");
384 // extra sanity check
385 if (!m_sources.empty() ||
386 !m_syncProps.empty() ||
387 !m_sourceProps.empty()) {
388 usage(true, "too many parameters for --remove");
391 boost::shared_ptr<EvolutionSyncConfig> config;
392 config.reset(new EvolutionSyncConfig(m_server));
397 EvolutionSyncClient client(m_server, true, m_sources);
398 client.setQuiet(m_quiet);
399 client.setDryRun(m_dryrun);
400 client.setConfigFilter(true, m_syncProps);
401 client.setConfigFilter(false, m_sourceProps);
404 } else if (m_printSessions) {
406 client.getSessions(dirs);
408 BOOST_FOREACH(const string &dir, dirs) {
411 } else if(!m_quiet) {
417 client.readSessionInfo(dir, report);
421 } else if (!m_restore.empty()) {
422 // sanity checks: either --after or --before must be given, sources must be selected
423 if ((!m_after && !m_before) ||
424 (m_after && m_before)) {
425 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)");
428 if (m_sources.empty()) {
429 usage(false, "Sources must be selected explicitly for --restore to prevent accidental restore.");
432 client.restore(m_restore,
434 EvolutionSyncClient::DATABASE_AFTER_SYNC :
435 EvolutionSyncClient::DATABASE_BEFORE_SYNC);
438 EvolutionSyncClient::throwError("--dry-run not supported for running a synchronization");
441 // safety catch: if props are given, then --run
444 (m_syncProps.size() || m_sourceProps.size())) {
445 usage(false, "Properties specified, but neither '--configure' nor '--run' - what did you want?");
449 return ( client.sync() == STATUS_OK);
456 string SyncEvolutionCmdline::cmdOpt(const char *opt, const char *param)
468 bool SyncEvolutionCmdline::parseProp(const ConfigPropertyRegistry &validProps,
469 FilterConfigNode::ConfigFilter &props,
472 const char *propname)
475 usage(true, string("missing parameter for ") + cmdOpt(opt, param));
477 } else if (boost::trim_copy(string(param)) == "?") {
480 return listPropValues(validProps, propname, opt);
482 return listProperties(validProps, opt);
491 const char *equal = strchr(param, '=');
493 usage(true, string("the '=<value>' part is missing in: ") + cmdOpt(opt, param));
496 propstr.assign(param, equal - param);
497 paramstr.assign(equal + 1);
500 boost::trim(propstr);
501 boost::trim_left(paramstr);
503 if (boost::trim_copy(paramstr) == "?") {
505 return listPropValues(validProps, propstr, cmdOpt(opt, param));
507 const ConfigProperty *prop = validProps.find(propstr);
509 m_err << "ERROR: " << cmdOpt(opt, param) << ": no such property" << endl;
513 if (!prop->checkValue(paramstr, error)) {
514 m_err << "ERROR: " << cmdOpt(opt, param) << ": " << error << endl;
517 props[propstr] = paramstr;
525 bool SyncEvolutionCmdline::listPropValues(const ConfigPropertyRegistry &validProps,
526 const string &propName,
529 const ConfigProperty *prop = validProps.find(propName);
531 m_err << "ERROR: "<< opt << ": no such property" << endl;
534 m_out << opt << endl;
535 string comment = prop->getComment();
538 list<string> commentLines;
539 ConfigProperty::splitComment(comment, commentLines);
540 BOOST_FOREACH(const string &line, commentLines) {
541 m_out << " " << line << endl;
544 m_out << " no documentation available" << endl;
550 bool SyncEvolutionCmdline::listProperties(const ConfigPropertyRegistry &validProps,
553 // The first of several related properties has a comment.
554 // Remember that comment and print it as late as possible,
555 // that way related properties preceed their comment.
557 BOOST_FOREACH(const ConfigProperty *prop, validProps) {
558 if (!prop->isHidden()) {
559 string newComment = prop->getComment();
561 if (newComment != "") {
562 if (!comment.empty()) {
563 dumpComment(m_out, " ", comment);
566 comment = newComment;
568 m_out << prop->getName() << ":" << endl;
571 dumpComment(m_out, " ", comment);
575 void SyncEvolutionCmdline::listSources(EvolutionSyncSource &syncSource, const string &header)
577 m_out << header << ":\n";
578 EvolutionSyncSource::Databases databases = syncSource.getDatabases();
580 BOOST_FOREACH(const EvolutionSyncSource::Database &database, databases) {
581 m_out << " " << database.m_name << " (" << database.m_uri << ")";
582 if (database.m_isDefault) {
583 m_out << " <default>";
589 void SyncEvolutionCmdline::dumpServers(const string &preamble,
590 const EvolutionSyncConfig::ServerList &servers)
592 m_out << preamble << endl;
593 BOOST_FOREACH(const EvolutionSyncConfig::ServerList::value_type &server,servers) {
594 m_out << " " << server.first << " = " << server.second << endl;
596 if (!servers.size()) {
597 m_out << " none" << endl;
601 void SyncEvolutionCmdline::dumpProperties(const ConfigNode &configuredProps,
602 const ConfigPropertyRegistry &allProps)
604 BOOST_FOREACH(const ConfigProperty *prop, allProps) {
605 if (prop->isHidden()) {
609 string comment = prop->getComment();
610 if (!comment.empty()) {
612 dumpComment(m_out, "# ", comment);
616 prop->getProperty(configuredProps, &isDefault);
620 m_out << prop->getName() << " = " << prop->getProperty(configuredProps) << endl;
624 void SyncEvolutionCmdline::dumpComment(ostream &stream,
625 const string &prefix,
626 const string &comment)
628 list<string> commentLines;
629 ConfigProperty::splitComment(comment, commentLines);
630 BOOST_FOREACH(const string &line, commentLines) {
631 stream << prefix << line << endl;
635 void SyncEvolutionCmdline::usage(bool full, const string &error, const string ¶m)
637 ostream &out(error.empty() ? m_out : m_err);
639 out << "Show available sources:" << endl;
640 out << " " << m_argv[0] << endl;
641 out << "Show information about configuration(s) and sync sessions:" << endl;
642 out << " " << m_argv[0] << " --print-servers" << endl;
643 out << " " << m_argv[0] << " --print-config [--quiet] <server> [sync|<source ...]" << endl;
644 out << " " << m_argv[0] << " --print-sessions [--quiet] <server>" << endl;
645 out << "Show information about SyncEvolution:" << endl;
646 out << " " << m_argv[0] << " --help|-h" << endl;
647 out << " " << m_argv[0] << " --version" << endl;
648 out << "Run a synchronization:" << endl;
649 out << " " << m_argv[0] << " <server> [<source> ...]" << endl;
650 out << " " << m_argv[0] << " --run <options for run> <server> [<source> ...]" << endl;
651 out << "Restore data from the automatic backups:" << endl;
652 out << " " << m_argv[0] << " --restore <session directory> --before|--after [--dry-run] <server> <source> ..." << endl;
653 out << "Remove a configuration:" << endl;
654 out << " " << m_argv[0] << " --remove <server>" << endl;
655 out << "Modify configuration:" << endl;
656 out << " " << m_argv[0] << " --configure <options for configuration> <server> [<source> ...]" << endl;
657 out << " " << m_argv[0] << " --migrate <server>" << endl;
660 "Options:" << endl <<
661 "--sync|-s <mode>" << endl <<
662 "--sync|-s ?" << endl <<
663 " Temporarily synchronize the active sources in that mode. Useful" << endl <<
664 " for a \"refresh-from-server\" or \"refresh-from-client\" sync which" << endl <<
665 " clears all data at one end and copies all items from the other." << endl <<
667 "--print-servers" << endl <<
668 " Prints the names of all configured servers to stdout." << endl <<
670 "--print-config|-p" << endl <<
671 " Prints the complete configuration for the selected server" << endl <<
672 " to stdout, including up-to-date comments for all properties. The" << endl <<
673 " format is the normal .ini format with source configurations in" << endl <<
674 " different sections introduced with [<source>] lines. Can be combined" << endl <<
675 " with --sync-property and --source-property to modify the configuration" << endl <<
676 " on-the-fly. When one or more sources are listed after the <server>" << endl <<
677 " name on the command line, then only the configs of those sources are" << endl <<
678 " printed. Using --quiet suppresses the comments for each property." << endl <<
679 " When setting a --template, then the reference configuration for" << endl <<
680 " that server is printed instead of an existing configuration." << endl <<
682 "--print-sessions" << endl <<
683 " Prints a list of all previous log directories. Unless --quiet is used, each" << endl <<
684 " file name is followed by the original sync report." << endl <<
686 "-–configure|-c" << endl <<
687 " Modify the configuration files for the selected server. If no such" << endl <<
688 " configuration exists, then a new one is created using one of the" << endl <<
689 " template configurations (see --template option). When creating" << endl <<
690 " a new configuration only the active sources will be set to active" << endl <<
691 " in the new configuration, i.e. \"syncevolution -c scheduleworld addressbook\"" << endl <<
692 " followed by \"syncevolution scheduleworld\" will only synchronize the" << endl <<
693 " address book. The other sources are created in a disabled state." << endl <<
694 " When modifying an existing configuration and sources are specified," << endl <<
695 " then the source properties of only those sources are modified." << endl <<
697 "--migrate" << endl <<
698 " In SyncEvolution <= 0.7 a different layout of configuration files" << endl <<
699 " was used. Using --migrate will automatically migrate to the new" << endl <<
700 " layout and rename the old directory $HOME/.sync4j/evolution/<server> " << endl <<
701 " into $HOME/.sync4j/evolution/<server>.old to prevent accidental use" << endl <<
702 " of the old configuration. WARNING: old SyncEvolution releases cannot" << endl <<
703 " use the new configuration!" << endl <<
704 " The switch can also be used to migrate a configuration in the current" << endl <<
705 " configuration directory: this preserves all property values, discards" << endl <<
706 " obsolete properties and sets all comments exactly as if the configuration" << endl <<
707 " had been created from scratch. WARNING: custom comments in the" << endl <<
708 " configuration are not preserved." << endl <<
709 " --migrate implies --configure and can be combined with modifying" << endl <<
710 " properties." << endl <<
712 "--restore" << endl <<
713 " Restores the data of the selected sources to the state from before or after the" << endl <<
714 " selected synchronization. The synchronization is selected via its log directory" << endl <<
715 " (see --print-sessions). Other directories can also be given as long as" << endl <<
716 " they contain database dumps in the format created by SyncEvolution." << endl <<
717 " The output includes information about the changes made during the" << endl <<
718 " restore, both in terms of item changes and content changes (which is" << endl <<
719 " not always the same, see manual for details). This output can be suppressed" << endl <<
720 " with --quiet." << endl <<
721 " In combination with --dry-run, the changes to local data are only simulated." << endl <<
722 " This can be used to check that --restore will not remove valuable information." << endl <<
724 "--remove" << endl <<
725 " This removes only the configuration files and related meta information." << endl <<
726 " If other files were added to the config directory of the server, then" << endl <<
727 " those and the directory will not be removed. Log directories will also" << endl <<
728 " not be removed." << endl <<
730 "--sync-property|-y <property>=<value>" << endl <<
731 "--sync-property|-y ?" << endl <<
732 "--sync-property|-y <property>=?" << endl <<
733 " Overrides a configuration property in the <server>/config.ini file" << endl <<
734 " for the current synchronization run or permanently when --configure" << endl <<
735 " is used to update the configuration. Can be used multiple times." << endl <<
736 " Specifying an unused property will trigger an error message." << endl <<
738 "--source-property|-z <property>=<value>" << endl <<
739 "--source-property|-z ?" << endl <<
740 "--source-property|-z <property>=?" << endl <<
741 " Same as --sync-option, but applies to the configuration of all active" << endl <<
742 " sources. \"--sync <mode>\" is a shortcut for \"--source-option sync=<mode>\"." << endl <<
744 "--template|-l <server name>|default|?" << endl <<
745 " Can be used to select from one of the built-in default configurations" << endl <<
746 " for known SyncML servers. Defaults to the <server> name, so --template" << endl <<
747 " only has to be specified when creating multiple different configurations" << endl <<
748 " for the same server. \"default\" is an alias for \"scheduleworld\" and can be" << endl <<
749 " used as the starting point for servers which do not have a built-in" << endl <<
750 " configuration." << endl <<
751 " Each template contains a pseudo-random device ID. Therefore setting the" << endl <<
752 " \"deviceId\" sync property is only necessary when manually recreating a" << endl <<
753 " configuration or when a more descriptive name is desired." << endl <<
755 "--status|-t" << endl <<
756 " The changes made to local data since the last synchronization are" << endl <<
757 " shown without starting a new one. This can be used to see in advance" << endl <<
758 " whether the local data needs to be synchronized with the server." << endl <<
760 "--quiet|-q" << endl <<
761 " Suppresses most of the normal output during a synchronization. The" << endl <<
762 " log file still contains all the information." << endl <<
764 "--help|-h" << endl <<
765 " Prints usage information." << endl <<
767 "--version" << endl <<
768 " Prints the SyncEvolution version." << endl;
772 out << endl << "ERROR: " << error << endl;
775 out << "INFO: use '" << param << (param[param.size() - 1] == '=' ? "" : " ") <<
776 "?' to get a list of valid parameters" << endl;
780 #ifdef ENABLE_UNIT_TESTS
783 /** simple line-by-line diff */
784 static string diffStrings(const string &lhs, const string &rhs)
788 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
789 string_split_iterator lit =
790 boost::make_split_iterator(lhs, boost::first_finder("\n", boost::is_iequal()));
791 string_split_iterator rit =
792 boost::make_split_iterator(rhs, boost::first_finder("\n", boost::is_iequal()));
793 while (lit != string_split_iterator() &&
794 rit != string_split_iterator()) {
796 res << "< " << *lit << endl;
797 res << "> " << *rit << endl;
803 while (lit != string_split_iterator()) {
804 res << "< " << *lit << endl;
808 while (rit != string_split_iterator()) {
809 res << "> " << *rit << endl;
816 # define CPPUNIT_ASSERT_EQUAL_DIFF( expected, actual ) \
818 string expected_ = (expected); \
819 string actual_ = (actual); \
820 if (expected_ != actual_) { \
821 CPPUNIT_NS::Message cpputMsg_(string("expected:\n") + \
823 cpputMsg_.addDetail(string("actual:\n") + \
825 cpputMsg_.addDetail(string("diff:\n") + \
826 diffStrings(expected_, actual_)); \
827 CPPUNIT_NS::Asserter::fail( cpputMsg_, \
828 CPPUNIT_SOURCELINE() ); \
832 // returns last line, including trailing line break, empty if input is empty
833 static string lastLine(const string &buffer)
835 if (buffer.size() < 2) {
839 size_t line = buffer.rfind("\n", buffer.size() - 2);
840 if (line == buffer.npos) {
844 return buffer.substr(line + 1);
848 static bool isPropAssignment(const string &buffer) {
850 while (start < buffer.size() &&
851 !isspace(buffer[start])) {
854 if (start + 3 <= buffer.size() &&
855 buffer.substr(start, 3) == " = ") {
862 // remove pure comment lines from buffer,
864 static string filterConfig(const string &buffer)
868 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
869 for (string_split_iterator it =
870 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
871 it != string_split_iterator();
873 string line = boost::copy_range<string>(*it);
875 (!boost::starts_with(line, "# ") ||
876 isPropAssignment(line.substr(2)))) {
884 // remove lines indented with spaces
885 static string filterIndented(const string &buffer)
890 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
891 for (string_split_iterator it =
892 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
893 it != string_split_iterator();
895 if (!boost::starts_with(*it, " ")) {
908 // convert the internal config dump to .ini style
909 static string internalToIni(const string &config)
914 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
915 for (string_split_iterator it =
916 boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal()));
917 it != string_split_iterator();
919 string line(it->begin(), it->end());
924 size_t colon = line.find(':');
925 string prefix = line.substr(0, colon);
926 if (boost::contains(prefix, ".internal.ini") ||
927 boost::contains(line, "= internal value")) {
930 // sources/<name>/config.ini or
931 // spds/sources/<name>/config.ini
932 size_t endslash = prefix.rfind('/');
933 if (endslash != line.npos && endslash > 1) {
934 size_t slash = prefix.rfind('/', endslash - 1);
935 if (slash != line.npos) {
936 string newsource = prefix.substr(slash + 1, endslash - slash - 1);
937 if (newsource != section &&
938 newsource != "syncml") {
939 res << endl << "[" << newsource << "]" << endl;
944 string assignment = line.substr(colon + 1);
945 // substitude aliases with generic values
946 boost::replace_first(assignment, "= F", "= 0");
947 boost::replace_first(assignment, "= T", "= 1");
948 boost::replace_first(assignment, "= syncml:auth-md5", "= md5");
949 boost::replace_first(assignment, "= syncml:auth-basix", "= basic");
950 res << assignment << endl;
958 * Testing is based on a text representation of a directory
959 * hierarchy where each line is of the format
960 * <file path>:<line in file>
962 * The order of files is alphabetical, of lines in the file as
963 * in the file. Lines in the file without line break cannot
966 * The root of the hierarchy is not part of the representation
969 class SyncEvolutionCmdlineTest : public CppUnit::TestFixture {
970 CPPUNIT_TEST_SUITE(SyncEvolutionCmdlineTest);
971 CPPUNIT_TEST(testFramework);
972 CPPUNIT_TEST(testSetupScheduleWorld);
973 CPPUNIT_TEST(testSetupDefault);
974 CPPUNIT_TEST(testSetupRenamed);
975 CPPUNIT_TEST(testSetupFunambol);
976 CPPUNIT_TEST(testSetupSynthesis);
977 CPPUNIT_TEST(testPrintServers);
978 CPPUNIT_TEST(testPrintConfig);
979 CPPUNIT_TEST(testTemplate);
980 CPPUNIT_TEST(testSync);
981 CPPUNIT_TEST(testConfigure);
982 CPPUNIT_TEST(testOldConfigure);
983 CPPUNIT_TEST(testListSources);
984 CPPUNIT_TEST(testMigrate);
985 CPPUNIT_TEST_SUITE_END();
988 SyncEvolutionCmdlineTest() :
989 m_testDir("SyncEvolutionCmdlineTest"),
990 m_scheduleWorldConfig(".internal.ini:# HashCode = 0\n"
991 ".internal.ini:# ConfigDate = \n"
992 "config.ini:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
993 "config.ini:username = your SyncML server account name\n"
994 "config.ini:password = your SyncML server password\n"
995 "config.ini:# logdir = \n"
996 "config.ini:# loglevel = 0\n"
997 "config.ini:# printChanges = 1\n"
998 "config.ini:# maxlogdirs = 10\n"
999 "config.ini:# useProxy = 0\n"
1000 "config.ini:# proxyHost = \n"
1001 "config.ini:# proxyUsername = \n"
1002 "config.ini:# proxyPassword = \n"
1003 "config.ini:# clientAuthType = md5\n"
1004 "config.ini:deviceId = fixed-devid\n" /* this is not the default! */
1005 "config.ini:# enableWBXML = 1\n"
1006 "config.ini:# maxMsgSize = 20000\n"
1007 "config.ini:# maxObjSize = 4000000\n"
1008 "config.ini:# enableCompression = 0\n"
1009 "config.ini:# SSLServerCertificates = \n"
1010 "config.ini:# SSLVerifyServer = 1\n"
1011 "config.ini:# SSLVerifyHost = 1\n"
1012 "config.ini:WebURL = http://sync.scheduleworld.com\n"
1013 "config.ini:# IconURI = \n"
1014 "config.ini:ConsumerReady = 1\n"
1015 "sources/addressbook/.internal.ini:# last = 0\n"
1016 "sources/addressbook/config.ini:sync = two-way\n"
1017 "sources/addressbook/config.ini:type = addressbook:text/vcard\n"
1018 "sources/addressbook/config.ini:# evolutionsource = \n"
1019 "sources/addressbook/config.ini:uri = card3\n"
1020 "sources/addressbook/config.ini:# evolutionuser = \n"
1021 "sources/addressbook/config.ini:# evolutionpassword = \n"
1022 "sources/calendar/.internal.ini:# last = 0\n"
1023 "sources/calendar/config.ini:sync = two-way\n"
1024 "sources/calendar/config.ini:type = calendar\n"
1025 "sources/calendar/config.ini:# evolutionsource = \n"
1026 "sources/calendar/config.ini:uri = cal2\n"
1027 "sources/calendar/config.ini:# evolutionuser = \n"
1028 "sources/calendar/config.ini:# evolutionpassword = \n"
1029 "sources/memo/.internal.ini:# last = 0\n"
1030 "sources/memo/config.ini:sync = two-way\n"
1031 "sources/memo/config.ini:type = memo\n"
1032 "sources/memo/config.ini:# evolutionsource = \n"
1033 "sources/memo/config.ini:uri = note\n"
1034 "sources/memo/config.ini:# evolutionuser = \n"
1035 "sources/memo/config.ini:# evolutionpassword = \n"
1036 "sources/todo/.internal.ini:# last = 0\n"
1037 "sources/todo/config.ini:sync = two-way\n"
1038 "sources/todo/config.ini:type = todo\n"
1039 "sources/todo/config.ini:# evolutionsource = \n"
1040 "sources/todo/config.ini:uri = task2\n"
1041 "sources/todo/config.ini:# evolutionuser = \n"
1042 "sources/todo/config.ini:# evolutionpassword = \n")
1044 #ifdef ENABLE_LIBSOUP
1045 // path to SSL certificates has to be set only for libsoup
1046 boost::replace_first(m_scheduleWorldConfig,
1047 "SSLServerCertificates = ",
1048 "SSLServerCertificates = /etc/ssl/certs/ca-certificates.crt:/etc/pki/tls/certs/ca-bundle.crt:/usr/share/ssl/certs/ca-bundle.crt");
1054 /** verify that createFiles/scanFiles themselves work */
1055 void testFramework() {
1056 const string root(m_testDir);
1057 const string content("baz:line\n"
1059 "caz/subdir2/sub:# comment\n"
1060 "caz/subdir2/sub:# foo = bar\n"
1061 "caz/subdir2/sub:# empty = \n"
1062 "caz/subdir2/sub:# another comment\n"
1067 const string filtered("baz:line\n"
1069 "caz/subdir2/sub:# foo = bar\n"
1070 "caz/subdir2/sub:# empty = \n"
1074 createFiles(root, content);
1075 string res = scanFiles(root);
1076 CPPUNIT_ASSERT_EQUAL_DIFF(filtered, res);
1079 void removeRandomUUID(string &buffer) {
1080 string uuidstr = "deviceId = sc-pim-";
1081 size_t uuid = buffer.find(uuidstr);
1082 CPPUNIT_ASSERT(uuid != buffer.npos);
1083 size_t end = buffer.find("\n", uuid + uuidstr.size());
1084 CPPUNIT_ASSERT(end != buffer.npos);
1085 buffer.replace(uuid, end - uuid, "deviceId = fixed-devid");
1088 /** create new configurations */
1089 void testSetupScheduleWorld() {
1091 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1092 ScopedEnvChange home("HOME", m_testDir);
1095 root += "/syncevolution/scheduleworld";
1099 TestCmdline cmdline("--configure",
1100 "--sync-property", "proxyHost = proxy",
1105 string res = scanFiles(root);
1106 removeRandomUUID(res);
1107 string expected = ScheduleWorldConfig();
1108 boost::replace_first(expected,
1110 "proxyHost = proxy");
1111 boost::replace_all(expected,
1114 boost::replace_first(expected,
1115 "addressbook/config.ini:sync = disabled",
1116 "addressbook/config.ini:sync = two-way");
1117 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
1122 TestCmdline cmdline("--configure",
1123 "--sync-property", "deviceID = fixed-devid",
1127 string res = scanFiles(root);
1128 CPPUNIT_ASSERT_EQUAL_DIFF(ScheduleWorldConfig(), res);
1132 void testSetupDefault() {
1134 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1135 ScopedEnvChange home("HOME", m_testDir);
1138 root += "/syncevolution/some-other-server";
1140 TestCmdline cmdline("--configure",
1141 "--template", "default",
1142 "--sync-property", "deviceID = fixed-devid",
1143 "some-other-server",
1146 string res = scanFiles(root);
1147 CPPUNIT_ASSERT_EQUAL_DIFF(ScheduleWorldConfig(), res);
1149 void testSetupRenamed() {
1151 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1152 ScopedEnvChange home("HOME", m_testDir);
1155 root += "/syncevolution/scheduleworld2";
1157 TestCmdline cmdline("--configure",
1158 "--template", "scheduleworld",
1159 "--sync-property", "deviceID = fixed-devid",
1163 string res = scanFiles(root);
1164 CPPUNIT_ASSERT_EQUAL_DIFF(ScheduleWorldConfig(), res);
1166 void testSetupFunambol() {
1168 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1169 ScopedEnvChange home("HOME", m_testDir);
1172 root += "/syncevolution/funambol";
1174 TestCmdline cmdline("--configure",
1175 "--sync-property", "deviceID = fixed-devid",
1176 // templates are case-insensitive
1180 string res = scanFiles(root);
1181 CPPUNIT_ASSERT_EQUAL_DIFF(FunambolConfig(), res);
1184 void testSetupSynthesis() {
1186 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1187 ScopedEnvChange home("HOME", m_testDir);
1190 root += "/syncevolution/synthesis";
1192 TestCmdline cmdline("--configure",
1193 "--sync-property", "deviceID = fixed-devid",
1197 string res = scanFiles(root);
1198 CPPUNIT_ASSERT_EQUAL_DIFF(SynthesisConfig(), res);
1201 void testTemplate() {
1202 TestCmdline failure("--template", NULL);
1203 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
1204 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1205 CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--template'\n"), lastLine(failure.m_err.str()));
1207 TestCmdline help("--template", "? ", NULL);
1209 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
1210 " Funambol = http://my.funambol.com\n"
1211 " Google = http://m.google.com/sync\n"
1212 " Memotoo = http://www.memotoo.com\n"
1213 " ScheduleWorld = http://sync.scheduleworld.com\n"
1214 " Synthesis = http://www.synthesis.ch\n"
1215 " ZYB = http://www.zyb.com\n",
1217 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
1220 void testPrintServers() {
1221 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1222 ScopedEnvChange home("HOME", m_testDir);
1225 testSetupScheduleWorld();
1226 testSetupSynthesis();
1227 testSetupFunambol();
1229 TestCmdline cmdline("--print-servers", NULL);
1231 CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n"
1232 " scheduleworld = SyncEvolutionCmdlineTest/syncevolution/scheduleworld\n"
1233 " synthesis = SyncEvolutionCmdlineTest/syncevolution/synthesis\n"
1234 " funambol = SyncEvolutionCmdlineTest/syncevolution/funambol\n",
1235 cmdline.m_out.str());
1236 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1239 void testPrintConfig() {
1240 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1241 ScopedEnvChange home("HOME", m_testDir);
1244 testSetupFunambol();
1247 TestCmdline failure("--print-config", NULL);
1248 CPPUNIT_ASSERT(failure.m_cmdline->parse());
1249 CPPUNIT_ASSERT(!failure.m_cmdline->run());
1250 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1251 CPPUNIT_ASSERT_EQUAL(string("ERROR: --print-config requires either a --template or a server name.\n"),
1252 lastLine(failure.m_err.str()));
1256 TestCmdline failure("--print-config", "foo", NULL);
1257 CPPUNIT_ASSERT(failure.m_cmdline->parse());
1258 CPPUNIT_ASSERT(!failure.m_cmdline->run());
1259 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1260 CPPUNIT_ASSERT_EQUAL(string("ERROR: server 'foo' has not been configured yet.\n"),
1261 lastLine(failure.m_err.str()));
1265 TestCmdline failure("--print-config", "--template", "foo", NULL);
1266 CPPUNIT_ASSERT(failure.m_cmdline->parse());
1267 CPPUNIT_ASSERT(!failure.m_cmdline->run());
1268 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1269 CPPUNIT_ASSERT_EQUAL(string("ERROR: no configuration template for 'foo' available.\n"),
1270 lastLine(failure.m_err.str()));
1274 TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL);
1276 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1277 string actual = cmdline.m_out.str();
1278 removeRandomUUID(actual);
1279 string filtered = filterConfig(actual);
1280 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
1282 // there should have been comments
1283 CPPUNIT_ASSERT(actual.size() > filtered.size());
1287 TestCmdline cmdline("--print-config", "--template", "Default", NULL);
1289 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1290 string actual = filterConfig(cmdline.m_out.str());
1291 removeRandomUUID(actual);
1292 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(ScheduleWorldConfig())),
1297 TestCmdline cmdline("--print-config", "funambol", NULL);
1299 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1300 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())),
1301 filterConfig(cmdline.m_out.str()));
1305 TestCmdline cmdline("--print-config", "--template", "scheduleworld",
1306 "--sync-property", "syncURL=foo",
1307 "--source-property", "sync=disabled",
1310 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1311 string expected = filterConfig(internalToIni(ScheduleWorldConfig()));
1312 boost::replace_first(expected,
1313 "syncURL = http://sync.scheduleworld.com/funambol/ds",
1315 boost::replace_all(expected,
1318 string actual = filterConfig(cmdline.m_out.str());
1319 removeRandomUUID(actual);
1320 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
1325 TestCmdline cmdline("--print-config", "--quiet",
1326 "--template", "scheduleworld",
1330 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1331 string actual = cmdline.m_out.str();
1332 removeRandomUUID(actual);
1333 CPPUNIT_ASSERT_EQUAL_DIFF(internalToIni(ScheduleWorldConfig()),
1340 TestCmdline failure("--sync", NULL);
1341 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
1342 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
1343 CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--sync'\n"), lastLine(failure.m_err.str()));
1345 TestCmdline failure2("--sync", "foo", NULL);
1346 CPPUNIT_ASSERT(!failure2.m_cmdline->parse());
1347 CPPUNIT_ASSERT_EQUAL_DIFF("", failure2.m_out.str());
1348 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()));
1350 TestCmdline help("--sync", " ?", NULL);
1352 CPPUNIT_ASSERT_EQUAL_DIFF("--sync\n"
1353 " requests a certain synchronization mode:\n"
1354 " two-way = only send/receive changes since last sync\n"
1355 " slow = exchange all items\n"
1356 " refresh-from-client = discard all remote items and replace with\n"
1357 " the items on the client\n"
1358 " refresh-from-server = discard all local items and replace with\n"
1359 " the items on the server\n"
1360 " one-way-from-client = transmit changes from client\n"
1361 " one-way-from-server = transmit changes from server\n"
1362 " none (or disabled) = synchronization disabled\n",
1364 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
1366 TestCmdline filter("--sync", "refresh-from-server", NULL);
1367 CPPUNIT_ASSERT(filter.m_cmdline->parse());
1368 CPPUNIT_ASSERT(!filter.m_cmdline->run());
1369 CPPUNIT_ASSERT_EQUAL_DIFF("", filter.m_out.str());
1370 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh-from-server",
1371 string(filter.m_cmdline->m_sourceProps));
1372 CPPUNIT_ASSERT_EQUAL_DIFF("",
1373 string(filter.m_cmdline->m_syncProps));
1375 TestCmdline filter2("--source-property", "sync=refresh", NULL);
1376 CPPUNIT_ASSERT(filter2.m_cmdline->parse());
1377 CPPUNIT_ASSERT(!filter2.m_cmdline->run());
1378 CPPUNIT_ASSERT_EQUAL_DIFF("", filter2.m_out.str());
1379 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh",
1380 string(filter2.m_cmdline->m_sourceProps));
1381 CPPUNIT_ASSERT_EQUAL_DIFF("",
1382 string(filter2.m_cmdline->m_syncProps));
1385 void testConfigure() {
1386 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1387 ScopedEnvChange home("HOME", m_testDir);
1390 testSetupScheduleWorld();
1391 doConfigure(ScheduleWorldConfig(), "sources/addressbook/config.ini:");
1393 string syncProperties("syncURL:\n"
1424 "enableCompression:\n"
1426 "SSLServerCertificates:\n"
1428 "SSLVerifyServer:\n"
1436 "ConsumerReady:\n");
1437 string sourceProperties("sync:\n"
1441 "evolutionsource:\n"
1446 "evolutionpassword:\n");
1449 TestCmdline cmdline("--sync-property", "?",
1452 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1453 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties,
1454 filterIndented(cmdline.m_out.str()));
1458 TestCmdline cmdline("--source-property", "?",
1461 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1462 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties,
1463 filterIndented(cmdline.m_out.str()));
1467 TestCmdline cmdline("--source-property", "?",
1468 "--sync-property", "?",
1471 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1472 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties + syncProperties,
1473 filterIndented(cmdline.m_out.str()));
1477 TestCmdline cmdline("--sync-property", "?",
1478 "--source-property", "?",
1481 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1482 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties + sourceProperties,
1483 filterIndented(cmdline.m_out.str()));
1487 void testOldConfigure() {
1488 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1489 ScopedEnvChange home("HOME", m_testDir);
1491 string oldConfig = OldScheduleWorldConfig();
1492 InitList<string> props = InitList<string>("serverNonce") +
1498 BOOST_FOREACH(string &prop, props) {
1499 boost::replace_all(oldConfig,
1501 prop + " = internal value");
1505 createFiles(m_testDir + "/.sync4j/evolution/scheduleworld", oldConfig);
1506 doConfigure(oldConfig, "spds/sources/addressbook/config.txt:");
1509 void doConfigure(const string &SWConfig, const string &addressbookPrefix) {
1513 TestCmdline cmdline("--configure",
1514 "--source-property", "sync = disabled",
1518 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1519 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
1520 expected = filterConfig(internalToIni(SWConfig));
1521 boost::replace_all(expected,
1524 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
1525 filterConfig(printConfig("scheduleworld")));
1529 TestCmdline cmdline("--configure",
1530 "--source-property", "sync = one-way-from-server",
1535 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1536 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
1537 expected = SWConfig;
1538 boost::replace_all(expected,
1541 boost::replace_first(expected,
1542 addressbookPrefix + "sync = disabled",
1543 addressbookPrefix + "sync = one-way-from-server");
1544 expected = filterConfig(internalToIni(expected));
1545 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
1546 filterConfig(printConfig("scheduleworld")));
1550 TestCmdline cmdline("--configure",
1551 "--sync", "two-way",
1552 "-z", "evolutionsource=source",
1553 "--sync-property", "maxlogdirs=20",
1554 "-y", "LOGDIR=logdir",
1558 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1559 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
1560 boost::replace_all(expected,
1561 "sync = one-way-from-server",
1563 boost::replace_all(expected,
1566 boost::replace_all(expected,
1567 "# evolutionsource = ",
1568 "evolutionsource = source");
1569 boost::replace_all(expected,
1570 "# maxlogdirs = 10",
1572 boost::replace_all(expected,
1575 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
1576 filterConfig(printConfig("scheduleworld")));
1580 void testListSources() {
1581 TestCmdline cmdline(NULL);
1583 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1584 // exact output varies, do not test
1587 void testMigrate() {
1588 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1589 ScopedEnvChange home("HOME", m_testDir);
1592 string oldRoot = m_testDir + "/.sync4j/evolution/scheduleworld";
1593 string newRoot = m_testDir + "/syncevolution/scheduleworld";
1595 string oldConfig = OldScheduleWorldConfig();
1598 // migrate old config
1599 createFiles(oldRoot, oldConfig);
1600 string createdConfig = scanFiles(oldRoot);
1601 TestCmdline cmdline("--migrate",
1605 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1606 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
1608 string migratedConfig = scanFiles(newRoot);
1609 CPPUNIT_ASSERT_EQUAL_DIFF(m_scheduleWorldConfig, migratedConfig);
1610 string renamedConfig = scanFiles(oldRoot + ".old");
1611 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
1615 // rewrite existing config
1616 createFiles(newRoot,
1617 "config.ini:# obsolete comment\n"
1618 "config.ini:obsoleteprop = foo\n",
1620 string createdConfig = scanFiles(newRoot);
1622 TestCmdline cmdline("--migrate",
1626 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1627 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
1629 string migratedConfig = scanFiles(newRoot);
1630 CPPUNIT_ASSERT_EQUAL_DIFF(m_scheduleWorldConfig, migratedConfig);
1631 string renamedConfig = scanFiles(newRoot + ".old");
1632 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
1636 // migrate old config with changes, a second time
1637 createFiles(oldRoot, oldConfig);
1638 createFiles(oldRoot,
1639 "spds/sources/addressbook/changes/config.txt:foo = bar\n"
1640 "spds/sources/addressbook/changes/config.txt:foo2 = bar2\n",
1642 string createdConfig = scanFiles(oldRoot);
1644 TestCmdline cmdline("--migrate",
1648 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1649 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
1651 string migratedConfig = scanFiles(newRoot);
1652 string expected = m_scheduleWorldConfig;
1653 boost::replace_first(expected,
1654 "sources/addressbook/config.ini",
1655 "sources/addressbook/.other.ini:foo = bar\n"
1656 "sources/addressbook/.other.ini:foo2 = bar2\n"
1657 "sources/addressbook/config.ini");
1658 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
1659 string renamedConfig = scanFiles(oldRoot + ".old.1");
1660 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
1664 const string m_testDir;
1665 string m_scheduleWorldConfig;
1671 * vararg constructor with NULL termination,
1672 * out and error stream into stringstream members
1676 TestCmdline(const char *arg, ...) {
1678 va_start (argList, arg);
1679 for (const char *curr = arg;
1681 curr = va_arg(argList, const char *)) {
1682 m_argvstr.push_back(curr);
1686 m_argv.reset(new const char *[m_argvstr.size() + 1]);
1687 m_argv[0] = "client-test";
1688 for (size_t index = 0;
1689 index < m_argvstr.size();
1691 m_argv[index + 1] = m_argvstr[index].c_str();
1694 m_cmdline.set(new SyncEvolutionCmdline(m_argvstr.size() + 1, m_argv.get(), m_out, m_err), "cmdline");
1699 success = m_cmdline->parse() &&
1701 if (m_err.str().size()) {
1702 cout << endl << m_err.str();
1704 CPPUNIT_ASSERT(success);
1707 ostringstream m_out, m_err;
1708 cxxptr<SyncEvolutionCmdline> m_cmdline;
1711 vector<string> m_argvstr;
1712 boost::scoped_array<const char *> m_argv;
1715 string ScheduleWorldConfig() {
1716 string config = m_scheduleWorldConfig;
1718 if (isDir(string(TEMPLATE_DIR) + "/ScheduleWorld")) {
1719 boost::replace_all(config,
1721 "IconURI = file://" TEMPLATE_DIR "/ScheduleWorld/icon.png");
1726 string OldScheduleWorldConfig() {
1727 string oldConfig = m_scheduleWorldConfig;
1728 boost::replace_all(oldConfig,
1731 InitList<string> sources = InitList<string>("addressbook") +
1735 BOOST_FOREACH(string &source, sources) {
1736 boost::replace_all(oldConfig,
1737 string("sources/") + source + "/config.ini",
1738 string("spds/sources/") + source + "/config.txt");
1740 boost::replace_all(oldConfig,
1742 "spds/syncml/config.txt");
1746 string FunambolConfig() {
1747 string config = m_scheduleWorldConfig;
1749 boost::replace_first(config,
1750 "syncURL = http://sync.scheduleworld.com/funambol/ds",
1751 "syncURL = http://my.funambol.com/sync");
1753 boost::replace_first(config,
1754 "WebURL = http://sync.scheduleworld.com",
1755 "WebURL = http://my.funambol.com");
1757 boost::replace_first(config,
1758 "# enableWBXML = 1",
1761 boost::replace_first(config,
1762 "addressbook/config.ini:uri = card3",
1763 "addressbook/config.ini:uri = card");
1764 boost::replace_first(config,
1765 "addressbook/config.ini:type = addressbook:text/vcard",
1766 "addressbook/config.ini:type = addressbook");
1768 boost::replace_first(config,
1769 "calendar/config.ini:uri = cal2",
1770 "calendar/config.ini:uri = event");
1771 boost::replace_first(config,
1772 "calendar/config.ini:type = calendar",
1773 "calendar/config.ini:type = calendar:text/calendar!");
1775 boost::replace_first(config,
1776 "todo/config.ini:uri = task2",
1777 "todo/config.ini:uri = task");
1778 boost::replace_first(config,
1779 "todo/config.ini:type = todo",
1780 "todo/config.ini:type = todo:text/calendar!");
1785 string SynthesisConfig() {
1786 string config = m_scheduleWorldConfig;
1787 boost::replace_first(config,
1788 "syncURL = http://sync.scheduleworld.com/funambol/ds",
1789 "syncURL = http://www.synthesis.ch/sync");
1791 boost::replace_first(config,
1792 "WebURL = http://sync.scheduleworld.com",
1793 "WebURL = http://www.synthesis.ch");
1795 boost::replace_first(config,
1796 "ConsumerReady = 1",
1797 "# ConsumerReady = 0");
1799 boost::replace_first(config,
1800 "addressbook/config.ini:uri = card3",
1801 "addressbook/config.ini:uri = contacts");
1802 boost::replace_first(config,
1803 "addressbook/config.ini:type = addressbook:text/vcard",
1804 "addressbook/config.ini:type = addressbook");
1806 boost::replace_first(config,
1807 "calendar/config.ini:uri = cal2",
1808 "calendar/config.ini:uri = events");
1809 boost::replace_first(config,
1810 "calendar/config.ini:sync = two-way",
1811 "calendar/config.ini:sync = disabled");
1813 boost::replace_first(config,
1814 "memo/config.ini:uri = note",
1815 "memo/config.ini:uri = notes");
1817 boost::replace_first(config,
1818 "todo/config.ini:uri = task2",
1819 "todo/config.ini:uri = tasks");
1820 boost::replace_first(config,
1821 "todo/config.ini:sync = two-way",
1822 "todo/config.ini:sync = disabled");
1827 /** temporarily set env variable, restore old value on destruction */
1828 class ScopedEnvChange {
1830 ScopedEnvChange(const string &var, const string &value) :
1833 const char *oldval = getenv(var.c_str());
1838 m_oldvalset = false;
1840 setenv(var.c_str(), value.c_str(), 1);
1845 setenv(m_var.c_str(), m_oldval.c_str(), 1);
1847 unsetenv(m_var.c_str());
1851 string m_var, m_oldval;
1856 /** create directory hierarchy, overwriting previous content */
1857 void createFiles(const string &root, const string &content, bool append = false) {
1866 out.exceptions(ios_base::badbit|ios_base::failbit);
1867 while (start < content.size()) {
1868 size_t delim = content.find(':', start);
1869 size_t end = content.find('\n', start);
1870 if (delim == content.npos ||
1871 end == content.npos) {
1872 // invalid content ?!
1875 string newname = content.substr(start, delim - start);
1876 string line = content.substr(delim + 1, end - delim - 1);
1877 if (newname != outname) {
1878 if (out.is_open()) {
1881 string fullpath = root + "/" + newname;
1882 size_t fileoff = fullpath.rfind('/');
1883 mkdir_p(fullpath.substr(0, fileoff));
1884 out.open(fullpath.c_str(),
1885 append ? ios_base::out : (ios_base::out|ios_base::trunc));
1888 out << line << endl;
1893 /** turn directory hierarchy into string */
1894 string scanFiles(const string &root, bool onlyProps = true) {
1897 scanFiles(root, "", out, onlyProps);
1901 void scanFiles(const string &root, const string &dir, ostringstream &out, bool onlyProps) {
1902 string newroot = root;
1905 ReadDir readDir(newroot);
1906 sort(readDir.begin(), readDir.end());
1908 BOOST_FOREACH(const string &entry, readDir) {
1909 if (isDir(newroot + "/" + entry)) {
1910 scanFiles(root, dir + (dir.empty() ? "" : "/") + entry, out, onlyProps);
1913 in.exceptions(ios_base::badbit /* failbit must not trigger exception because is set when reaching eof ?! */);
1914 in.open((newroot + "/" + entry).c_str());
1918 if ((line.size() || !in.eof()) &&
1920 (boost::starts_with(line, "# ") ?
1921 isPropAssignment(line.substr(2)) :
1926 out << entry << ":";
1927 out << line << '\n';
1934 string printConfig(const string &server) {
1935 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
1936 ScopedEnvChange home("HOME", m_testDir);
1938 TestCmdline cmdline("--print-config", server.c_str(), NULL);
1940 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
1941 return cmdline.m_out.str();
1945 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(SyncEvolutionCmdlineTest);
1947 #endif // ENABLE_UNIT_TESTS