2005-11-26 22:16:03 +01:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2005 Patrick Ohly
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "EvolutionSyncClient.h"
|
|
|
|
#include "EvolutionSyncSource.h"
|
|
|
|
|
|
|
|
#include <spdm/DMTree.h>
|
2006-03-19 22:37:30 +01:00
|
|
|
#include <posix/base/posixlog.h>
|
2005-11-26 22:16:03 +01:00
|
|
|
|
|
|
|
#include <list>
|
|
|
|
#include <memory>
|
|
|
|
#include <vector>
|
2006-03-19 22:37:30 +01:00
|
|
|
#include <sstream>
|
|
|
|
#include <fstream>
|
|
|
|
#include <iomanip>
|
|
|
|
#include <iostream>
|
2005-11-26 22:16:03 +01:00
|
|
|
using namespace std;
|
|
|
|
|
2006-03-19 22:37:30 +01:00
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <errno.h>
|
|
|
|
|
2006-04-09 13:48:11 +02:00
|
|
|
EvolutionSyncClient::EvolutionSyncClient(const string &server, const set<string> &sources) :
|
2005-11-26 22:16:03 +01:00
|
|
|
m_client(Sync4jClient::getInstance()),
|
|
|
|
m_server(server),
|
2006-04-09 13:48:11 +02:00
|
|
|
m_sources(sources),
|
2005-11-26 22:16:03 +01:00
|
|
|
m_configPath(string("evolution/") + server)
|
|
|
|
{
|
|
|
|
m_client.setDMConfig(m_configPath.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
EvolutionSyncClient::~EvolutionSyncClient()
|
|
|
|
{
|
|
|
|
Sync4jClient::dispose();
|
|
|
|
}
|
|
|
|
|
2006-03-19 22:37:30 +01:00
|
|
|
/// remove all files in the given directory and the directory itself
|
|
|
|
static void rmBackupDir(const string &dirname)
|
2005-11-26 22:16:03 +01:00
|
|
|
{
|
2006-03-19 22:37:30 +01:00
|
|
|
DIR *dir = opendir(dirname.c_str());
|
|
|
|
if (!dir) {
|
|
|
|
throw dirname + ": " + strerror(errno);
|
|
|
|
}
|
|
|
|
vector<string> entries;
|
|
|
|
struct dirent *entry;
|
|
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
|
|
entries.push_back(entry->d_name);
|
|
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
|
|
|
|
for (vector<string>::iterator it = entries.begin();
|
|
|
|
it != entries.end();
|
|
|
|
++it) {
|
|
|
|
string path = dirname + "/" + *it;
|
|
|
|
if (unlink(path.c_str())
|
|
|
|
&& errno != ENOENT
|
|
|
|
#ifdef EISDIR
|
|
|
|
&& errno != EISDIR
|
|
|
|
#endif
|
|
|
|
) {
|
|
|
|
throw path + ": " + strerror(errno);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rmdir(dirname.c_str())) {
|
|
|
|
throw dirname + ": " + strerror(errno);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// this class owns the logging directory and is responsible
|
|
|
|
// for redirecting output at the start and end of sync (even
|
|
|
|
// in case of exceptions thrown!)
|
|
|
|
class LogDir {
|
|
|
|
string m_logdir; /**< configured backup root dir, empty if none */
|
|
|
|
int m_maxlogdirs; /**< number of backup dirs to preserve, 0 if unlimited */
|
|
|
|
string m_prefix; /**< common prefix of backup dirs */
|
|
|
|
string m_path; /**< path to current logging and backup dir */
|
|
|
|
string m_logfile; /**< path to log file there */
|
|
|
|
const string &m_server; /**< name of the server for this synchronization */
|
2006-04-06 19:02:43 +02:00
|
|
|
LogLevel m_oldLogLevel; /**< logging level to restore */
|
|
|
|
|
2006-03-19 22:37:30 +01:00
|
|
|
|
|
|
|
public:
|
2006-04-06 19:02:43 +02:00
|
|
|
LogDir(const string &server) : m_server(server),
|
|
|
|
m_oldLogLevel(LOG.getLevel())
|
|
|
|
{}
|
2006-03-19 22:37:30 +01:00
|
|
|
|
|
|
|
// setup log directory and redirect logging into it
|
|
|
|
// @param path path to configured backup directy, NULL if defaulting to /tmp
|
|
|
|
// @param maxlogdirs number of backup dirs to preserve in path, 0 if unlimited
|
|
|
|
void setLogdir(const char *path, int maxlogdirs) {
|
|
|
|
m_maxlogdirs = maxlogdirs;
|
|
|
|
if (path && path[0]) {
|
|
|
|
m_logdir = path;
|
|
|
|
|
|
|
|
// create unique directory name in the given directory
|
|
|
|
time_t ts = time(NULL);
|
|
|
|
struct tm *tm = localtime(&ts);
|
|
|
|
stringstream base;
|
|
|
|
// SyncEvolution-<server>-<yyyy>-<mm>-<dd>-<hh>-<mm>
|
|
|
|
m_prefix = "SyncEvolution-";
|
|
|
|
m_prefix += m_server;
|
|
|
|
base << path << "/"
|
|
|
|
<< m_prefix
|
|
|
|
<< "-"
|
|
|
|
<< setfill('0')
|
|
|
|
<< setw(4) << tm->tm_year + 1900 << "-"
|
|
|
|
<< setw(2) << tm->tm_mon << "-"
|
|
|
|
<< tm->tm_mday << "-"
|
|
|
|
<< tm->tm_hour << "-"
|
|
|
|
<< tm->tm_min;
|
|
|
|
int seq = 0;
|
|
|
|
while (true) {
|
|
|
|
stringstream path;
|
|
|
|
path << base.str();
|
|
|
|
if (seq) {
|
|
|
|
path << "-" << seq;
|
|
|
|
}
|
|
|
|
m_path = path.str();
|
|
|
|
if (!mkdir(m_path.c_str(), S_IRWXU)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (errno != EEXIST) {
|
|
|
|
throw m_path + ": " + strerror(errno);
|
|
|
|
}
|
|
|
|
seq++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// create temporary directory: $TMPDIR/SyncEvolution-<username>
|
|
|
|
stringstream path;
|
|
|
|
char *tmp = getenv("TMPDIR");
|
|
|
|
if (tmp) {
|
|
|
|
path << tmp;
|
|
|
|
} else {
|
|
|
|
path << "/tmp";
|
|
|
|
}
|
|
|
|
path << "/SyncEvolution-";
|
|
|
|
struct passwd *user = getpwuid(getuid());
|
|
|
|
if (user && user->pw_name) {
|
|
|
|
path << user->pw_name;
|
|
|
|
} else {
|
|
|
|
path << getuid();
|
|
|
|
}
|
|
|
|
path << "-" << m_server;
|
|
|
|
|
|
|
|
m_path = path.str();
|
|
|
|
if (mkdir(m_path.c_str(), S_IRWXU)) {
|
|
|
|
if (errno != EEXIST) {
|
|
|
|
throw m_path + ": " + strerror(errno);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// redirect logging into that directory, including stderr,
|
|
|
|
// after truncating it
|
|
|
|
m_logfile = m_path + "/client.log";
|
|
|
|
ofstream out;
|
|
|
|
out.exceptions(ios_base::badbit|ios_base::failbit|ios_base::eofbit);
|
|
|
|
out.open(m_logfile.c_str());
|
|
|
|
out.close();
|
|
|
|
setLogFile(m_logfile.c_str(), true);
|
|
|
|
LOG.setLevel(LOG_LEVEL_DEBUG);
|
|
|
|
}
|
|
|
|
|
|
|
|
// return log directory, empty if not enabled
|
|
|
|
const string &getLogdir() {
|
|
|
|
return m_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
// return log file, empty if not enabled
|
|
|
|
const string &getLogfile() {
|
|
|
|
return m_logfile;
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove oldest backup dirs if exceeding limit
|
|
|
|
void expire() {
|
|
|
|
if (m_logdir.size() && m_maxlogdirs > 0 ) {
|
|
|
|
DIR *dir = opendir(m_logdir.c_str());
|
|
|
|
if (!dir) {
|
|
|
|
throw m_logdir + ": " + strerror(errno);
|
|
|
|
}
|
|
|
|
vector<string> entries;
|
|
|
|
struct dirent *entry;
|
|
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
|
|
if (strlen(entry->d_name) >= m_prefix.size() &&
|
|
|
|
!m_prefix.compare(0, m_prefix.size(), entry->d_name, m_prefix.size())) {
|
|
|
|
entries.push_back(entry->d_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
|
|
|
|
sort(entries.begin(), entries.end());
|
|
|
|
|
|
|
|
int deleted = 0;
|
|
|
|
for (vector<string>::iterator it = entries.begin();
|
|
|
|
it != entries.end() && entries.size() - deleted > m_maxlogdirs;
|
|
|
|
++it, ++deleted) {
|
|
|
|
string path = m_logdir + "/" + *it;
|
|
|
|
string msg = "removing " + path;
|
|
|
|
LOG.info(msg.c_str());
|
|
|
|
rmBackupDir(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove redirection of stderr and (optionally) also of logging
|
|
|
|
void restore(bool all) {
|
|
|
|
if (all) {
|
|
|
|
setLogFile("-", false);
|
2006-04-06 19:02:43 +02:00
|
|
|
LOG.setLevel(m_oldLogLevel);
|
2006-03-19 22:37:30 +01:00
|
|
|
} else {
|
|
|
|
setLogFile(m_logfile.c_str(), false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
~LogDir() {
|
|
|
|
restore(true);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// this class owns the sync sources and (together with
|
|
|
|
// a logdir) handles writing of per-sync files as well
|
|
|
|
// as the final report (
|
|
|
|
class SourceList : public list<EvolutionSyncSource *> {
|
|
|
|
LogDir m_logdir; /**< our logging directory */
|
|
|
|
bool m_doLogging; /**< true iff additional files are to be written during sync */
|
|
|
|
bool m_reportTodo; /**< true if syncDone() shall print a final report */
|
|
|
|
|
|
|
|
string databaseName(EvolutionSyncSource &source, const string suffix) {
|
|
|
|
return m_logdir.getLogdir() + "/" +
|
|
|
|
source.getName() + "." + suffix + "." +
|
|
|
|
source.fileSuffix();
|
|
|
|
}
|
|
|
|
|
|
|
|
void dumpDatabases(const string &suffix) {
|
|
|
|
ofstream out;
|
|
|
|
out.exceptions(ios_base::badbit|ios_base::failbit|ios_base::eofbit);
|
|
|
|
|
|
|
|
for( iterator it = begin();
|
|
|
|
it != end();
|
|
|
|
++it ) {
|
|
|
|
string file = databaseName(**it, suffix);
|
|
|
|
out.open(file.c_str());
|
|
|
|
(*it)->exportData(out);
|
|
|
|
out.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
|
|
SourceList(const string &server, bool doLogging) :
|
|
|
|
m_logdir(server),
|
|
|
|
m_doLogging(doLogging),
|
|
|
|
m_reportTodo(false) {
|
|
|
|
}
|
|
|
|
|
|
|
|
// call as soon as logdir settings are known
|
|
|
|
void setLogdir(const char *logDirPath, int maxlogdirs) {
|
|
|
|
if (m_doLogging) {
|
|
|
|
m_logdir.setLogdir(logDirPath, maxlogdirs);
|
2006-04-06 19:02:43 +02:00
|
|
|
} else {
|
|
|
|
// at least increase log level
|
|
|
|
LOG.setLevel(LOG_LEVEL_DEBUG);
|
|
|
|
}
|
2006-03-19 22:37:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// call when all sync sources are ready to dump
|
|
|
|
// pre-sync databases
|
|
|
|
void syncPrepare() {
|
|
|
|
if (m_doLogging) {
|
|
|
|
m_reportTodo = true;
|
|
|
|
|
|
|
|
// dump initial databases
|
|
|
|
dumpDatabases("before");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// call at the end of a sync with success == true
|
|
|
|
// if all went well to print report
|
|
|
|
void syncDone(bool success) {
|
|
|
|
if (m_doLogging) {
|
|
|
|
// ensure that stderr is seen again
|
|
|
|
m_logdir.restore(false);
|
|
|
|
|
|
|
|
if (m_reportTodo) {
|
|
|
|
// haven't looked at result of sync yet;
|
|
|
|
// don't do it again
|
|
|
|
m_reportTodo = false;
|
|
|
|
|
|
|
|
// dump datatbase after sync
|
|
|
|
dumpDatabases("after");
|
|
|
|
|
|
|
|
// scan for error messages
|
|
|
|
ifstream in;
|
|
|
|
in.open(m_logdir.getLogfile().c_str());
|
|
|
|
while (in.good()) {
|
|
|
|
string line;
|
|
|
|
getline(in, line);
|
|
|
|
if (line.find("[ERROR]") != line.npos) {
|
|
|
|
success = false;
|
|
|
|
cout << line << "\n";
|
2006-04-09 13:48:11 +02:00
|
|
|
} else if (line.find("[INFO]") != line.npos) {
|
|
|
|
cout << line << "\n";
|
2006-03-19 22:37:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
in.close();
|
|
|
|
cout << flush;
|
|
|
|
|
|
|
|
cout << "\n";
|
|
|
|
if (success) {
|
|
|
|
cout << "Synchronization successful.\n";
|
|
|
|
} else {
|
|
|
|
cout << "Synchronization failed, see "
|
|
|
|
<< m_logdir.getLogdir()
|
|
|
|
<< " for details.\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
// compare databases
|
|
|
|
cout << "\nModifications:\n";
|
|
|
|
for( iterator it = begin();
|
|
|
|
it != end();
|
|
|
|
++it ) {
|
|
|
|
cout << "*** " << (*it)->getName() << " ***\n" << flush;
|
|
|
|
|
|
|
|
string before = databaseName(**it, "before");
|
|
|
|
string after = databaseName(**it, "after");
|
2006-05-24 21:38:45 +02:00
|
|
|
string cmd = string("synccompare '" ) +
|
2006-03-19 22:37:30 +01:00
|
|
|
before + "' '" + after +
|
|
|
|
"' && echo 'no changes'";
|
|
|
|
system(cmd.c_str());
|
|
|
|
}
|
|
|
|
cout << "\n";
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
m_logdir.expire();
|
|
|
|
}
|
2005-11-26 22:16:03 +01:00
|
|
|
}
|
|
|
|
}
|
2006-03-19 22:37:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
~SourceList() {
|
|
|
|
// if we get here without a previous report,
|
|
|
|
// something went wrong
|
|
|
|
syncDone(false);
|
|
|
|
|
|
|
|
// free sync sources
|
|
|
|
for( iterator it = begin();
|
|
|
|
it != end();
|
|
|
|
++it ) {
|
|
|
|
delete *it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
void EvolutionSyncClient::sync(SyncMode syncMode, bool doLogging)
|
|
|
|
{
|
|
|
|
SourceList sources(m_server, doLogging);
|
|
|
|
|
2005-11-26 22:16:03 +01:00
|
|
|
DMTree config(m_configPath.c_str());
|
|
|
|
|
|
|
|
// find server URL (part of change id)
|
|
|
|
string serverPath = m_configPath + "/spds/syncml";
|
|
|
|
auto_ptr<ManagementNode> serverNode(config.getManagementNode(serverPath.c_str()));
|
|
|
|
string url = EvolutionSyncSource::getPropertyValue(*serverNode, "syncURL");
|
|
|
|
|
2006-03-19 22:37:30 +01:00
|
|
|
// redirect logging as soon as possible
|
|
|
|
sources.setLogdir(serverNode->getPropertyValue("logdir"),
|
|
|
|
atoi(serverNode->getPropertyValue("maxlogdirs")));
|
|
|
|
|
2005-11-26 22:16:03 +01:00
|
|
|
// find sources
|
|
|
|
string sourcesPath = m_configPath + "/spds/sources";
|
|
|
|
auto_ptr<ManagementNode> sourcesNode(config.getManagementNode(sourcesPath.c_str()));
|
|
|
|
int index, numSources = sourcesNode->getChildrenMaxCount();
|
|
|
|
char **sourceNamesPtr = sourcesNode->getChildrenNames();
|
|
|
|
|
|
|
|
// copy source names into format that will be
|
|
|
|
// freed in case of exception
|
|
|
|
vector<string> sourceNames;
|
|
|
|
for ( index = 0; index < numSources; index++ ) {
|
|
|
|
sourceNames.push_back(sourceNamesPtr[index]);
|
|
|
|
delete [] sourceNamesPtr[index];
|
|
|
|
}
|
|
|
|
delete [] sourceNamesPtr;
|
|
|
|
|
|
|
|
// iterate over sources
|
|
|
|
for ( index = 0; index < numSources; index++ ) {
|
|
|
|
// is the source enabled?
|
|
|
|
string sourcePath(sourcesPath + "/" + sourceNames[index]);
|
|
|
|
auto_ptr<ManagementNode> sourceNode(config.getManagementNode(sourcePath.c_str()));
|
2006-04-09 13:48:11 +02:00
|
|
|
string sync = EvolutionSyncSource::getPropertyValue(*sourceNode, "sync");
|
|
|
|
bool enabled = sync != "none";
|
|
|
|
SyncMode overrideMode = SYNC_NONE;
|
|
|
|
|
|
|
|
// override state?
|
|
|
|
if (m_sources.size()) {
|
|
|
|
if (m_sources.find(sourceNames[index]) != m_sources.end()) {
|
|
|
|
if (!enabled) {
|
|
|
|
overrideMode = SYNC_TWO_WAY;
|
|
|
|
enabled = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
enabled = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (enabled) {
|
2005-11-26 22:16:03 +01:00
|
|
|
// create it
|
|
|
|
string type = EvolutionSyncSource::getPropertyValue(*sourceNode, "type");
|
|
|
|
EvolutionSyncSource *syncSource =
|
|
|
|
EvolutionSyncSource::createSource(
|
|
|
|
sourceNames[index],
|
|
|
|
string("sync4jevolution:") + url + "/" + EvolutionSyncSource::getPropertyValue(*sourceNode, "name"),
|
|
|
|
EvolutionSyncSource::getPropertyValue(*sourceNode, "evolutionsource"),
|
|
|
|
type
|
|
|
|
);
|
|
|
|
if (!syncSource) {
|
|
|
|
throw sourceNames[index] + ": type " +
|
|
|
|
( type.size() ? string("not configured") :
|
|
|
|
string("'") + type + "' empty or unknown" );
|
|
|
|
}
|
2006-04-09 13:48:11 +02:00
|
|
|
|
2006-04-06 19:02:43 +02:00
|
|
|
if (syncMode != SYNC_NONE) {
|
2006-04-09 13:48:11 +02:00
|
|
|
// caller overrides mode
|
|
|
|
syncSource->setFixedSyncMode(syncMode);
|
|
|
|
} else if (overrideMode != SYNC_NONE) {
|
|
|
|
// disabled source selected via source name
|
|
|
|
syncSource->setFixedSyncMode(overrideMode);
|
2006-04-06 19:02:43 +02:00
|
|
|
}
|
2006-04-09 13:48:11 +02:00
|
|
|
|
2005-11-26 22:16:03 +01:00
|
|
|
sources.push_back(syncSource);
|
|
|
|
|
|
|
|
// also open it; failing now is still safe
|
|
|
|
syncSource->open();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!sources.size()) {
|
2006-03-19 22:37:30 +01:00
|
|
|
throw string("no sources configured");
|
2005-11-26 22:16:03 +01:00
|
|
|
}
|
|
|
|
|
2006-03-19 22:37:30 +01:00
|
|
|
// ready to go: dump initial databases and prepare for final report
|
|
|
|
sources.syncPrepare();
|
|
|
|
|
2005-11-26 22:16:03 +01:00
|
|
|
// build array as sync wants it, then sync
|
|
|
|
// (no exceptions allowed here)
|
|
|
|
SyncSource **sourceArray = new SyncSource *[sources.size() + 1];
|
|
|
|
index = 0;
|
|
|
|
for ( list<EvolutionSyncSource *>::iterator it = sources.begin();
|
|
|
|
it != sources.end();
|
|
|
|
++it ) {
|
|
|
|
sourceArray[index] = *it;
|
|
|
|
++index;
|
|
|
|
}
|
|
|
|
sourceArray[index] = NULL;
|
|
|
|
int res = m_client.sync( sourceArray );
|
|
|
|
delete [] sourceArray;
|
|
|
|
|
2005-12-03 17:06:47 +01:00
|
|
|
// TODO: force slow sync in case of failure or failed Evolution source
|
2005-11-26 22:16:03 +01:00
|
|
|
|
2005-12-03 17:06:47 +01:00
|
|
|
if (res) {
|
|
|
|
if (lastErrorCode) {
|
|
|
|
throw lastErrorCode;
|
|
|
|
}
|
|
|
|
// no error code?!
|
|
|
|
lastErrorCode = res;
|
|
|
|
if (!lastErrorMsg[0]) {
|
|
|
|
strcpy(lastErrorMsg, "sync() failed without setting an error description");
|
|
|
|
}
|
|
|
|
throw res;
|
2005-11-26 22:16:03 +01:00
|
|
|
}
|
2006-03-19 22:37:30 +01:00
|
|
|
|
|
|
|
// all went well: print final report before cleaning up
|
|
|
|
sources.syncDone(true);
|
2005-11-26 22:16:03 +01:00
|
|
|
}
|