syncevolution/src/core/SyncEvolutionCmdline.cpp

1777 lines
72 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2008 Patrick Ohly
*/
#include "SyncEvolutionCmdline.h"
#include "FilterConfigNode.h"
#include "VolatileConfigNode.h"
#include "EvolutionSyncSource.h"
#include "EvolutionSyncClient.h"
#include "SyncEvolutionUtil.h"
#include <unistd.h>
#include <errno.h>
#include <iostream>
#include <sstream>
#include <memory>
#include <set>
#include <algorithm>
using namespace std;
#include <boost/shared_ptr.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
SyncEvolutionCmdline::SyncEvolutionCmdline(int argc, const char * const * argv, ostream &out, ostream &err) :
m_argc(argc),
m_argv(argv),
m_out(out),
m_err(err),
m_validSyncProps(EvolutionSyncConfig::getRegistry()),
m_validSourceProps(EvolutionSyncSourceConfig::getRegistry())
{}
bool SyncEvolutionCmdline::parse()
{
int opt = 1;
while (opt < m_argc) {
if (m_argv[opt][0] != '-') {
break;
}
if (boost::iequals(m_argv[opt], "--sync") ||
boost::iequals(m_argv[opt], "-s")) {
opt++;
string param;
string cmdopt(m_argv[opt - 1]);
if (!parseProp(m_validSourceProps, m_sourceProps,
m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt],
EvolutionSyncSourceConfig::m_sourcePropSync.getName().c_str())) {
return false;
}
// disable requirement to add --run explicitly in order to
// be compatible with traditional command lines
m_run = true;
} else if(boost::iequals(m_argv[opt], "--sync-property") ||
boost::iequals(m_argv[opt], "-y")) {
opt++;
if (!parseProp(m_validSyncProps, m_syncProps,
m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
return false;
}
} else if(boost::iequals(m_argv[opt], "--source-property") ||
boost::iequals(m_argv[opt], "-z")) {
opt++;
if (!parseProp(m_validSourceProps, m_sourceProps,
m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
return false;
}
} else if(boost::iequals(m_argv[opt], "--properties") ||
boost::iequals(m_argv[opt], "-r")) {
opt++;
/* TODO */
m_err << "ERROR: not implemented yet: " << m_argv[opt - 1] << endl;
return false;
} else if(boost::iequals(m_argv[opt], "--template") ||
boost::iequals(m_argv[opt], "-l")) {
opt++;
if (opt >= m_argc) {
usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1]));
return false;
}
m_template = m_argv[opt];
m_configure = true;
if (boost::trim_copy(m_template) == "?") {
dumpServers("Available configuration templates:",
EvolutionSyncConfig::getServerTemplates());
m_dontrun = true;
}
} else if(boost::iequals(m_argv[opt], "--print-servers")) {
m_printServers = true;
} else if(boost::iequals(m_argv[opt], "--print-config") ||
boost::iequals(m_argv[opt], "-p")) {
m_printConfig = true;
} else if(boost::iequals(m_argv[opt], "--configure") ||
boost::iequals(m_argv[opt], "-c")) {
m_configure = true;
} else if(boost::iequals(m_argv[opt], "--run") ||
boost::iequals(m_argv[opt], "-r")) {
m_run = true;
} else if(boost::iequals(m_argv[opt], "--migrate")) {
m_migrate = true;
} else if(boost::iequals(m_argv[opt], "--status") ||
boost::iequals(m_argv[opt], "-t")) {
m_status = true;
} else if(boost::iequals(m_argv[opt], "--quiet") ||
boost::iequals(m_argv[opt], "-q")) {
m_quiet = true;
} else if(boost::iequals(m_argv[opt], "--help") ||
boost::iequals(m_argv[opt], "-h")) {
m_usage = true;
} else if(boost::iequals(m_argv[opt], "--version")) {
m_version = true;
} else {
usage(false, string(m_argv[opt]) + ": unknown parameter");
return false;
}
opt++;
}
if (opt < m_argc) {
m_server = m_argv[opt++];
while (opt < m_argc) {
m_sources.insert(m_argv[opt++]);
}
}
return true;
}
bool SyncEvolutionCmdline::run() {
if (m_usage) {
usage(true);
} else if (m_version) {
printf("SyncEvolution %s\n", VERSION);
printf("%s", EDSAbiWrapperInfo());
} else if (m_printServers || boost::trim_copy(m_server) == "?") {
dumpServers("Configured servers:",
EvolutionSyncConfig::getServers());
} else if (m_dontrun) {
// user asked for information
} else if (m_argc == 1) {
const SourceRegistry &registry(EvolutionSyncSource::getSourceRegistry());
boost::shared_ptr<FilterConfigNode> configNode(new VolatileConfigNode());
boost::shared_ptr<FilterConfigNode> hiddenNode(new VolatileConfigNode());
boost::shared_ptr<FilterConfigNode> trackingNode(new VolatileConfigNode());
SyncSourceNodes nodes(configNode, hiddenNode, trackingNode);
EvolutionSyncSourceParams params("list", nodes, "");
BOOST_FOREACH(const RegisterSyncSource *source, registry) {
BOOST_FOREACH(const Values::value_type &alias, source->m_typeValues) {
if (!alias.empty() && source->m_enabled) {
configNode->setProperty("type", *alias.begin());
auto_ptr<EvolutionSyncSource> source(EvolutionSyncSource::createSource(params, false));
if (source.get() != NULL) {
listSources(*source, boost::join(alias, " = "));
m_out << "\n";
}
}
}
}
usage(false);
} else if (m_printConfig) {
boost::shared_ptr<EvolutionSyncConfig> config;
if (m_template.empty()) {
if (m_server.empty()) {
m_err << "ERROR: --print-config requires either a --template or a server name." << endl;
return false;
}
config.reset(new EvolutionSyncConfig(m_server));
if (!config->exists()) {
m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
return false;
}
} else {
config = EvolutionSyncConfig::createServerTemplate(m_template);
if (!config.get()) {
m_err << "ERROR: no configuration template for '" << m_template << "' available." << endl;
return false;
}
}
if (m_sources.empty() ||
m_sources.find("main") != m_sources.end()) {
boost::shared_ptr<FilterConfigNode> syncProps(config->getProperties());
syncProps->setFilter(m_syncProps);
dumpProperties(*syncProps, config->getRegistry());
}
list<string> sources = config->getSyncSources();
sources.sort();
BOOST_FOREACH(const string &name, sources) {
if (m_sources.empty() ||
m_sources.find(name) != m_sources.end()) {
m_out << endl << "[" << name << "]" << endl;
ConstSyncSourceNodes nodes = config->getSyncSourceNodes(name);
boost::shared_ptr<FilterConfigNode> sourceProps(new FilterConfigNode(boost::shared_ptr<const ConfigNode>(nodes.m_configNode)));
sourceProps->setFilter(m_sourceProps);
dumpProperties(*sourceProps, EvolutionSyncSourceConfig::getRegistry());
}
}
} else if (m_server == "" && m_argc > 1) {
// Options given, but no server - not sure what the user wanted?!
usage(true, "server name missing");
return false;
} else if (m_configure || m_migrate) {
bool fromScratch = false;
// Both config changes and migration are implemented as copying from
// another config (template resp. old one). Migration also moves
// the old config.
boost::shared_ptr<EvolutionSyncConfig> from;
if (m_migrate) {
from.reset(new EvolutionSyncConfig(m_server));
if (!from->exists()) {
m_err << "ERROR: server '" << m_server << "' has not been configured yet." << endl;
return false;
}
int counter = 0;
string oldRoot = from->getRootPath();
string suffix;
while (true) {
string newname;
ostringstream newsuffix;
newsuffix << ".old";
if (counter) {
newsuffix << "." << counter;
}
suffix = newsuffix.str();
newname = oldRoot + suffix;
if (!rename(oldRoot.c_str(),
newname.c_str())) {
break;
} else if (errno != EEXIST && errno != ENOTEMPTY) {
m_err << "ERROR: renaming " << oldRoot << " to " <<
newname << ": " << strerror(errno) << endl;
return false;
}
counter++;
}
from.reset(new EvolutionSyncConfig(m_server + suffix));
} else {
from.reset(new EvolutionSyncConfig(m_server));
if (!from->exists()) {
// creating from scratch, look for template
fromScratch = true;
string configTemplate = m_template.empty() ? m_server : m_template;
from = EvolutionSyncConfig::createServerTemplate(configTemplate);
if (!from.get()) {
m_err << "ERROR: no configuration template for '" << configTemplate << "' available." << endl;
dumpServers("Available configuration templates:",
EvolutionSyncConfig::getServerTemplates());
return false;
}
}
}
// apply config changes on-the-fly
from->setConfigFilter(true, m_syncProps);
from->setConfigFilter(false, m_sourceProps);
// write into the requested configuration, creating it if necessary
boost::shared_ptr<EvolutionSyncConfig> to(new EvolutionSyncConfig(m_server));
to->copy(*from, !fromScratch && !m_sources.empty() ? &m_sources : NULL);
// Sources are active now according to the server default.
// Disable all sources not selected by user (if any selected)
// and those which have no database.
if (fromScratch) {
list<string> configuredSources = to->getSyncSources();
set<string> sources = m_sources;
BOOST_FOREACH(const string &source, configuredSources) {
boost::shared_ptr<PersistentEvolutionSyncSourceConfig> sourceConfig(to->getSyncSourceConfig(source));
string disable = "";
set<string>::iterator entry = sources.find(source);
bool selected = entry != sources.end();
if (!m_sources.empty() &&
!selected) {
disable = "not selected";
} else {
if (entry != sources.end()) {
// The command line parameter matched a valid source.
// All entries left afterwards must have been typos.
sources.erase(entry);
}
// check whether the sync source works
EvolutionSyncSourceParams params("list", to->getSyncSourceNodes(source), "");
auto_ptr<EvolutionSyncSource> syncSource(EvolutionSyncSource::createSource(params, false));
if (syncSource.get() == NULL) {
disable = "no backend available";
} else {
try {
EvolutionSyncSource::Databases databases = syncSource->getDatabases();
if (databases.empty()) {
disable = "no database to synchronize";
}
} catch (...) {
disable = "backend failed";
}
}
}
if (!disable.empty()) {
// abort if the user explicitly asked for the sync source
// and it cannot be enabled, otherwise disable it silently
if (selected) {
EvolutionSyncClient::throwError(source + ": " + disable);
}
sourceConfig->setSync("disabled");
} else if (selected) {
// user absolutely wants it: enable even if off by default
FilterConfigNode::ConfigFilter::const_iterator sync =
m_sourceProps.find(EvolutionSyncSourceConfig::m_sourcePropSync.getName());
sourceConfig->setSync(sync == m_sourceProps.end() ? "two-way" : sync->second);
}
}
if (!sources.empty()) {
EvolutionSyncClient::throwError(string("no such source(s): ") + boost::join(sources, " "));
}
}
// done, now write it
to->flush();
} else {
EvolutionSyncClient client(m_server, true, m_sources);
client.setQuiet(m_quiet);
client.setConfigFilter(true, m_syncProps);
client.setConfigFilter(false, m_sourceProps);
if (m_status) {
client.status();
} else {
// safety catch: if props are given, then --run
// is required
if (!m_run &&
(m_syncProps.size() || m_sourceProps.size())) {
usage(false, "Properties specified, but neither '--configure' nor '--run' - what did you want?");
return false;
}
return client.sync() == 0;
}
}
return true;
}
string SyncEvolutionCmdline::cmdOpt(const char *opt, const char *param)
{
string res = "'";
res += opt;
if (param) {
res += " ";
res += param;
}
res += "'";
return res;
}
bool SyncEvolutionCmdline::parseProp(const ConfigPropertyRegistry &validProps,
FilterConfigNode::ConfigFilter &props,
const char *opt,
const char *param,
const char *propname)
{
if (!param) {
usage(true, string("missing parameter for ") + cmdOpt(opt, param));
return false;
} else if (boost::trim_copy(string(param)) == "?") {
m_dontrun = true;
if (propname) {
return listPropValues(validProps, propname, opt);
} else {
return listProperties(validProps, opt);
}
} else {
string propstr;
string paramstr;
if (propname) {
propstr = propname;
paramstr = param;
} else {
const char *equal = strchr(param, '=');
if (!equal) {
usage(true, string("the '=<value>' part is missing in: ") + cmdOpt(opt, param));
return false;
}
propstr.assign(param, equal - param);
paramstr.assign(equal + 1);
}
boost::trim(propstr);
boost::trim_left(paramstr);
if (boost::trim_copy(paramstr) == "?") {
m_dontrun = true;
return listPropValues(validProps, propstr, cmdOpt(opt, param));
} else {
const ConfigProperty *prop = validProps.find(propstr);
if (!prop) {
m_err << "ERROR: " << cmdOpt(opt, param) << ": no such property" << endl;
return false;
} else {
string error;
if (!prop->checkValue(paramstr, error)) {
m_err << "ERROR: " << cmdOpt(opt, param) << ": " << error << endl;
return false;
} else {
props[propstr] = paramstr;
return true;
}
}
}
}
}
bool SyncEvolutionCmdline::listPropValues(const ConfigPropertyRegistry &validProps,
const string &propName,
const string &opt)
{
const ConfigProperty *prop = validProps.find(propName);
if (!prop) {
m_err << "ERROR: "<< opt << ": no such property" << endl;
return false;
} else {
m_out << opt << endl;
string comment = prop->getComment();
if (comment != "") {
list<string> commentLines;
ConfigProperty::splitComment(comment, commentLines);
BOOST_FOREACH(const string &line, commentLines) {
m_out << " " << line << endl;
}
} else {
m_out << " no documentation available" << endl;
}
return true;
}
}
bool SyncEvolutionCmdline::listProperties(const ConfigPropertyRegistry &validProps,
const string &opt)
{
// The first of several related properties has a comment.
// Remember that comment and print it as late as possible,
// that way related properties preceed their comment.
string comment;
BOOST_FOREACH(const ConfigProperty *prop, validProps) {
if (!prop->isHidden()) {
string newComment = prop->getComment();
if (newComment != "") {
if (!comment.empty()) {
dumpComment(m_out, " ", comment);
m_out << endl;
}
comment = newComment;
}
m_out << prop->getName() << ":" << endl;
}
}
dumpComment(m_out, " ", comment);
return true;
}
void SyncEvolutionCmdline::listSources(EvolutionSyncSource &syncSource, const string &header)
{
m_out << header << ":\n";
EvolutionSyncSource::Databases databases = syncSource.getDatabases();
BOOST_FOREACH(const EvolutionSyncSource::Database &database, databases) {
m_out << " " << database.m_name << " (" << database.m_uri << ")";
if (database.m_isDefault) {
m_out << " <default>";
}
m_out << endl;
}
}
void SyncEvolutionCmdline::dumpServers(const string &preamble,
const EvolutionSyncConfig::ServerList &servers)
{
m_out << preamble << endl;
BOOST_FOREACH(const EvolutionSyncConfig::ServerList::value_type &server,servers) {
m_out << " " << server.first << " = " << server.second << endl;
}
if (!servers.size()) {
m_out << " none" << endl;
}
}
void SyncEvolutionCmdline::dumpProperties(const ConfigNode &configuredProps,
const ConfigPropertyRegistry &allProps)
{
BOOST_FOREACH(const ConfigProperty *prop, allProps) {
if (prop->isHidden()) {
continue;
}
if (!m_quiet) {
string comment = prop->getComment();
if (!comment.empty()) {
m_out << endl;
dumpComment(m_out, "# ", comment);
}
}
bool isDefault;
prop->getProperty(configuredProps, &isDefault);
if (isDefault) {
m_out << "# ";
}
m_out << prop->getName() << " = " << prop->getProperty(configuredProps) << endl;
}
}
void SyncEvolutionCmdline::dumpComment(ostream &stream,
const string &prefix,
const string &comment)
{
list<string> commentLines;
ConfigProperty::splitComment(comment, commentLines);
BOOST_FOREACH(const string &line, commentLines) {
stream << prefix << line << endl;
}
}
void SyncEvolutionCmdline::usage(bool full, const string &error, const string &param)
{
ostream &out(error.empty() ? m_out : m_err);
out << "Show available sources:" << endl;
out << " " << m_argv[0] << endl;
out << "Show information about configuration(s):" << endl;
out << " " << m_argv[0] << " --print-servers" << endl;
out << " " << m_argv[0] << " --print-config [--quiet] <server> [sync|<source ...]" << endl;
out << "Show information about SyncEvolution:" << endl;
out << " " << m_argv[0] << " --help|-h" << endl;
out << " " << m_argv[0] << " --version" << endl;
out << "Run a synchronization:" << endl;
out << " " << m_argv[0] << " <server> [<source> ...]" << endl;
out << " " << m_argv[0] << " --run <options for run> <server> [<source> ...]" << endl;
out << "Modify configuration:" << endl;
out << " " << m_argv[0] << " --configure <options for configuration> <server> [<source> ...]" << endl;
out << " " << m_argv[0] << " --migrate <server>" << endl;
if (full) {
out << endl <<
"Options:" << endl <<
"--sync|-s <mode>" << endl <<
"--sync|-s ?" << endl <<
" Temporarily synchronize the active sources in that mode. Useful" << endl <<
" for a \"refresh-from-server\" or \"refresh-from-client\" sync which" << endl <<
" clears all data at one end and copies all items from the other." << endl <<
"" << endl <<
"--print-servers" << endl <<
" Prints the names of all configured servers to stdout." << endl <<
"" << endl <<
"--print-config|-p" << endl <<
" Prints the complete configuration for the selected server" << endl <<
" to stdout, including up-to-date comments for all properties. The" << endl <<
" format is the normal .ini format with source configurations in" << endl <<
" different sections introduced with [<source>] lines. Can be combined" << endl <<
" with --sync-property and --source-property to modify the configuration" << endl <<
" on-the-fly. When one or more sources are listed after the <server>" << endl <<
" name on the command line, then only the configs of those sources are" << endl <<
" printed. Using --quiet suppresses the comments for each property." << endl <<
" When setting a --template, then the reference configuration for" << endl <<
" that server is printed instead of an existing configuration." << endl <<
"" << endl <<
"-configure|-c" << endl <<
" Modify the configuration files for the selected server. If no such" << endl <<
" configuration exists, then a new one is created using one of the" << endl <<
" template configurations (see --template option). When creating" << endl <<
" a new configuration only the active sources will be set to active" << endl <<
" in the new configuration, i.e. \"syncevolution -c scheduleworld addressbook\"" << endl <<
" followed by \"syncevolution scheduleworld\" will only synchronize the" << endl <<
" address book. The other sources are created in a disabled state." << endl <<
" When modifying an existing configuration and sources are specified," << endl <<
" then the source properties of only those sources are modified." << endl <<
"" << endl <<
"--migrate" << endl <<
" In SyncEvolution <= 0.7 a different layout of configuration files" << endl <<
" was used. Using --migrate will automatically migrate to the new" << endl <<
" layout and rename the old directory $HOME/.sync4j/evolution/<server> " << endl <<
" into $HOME/.sync4j/evolution/<server>.old to prevent accidental use" << endl <<
" of the old configuration. WARNING: old SyncEvolution releases cannot" << endl <<
" use the new configuration!" << endl <<
" The switch can also be used to migrate a configuration in the current" << endl <<
" configuration directory: this preserves all property values, discards" << endl <<
" obsolete properties and sets all comments exactly as if the configuration" << endl <<
" had been created from scratch. WARNING: custom comments in the" << endl <<
" configuration are not preserved." << endl <<
" --migrate implies --configure and can be combined with modifying" << endl <<
" properties." << endl <<
"" << endl <<
"--sync-property|-y <property>=<value>" << endl <<
"--sync-property|-y ?" << endl <<
"--sync-property|-y <property>=?" << endl <<
" Overrides a configuration property in the <server>/config.ini file" << endl <<
" for the current synchronization run or permanently when --configure" << endl <<
" is used to update the configuration. Can be used multiple times." << endl <<
" Specifying an unused property will trigger an error message." << endl <<
"" << endl <<
"--source-property|-z <property>=<value>" << endl <<
"--source-property|-z ?" << endl <<
"--source-property|-z <property>=?" << endl <<
" Same as --sync-option, but applies to the configuration of all active" << endl <<
" sources. \"--sync <mode>\" is a shortcut for \"--source-option sync=<mode>\"." << endl <<
"" << endl <<
"--template|-l <server name>|default|?" << endl <<
" Can be used to select from one of the built-in default configurations" << endl <<
" for known SyncML servers. Defaults to the <server> name, so --template" << endl <<
" only has to be specified when creating multiple different configurations" << endl <<
" for the same server. \"default\" is an alias for \"scheduleworld\" and can be" << endl <<
" used as the starting point for servers which do not have a built-in" << endl <<
" configuration." << endl <<
" Each template contains a pseudo-random device ID. Therefore setting the" << endl <<
" \"deviceId\" sync property is only necessary when manually recreating a" << endl <<
" configuration or when a more descriptive name is desired." << endl <<
"" << endl <<
"--status|-t" << endl <<
" The changes made to local data since the last synchronization are" << endl <<
" shown without starting a new one. This can be used to see in advance" << endl <<
" whether the local data needs to be synchronized with the server." << endl <<
"" << endl <<
"--quiet|-q" << endl <<
" Suppresses most of the normal output during a synchronization. The" << endl <<
" log file still contains all the information." << endl <<
"" << endl <<
"--help|-h" << endl <<
" Prints usage information." << endl <<
"" << endl <<
"--version" << endl <<
" Prints the SyncEvolution version." << endl;
}
if (error != "") {
out << endl << "ERROR: " << error << endl;
}
if (param != "") {
out << "INFO: use '" << param << (param[param.size() - 1] == '=' ? "" : " ") <<
"?' to get a list of valid parameters" << endl;
}
}
#ifdef ENABLE_UNIT_TESTS
/** simple line-by-line diff */
static string diffStrings(const string &lhs, const string &rhs)
{
ostringstream res;
typedef boost::split_iterator<string::const_iterator> string_split_iterator;
string_split_iterator lit =
boost::make_split_iterator(lhs, boost::first_finder("\n", boost::is_iequal()));
string_split_iterator rit =
boost::make_split_iterator(rhs, boost::first_finder("\n", boost::is_iequal()));
while (lit != string_split_iterator() &&
rit != string_split_iterator()) {
if (*lit != *rit) {
res << "< " << *lit << endl;
res << "> " << *rit << endl;
}
++lit;
++rit;
}
while (lit != string_split_iterator()) {
res << "< " << *lit << endl;
++lit;
}
while (rit != string_split_iterator()) {
res << "> " << *rit << endl;
++rit;
}
return res.str();
}
# define CPPUNIT_ASSERT_EQUAL_DIFF( expected, actual ) \
do { \
string expected_ = (expected); \
string actual_ = (actual); \
if (expected_ != actual_) { \
CPPUNIT_NS::Message cpputMsg_(string("expected:\n") + \
expected_); \
cpputMsg_.addDetail(string("actual:\n") + \
actual_); \
cpputMsg_.addDetail(string("diff:\n") + \
diffStrings(expected_, actual_)); \
CPPUNIT_NS::Asserter::fail( cpputMsg_, \
CPPUNIT_SOURCELINE() ); \
} \
} while ( false )
// returns last line, including trailing line break, empty if input is empty
static string lastLine(const string &buffer)
{
if (buffer.size() < 2) {
return buffer;
}
size_t line = buffer.rfind("\n", buffer.size() - 2);
if (line == buffer.npos) {
return buffer;
}
return buffer.substr(line + 1);
}
// true if <word> =
static bool isPropAssignment(const string &buffer) {
size_t start = 0;
while (start < buffer.size() &&
!isspace(buffer[start])) {
start++;
}
if (start + 3 <= buffer.size() &&
buffer.substr(start, 3) == " = ") {
return true;
} else {
return false;
}
}
// remove pure comment lines from buffer,
// also empty lines
static string filterConfig(const string &buffer)
{
ostringstream res;
typedef boost::split_iterator<string::const_iterator> string_split_iterator;
for (string_split_iterator it =
boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
it != string_split_iterator();
++it) {
string line = boost::copy_range<string>(*it);
if (!line.empty() &&
(!boost::starts_with(line, "# ") ||
isPropAssignment(line.substr(2)))) {
res << line << endl;
}
}
return res.str();
}
// remove lines indented with spaces
static string filterIndented(const string &buffer)
{
ostringstream res;
bool first = true;
typedef boost::split_iterator<string::const_iterator> string_split_iterator;
for (string_split_iterator it =
boost::make_split_iterator(buffer, boost::first_finder("\n", boost::is_iequal()));
it != string_split_iterator();
++it) {
if (!boost::starts_with(*it, " ")) {
if (!first) {
res << endl;
} else {
first = false;
}
res << *it;
}
}
return res.str();
}
// convert the internal config dump to .ini style
static string internalToIni(const string &config)
{
ostringstream res;
string section;
typedef boost::split_iterator<string::const_iterator> string_split_iterator;
for (string_split_iterator it =
boost::make_split_iterator(config, boost::first_finder("\n", boost::is_iequal()));
it != string_split_iterator();
++it) {
string line(it->begin(), it->end());
if (line.empty()) {
continue;
}
size_t colon = line.find(':');
string prefix = line.substr(0, colon);
if (boost::contains(prefix, ".internal.ini") ||
boost::contains(line, "= internal value")) {
continue;
}
// sources/<name>/config.ini or
// spds/sources/<name>/config.ini
size_t endslash = prefix.rfind('/');
if (endslash != line.npos && endslash > 1) {
size_t slash = prefix.rfind('/', endslash - 1);
if (slash != line.npos) {
string newsource = prefix.substr(slash + 1, endslash - slash - 1);
if (newsource != section &&
newsource != "syncml") {
res << endl << "[" << newsource << "]" << endl;
section = newsource;
}
}
}
string assignment = line.substr(colon + 1);
// substitude aliases with generic values
boost::replace_first(assignment, "= F", "= 0");
boost::replace_first(assignment, "= T", "= 1");
boost::replace_first(assignment, "= md5", "= syncml:auth-md5");
res << assignment << endl;
}
return res.str();
}
/**
* Testing is based on a text representation of a directory
* hierarchy where each line is of the format
* <file path>:<line in file>
*
* The order of files is alphabetical, of lines in the file as
* in the file. Lines in the file without line break cannot
* be represented.
*
* The root of the hierarchy is not part of the representation
* itself.
*/
class SyncEvolutionCmdlineTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(SyncEvolutionCmdlineTest);
CPPUNIT_TEST(testFramework);
CPPUNIT_TEST(testSetupScheduleWorld);
CPPUNIT_TEST(testSetupDefault);
CPPUNIT_TEST(testSetupRenamed);
CPPUNIT_TEST(testSetupFunambol);
CPPUNIT_TEST(testSetupSynthesis);
CPPUNIT_TEST(testPrintServers);
CPPUNIT_TEST(testPrintConfig);
CPPUNIT_TEST(testTemplate);
CPPUNIT_TEST(testSync);
CPPUNIT_TEST(testConfigure);
CPPUNIT_TEST(testOldConfigure);
CPPUNIT_TEST(testListSources);
CPPUNIT_TEST(testMigrate);
CPPUNIT_TEST_SUITE_END();
public:
SyncEvolutionCmdlineTest() :
m_testDir("SyncEvolutionCmdlineTest"),
m_scheduleWorldConfig(".internal.ini:# serverNonce = \n"
".internal.ini:# clientNonce = \n"
".internal.ini:# devInfoHash = \n"
"config.ini:syncURL = http://sync.scheduleworld.com/funambol/ds\n"
"config.ini:username = your SyncML server account name\n"
"config.ini:password = your SyncML server password\n"
"config.ini:# logdir = \n"
"config.ini:# loglevel = 0\n"
"config.ini:# maxlogdirs = 0\n"
"config.ini:# useProxy = 0\n"
"config.ini:# proxyHost = \n"
"config.ini:# proxyUsername = \n"
"config.ini:# proxyPassword = \n"
"config.ini:# clientAuthType = syncml:auth-md5\n"
"config.ini:deviceId = fixed-devid\n" /* this is not the default! */
"config.ini:# maxMsgSize = 8192\n"
"config.ini:# maxObjSize = 500000\n"
"config.ini:# loSupport = 1\n"
"config.ini:# enableCompression = 0\n"
"config.ini:# SSLServerCertificates = \n"
"config.ini:# SSLVerifyServer = 1\n"
"config.ini:# SSLVerifyHost = 1\n"
"sources/addressbook/.internal.ini:# last = 0\n"
"sources/addressbook/config.ini:sync = two-way\n"
"sources/addressbook/config.ini:type = addressbook:text/vcard\n"
"sources/addressbook/config.ini:# evolutionsource = \n"
"sources/addressbook/config.ini:uri = card3\n"
"sources/addressbook/config.ini:# evolutionuser = \n"
"sources/addressbook/config.ini:# evolutionpassword = \n"
"sources/addressbook/config.ini:# encoding = \n"
"sources/calendar/.internal.ini:# last = 0\n"
"sources/calendar/config.ini:sync = two-way\n"
"sources/calendar/config.ini:type = calendar\n"
"sources/calendar/config.ini:# evolutionsource = \n"
"sources/calendar/config.ini:uri = cal2\n"
"sources/calendar/config.ini:# evolutionuser = \n"
"sources/calendar/config.ini:# evolutionpassword = \n"
"sources/calendar/config.ini:# encoding = \n"
"sources/memo/.internal.ini:# last = 0\n"
"sources/memo/config.ini:sync = two-way\n"
"sources/memo/config.ini:type = memo\n"
"sources/memo/config.ini:# evolutionsource = \n"
"sources/memo/config.ini:uri = note\n"
"sources/memo/config.ini:# evolutionuser = \n"
"sources/memo/config.ini:# evolutionpassword = \n"
"sources/memo/config.ini:# encoding = \n"
"sources/todo/.internal.ini:# last = 0\n"
"sources/todo/config.ini:sync = two-way\n"
"sources/todo/config.ini:type = todo\n"
"sources/todo/config.ini:# evolutionsource = \n"
"sources/todo/config.ini:uri = task2\n"
"sources/todo/config.ini:# evolutionuser = \n"
"sources/todo/config.ini:# evolutionpassword = \n"
"sources/todo/config.ini:# encoding = \n")
{}
protected:
/** verify that createFiles/scanFiles themselves work */
void testFramework() {
const string root(m_testDir);
const string content("baz:line\n"
"caz/subdir:booh\n"
"caz/subdir2/sub:# comment\n"
"caz/subdir2/sub:# foo = bar\n"
"caz/subdir2/sub:# empty = \n"
"caz/subdir2/sub:# another comment\n"
"foo:bar1\n"
"foo:\n"
"foo: \n"
"foo:bar2\n");
const string filtered("baz:line\n"
"caz/subdir:booh\n"
"caz/subdir2/sub:# foo = bar\n"
"caz/subdir2/sub:# empty = \n"
"foo:bar1\n"
"foo: \n"
"foo:bar2\n");
createFiles(root, content);
string res = scanFiles(root);
CPPUNIT_ASSERT_EQUAL_DIFF(filtered, res);
}
void removeRandomUUID(string &buffer) {
string uuidstr = "deviceId = sc-pim-";
size_t uuid = buffer.find(uuidstr);
CPPUNIT_ASSERT(uuid != buffer.npos);
size_t end = buffer.find("\n", uuid + uuidstr.size());
CPPUNIT_ASSERT(end != buffer.npos);
buffer.replace(uuid, end - uuid, "deviceId = fixed-devid");
}
/** create new configurations */
void testSetupScheduleWorld() {
string root;
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
ScopedEnvChange home("HOME", m_testDir);
root = m_testDir;
root += "/syncevolution/scheduleworld";
{
rm_r(root);
TestCmdline cmdline("--configure",
"--sync-property", "proxyHost = proxy",
"scheduleworld",
"addressbook",
NULL);
cmdline.doit();
string res = scanFiles(root);
removeRandomUUID(res);
string expected = m_scheduleWorldConfig;
boost::replace_first(expected,
"# proxyHost = ",
"proxyHost = proxy");
boost::replace_all(expected,
"sync = two-way",
"sync = disabled");
boost::replace_first(expected,
"addressbook/config.ini:sync = disabled",
"addressbook/config.ini:sync = two-way");
CPPUNIT_ASSERT_EQUAL_DIFF(expected, res);
}
{
rm_r(root);
TestCmdline cmdline("--configure",
"--sync-property", "deviceID = fixed-devid",
"scheduleworld",
NULL);
cmdline.doit();
string res = scanFiles(root);
CPPUNIT_ASSERT_EQUAL_DIFF(string(m_scheduleWorldConfig), res);
}
}
void testSetupDefault() {
string root;
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
ScopedEnvChange home("HOME", m_testDir);
root = m_testDir;
root += "/syncevolution/some-other-server";
rm_r(root);
TestCmdline cmdline("--configure",
"--template", "default",
"--sync-property", "deviceID = fixed-devid",
"some-other-server",
NULL);
cmdline.doit();
string res = scanFiles(root);
CPPUNIT_ASSERT_EQUAL_DIFF(string(m_scheduleWorldConfig), res);
}
void testSetupRenamed() {
string root;
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
ScopedEnvChange home("HOME", m_testDir);
root = m_testDir;
root += "/syncevolution/scheduleworld2";
rm_r(root);
TestCmdline cmdline("--configure",
"--template", "scheduleworld",
"--sync-property", "deviceID = fixed-devid",
"scheduleworld2",
NULL);
cmdline.doit();
string res = scanFiles(root);
CPPUNIT_ASSERT_EQUAL_DIFF(string(m_scheduleWorldConfig), res);
}
void testSetupFunambol() {
string root;
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
ScopedEnvChange home("HOME", m_testDir);
root = m_testDir;
root += "/syncevolution/funambol";
rm_r(root);
TestCmdline cmdline("--configure",
"--sync-property", "deviceID = fixed-devid",
"funambol",
NULL);
cmdline.doit();
string res = scanFiles(root);
CPPUNIT_ASSERT_EQUAL_DIFF(FunambolConfig(), res);
}
void testSetupSynthesis() {
string root;
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
ScopedEnvChange home("HOME", m_testDir);
root = m_testDir;
root += "/syncevolution/synthesis";
rm_r(root);
TestCmdline cmdline("--configure",
"--sync-property", "deviceID = fixed-devid",
"synthesis",
NULL);
cmdline.doit();
string res = scanFiles(root);
CPPUNIT_ASSERT_EQUAL_DIFF(SynthesisConfig(), res);
}
void testTemplate() {
TestCmdline failure("--template", NULL);
CPPUNIT_ASSERT(!failure.m_cmdline->parse());
CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--template'\n"), lastLine(failure.m_err.str()));
TestCmdline help("--template", "? ", NULL);
help.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
" funambol = http://my.funambol.com\n"
" scheduleworld = http://sync.scheduleworld.com\n"
" synthesis = http://www.synthesis.ch\n"
" memotoo = http://www.memotoo.com\n",
help.m_out.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
}
void testPrintServers() {
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
ScopedEnvChange home("HOME", m_testDir);
rm_r(m_testDir);
testSetupScheduleWorld();
testSetupSynthesis();
testSetupFunambol();
TestCmdline cmdline("--print-servers", NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("Configured servers:\n"
" scheduleworld = SyncEvolutionCmdlineTest/syncevolution/scheduleworld\n"
" synthesis = SyncEvolutionCmdlineTest/syncevolution/synthesis\n"
" funambol = SyncEvolutionCmdlineTest/syncevolution/funambol\n",
cmdline.m_out.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
}
void testPrintConfig() {
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
ScopedEnvChange home("HOME", m_testDir);
rm_r(m_testDir);
testSetupFunambol();
{
TestCmdline failure("--print-config", NULL);
CPPUNIT_ASSERT(failure.m_cmdline->parse());
CPPUNIT_ASSERT(!failure.m_cmdline->run());
CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
CPPUNIT_ASSERT_EQUAL(string("ERROR: --print-config requires either a --template or a server name.\n"),
lastLine(failure.m_err.str()));
}
{
TestCmdline failure("--print-config", "foo", NULL);
CPPUNIT_ASSERT(failure.m_cmdline->parse());
CPPUNIT_ASSERT(!failure.m_cmdline->run());
CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
CPPUNIT_ASSERT_EQUAL(string("ERROR: server 'foo' has not been configured yet.\n"),
lastLine(failure.m_err.str()));
}
{
TestCmdline failure("--print-config", "--template", "foo", NULL);
CPPUNIT_ASSERT(failure.m_cmdline->parse());
CPPUNIT_ASSERT(!failure.m_cmdline->run());
CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
CPPUNIT_ASSERT_EQUAL(string("ERROR: no configuration template for 'foo' available.\n"),
lastLine(failure.m_err.str()));
}
{
TestCmdline cmdline("--print-config", "--template", "scheduleworld", NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
string actual = cmdline.m_out.str();
removeRandomUUID(actual);
string filtered = filterConfig(actual);
CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(m_scheduleWorldConfig)),
filtered);
// there should have been comments
CPPUNIT_ASSERT(actual.size() > filtered.size());
}
{
TestCmdline cmdline("--print-config", "--template", "default", NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
string actual = filterConfig(cmdline.m_out.str());
removeRandomUUID(actual);
CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(m_scheduleWorldConfig)),
actual);
}
{
TestCmdline cmdline("--print-config", "funambol", NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF(filterConfig(internalToIni(FunambolConfig())),
filterConfig(cmdline.m_out.str()));
}
{
TestCmdline cmdline("--print-config", "--template", "scheduleworld",
"--sync-property", "syncURL=foo",
"--source-property", "sync=disabled",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
string expected = filterConfig(internalToIni(m_scheduleWorldConfig));
boost::replace_first(expected,
"syncURL = http://sync.scheduleworld.com/funambol/ds",
"syncURL = foo");
boost::replace_all(expected,
"sync = two-way",
"sync = disabled");
string actual = filterConfig(cmdline.m_out.str());
removeRandomUUID(actual);
CPPUNIT_ASSERT_EQUAL_DIFF(expected,
actual);
}
{
TestCmdline cmdline("--print-config", "--quiet",
"--template", "scheduleworld",
"funambol",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
string actual = cmdline.m_out.str();
removeRandomUUID(actual);
CPPUNIT_ASSERT_EQUAL_DIFF(internalToIni(m_scheduleWorldConfig),
actual);
}
}
void testSync() {
TestCmdline failure("--sync", NULL);
CPPUNIT_ASSERT(!failure.m_cmdline->parse());
CPPUNIT_ASSERT_EQUAL_DIFF("", failure.m_out.str());
CPPUNIT_ASSERT_EQUAL(string("ERROR: missing parameter for '--sync'\n"), lastLine(failure.m_err.str()));
TestCmdline failure2("--sync", "foo", NULL);
CPPUNIT_ASSERT(!failure2.m_cmdline->parse());
CPPUNIT_ASSERT_EQUAL_DIFF("", failure2.m_out.str());
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()));
TestCmdline help("--sync", " ?", NULL);
help.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("--sync\n"
" requests a certain synchronization mode:\n"
" two-way = only send/receive changes since last sync\n"
" slow = exchange all items\n"
" refresh-from-client = discard all remote items and replace with\n"
" the items on the client\n"
" refresh-from-server = discard all local items and replace with\n"
" the items on the server\n"
" one-way-from-client = transmit changes from client\n"
" one-way-from-server = transmit changes from server\n"
" none (or disabled) = synchronization disabled\n",
help.m_out.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
TestCmdline filter("--sync", "refresh-from-server", NULL);
CPPUNIT_ASSERT(filter.m_cmdline->parse());
CPPUNIT_ASSERT(!filter.m_cmdline->run());
CPPUNIT_ASSERT_EQUAL_DIFF("", filter.m_out.str());
CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh-from-server",
string(filter.m_cmdline->m_sourceProps));
CPPUNIT_ASSERT_EQUAL_DIFF("",
string(filter.m_cmdline->m_syncProps));
TestCmdline filter2("--source-property", "sync=refresh", NULL);
CPPUNIT_ASSERT(filter2.m_cmdline->parse());
CPPUNIT_ASSERT(!filter2.m_cmdline->run());
CPPUNIT_ASSERT_EQUAL_DIFF("", filter2.m_out.str());
CPPUNIT_ASSERT_EQUAL_DIFF("sync = refresh",
string(filter2.m_cmdline->m_sourceProps));
CPPUNIT_ASSERT_EQUAL_DIFF("",
string(filter2.m_cmdline->m_syncProps));
}
void testConfigure() {
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
ScopedEnvChange home("HOME", m_testDir);
rm_r(m_testDir);
testSetupScheduleWorld();
doConfigure(ScheduleWorldConfig(), "sources/addressbook/config.ini:");
string syncProperties("syncURL:\n"
"\n"
"username:\n"
"\n"
"password:\n"
"\n"
"logdir:\n"
"\n"
"loglevel:\n"
"\n"
"maxlogdirs:\n"
"\n"
"useProxy:\n"
"\n"
"proxyHost:\n"
"\n"
"proxyUsername:\n"
"\n"
"proxyPassword:\n"
"\n"
"clientAuthType:\n"
"\n"
"deviceId:\n"
"\n"
"maxMsgSize:\n"
"maxObjSize:\n"
"loSupport:\n"
"\n"
"enableCompression:\n"
"\n"
"SSLServerCertificates:\n"
"\n"
"SSLVerifyServer:\n"
"\n"
"SSLVerifyHost:\n");
string sourceProperties("sync:\n"
"\n"
"type:\n"
"\n"
"evolutionsource:\n"
"\n"
"uri:\n"
"\n"
"evolutionuser:\n"
"evolutionpassword:\n"
"\n"
"encoding:\n");
{
TestCmdline cmdline("--sync-property", "?",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties,
filterIndented(cmdline.m_out.str()));
}
{
TestCmdline cmdline("--source-property", "?",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties,
filterIndented(cmdline.m_out.str()));
}
{
TestCmdline cmdline("--source-property", "?",
"--sync-property", "?",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF(sourceProperties + syncProperties,
filterIndented(cmdline.m_out.str()));
}
{
TestCmdline cmdline("--sync-property", "?",
"--source-property", "?",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF(syncProperties + sourceProperties,
filterIndented(cmdline.m_out.str()));
}
}
void testOldConfigure() {
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
ScopedEnvChange home("HOME", m_testDir);
string oldConfig = OldScheduleWorldConfig();
InitList<string> props = InitList<string>("serverNonce") +
"clientNonce" +
"devInfoHash" +
"last";
BOOST_FOREACH(string &prop, props) {
boost::replace_all(oldConfig,
prop + " = ",
prop + " = internal value");
}
rm_r(m_testDir);
createFiles(m_testDir + "/.sync4j/evolution/scheduleworld", oldConfig);
doConfigure(oldConfig, "spds/sources/addressbook/config.txt:");
}
void doConfigure(const string &SWConfig, const string &addressbookPrefix) {
string expected;
{
TestCmdline cmdline("--configure",
"--source-property", "sync = disabled",
"scheduleworld",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
expected = filterConfig(internalToIni(SWConfig));
boost::replace_all(expected,
"sync = two-way",
"sync = disabled");
CPPUNIT_ASSERT_EQUAL_DIFF(expected,
filterConfig(printConfig("scheduleworld")));
}
{
TestCmdline cmdline("--configure",
"--source-property", "sync = one-way-from-server",
"scheduleworld",
"addressbook",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
expected = SWConfig;
boost::replace_all(expected,
"sync = two-way",
"sync = disabled");
boost::replace_first(expected,
addressbookPrefix + "sync = disabled",
addressbookPrefix + "sync = one-way-from-server");
expected = filterConfig(internalToIni(expected));
CPPUNIT_ASSERT_EQUAL_DIFF(expected,
filterConfig(printConfig("scheduleworld")));
}
{
TestCmdline cmdline("--configure",
"--sync", "two-way",
"-z", "evolutionsource=source",
"--sync-property", "maxlogdirs=10",
"-y", "LOGDIR=logdir",
"scheduleworld",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
boost::replace_all(expected,
"sync = one-way-from-server",
"sync = two-way");
boost::replace_all(expected,
"sync = disabled",
"sync = two-way");
boost::replace_all(expected,
"# evolutionsource = ",
"evolutionsource = source");
boost::replace_all(expected,
"# maxlogdirs = 0",
"maxlogdirs = 10");
boost::replace_all(expected,
"# logdir = ",
"logdir = logdir");
CPPUNIT_ASSERT_EQUAL_DIFF(expected,
filterConfig(printConfig("scheduleworld")));
}
}
void testListSources() {
TestCmdline cmdline(NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
// exact output varies, do not test
}
void testMigrate() {
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
ScopedEnvChange home("HOME", m_testDir);
rm_r(m_testDir);
string oldRoot = m_testDir + "/.sync4j/evolution/scheduleworld";
string newRoot = m_testDir + "/syncevolution/scheduleworld";
string oldConfig = OldScheduleWorldConfig();
{
// migrate old config
createFiles(oldRoot, oldConfig);
string createdConfig = scanFiles(oldRoot);
TestCmdline cmdline("--migrate",
"scheduleworld",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
string migratedConfig = scanFiles(newRoot);
CPPUNIT_ASSERT_EQUAL_DIFF(m_scheduleWorldConfig, migratedConfig);
string renamedConfig = scanFiles(oldRoot + ".old");
CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
}
{
// rewrite existing config
createFiles(newRoot,
"config.ini:# obsolete comment\n"
"config.ini:obsoleteprop = foo\n",
true);
string createdConfig = scanFiles(newRoot);
TestCmdline cmdline("--migrate",
"scheduleworld",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
string migratedConfig = scanFiles(newRoot);
CPPUNIT_ASSERT_EQUAL_DIFF(m_scheduleWorldConfig, migratedConfig);
string renamedConfig = scanFiles(newRoot + ".old");
CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
}
{
// migrate old config with changes, a second time
createFiles(oldRoot, oldConfig);
createFiles(oldRoot,
"spds/sources/addressbook/changes/config.txt:foo = bar\n"
"spds/sources/addressbook/changes/config.txt:foo2 = bar2\n",
true);
string createdConfig = scanFiles(oldRoot);
rm_r(newRoot);
TestCmdline cmdline("--migrate",
"scheduleworld",
NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_out.str());
string migratedConfig = scanFiles(newRoot);
string expected = m_scheduleWorldConfig;
boost::replace_first(expected,
"sources/addressbook/config.ini",
"sources/addressbook/.other.ini:foo = bar\n"
"sources/addressbook/.other.ini:foo2 = bar2\n"
"sources/addressbook/config.ini");
CPPUNIT_ASSERT_EQUAL_DIFF(expected, migratedConfig);
string renamedConfig = scanFiles(oldRoot + ".old.1");
CPPUNIT_ASSERT_EQUAL_DIFF(createdConfig, renamedConfig);
}
}
const string m_testDir;
const string m_scheduleWorldConfig;
private:
/**
* vararg constructor with NULL termination,
* out and error stream into stringstream members
*/
class TestCmdline {
public:
TestCmdline(const char *arg, ...) {
va_list argList;
va_start (argList, arg);
for (const char *curr = arg;
curr;
curr = va_arg(argList, const char *)) {
m_argvstr.push_back(curr);
}
va_end(argList);
m_argv.reset(new const char *[m_argvstr.size() + 1]);
m_argv[0] = "client-test";
for (size_t index = 0;
index < m_argvstr.size();
++index) {
m_argv[index + 1] = m_argvstr[index].c_str();
}
m_cmdline.set(new SyncEvolutionCmdline(m_argvstr.size() + 1, m_argv.get(), m_out, m_err), "cmdline");
}
void doit() {
bool success;
success = m_cmdline->parse() &&
m_cmdline->run();
if (m_err.str().size()) {
cerr << endl << m_err.str();
}
CPPUNIT_ASSERT(success);
}
ostringstream m_out, m_err;
cxxptr<SyncEvolutionCmdline> m_cmdline;
private:
vector<string> m_argvstr;
boost::scoped_array<const char *> m_argv;
};
string ScheduleWorldConfig() {
return m_scheduleWorldConfig;
}
string OldScheduleWorldConfig() {
string oldConfig = m_scheduleWorldConfig;
boost::replace_all(oldConfig,
".internal.ini",
"config.ini");
InitList<string> sources = InitList<string>("addressbook") +
"calendar" +
"memo" +
"todo";
BOOST_FOREACH(string &source, sources) {
boost::replace_all(oldConfig,
string("sources/") + source + "/config.ini",
string("spds/sources/") + source + "/config.txt");
}
boost::replace_all(oldConfig,
"config.ini",
"spds/syncml/config.txt");
return oldConfig;
}
string FunambolConfig() {
string config = m_scheduleWorldConfig;
boost::replace_first(config,
"syncURL = http://sync.scheduleworld.com/funambol/ds",
"syncURL = http://my.funambol.com/sync");
boost::replace_first(config,
"addressbook/config.ini:uri = card3",
"addressbook/config.ini:uri = card");
boost::replace_first(config,
"addressbook/config.ini:type = addressbook:text/vcard",
"addressbook/config.ini:type = addressbook");
boost::replace_first(config,
"calendar/config.ini:uri = cal2",
"calendar/config.ini:uri = event");
boost::replace_first(config,
"calendar/config.ini:sync = two-way",
"calendar/config.ini:sync = disabled");
boost::replace_first(config,
"todo/config.ini:uri = task2",
"todo/config.ini:uri = task");
boost::replace_first(config,
"todo/config.ini:sync = two-way",
"todo/config.ini:sync = disabled");
return config;
}
string SynthesisConfig() {
string config = m_scheduleWorldConfig;
boost::replace_first(config,
"syncURL = http://sync.scheduleworld.com/funambol/ds",
"syncURL = http://www.synthesis.ch/sync");
boost::replace_first(config,
"addressbook/config.ini:uri = card3",
"addressbook/config.ini:uri = contacts");
boost::replace_first(config,
"addressbook/config.ini:type = addressbook:text/vcard",
"addressbook/config.ini:type = addressbook");
boost::replace_first(config,
"calendar/config.ini:uri = cal2",
"calendar/config.ini:uri = events");
boost::replace_first(config,
"calendar/config.ini:sync = two-way",
"calendar/config.ini:sync = disabled");
boost::replace_first(config,
"memo/config.ini:uri = note",
"memo/config.ini:uri = notes");
boost::replace_first(config,
"todo/config.ini:uri = task2",
"todo/config.ini:uri = tasks");
boost::replace_first(config,
"todo/config.ini:sync = two-way",
"todo/config.ini:sync = disabled");
return config;
}
/** temporarily set env variable, restore old value on destruction */
class ScopedEnvChange {
public:
ScopedEnvChange(const string &var, const string &value) :
m_var(var)
{
const char *oldval = getenv(var.c_str());
if (oldval) {
m_oldvalset = true;
m_oldval = oldval;
} else {
m_oldvalset = false;
}
setenv(var.c_str(), value.c_str(), 1);
}
~ScopedEnvChange()
{
if (m_oldvalset) {
setenv(m_var.c_str(), m_oldval.c_str(), 1);
} else {
unsetenv(m_var.c_str());
}
}
private:
string m_var, m_oldval;
bool m_oldvalset;
};
/** create directory hierarchy, overwriting previous content */
void createFiles(const string &root, const string &content, bool append = false) {
if (!append) {
rm_r(root);
}
size_t start = 0;
ofstream out;
string outname;
out.exceptions(ios_base::badbit|ios_base::failbit);
while (start < content.size()) {
size_t delim = content.find(':', start);
size_t end = content.find('\n', start);
if (delim == content.npos ||
end == content.npos) {
// invalid content ?!
break;
}
string newname = content.substr(start, delim - start);
string line = content.substr(delim + 1, end - delim - 1);
if (newname != outname) {
if (out.is_open()) {
out.close();
}
string fullpath = root + "/" + newname;
size_t fileoff = fullpath.rfind('/');
mkdir_p(fullpath.substr(0, fileoff));
out.open(fullpath.c_str(),
append ? ios_base::out : (ios_base::out|ios_base::trunc));
outname = newname;
}
out << line << endl;
start = end + 1;
}
}
/** turn directory hierarchy into string */
string scanFiles(const string &root, bool onlyProps = true) {
ostringstream out;
scanFiles(root, "", out, onlyProps);
return out.str();
}
void scanFiles(const string &root, const string &dir, ostringstream &out, bool onlyProps) {
string newroot = root;
newroot += "/";
newroot += dir;
ReadDir readDir(newroot);
sort(readDir.begin(), readDir.end());
BOOST_FOREACH(const string &entry, readDir) {
if (isDir(newroot + "/" + entry)) {
scanFiles(root, dir + (dir.empty() ? "" : "/") + entry, out, onlyProps);
} else {
ifstream in;
in.exceptions(ios_base::badbit /* failbit must not trigger exception because is set when reaching eof ?! */);
in.open((newroot + "/" + entry).c_str());
string line;
while (!in.eof()) {
getline(in, line);
if ((line.size() || !in.eof()) &&
(!onlyProps ||
(boost::starts_with(line, "# ") ?
isPropAssignment(line.substr(2)) :
!line.empty()))) {
if (dir.size()) {
out << dir << "/";
}
out << entry << ":";
out << line << '\n';
}
}
}
}
}
string printConfig(const string &server) {
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);
ScopedEnvChange home("HOME", m_testDir);
TestCmdline cmdline("--print-config", server.c_str(), NULL);
cmdline.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("", cmdline.m_err.str());
return cmdline.m_out.str();
}
};
SYNCEVOLUTION_TEST_SUITE_REGISTRATION(SyncEvolutionCmdlineTest);
#endif // ENABLE_UNIT_TESTS