00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "SyncEvolutionCmdline.h"
00021 #include "FilterConfigNode.h"
00022 #include "VolatileConfigNode.h"
00023 #include "EvolutionSyncSource.h"
00024 #include "EvolutionSyncClient.h"
00025 #include "SyncEvolutionUtil.h"
00026
00027 #include <unistd.h>
00028 #include <errno.h>
00029
00030 #include <iostream>
00031 #include <sstream>
00032 #include <memory>
00033 #include <set>
00034 #include <algorithm>
00035 using namespace std;
00036
00037 #include <boost/shared_ptr.hpp>
00038 #include <boost/algorithm/string.hpp>
00039 #include <boost/foreach.hpp>
00040
00041 SyncEvolutionCmdline::SyncEvolutionCmdline(int argc, const char * const * argv, ostream &out, ostream &err) :
00042 m_argc(argc),
00043 m_argv(argv),
00044 m_out(out),
00045 m_err(err),
00046 m_validSyncProps(EvolutionSyncConfig::getRegistry()),
00047 m_validSourceProps(EvolutionSyncSourceConfig::getRegistry())
00048 {}
00049
00050 bool SyncEvolutionCmdline::parse()
00051 {
00052 int opt = 1;
00053 while (opt < m_argc) {
00054 if (m_argv[opt][0] != '-') {
00055 break;
00056 }
00057 if (boost::iequals(m_argv[opt], "--sync") ||
00058 boost::iequals(m_argv[opt], "-s")) {
00059 opt++;
00060 string param;
00061 string cmdopt(m_argv[opt - 1]);
00062 if (!parseProp(m_validSourceProps, m_sourceProps,
00063 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt],
00064 EvolutionSyncSourceConfig::m_sourcePropSync.getName().c_str())) {
00065 return false;
00066 }
00067
00068
00069
00070 m_run = true;
00071 } else if(boost::iequals(m_argv[opt], "--sync-property") ||
00072 boost::iequals(m_argv[opt], "-y")) {
00073 opt++;
00074 if (!parseProp(m_validSyncProps, m_syncProps,
00075 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
00076 return false;
00077 }
00078 } else if(boost::iequals(m_argv[opt], "--source-property") ||
00079 boost::iequals(m_argv[opt], "-z")) {
00080 opt++;
00081 if (!parseProp(m_validSourceProps, m_sourceProps,
00082 m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
00083 return false;
00084 }
00085 } else if(boost::iequals(m_argv[opt], "--properties") ||
00086 boost::iequals(m_argv[opt], "-r")) {
00087 opt++;
00088
00089 m_err << "ERROR: not implemented yet: " << m_argv[opt - 1] << endl;
00090 return false;
00091 } else if(boost::iequals(m_argv[opt], "--template") ||
00092 boost::iequals(m_argv[opt], "-l")) {
00093 opt++;
00094 if (opt >= m_argc) {
00095 usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
00096 return false;
00097 }
00098 m_template = m_argv[opt];
00099 m_configure = true;
00100 if (boost::trim_copy(m_template) == "?") {
00101 dumpServers("Available configuration templates:",
00102 EvolutionSyncConfig::getServerTemplates());
00103 m_dontrun = true;
00104 }
00105 } else if(boost::iequals(m_argv[opt], "--print-servers")) {
00106 m_printServers = true;
00107 } else if(boost::iequals(m_argv[opt], "--print-config") ||
00108 boost::iequals(m_argv[opt], "-p")) {
00109 m_printConfig = true;
00110 } else if(boost::iequals(m_argv[opt], "--configure") ||
00111 boost::iequals(m_argv[opt], "-c")) {
00112 m_configure = true;
00113 } else if(boost::iequals(m_argv[opt], "--run") ||
00114 boost::iequals(m_argv[opt], "-r")) {
00115 m_run = true;
00116 } else if(boost::iequals(m_argv[opt], "--migrate")) {
00117 m_migrate = true;
00118 } else if(boost::iequals(m_argv[opt], "--status") ||
00119 boost::iequals(m_argv[opt], "-t")) {
00120 m_status = true;
00121 } else if(boost::iequals(m_argv[opt], "--quiet") ||
00122 boost::iequals(m_argv[opt], "-q")) {
00123 m_quiet = true;
00124 } else if(boost::iequals(m_argv[opt], "--help") ||
00125 boost::iequals(m_argv[opt], "-h")) {
00126 m_usage = true;
00127 } else if(boost::iequals(m_argv[opt], "--version")) {
00128 m_version = true;
00129 } else {
00130 usage(false, string(m_argv[opt]) + ": unknown parameter");
00131 return false;
00132 }
00133 opt++;
00134 }
00135
00136 if (opt < m_argc) {
00137 m_server = m_argv[opt++];
00138 while (opt < m_argc) {
00139 m_sources.insert(m_argv[opt++]);
00140 }
00141 }
00142
00143 return true;
00144 }
00145
00146 bool SyncEvolutionCmdline::run() {
00147 if (m_usage) {
00148 usage(true);
00149 } else if (m_version) {
00150 printf("SyncEvolution %s\n", VERSION);
00151 printf("%s", EDSAbiWrapperInfo());
00152 } else if (m_printServers || boost::trim_copy(m_server) == "?") {
00153 dumpServers("Configured servers:",
00154 EvolutionSyncConfig::getServers());
00155 } else if (m_dontrun) {
00156
00157 } else if (m_argc == 1) {
00158 const SourceRegistry ®istry(EvolutionSyncSource::getSourceRegistry());
00159 boost::shared_ptr<FilterConfigNode> configNode(new VolatileConfigNode());
00160 boost::shared_ptr<FilterConfigNode> hiddenNode(new VolatileConfigNode());
00161 boost::shared_ptr<FilterConfigNode> trackingNode(new VolatileConfigNode());
00162 SyncSourceNodes nodes(configNode, hiddenNode, trackingNode);
00163 EvolutionSyncSourceParams params("list", nodes, "");
00164
00165 BOOST_FOREACH(const RegisterSyncSource *source, registry) {
00166 BOOST_FOREACH(const Values::value_type &alias, source->m_typeValues) {
00167 if (!alias.empty() && source->m_enabled) {
00168 configNode->setProperty("type", *alias.begin());
00169 auto_ptr<EvolutionSyncSource> source(EvolutionSyncSource::createSource(params, false));
00170 if (source.get() != NULL) {
00171 listSources(*source, boost::join(alias, " = "));
00172 m_out << "\n";
00173 }
00174 }
00175 }
00176 }
00177
00178 usage(false);
00179 } else if (m_printConfig) {
00180 boost::shared_ptr<EvolutionSyncConfig> config;
00181
00182 if (m_template.empty()) {
00183 if (m_server.empty()) {
00184 m_err << "ERROR: --print-config requires either a --template or a server name." << endl;
00185 return false;
00186 }
00187 config.reset(new EvolutionSyncConfig(m_server));
00188 if (!config->exists()) {
00189 m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
00190 return false;
00191 }
00192 } else {
00193 config = EvolutionSyncConfig::createServerTemplate(m_template);
00194 if (!config.get()) {
00195 m_err << "ERROR: no configuration template for '" << m_template << "' available." << endl;
00196 return false;
00197 }
00198 }
00199
00200 if (m_sources.empty() ||
00201 m_sources.find("main") != m_sources.end()) {
00202 boost::shared_ptr<FilterConfigNode> syncProps(config->getProperties());
00203 syncProps->setFilter(m_syncProps);
00204 dumpProperties(*syncProps, config->getRegistry());
00205 }
00206
00207 list<string> sources = config->getSyncSources();
00208 sources.sort();
00209 BOOST_FOREACH(const string &name, sources) {
00210 if (m_sources.empty() ||
00211 m_sources.find(name) != m_sources.end()) {
00212 m_out << endl << "[" << name << "]" << endl;
00213 ConstSyncSourceNodes nodes = config->getSyncSourceNodes(name);
00214 boost::shared_ptr<FilterConfigNode> sourceProps(new FilterConfigNode(boost::shared_ptr<const ConfigNode>(nodes.m_configNode)));
00215 sourceProps->setFilter(m_sourceProps);
00216 dumpProperties(*sourceProps, EvolutionSyncSourceConfig::getRegistry());
00217 }
00218 }
00219 } else if (m_server == "" && m_argc > 1) {
00220
00221 usage(true, "server name missing");
00222 return false;
00223 } else if (m_configure || m_migrate) {
00224 bool fromScratch = false;
00225
00226
00227
00228
00229 boost::shared_ptr<EvolutionSyncConfig> from;
00230 if (m_migrate) {
00231 from.reset(new EvolutionSyncConfig(m_server));
00232 if (!from->exists()) {
00233 m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
00234 return false;
00235 }
00236
00237 int counter = 0;
00238 string oldRoot = from->getRootPath();
00239 string suffix;
00240 while (true) {
00241 string newname;
00242 ostringstream newsuffix;
00243 newsuffix << ".old";
00244 if (counter) {
00245 newsuffix << "." << counter;
00246 }
00247 suffix = newsuffix.str();
00248 newname = oldRoot + suffix;
00249 if (!rename(oldRoot.c_str(),
00250 newname.c_str())) {
00251 break;
00252 } else if (errno != EEXIST && errno != ENOTEMPTY) {
00253 m_err << "ERROR: renaming " << oldRoot << " to " <<
00254 newname << ": " << strerror(errno) << endl;
00255 return false;
00256 }
00257 counter++;
00258 }
00259
00260 from.reset(new EvolutionSyncConfig(m_server + suffix));
00261 } else {
00262 from.reset(new EvolutionSyncConfig(m_server));
00263 if (!from->exists()) {
00264
00265 fromScratch = true;
00266 string configTemplate = m_template.empty() ? m_server : m_template;
00267 from = EvolutionSyncConfig::createServerTemplate(configTemplate);
00268 if (!from.get()) {
00269 m_err << "ERROR: no configuration template for '" << configTemplate << "' available." << endl;
00270 dumpServers("Available configuration templates:",
00271 EvolutionSyncConfig::getServerTemplates());
00272 return false;
00273 }
00274 }
00275 }
00276
00277
00278 from->setConfigFilter(true, m_syncProps);
00279 from->setConfigFilter(false, m_sourceProps);
00280
00281
00282 boost::shared_ptr<EvolutionSyncConfig> to(new EvolutionSyncConfig(m_server));
00283 to->copy(*from, !fromScratch && !m_sources.empty() ? &m_sources : NULL);
00284
00285
00286
00287
00288 if (fromScratch) {
00289 list<string> configuredSources = to->getSyncSources();
00290 set<string> sources = m_sources;
00291
00292 BOOST_FOREACH(const string &source, configuredSources) {
00293 boost::shared_ptr<PersistentEvolutionSyncSourceConfig> sourceConfig(to->getSyncSourceConfig(source));
00294 string disable = "";
00295 set<string>::iterator entry = sources.find(source);
00296 bool selected = entry != sources.end();
00297
00298 if (!m_sources.empty() &&
00299 !selected) {
00300 disable = "not selected";
00301 } else {
00302 if (entry != sources.end()) {
00303
00304
00305 sources.erase(entry);
00306 }
00307
00308
00309 EvolutionSyncSourceParams params("list", to->getSyncSourceNodes(source), "");
00310 auto_ptr<EvolutionSyncSource> syncSource(EvolutionSyncSource::createSource(params, false));
00311 if (syncSource.get() == NULL) {
00312 disable = "no backend available";
00313 } else {
00314 try {
00315 EvolutionSyncSource::Databases databases = syncSource->getDatabases();
00316 if (databases.empty()) {
00317 disable = "no database to synchronize";
00318 }
00319 } catch (...) {
00320 disable = "backend failed";
00321 }
00322 }
00323 }
00324
00325 if (!disable.empty()) {
00326
00327
00328 if (selected) {
00329 EvolutionSyncClient::throwError(source + ": " + disable);
00330 }
00331 sourceConfig->setSync("disabled");
00332 } else if (selected) {
00333
00334 FilterConfigNode::ConfigFilter::const_iterator sync =
00335 m_sourceProps.find(EvolutionSyncSourceConfig::m_sourcePropSync.getName());
00336 sourceConfig->setSync(sync == m_sourceProps.end() ? "two-way" : sync->second);
00337 }
00338 }
00339
00340 if (!sources.empty()) {
00341 EvolutionSyncClient::throwError(string("no such source(s): ") + boost::join(sources, " "));
00342 }
00343 }
00344
00345
00346 to->flush();
00347 } else {
00348 EvolutionSyncClient client(m_server, true, m_sources);
00349 client.setQuiet(m_quiet);
00350 client.setConfigFilter(true, m_syncProps);
00351 client.setConfigFilter(false, m_sourceProps);
00352 if (m_status) {
00353 client.status();
00354 } else {
00355
00356
00357 if (!m_run &&
00358 (m_syncProps.size() || m_sourceProps.size())) {
00359 usage(false, "Properties specified, but neither '--configure' nor '--run' - what did you want?");
00360 return false;
00361 }
00362
00363 return client.sync() == 0;
00364 }
00365 }
00366
00367 return true;
00368 }
00369
00370 string SyncEvolutionCmdline::cmdOpt(const char *opt, const char *param)
00371 {
00372 string res = "'";
00373 res += opt;
00374 if (param) {
00375 res += " ";
00376 res += param;
00377 }
00378 res += "'";
00379 return res;
00380 }
00381
00382 bool SyncEvolutionCmdline::parseProp(const ConfigPropertyRegistry &validProps,
00383 FilterConfigNode::ConfigFilter &props,
00384 const char *opt,
00385 const char *param,
00386 const char *propname)
00387 {
00388 if (!param) {
00389 usage(true, string("missing parameter for ") + cmdOpt(opt, param));
00390 return false;
00391 } else if (boost::trim_copy(string(param)) == "?") {
00392 m_dontrun = true;
00393 if (propname) {
00394 return listPropValues(validProps, propname, opt);
00395 } else {
00396 return listProperties(validProps, opt);
00397 }
00398 } else {
00399 string propstr;
00400 string paramstr;
00401 if (propname) {
00402 propstr = propname;
00403 paramstr = param;
00404 } else {
00405 const char *equal = strchr(param, '=');
00406 if (!equal) {
00407 usage(true, string("the '=<value>' part is missing in: ") + cmdOpt(opt, param));
00408 return false;
00409 }
00410 propstr.assign(param, equal - param);
00411 paramstr.assign(equal + 1);
00412 }
00413
00414 boost::trim(propstr);
00415 boost::trim_left(paramstr);
00416
00417 if (boost::trim_copy(paramstr) == "?") {
00418 m_dontrun = true;
00419 return listPropValues(validProps, propstr, cmdOpt(opt, param));
00420 } else {
00421 const ConfigProperty *prop = validProps.find(propstr);
00422 if (!prop) {
00423 m_err << "ERROR: " << cmdOpt(opt, param) << ": no such property" << endl;
00424 return false;
00425 } else {
00426 string error;
00427 if (!prop->checkValue(paramstr, error)) {
00428 m_err << "ERROR: " << cmdOpt(opt, param) << ": " << error << endl;
00429 return false;
00430 } else {
00431 props[propstr] = paramstr;
00432 return true;
00433 }
00434 }
00435 }
00436 }
00437 }
00438
00439 bool SyncEvolutionCmdline::listPropValues(const ConfigPropertyRegistry &validProps,
00440 const string &propName,
00441 const string &opt)
00442 {
00443 const ConfigProperty *prop = validProps.find(propName);
00444 if (!prop) {
00445 m_err << "ERROR: "<< opt << ": no such property" << endl;
00446 return false;
00447 } else {
00448 m_out << opt << endl;
00449 string comment = prop->getComment();
00450
00451 if (comment != "") {
00452 list<string> commentLines;
00453 ConfigProperty::splitComment(comment, commentLines);
00454 BOOST_FOREACH(const string &line, commentLines) {
00455 m_out << " " << line << endl;
00456 }
00457 } else {
00458 m_out << " no documentation available" << endl;
00459 }
00460 return true;
00461 }
00462 }
00463
00464 bool SyncEvolutionCmdline::listProperties(const ConfigPropertyRegistry &validProps,
00465 const string &opt)
00466 {
00467
00468
00469
00470 string comment;
00471 BOOST_FOREACH(const ConfigProperty *prop, validProps) {
00472 if (!prop->isHidden()) {
00473 string newComment = prop->getComment();
00474
00475 if (newComment != "") {
00476 if (!comment.empty()) {
00477 dumpComment(m_out, " ", comment);
00478 m_out << endl;
00479 }
00480 comment = newComment;
00481 }
00482 m_out << prop->getName() << ":" << endl;
00483 }
00484 }
00485 dumpComment(m_out, " ", comment);
00486 return true;
00487 }
00488
00489 void SyncEvolutionCmdline::listSources(EvolutionSyncSource &syncSource, const string &header)
00490 {
00491 m_out << header << ":\n";
00492 EvolutionSyncSource::Databases databases = syncSource.getDatabases();
00493
00494 BOOST_FOREACH(const EvolutionSyncSource::Database &database, databases) {
00495 m_out << " " << database.m_name << " (" << database.m_uri << ")";
00496 if (database.m_isDefault) {
00497 m_out << " <default>";
00498 }
00499 m_out << endl;
00500 }
00501 }
00502
00503 void SyncEvolutionCmdline::dumpServers(const string &preamble,
00504 const EvolutionSyncConfig::ServerList &servers)
00505 {
00506 m_out << preamble << endl;
00507 BOOST_FOREACH(const EvolutionSyncConfig::ServerList::value_type &server,servers) {
00508 m_out << " " << server.first << " = " << server.second << endl;
00509 }
00510 if (!servers.size()) {
00511 m_out << " none" << endl;
00512 }
00513 }
00514
00515 void SyncEvolutionCmdline::dumpProperties(const ConfigNode &configuredProps,
00516 const ConfigPropertyRegistry &allProps)
00517 {
00518 BOOST_FOREACH(const ConfigProperty *prop, allProps) {
00519 if (prop->isHidden()) {
00520 continue;
00521 }
00522 if (!m_quiet) {
00523 string comment = prop->getComment();
00524 if (!comment.empty()) {
00525 m_out << endl;
00526 dumpComment(m_out, "# ", comment);
00527 }
00528 }
00529 bool isDefault;
00530 prop->getProperty(configuredProps, &isDefault);
00531 if (isDefault) {
00532 m_out << "# ";
00533 }
00534 m_out << prop->getName() << " = " << prop->getProperty(configuredProps) << endl;
00535 }
00536 }
00537
00538 void SyncEvolutionCmdline::dumpComment(ostream &stream,
00539 const string &prefix,
00540 const string &comment)
00541 {
00542 list<string> commentLines;
00543 ConfigProperty::splitComment(comment, commentLines);
00544 BOOST_FOREACH(const string &line, commentLines) {
00545 stream << prefix << line << endl;
00546 }
00547 }
00548
00549 void SyncEvolutionCmdline::usage(bool full, const string &error, const string ¶m)
00550 {
00551 ostream &out(error.empty() ? m_out : m_err);
00552
00553 out << "Show available sources:" << endl;
00554 out << " " << m_argv[0] << endl;
00555 out << "Show information about configuration(s):" << endl;
00556 out << " " << m_argv[0] << " --print-servers" << endl;
00557 out << " " << m_argv[0] << " --print-config [--quiet] <server> [sync|<source ...]" << endl;
00558 out << "Show information about SyncEvolution:" << endl;
00559 out << " " << m_argv[0] << " --help|-h" << endl;
00560 out << " " << m_argv[0] << " --version" << endl;
00561 out << "Run a synchronization:" << endl;
00562 out << " " << m_argv[0] << " <server> [<source> ...]" << endl;
00563 out << " " << m_argv[0] << " --run <options for run> <server> [<source> ...]" << endl;
00564 out << "Modify configuration:" << endl;
00565 out << " " << m_argv[0] << " --configure <options for configuration> <server> [<source> ...]" << endl;
00566 out << " " << m_argv[0] << " --migrate <server>" << endl;
00567 if (full) {
00568 out << endl <<
00569 "Options:" << endl <<
00570 "--sync|-s <mode>" << endl <<
00571 "--sync|-s ?" << endl <<
00572 " Temporarily synchronize the active sources in that mode. Useful" << endl <<
00573 " for a \"refresh-from-server\" or \"refresh-from-client\" sync which" << endl <<
00574 " clears all data at one end and copies all items from the other." << endl <<
00575 "" << endl <<
00576 "--print-servers" << endl <<
00577 " Prints the names of all configured servers to stdout." << endl <<
00578 "" << endl <<
00579 "--print-config|-p" << endl <<
00580 " Prints the complete configuration for the selected server" << endl <<
00581 " to stdout, including up-to-date comments for all properties. The" << endl <<
00582 " format is the normal .ini format with source configurations in" << endl <<
00583 " different sections introduced with [<source>] lines. Can be combined" << endl <<
00584 " with --sync-property and --source-property to modify the configuration" << endl <<
00585 " on-the-fly. When one or more sources are listed after the <server>" << endl <<
00586 " name on the command line, then only the configs of those sources are" << endl <<
00587 " printed. Using --quiet suppresses the comments for each property." << endl <<
00588 " When setting a --template, then the reference configuration for" << endl <<
00589 " that server is printed instead of an existing configuration." << endl <<
00590 "" << endl <<
00591 "-–configure|-c" << endl <<
00592 " Modify the configuration files for the selected server. If no such" << endl <<
00593 " configuration exists, then a new one is created using one of the" << endl <<
00594 " template configurations (see --template option). When creating" << endl <<
00595 " a new configuration only the active sources will be set to active" << endl <<
00596 " in the new configuration, i.e. \"syncevolution -c scheduleworld addressbook\"" << endl <<
00597 " followed by \"syncevolution scheduleworld\" will only synchronize the" << endl <<
00598 " address book. The other sources are created in a disabled state." << endl <<
00599 " When modifying an existing configuration and sources are specified," << endl <<
00600 " then the source properties of only those sources are modified." << endl <<
00601 "" << endl <<
00602 "--migrate" << endl <<
00603 " In SyncEvolution <= 0.7 a different layout of configuration files" << endl <<
00604 " was used. Using --migrate will automatically migrate to the new" << endl <<
00605 " layout and rename the old directory $HOME/.sync4j/evolution/<server> " << endl <<
00606 " into $HOME/.sync4j/evolution/<server>.old to prevent accidental use" << endl <<
00607 " of the old configuration. WARNING: old SyncEvolution releases cannot" << endl <<
00608 " use the new configuration!" << endl <<
00609 " The switch can also be used to migrate a configuration in the current" << endl <<
00610 " configuration directory: this preserves all property values, discards" << endl <<
00611 " obsolete properties and sets all comments exactly as if the configuration" << endl <<
00612 " had been created from scratch. WARNING: custom comments in the" << endl <<
00613 " configuration are not preserved." << endl <<
00614 " --migrate implies --configure and can be combined with modifying" << endl <<
00615 " properties." << endl <<
00616 "" << endl <<
00617 "--sync-property|-y <property>=<value>" << endl <<
00618 "--sync-property|-y ?" << endl <<
00619 "--sync-property|-y <property>=?" << endl <<
00620 " Overrides a configuration property in the <server>/config.ini file" << endl <<
00621 " for the current synchronization run or permanently when --configure" << endl <<
00622 " is used to update the configuration. Can be used multiple times." << endl <<
00623 " Specifying an unused property will trigger an error message." << endl <<
00624 "" << endl <<
00625 "--source-property|-z <property>=<value>" << endl <<
00626 "--source-property|-z ?" << endl <<
00627 "--source-property|-z <property>=?" << endl <<
00628 " Same as --sync-option, but applies to the configuration of all active" << endl <<
00629 " sources. \"--sync <mode>\" is a shortcut for \"--source-option sync=<mode>\"." << endl <<
00630 "" << endl <<
00631 "--template|-l <server name>|default|?" << endl <<
00632 " Can be used to select from one of the built-in default configurations" << endl <<
00633 " for known SyncML servers. Defaults to the <server> name, so --template" << endl <<
00634 " only has to be specified when creating multiple different configurations" << endl <<
00635 " for the same server. \"default\" is an alias for \"scheduleworld\" and can be" << endl <<
00636 " used as the starting point for servers which do not have a built-in" << endl <<
00637 " configuration." << endl <<
00638 " Each template contains a pseudo-random device ID. Therefore setting the" << endl <<
00639 " \"deviceId\" sync property is only necessary when manually recreating a" << endl <<
00640 " configuration or when a more descriptive name is desired." << endl <<
00641 "" << endl <<
00642 "--status|-t" << endl <<
00643 " The changes made to local data since the last synchronization are" << endl <<
00644 " shown without starting a new one. This can be used to see in advance" << endl <<
00645 " whether the local data needs to be synchronized with the server." << endl <<
00646 "" << endl <<
00647 "--quiet|-q" << endl <<
00648 " Suppresses most of the normal output during a synchronization. The" << endl <<
00649 " log file still contains all the information." << endl <<
00650 "" << endl <<
00651 "--help|-h" << endl <<
00652 " Prints usage information." << endl <<
00653 "" << endl <<
00654 "--version" << endl <<
00655 " Prints the SyncEvolution version." << endl;
00656 }
00657
00658 if (error != "") {
00659 out << endl << "ERROR: " << error << endl;
00660 }
00661 if (param != "") {
00662 out << "INFO: use '" << param << (param[param.size() - 1] == '=' ? "" : " ") <<
00663 "?' to get a list of valid parameters" << endl;
00664 }
00665 }
00666
00667 #ifdef ENABLE_UNIT_TESTS
00668
00669
00670 static string diffStrings(const string &lhs, const string &rhs)
00671 {
00672 ostringstream res;
00673
00674 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
00675 string_split_iterator lit =
00676 boost::make_split_iterator(lhs, boost::first_finder("\n", boost::is_iequal()));
00677 string_split_iterator rit =
00678 boost::make_split_iterator(rhs, boost::first_finder("\n", boost::is_iequal()));
00679 while (lit != string_split_iterator() &&
00680 rit != string_split_iterator()) {
00681 if (*lit != *rit) {
00682 res << "< " << *lit << endl;
00683 res << "> " << *rit << endl;
00684 }
00685 ++lit;
00686 ++rit;
00687 }
00688
00689 while (lit != string_split_iterator()) {
00690 res << "< " << *lit << endl;
00691 ++lit;
00692 }
00693
00694 while (rit != string_split_iterator()) {
00695 res << "> " << *rit << endl;
00696 ++rit;
00697 }
00698
00699 return res.str();
00700 }
00701
00702 # define CPPUNIT_ASSERT_EQUAL_DIFF( expected, actual ) \
00703 do { \
00704 string expected_ = (expected); \
00705 string actual_ = (actual); \
00706 if (expected_ != actual_) { \
00707 CPPUNIT_NS::Message cpputMsg_(string("expected:\n") + \
00708 expected_); \
00709 cpputMsg_.addDetail(string("actual:\n") + \
00710 actual_); \
00711 cpputMsg_.addDetail(string("diff:\n") + \
00712 diffStrings(expected_, actual_)); \
00713 CPPUNIT_NS::Asserter::fail( cpputMsg_, \
00714 CPPUNIT_SOURCELINE() ); \
00715 } \
00716 } while ( false )
00717
00718
00719 static string lastLine(const string &buffer)
00720 {
00721 if (buffer.size() < 2) {
00722 return buffer;
00723 }
00724
00725 size_t line = buffer.rfind("\n", buffer.size() - 2);
00726 if (line == buffer.npos) {
00727 return buffer;
00728 }
00729
00730 return buffer.substr(line + 1);
00731 }
00732
00733
00734 static bool isPropAssignment(const string &buffer) {
00735 size_t start = 0;
00736 while (start < buffer.size() &&
00737 !isspace(buffer[start])) {
00738 start++;
00739 }
00740 if (start + 3 <= buffer.size() &&
00741 buffer.substr(start, 3) == " = ") {
00742 return true;
00743 } else {
00744 return false;
00745 }
00746 }
00747
00748
00749
00750 static string filterConfig(const string &buffer)
00751 {
00752 ostringstream res;
00753
00754 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
00755 for (string_split_iterator it =
00756 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
00757 it != string_split_iterator();
00758 ++it) {
00759 string line = boost::copy_range<string>(*it);
00760 if (!line.empty() &&
00761 (!boost::starts_with(line, "# ") ||
00762 isPropAssignment(line.substr(2)))) {
00763 res << line << endl;
00764 }
00765 }
00766
00767 return res.str();
00768 }
00769
00770
00771 static string filterIndented(const string &buffer)
00772 {
00773 ostringstream res;
00774 bool first = true;
00775
00776 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
00777 for (string_split_iterator it =
00778 boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
00779 it != string_split_iterator();
00780 ++it) {
00781 if (!boost::starts_with(*it, " ")) {
00782 if (!first) {
00783 res << endl;
00784 } else {
00785 first = false;
00786 }
00787 res << *it;
00788 }
00789 }
00790
00791 return res.str();
00792 }
00793
00794
00795 static string internalToIni(const string &config)
00796 {
00797 ostringstream res;
00798
00799 string section;
00800 typedef boost::split_iterator<string::const_iterator> string_split_iterator;
00801 for (string_split_iterator it =
00802 boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal()));
00803 it != string_split_iterator();
00804 ++it) {
00805 string line(it->begin(), it->end());
00806 if (line.empty()) {
00807 continue;
00808 }
00809
00810 size_t colon = line.find(':');
00811 string prefix = line.substr(0, colon);
00812 if (boost::contains(prefix, ".internal.ini") ||
00813 boost::contains(line, "= internal value")) {
00814 continue;
00815 }
00816
00817
00818 size_t endslash = prefix.rfind('/');
00819 if (endslash != line.npos && endslash > 1) {
00820 size_t slash = prefix.rfind('/', endslash - 1);
00821 if (slash != line.npos) {
00822 string newsource = prefix.substr(slash + 1, endslash - slash - 1);
00823 if (newsource != section &&
00824 newsource != "syncml") {
00825 res << endl << "[" << newsource << "]" << endl;
00826 section = newsource;
00827 }
00828 }
00829 }
00830 string assignment = line.substr(colon + 1);
00831
00832 boost::replace_first(assignment, "= F", "= 0");
00833 boost::replace_first(assignment, "= T", "= 1");
00834 boost::replace_first(assignment, "= md5", "= syncml:auth-md5");
00835 res << assignment << endl;
00836 }
00837
00838 return res.str();
00839 }
00840
00841
00842
00843
00844
00845
00846
00847
00848
00849
00850
00851
00852
00853
00854 class SyncEvolutionCmdlineTest : public CppUnit::TestFixture {
00855 CPPUNIT_TEST_SUITE(SyncEvolutionCmdlineTest);
00856 CPPUNIT_TEST(testFramework);
00857 CPPUNIT_TEST(testSetupScheduleWorld);
00858 CPPUNIT_TEST(testSetupDefault);
00859 CPPUNIT_TEST(testSetupRenamed);
00860 CPPUNIT_TEST(testSetupFunambol);
00861 CPPUNIT_TEST(testSetupSynthesis);
00862 CPPUNIT_TEST(testPrintServers);
00863 CPPUNIT_TEST(testPrintConfig);
00864 CPPUNIT_TEST(testTemplate);
00865 CPPUNIT_TEST(testSync);
00866 CPPUNIT_TEST(testConfigure);
00867 CPPUNIT_TEST(testOldConfigure);
00868 CPPUNIT_TEST(testListSources);
00869 CPPUNIT_TEST(testMigrate);
00870 CPPUNIT_TEST_SUITE_END();
00871
00872 public:
00873 SyncEvolutionCmdlineTest() :
00874 m_testDir("SyncEvolutionCmdlineTest"),
00875 m_scheduleWorldConfig(".internal.ini:# serverNonce = \n"
00876 ".internal.ini:# clientNonce = \n"
00877 ".internal.ini:# devInfoHash = \n"
00878 "config.ini:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
00879 "config.ini:username = your SyncML server account name\n"
00880 "config.ini:password = your SyncML server password\n"
00881 "config.ini:# logdir = \n"
00882 "config.ini:# loglevel = 0\n"
00883 "config.ini:# maxlogdirs = 0\n"
00884 "config.ini:# useProxy = 0\n"
00885 "config.ini:# proxyHost = \n"
00886 "config.ini:# proxyUsername = \n"
00887 "config.ini:# proxyPassword = \n"
00888 "config.ini:# clientAuthType = syncml:auth-md5\n"
00889 "config.ini:deviceId = fixed-devid\n"
00890 "config.ini:# maxMsgSize = 8192\n"
00891 "config.ini:# maxObjSize = 500000\n"
00892 "config.ini:# loSupport = 1\n"
00893 "config.ini:# enableCompression = 0\n"
00894 "config.ini:# SSLServerCertificates = \n"
00895 "config.ini:# SSLVerifyServer = 1\n"
00896 "config.ini:# SSLVerifyHost = 1\n"
00897 "sources/addressbook/.internal.ini:# last = 0\n"
00898 "sources/addressbook/config.ini:sync = two-way\n"
00899 "sources/addressbook/config.ini:type = addressbook:text/vcard\n"
00900 "sources/addressbook/config.ini:# evolutionsource = \n"
00901 "sources/addressbook/config.ini:uri = card3\n"
00902 "sources/addressbook/config.ini:# evolutionuser = \n"
00903 "sources/addressbook/config.ini:# evolutionpassword = \n"
00904 "sources/addressbook/config.ini:# encoding = \n"
00905 "sources/calendar/.internal.ini:# last = 0\n"
00906 "sources/calendar/config.ini:sync = two-way\n"
00907 "sources/calendar/config.ini:type = calendar\n"
00908 "sources/calendar/config.ini:# evolutionsource = \n"
00909 "sources/calendar/config.ini:uri = cal2\n"
00910 "sources/calendar/config.ini:# evolutionuser = \n"
00911 "sources/calendar/config.ini:# evolutionpassword = \n"
00912 "sources/calendar/config.ini:# encoding = \n"
00913 "sources/memo/.internal.ini:# last = 0\n"
00914 "sources/memo/config.ini:sync = two-way\n"
00915 "sources/memo/config.ini:type = memo\n"
00916 "sources/memo/config.ini:# evolutionsource = \n"
00917 "sources/memo/config.ini:uri = note\n"
00918 "sources/memo/config.ini:# evolutionuser = \n"
00919 "sources/memo/config.ini:# evolutionpassword = \n"
00920 "sources/memo/config.ini:# encoding = \n"
00921 "sources/todo/.internal.ini:# last = 0\n"
00922 "sources/todo/config.ini:sync = two-way\n"
00923 "sources/todo/config.ini:type = todo\n"
00924 "sources/todo/config.ini:# evolutionsource = \n"
00925 "sources/todo/config.ini:uri = task2\n"
00926 "sources/todo/config.ini:# evolutionuser = \n"
00927 "sources/todo/config.ini:# evolutionpassword = \n"
00928 "sources/todo/config.ini:# encoding = \n")
00929 {}
00930
00931 protected:
00932
00933
00934 void testFramework() {
00935 const string root(m_testDir);
00936 const string content("baz:line\n"
00937 "caz/subdir:booh\n"
00938 "caz/subdir2/sub:# comment\n"
00939 "caz/subdir2/sub:# foo = bar\n"
00940 "caz/subdir2/sub:# empty = \n"
00941 "caz/subdir2/sub:# another comment\n"
00942 "foo:bar1\n"
00943 "foo:\n"
00944 "foo: \n"
00945 "foo:bar2\n");
00946 const string filtered("baz:line\n"
00947 "caz/subdir:booh\n"
00948 "caz/subdir2/sub:# foo = bar\n"
00949 "caz/subdir2/sub:# empty = \n"
00950 "foo:bar1\n"
00951 "foo: \n"
00952 "foo:bar2\n");
00953 createFiles(root, content);
00954 string res = scanFiles(root);
00955 CPPUNIT_ASSERT_EQUAL_DIFF(filtered, res);
00956 }
00957
00958 void removeRandomUUID(string &buffer) {
00959 string uuidstr = "deviceId = sc-pim-";
00960 size_t uuid = buffer.find(uuidstr);
00961 CPPUNIT_ASSERT(uuid != buffer.npos);
00962 size_t end = buffer.find("\n", uuid + uuidstr.size());
00963 CPPUNIT_ASSERT(end != buffer.npos);
00964 buffer.replace(uuid, end - uuid, "deviceId = fixed-devid");
00965 }
00966
00967
00968 void testSetupScheduleWorld() {
00969 string root;
00970 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
00971 ScopedEnvChange home("HOME", m_testDir);
00972
00973 root = m_testDir;
00974 root += "/syncevolution/scheduleworld";
00975
00976 {
00977 rm_r(root);
00978 TestCmdline cmdline("--configure",
00979 "--sync-property", "proxyHost = proxy",
00980 "scheduleworld",
00981 "addressbook",
00982 NULL);
00983 cmdline.doit();
00984 string res = scanFiles(root);
00985 removeRandomUUID(res);
00986 string expected = m_scheduleWorldConfig;
00987 boost::replace_first(expected,
00988 "# proxyHost = ",
00989 "proxyHost = proxy");
00990 boost::replace_all(expected,
00991 "sync = two-way",
00992 "sync = disabled");
00993 boost::replace_first(expected,
00994 "addressbook/config.ini:sync = disabled",
00995 "addressbook/config.ini:sync = two-way");
00996 CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
00997 }
00998
00999 {
01000 rm_r(root);
01001 TestCmdline cmdline("--configure",
01002 "--sync-property", "deviceID = fixed-devid",
01003 "scheduleworld",
01004 NULL);
01005 cmdline.doit();
01006 string res = scanFiles(root);
01007 CPPUNIT_ASSERT_EQUAL_DIFF(string(m_scheduleWorldConfig), res);
01008 }
01009 }
01010
01011 void testSetupDefault() {
01012 string root;
01013 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
01014 ScopedEnvChange home("HOME", m_testDir);
01015
01016 root = m_testDir;
01017 root += "/syncevolution/some-other-server";
01018 rm_r(root);
01019 TestCmdline cmdline("--configure",
01020 "--template", "default",
01021 "--sync-property", "deviceID = fixed-devid",
01022 "some-other-server",
01023 NULL);
01024 cmdline.doit();
01025 string res = scanFiles(root);
01026 CPPUNIT_ASSERT_EQUAL_DIFF(string(m_scheduleWorldConfig), res);
01027 }
01028 void testSetupRenamed() {
01029 string root;
01030 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
01031 ScopedEnvChange home("HOME", m_testDir);
01032
01033 root = m_testDir;
01034 root += "/syncevolution/scheduleworld2";
01035 rm_r(root);
01036 TestCmdline cmdline("--configure",
01037 "--template", "scheduleworld",
01038 "--sync-property", "deviceID = fixed-devid",
01039 "scheduleworld2",
01040 NULL);
01041 cmdline.doit();
01042 string res = scanFiles(root);
01043 CPPUNIT_ASSERT_EQUAL_DIFF(string(m_scheduleWorldConfig), res);
01044 }
01045 void testSetupFunambol() {
01046 string root;
01047 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
01048 ScopedEnvChange home("HOME", m_testDir);
01049
01050 root = m_testDir;
01051 root += "/syncevolution/funambol";
01052 rm_r(root);
01053 TestCmdline cmdline("--configure",
01054 "--sync-property", "deviceID = fixed-devid",
01055 "funambol",
01056 NULL);
01057 cmdline.doit();
01058 string res = scanFiles(root);
01059 CPPUNIT_ASSERT_EQUAL_DIFF(FunambolConfig(), res);
01060 }
01061
01062 void testSetupSynthesis() {
01063 string root;
01064 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
01065 ScopedEnvChange home("HOME", m_testDir);
01066
01067 root = m_testDir;
01068 root += "/syncevolution/synthesis";
01069 rm_r(root);
01070 TestCmdline cmdline("--configure",
01071 "--sync-property", "deviceID = fixed-devid",
01072 "synthesis",
01073 NULL);
01074 cmdline.doit();
01075 string res = scanFiles(root);
01076 CPPUNIT_ASSERT_EQUAL_DIFF(SynthesisConfig(), res);
01077 }
01078
01079 void testTemplate() {
01080 TestCmdline failure("--template", NULL);
01081 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
01082 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
01083 CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--template'\n"), lastLine(failure.m_err.str()));
01084
01085 TestCmdline help("--template", "? ", NULL);
01086 help.doit();
01087 CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
01088 " funambol = http://my.funambol.com\n"
01089 " scheduleworld = http://sync.scheduleworld.com\n"
01090 " synthesis = http://www.synthesis.ch\n"
01091 " memotoo = http://www.memotoo.com\n",
01092 help.m_out.str());
01093 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
01094 }
01095
01096 void testPrintServers() {
01097 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
01098 ScopedEnvChange home("HOME", m_testDir);
01099
01100 rm_r(m_testDir);
01101 testSetupScheduleWorld();
01102 testSetupSynthesis();
01103 testSetupFunambol();
01104
01105 TestCmdline cmdline("--print-servers", NULL);
01106 cmdline.doit();
01107 CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n"
01108 " scheduleworld = SyncEvolutionCmdlineTest/syncevolution/scheduleworld\n"
01109 " synthesis = SyncEvolutionCmdlineTest/syncevolution/synthesis\n"
01110 " funambol = SyncEvolutionCmdlineTest/syncevolution/funambol\n",
01111 cmdline.m_out.str());
01112 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01113 }
01114
01115 void testPrintConfig() {
01116 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
01117 ScopedEnvChange home("HOME", m_testDir);
01118
01119 rm_r(m_testDir);
01120 testSetupFunambol();
01121
01122 {
01123 TestCmdline failure("--print-config", NULL);
01124 CPPUNIT_ASSERT(failure.m_cmdline->parse());
01125 CPPUNIT_ASSERT(!failure.m_cmdline->run());
01126 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
01127 CPPUNIT_ASSERT_EQUAL(string("ERROR: --print-config requires either a --template or a server name.\n"),
01128 lastLine(failure.m_err.str()));
01129 }
01130
01131 {
01132 TestCmdline failure("--print-config", "foo", NULL);
01133 CPPUNIT_ASSERT(failure.m_cmdline->parse());
01134 CPPUNIT_ASSERT(!failure.m_cmdline->run());
01135 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
01136 CPPUNIT_ASSERT_EQUAL(string("ERROR: server 'foo' has not been configured yet.\n"),
01137 lastLine(failure.m_err.str()));
01138 }
01139
01140 {
01141 TestCmdline failure("--print-config", "--template", "foo", NULL);
01142 CPPUNIT_ASSERT(failure.m_cmdline->parse());
01143 CPPUNIT_ASSERT(!failure.m_cmdline->run());
01144 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
01145 CPPUNIT_ASSERT_EQUAL(string("ERROR: no configuration template for 'foo' available.\n"),
01146 lastLine(failure.m_err.str()));
01147 }
01148
01149 {
01150 TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL);
01151 cmdline.doit();
01152 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01153 string actual = cmdline.m_out.str();
01154 removeRandomUUID(actual);
01155 string filtered = filterConfig(actual);
01156 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(m_scheduleWorldConfig)),
01157 filtered);
01158
01159 CPPUNIT_ASSERT(actual.size() > filtered.size());
01160 }
01161
01162 {
01163 TestCmdline cmdline("--print-config", "--template", "default", NULL);
01164 cmdline.doit();
01165 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01166 string actual = filterConfig(cmdline.m_out.str());
01167 removeRandomUUID(actual);
01168 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(m_scheduleWorldConfig)),
01169 actual);
01170 }
01171
01172 {
01173 TestCmdline cmdline("--print-config", "funambol", NULL);
01174 cmdline.doit();
01175 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01176 CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())),
01177 filterConfig(cmdline.m_out.str()));
01178 }
01179
01180 {
01181 TestCmdline cmdline("--print-config", "--template", "scheduleworld",
01182 "--sync-property", "syncURL=foo",
01183 "--source-property", "sync=disabled",
01184 NULL);
01185 cmdline.doit();
01186 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01187 string expected = filterConfig(internalToIni(m_scheduleWorldConfig));
01188 boost::replace_first(expected,
01189 "syncURL = http://sync.scheduleworld.com/funambol/ds",
01190 "syncURL = foo");
01191 boost::replace_all(expected,
01192 "sync = two-way",
01193 "sync = disabled");
01194 string actual = filterConfig(cmdline.m_out.str());
01195 removeRandomUUID(actual);
01196 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
01197 actual);
01198 }
01199
01200 {
01201 TestCmdline cmdline("--print-config", "--quiet",
01202 "--template", "scheduleworld",
01203 "funambol",
01204 NULL);
01205 cmdline.doit();
01206 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01207 string actual = cmdline.m_out.str();
01208 removeRandomUUID(actual);
01209 CPPUNIT_ASSERT_EQUAL_DIFF(internalToIni(m_scheduleWorldConfig),
01210 actual);
01211 }
01212
01213 }
01214
01215 void testSync() {
01216 TestCmdline failure("--sync", NULL);
01217 CPPUNIT_ASSERT(!failure.m_cmdline->parse());
01218 CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
01219 CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--sync'\n"), lastLine(failure.m_err.str()));
01220
01221 TestCmdline failure2("--sync", "foo", NULL);
01222 CPPUNIT_ASSERT(!failure2.m_cmdline->parse());
01223 CPPUNIT_ASSERT_EQUAL_DIFF("", failure2.m_out.str());
01224 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()));
01225
01226 TestCmdline help("--sync", " ?", NULL);
01227 help.doit();
01228 CPPUNIT_ASSERT_EQUAL_DIFF("--sync\n"
01229 " requests a certain synchronization mode:\n"
01230 " two-way = only send/receive changes since last sync\n"
01231 " slow = exchange all items\n"
01232 " refresh-from-client = discard all remote items and replace with\n"
01233 " the items on the client\n"
01234 " refresh-from-server = discard all local items and replace with\n"
01235 " the items on the server\n"
01236 " one-way-from-client = transmit changes from client\n"
01237 " one-way-from-server = transmit changes from server\n"
01238 " none (or disabled) = synchronization disabled\n",
01239 help.m_out.str());
01240 CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
01241
01242 TestCmdline filter("--sync", "refresh-from-server", NULL);
01243 CPPUNIT_ASSERT(filter.m_cmdline->parse());
01244 CPPUNIT_ASSERT(!filter.m_cmdline->run());
01245 CPPUNIT_ASSERT_EQUAL_DIFF("", filter.m_out.str());
01246 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh-from-server",
01247 string(filter.m_cmdline->m_sourceProps));
01248 CPPUNIT_ASSERT_EQUAL_DIFF("",
01249 string(filter.m_cmdline->m_syncProps));
01250
01251 TestCmdline filter2("--source-property", "sync=refresh", NULL);
01252 CPPUNIT_ASSERT(filter2.m_cmdline->parse());
01253 CPPUNIT_ASSERT(!filter2.m_cmdline->run());
01254 CPPUNIT_ASSERT_EQUAL_DIFF("", filter2.m_out.str());
01255 CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh",
01256 string(filter2.m_cmdline->m_sourceProps));
01257 CPPUNIT_ASSERT_EQUAL_DIFF("",
01258 string(filter2.m_cmdline->m_syncProps));
01259 }
01260
01261 void testConfigure() {
01262 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
01263 ScopedEnvChange home("HOME", m_testDir);
01264
01265 rm_r(m_testDir);
01266 testSetupScheduleWorld();
01267 doConfigure(ScheduleWorldConfig(), "sources/addressbook/config.ini:");
01268
01269 string syncProperties("syncURL:\n"
01270 "\n"
01271 "username:\n"
01272 "\n"
01273 "password:\n"
01274 "\n"
01275 "logdir:\n"
01276 "\n"
01277 "loglevel:\n"
01278 "\n"
01279 "maxlogdirs:\n"
01280 "\n"
01281 "useProxy:\n"
01282 "\n"
01283 "proxyHost:\n"
01284 "\n"
01285 "proxyUsername:\n"
01286 "\n"
01287 "proxyPassword:\n"
01288 "\n"
01289 "clientAuthType:\n"
01290 "\n"
01291 "deviceId:\n"
01292 "\n"
01293 "maxMsgSize:\n"
01294 "maxObjSize:\n"
01295 "loSupport:\n"
01296 "\n"
01297 "enableCompression:\n"
01298 "\n"
01299 "SSLServerCertificates:\n"
01300 "\n"
01301 "SSLVerifyServer:\n"
01302 "\n"
01303 "SSLVerifyHost:\n");
01304 string sourceProperties("sync:\n"
01305 "\n"
01306 "type:\n"
01307 "\n"
01308 "evolutionsource:\n"
01309 "\n"
01310 "uri:\n"
01311 "\n"
01312 "evolutionuser:\n"
01313 "evolutionpassword:\n"
01314 "\n"
01315 "encoding:\n");
01316
01317 {
01318 TestCmdline cmdline("--sync-property", "?",
01319 NULL);
01320 cmdline.doit();
01321 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01322 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties,
01323 filterIndented(cmdline.m_out.str()));
01324 }
01325
01326 {
01327 TestCmdline cmdline("--source-property", "?",
01328 NULL);
01329 cmdline.doit();
01330 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01331 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties,
01332 filterIndented(cmdline.m_out.str()));
01333 }
01334
01335 {
01336 TestCmdline cmdline("--source-property", "?",
01337 "--sync-property", "?",
01338 NULL);
01339 cmdline.doit();
01340 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01341 CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties + syncProperties,
01342 filterIndented(cmdline.m_out.str()));
01343 }
01344
01345 {
01346 TestCmdline cmdline("--sync-property", "?",
01347 "--source-property", "?",
01348 NULL);
01349 cmdline.doit();
01350 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01351 CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties + sourceProperties,
01352 filterIndented(cmdline.m_out.str()));
01353 }
01354 }
01355
01356 void testOldConfigure() {
01357 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
01358 ScopedEnvChange home("HOME", m_testDir);
01359
01360 string oldConfig = OldScheduleWorldConfig();
01361 InitList<string> props = InitList<string>("serverNonce") +
01362 "clientNonce" +
01363 "devInfoHash" +
01364 "last";
01365 BOOST_FOREACH(string &prop, props) {
01366 boost::replace_all(oldConfig,
01367 prop + " = ",
01368 prop + " = internal value");
01369 }
01370
01371 rm_r(m_testDir);
01372 createFiles(m_testDir + "/.sync4j/evolution/scheduleworld", oldConfig);
01373 doConfigure(oldConfig, "spds/sources/addressbook/config.txt:");
01374 }
01375
01376 void doConfigure(const string &SWConfig, const string &addressbookPrefix) {
01377 string expected;
01378
01379 {
01380 TestCmdline cmdline("--configure",
01381 "--source-property", "sync = disabled",
01382 "scheduleworld",
01383 NULL);
01384 cmdline.doit();
01385 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01386 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
01387 expected = filterConfig(internalToIni(SWConfig));
01388 boost::replace_all(expected,
01389 "sync = two-way",
01390 "sync = disabled");
01391 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
01392 filterConfig(printConfig("scheduleworld")));
01393 }
01394
01395 {
01396 TestCmdline cmdline("--configure",
01397 "--source-property", "sync = one-way-from-server",
01398 "scheduleworld",
01399 "addressbook",
01400 NULL);
01401 cmdline.doit();
01402 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01403 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
01404 expected = SWConfig;
01405 boost::replace_all(expected,
01406 "sync = two-way",
01407 "sync = disabled");
01408 boost::replace_first(expected,
01409 addressbookPrefix + "sync = disabled",
01410 addressbookPrefix + "sync = one-way-from-server");
01411 expected = filterConfig(internalToIni(expected));
01412 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
01413 filterConfig(printConfig("scheduleworld")));
01414 }
01415
01416 {
01417 TestCmdline cmdline("--configure",
01418 "--sync", "two-way",
01419 "-z", "evolutionsource=source",
01420 "--sync-property", "maxlogdirs=10",
01421 "-y", "LOGDIR=logdir",
01422 "scheduleworld",
01423 NULL);
01424 cmdline.doit();
01425 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01426 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
01427 boost::replace_all(expected,
01428 "sync = one-way-from-server",
01429 "sync = two-way");
01430 boost::replace_all(expected,
01431 "sync = disabled",
01432 "sync = two-way");
01433 boost::replace_all(expected,
01434 "# evolutionsource = ",
01435 "evolutionsource = source");
01436 boost::replace_all(expected,
01437 "# maxlogdirs = 0",
01438 "maxlogdirs = 10");
01439 boost::replace_all(expected,
01440 "# logdir = ",
01441 "logdir = logdir");
01442 CPPUNIT_ASSERT_EQUAL_DIFF(expected,
01443 filterConfig(printConfig("scheduleworld")));
01444 }
01445 }
01446
01447 void testListSources() {
01448 TestCmdline cmdline(NULL);
01449 cmdline.doit();
01450 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01451
01452 }
01453
01454 void testMigrate() {
01455 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
01456 ScopedEnvChange home("HOME", m_testDir);
01457
01458 rm_r(m_testDir);
01459 string oldRoot = m_testDir + "/.sync4j/evolution/scheduleworld";
01460 string newRoot = m_testDir + "/syncevolution/scheduleworld";
01461
01462 string oldConfig = OldScheduleWorldConfig();
01463
01464 {
01465
01466 createFiles(oldRoot, oldConfig);
01467 string createdConfig = scanFiles(oldRoot);
01468 TestCmdline cmdline("--migrate",
01469 "scheduleworld",
01470 NULL);
01471 cmdline.doit();
01472 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01473 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
01474
01475 string migratedConfig = scanFiles(newRoot);
01476 CPPUNIT_ASSERT_EQUAL_DIFF(m_scheduleWorldConfig, migratedConfig);
01477 string renamedConfig = scanFiles(oldRoot + ".old");
01478 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
01479 }
01480
01481 {
01482
01483 createFiles(newRoot,
01484 "config.ini:# obsolete comment\n"
01485 "config.ini:obsoleteprop = foo\n",
01486 true);
01487 string createdConfig = scanFiles(newRoot);
01488
01489 TestCmdline cmdline("--migrate",
01490 "scheduleworld",
01491 NULL);
01492 cmdline.doit();
01493 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01494 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
01495
01496 string migratedConfig = scanFiles(newRoot);
01497 CPPUNIT_ASSERT_EQUAL_DIFF(m_scheduleWorldConfig, migratedConfig);
01498 string renamedConfig = scanFiles(newRoot + ".old");
01499 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
01500 }
01501
01502 {
01503
01504 createFiles(oldRoot, oldConfig);
01505 createFiles(oldRoot,
01506 "spds/sources/addressbook/changes/config.txt:foo = bar\n"
01507 "spds/sources/addressbook/changes/config.txt:foo2 = bar2\n",
01508 true);
01509 string createdConfig = scanFiles(oldRoot);
01510 rm_r(newRoot);
01511 TestCmdline cmdline("--migrate",
01512 "scheduleworld",
01513 NULL);
01514 cmdline.doit();
01515 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01516 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
01517
01518 string migratedConfig = scanFiles(newRoot);
01519 string expected = m_scheduleWorldConfig;
01520 boost::replace_first(expected,
01521 "sources/addressbook/config.ini",
01522 "sources/addressbook/.other.ini:foo = bar\n"
01523 "sources/addressbook/.other.ini:foo2 = bar2\n"
01524 "sources/addressbook/config.ini");
01525 CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
01526 string renamedConfig = scanFiles(oldRoot + ".old.1");
01527 CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
01528 }
01529 }
01530
01531 const string m_testDir;
01532 const string m_scheduleWorldConfig;
01533
01534
01535 private:
01536
01537
01538
01539
01540
01541 class TestCmdline {
01542 public:
01543 TestCmdline(const char *arg, ...) {
01544 va_list argList;
01545 va_start (argList, arg);
01546 for (const char *curr = arg;
01547 curr;
01548 curr = va_arg(argList, const char *)) {
01549 m_argvstr.push_back(curr);
01550 }
01551 va_end(argList);
01552
01553 m_argv.reset(new const char *[m_argvstr.size() + 1]);
01554 m_argv[0] = "client-test";
01555 for (size_t index = 0;
01556 index < m_argvstr.size();
01557 ++index) {
01558 m_argv[index + 1] = m_argvstr[index].c_str();
01559 }
01560
01561 m_cmdline.set(new SyncEvolutionCmdline(m_argvstr.size() + 1, m_argv.get(), m_out, m_err), "cmdline");
01562 }
01563
01564 void doit() {
01565 bool success;
01566 success = m_cmdline->parse() &&
01567 m_cmdline->run();
01568 if (m_err.str().size()) {
01569 cerr << endl << m_err.str();
01570 }
01571 CPPUNIT_ASSERT(success);
01572 }
01573
01574 ostringstream m_out, m_err;
01575 cxxptr<SyncEvolutionCmdline> m_cmdline;
01576
01577 private:
01578 vector<string> m_argvstr;
01579 boost::scoped_array<const char *> m_argv;
01580 };
01581
01582 string ScheduleWorldConfig() {
01583 return m_scheduleWorldConfig;
01584 }
01585
01586 string OldScheduleWorldConfig() {
01587 string oldConfig = m_scheduleWorldConfig;
01588 boost::replace_all(oldConfig,
01589 ".internal.ini",
01590 "config.ini");
01591 InitList<string> sources = InitList<string>("addressbook") +
01592 "calendar" +
01593 "memo" +
01594 "todo";
01595 BOOST_FOREACH(string &source, sources) {
01596 boost::replace_all(oldConfig,
01597 string("sources/") + source + "/config.ini",
01598 string("spds/sources/") + source + "/config.txt");
01599 }
01600 boost::replace_all(oldConfig,
01601 "config.ini",
01602 "spds/syncml/config.txt");
01603 return oldConfig;
01604 }
01605
01606 string FunambolConfig() {
01607 string config = m_scheduleWorldConfig;
01608
01609 boost::replace_first(config,
01610 "syncURL = http://sync.scheduleworld.com/funambol/ds",
01611 "syncURL = http://my.funambol.com/sync");
01612
01613 boost::replace_first(config,
01614 "addressbook/config.ini:uri = card3",
01615 "addressbook/config.ini:uri = card");
01616 boost::replace_first(config,
01617 "addressbook/config.ini:type = addressbook:text/vcard",
01618 "addressbook/config.ini:type = addressbook");
01619
01620 boost::replace_first(config,
01621 "calendar/config.ini:uri = cal2",
01622 "calendar/config.ini:uri = event");
01623 boost::replace_first(config,
01624 "calendar/config.ini:sync = two-way",
01625 "calendar/config.ini:sync = disabled");
01626
01627 boost::replace_first(config,
01628 "todo/config.ini:uri = task2",
01629 "todo/config.ini:uri = task");
01630 boost::replace_first(config,
01631 "todo/config.ini:sync = two-way",
01632 "todo/config.ini:sync = disabled");
01633
01634 return config;
01635 }
01636
01637 string SynthesisConfig() {
01638 string config = m_scheduleWorldConfig;
01639 boost::replace_first(config,
01640 "syncURL = http://sync.scheduleworld.com/funambol/ds",
01641 "syncURL = http://www.synthesis.ch/sync");
01642
01643 boost::replace_first(config,
01644 "addressbook/config.ini:uri = card3",
01645 "addressbook/config.ini:uri = contacts");
01646 boost::replace_first(config,
01647 "addressbook/config.ini:type = addressbook:text/vcard",
01648 "addressbook/config.ini:type = addressbook");
01649
01650 boost::replace_first(config,
01651 "calendar/config.ini:uri = cal2",
01652 "calendar/config.ini:uri = events");
01653 boost::replace_first(config,
01654 "calendar/config.ini:sync = two-way",
01655 "calendar/config.ini:sync = disabled");
01656
01657 boost::replace_first(config,
01658 "memo/config.ini:uri = note",
01659 "memo/config.ini:uri = notes");
01660
01661 boost::replace_first(config,
01662 "todo/config.ini:uri = task2",
01663 "todo/config.ini:uri = tasks");
01664 boost::replace_first(config,
01665 "todo/config.ini:sync = two-way",
01666 "todo/config.ini:sync = disabled");
01667
01668 return config;
01669 }
01670
01671
01672 class ScopedEnvChange {
01673 public:
01674 ScopedEnvChange(const string &var, const string &value) :
01675 m_var(var)
01676 {
01677 const char *oldval = getenv(var.c_str());
01678 if (oldval) {
01679 m_oldvalset = true;
01680 m_oldval = oldval;
01681 } else {
01682 m_oldvalset = false;
01683 }
01684 setenv(var.c_str(), value.c_str(), 1);
01685 }
01686 ~ScopedEnvChange()
01687 {
01688 if (m_oldvalset) {
01689 setenv(m_var.c_str(), m_oldval.c_str(), 1);
01690 } else {
01691 unsetenv(m_var.c_str());
01692 }
01693 }
01694 private:
01695 string m_var, m_oldval;
01696 bool m_oldvalset;
01697 };
01698
01699
01700
01701 void createFiles(const string &root, const string &content, bool append = false) {
01702 if (!append) {
01703 rm_r(root);
01704 }
01705
01706 size_t start = 0;
01707 ofstream out;
01708 string outname;
01709
01710 out.exceptions(ios_base::badbit|ios_base::failbit);
01711 while (start < content.size()) {
01712 size_t delim = content.find(':', start);
01713 size_t end = content.find('\n', start);
01714 if (delim == content.npos ||
01715 end == content.npos) {
01716
01717 break;
01718 }
01719 string newname = content.substr(start, delim - start);
01720 string line = content.substr(delim + 1, end - delim - 1);
01721 if (newname != outname) {
01722 if (out.is_open()) {
01723 out.close();
01724 }
01725 string fullpath = root + "/" + newname;
01726 size_t fileoff = fullpath.rfind('/');
01727 mkdir_p(fullpath.substr(0, fileoff));
01728 out.open(fullpath.c_str(),
01729 append ? ios_base::out : (ios_base::out|ios_base::trunc));
01730 outname = newname;
01731 }
01732 out << line << endl;
01733 start = end + 1;
01734 }
01735 }
01736
01737
01738 string scanFiles(const string &root, bool onlyProps = true) {
01739 ostringstream out;
01740
01741 scanFiles(root, "", out, onlyProps);
01742 return out.str();
01743 }
01744
01745 void scanFiles(const string &root, const string &dir, ostringstream &out, bool onlyProps) {
01746 string newroot = root;
01747 newroot += "/";
01748 newroot += dir;
01749 ReadDir readDir(newroot);
01750 sort(readDir.begin(), readDir.end());
01751
01752 BOOST_FOREACH(const string &entry, readDir) {
01753 if (isDir(newroot + "/" + entry)) {
01754 scanFiles(root, dir + (dir.empty() ? "" : "/") + entry, out, onlyProps);
01755 } else {
01756 ifstream in;
01757 in.exceptions(ios_base::badbit );
01758 in.open((newroot + "/" + entry).c_str());
01759 string line;
01760 while (!in.eof()) {
01761 getline(in, line);
01762 if ((line.size() || !in.eof()) &&
01763 (!onlyProps ||
01764 (boost::starts_with(line, "# ") ?
01765 isPropAssignment(line.substr(2)) :
01766 !line.empty()))) {
01767 if (dir.size()) {
01768 out << dir << "/";
01769 }
01770 out << entry << ":";
01771 out << line << '\n';
01772 }
01773 }
01774 }
01775 }
01776 }
01777
01778 string printConfig(const string &server) {
01779 ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
01780 ScopedEnvChange home("HOME", m_testDir);
01781
01782 TestCmdline cmdline("--print-config", server.c_str(), NULL);
01783 cmdline.doit();
01784 CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
01785 return cmdline.m_out.str();
01786 }
01787 };
01788
01789 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(SyncEvolutionCmdlineTest);
01790
01791 #endif // ENABLE_UNIT_TESTS