syncevolution/src/syncevo/SyncContext.cpp

5128 lines
207 KiB
C++
Raw Normal View History

/*
* Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
* Copyright (C) 2009 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef _GNU_SOURCE
# define _GNU_SOURCE 1
#endif
#include <dlfcn.h>
#include <syncevo/SyncContext.h>
#include <syncevo/SyncSource.h>
#include <syncevo/util.h>
rewrote signal handling Having the signal handling code in SyncContext created an unnecessary dependency of some classes (in particular the transports) on SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files. Cleaning up when the caller is done with signal handling is now part of the utility class (removed automatically when guard instance is freed). The signal handlers now push one byte for each caught signal into a pipe. That byte tells the rest of the code which message it needs to print, which cannot be done in the signal handlers (because the logging code is not reentrant and thus not safe to call from a signal handler). Compared to the previous solution, this solves several problems: - no more race condition between setting and printing the message - the pipe can be watched in a glib event loop, thus removing the need to poll at regular intervals; polling is still possible (and necessary) in those transports which do not integrate with the event loop (CurlTransport) while it can be removed from others (SoupTransport, OBEXTransport) A boost::signal is emitted when the global SuspendFlags change. Automatic connection management is used to disconnect instances which are managed by boost::shared_ptr. For example, the current transport's cancel() method is called when the state changes to "aborted". The early connection phase of the OBEX transport now also can be aborted (required cleaning up that transport!). Currently watching for aborts via the event loop only works for real Unix signals, but not for "abort" flags set in derived SyncContext instances. The plan is to change that by allowing a "set abort" on SuspendFlags and thus making SyncContext::checkForSuspend/checkForAbort() redundant. The new class is used as follows: - syncevolution command line without daemon uses it to control suspend/abort directly - syncevolution command line as client of syncevo-dbus-server connects to the state change signal and relays it to the syncevo-dbus-server session via D-Bus; now all operations are protected like that, not just syncing - syncevo-dbus-server installs its own handlers for SIGINT and SIGTERM and tries to shut down when either of them is received. SuspendFlags then doesn't activate its own handler. Instead that handler is invoked by the syncevo-dbus-server niam() handler, to suspend or abort a running sync. Once syncs run in a separate process, the syncevo-dbus-server should request that these processes suspend or abort before shutting down itself. - The syncevo-local-sync helper ignores SIGINT after a sync has started. It would receive that signal when forked by syncevolution in non-daemon mode and the user presses CTRL-C. Now the signal is only handled in the parent process, which suspends as part of its own side of the SyncML session and aborts by sending a SIGTERM+SIGINT to syncevo-local-sync. SIGTERM in syncevo-local-sync is handled by SuspendFlags and is meant to abort whatever is going on there at the moment (see below). Aborting long-running operations like import/export or communication via CardDAV or ActiveSync still needs further work. The backends need to check the abort state and return early instead of continuing.
2012-01-19 16:11:22 +01:00
#include <syncevo/SuspendFlags.h>
#include <syncevo/ThreadSupport.h>
#include <syncevo/IdentityProvider.h>
#include <syncevo/SafeConfigNode.h>
#include <syncevo/IniConfigNode.h>
#include <syncevo/LogStdout.h>
#include <syncevo/TransportAgent.h>
#include <syncevo/CurlTransportAgent.h>
#include <syncevo/SoupTransportAgent.h>
OBEX Client Transport: in-process OBEX client (binding over Bluetooth, #5188) Outgoing OBEX connection implementation, only binds over Bluetooth now. Integrates with gmainloop so that the opertaions in the transport will not block the whole application. It uses Bluetooth sdp to automatically discovery the corresponding service channel providing SyncML service; the process is asynchronous. Callback sdp_source_cb and sdp_callback are used for this purpose. sdp_source_cb is a GIOChannel watch event callback which poll the underlying sdp socket, the sdp_callback is invoked by Bluez during processing sdp packets. Callback obex_fd_source and obex_callback are related to the OBEX processing (Connect, Put, Get, Disconnect). obex_fd_source is a GIOChannel event source callback which poll the underlying OBEX interface, the obex_callback is invoked by libopenobex when it needs to delivering events to the application. Connect is splited by several steps, see CONNECT_STATUS for more detail. Disconnect will be invoked when shutDown is called or processing in obex_fd_source_cb is failed, timeout occurs or user suspention. It will first try to send a "Disconnect" command to server and waiting for response. If such opertaion is failed it will disconnect anyway. It is important to call wait after shutdown to ensure the transport is properly cleaned up. Each callback function is protected by a "Try-catch" block to ensure no exception is thrown in the C stack. This is important otherwise the application will abort if an exception is really thrown. Using several smart pointers to avoid potential resource leak. After initialized the resource is held by ObexTransportAgent. Copy the smart pointer to the local stack entering a function and return to ObexTransportAgent if the whole process is correct and we want to continue. First, it ensures the resource is released at least during ObexTransportAgent destructing; Second, it can also try to release the resource as early as possible. For example cxxptr<ObexEvent> will release the resource during each wait() so that the underlying poll will not be processed if no transport activity is expected by the application. "SyncURL" is used consistently for the address of the remote peer to contact with.
2009-11-13 06:13:12 +01:00
#include <syncevo/ObexTransportAgent.h>
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
#include <syncevo/LocalTransportAgent.h>
#include <functional>
#include <list>
#include <memory>
#include <vector>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <ctime>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/utility.hpp>
#include <sys/stat.h>
#include <sys/wait.h>
#include <pwd.h>
#include <unistd.h>
#include <signal.h>
#include <dirent.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <synthesis/enginemodulebridge.h>
#include <synthesis/SDK_util.h>
#include <synthesis/san.h>
#ifdef USE_DLT
# include <dlt.h>
#endif
#include "test.h"
#include <syncevo/declarations.h>
SE_BEGIN_CXX
SyncContext *SyncContext::m_activeContext;
static const char *LogfileBasename = "syncevolution-log";
static std::string RealPath(const std::string &path)
{
std::string buffer;
char *newPath = realpath(path.c_str(), nullptr);
if (newPath) {
buffer = newPath;
free(newPath);
return buffer;
} else {
return path;
}
}
config: share properties between peers, configuration view without peer This patch makes the configuration layout with per-source and per-peer properties the default for new configurations. Migrating old configurations is not implemented. The command line has not been updated at all (MB #8048). The D-Bus API is fairly complete, only listing sessions independently of a peer is missing (MB #8049). The key concept of this patch is that a pseudo-node implemented by MultiplexConfigNode provides a view on all user-visible or hidden properties. Based on the property name, it looks up the property definition, picks one of the underlying nodes based on the property visibility and sharing attributes, then reads and writes the property via that node. Clearing properties is not needed and not implemented, because of the uncertain semantic (really remove shared properties?!). The "sync" property must be available both in the per-source config (to pick a backend independently of a specific peer) and in the per-peer configuration (to select a specific data format). This is solved by making the property special (SHARED_AND_UNSHARED flag) and then writing it into two nodes. Reading is done from the more specific per-peer node, with the other node acting as fallback. The MultiplexConfigNode has to implement the FilterConfigNode API because it is used as one by the code which sets passwords in the filter. For this to work, the base FilterConfigNode implementation must use virtual method calls. The TestDBusSessionConfig.testUpdateConfigError checks that the error generated for an incorrect "sync" property contains the path of the config.ini file. The meaning of the error message in this case is that the wrong value is *for* that file, not that the property is already wrong *in* the file, but that's okay. The MultiplexConfigNode::getName() can only return a fixed name. To satisfy the test and because it is the right choice at the moment for all properties which might trigger such an error, it now is configured so that it returns the most specific path of the non-shared properties. "syncevolution --print-config" shows errors that are in files. Wrong command line parameters are rejected with a message that refers to the command line parameter ("--source-property sync=foo"). A future enhancement would be to make the name depend on the property (MB#8037). Because an empty string is now a valid configuration name (referencing the source properties without the per-peer properties) several checks for such empty strings were removed. The corresponding tests were updated resp. removed. Instead of talking about "server not found", the more neutral name "configuration" is used. The new TestMultipleConfigs.testSharing() covers the semantic of sharing properties between multiple configs. Access to non-existant nodes is routed into the new DevNullConfigNode. It always returns an empty string when reading and throws an error when trying to write into it. Unintentionally writing into a config.ini file therefore became harder, compared with the previous instantiation of SyncContext() with empty config name. The parsing of incoming messages uses a SyncContext which is bound to a VolatileConfigNode. This allows reading and writing of properties without any risk of touching files on disk. The patch which introduced the new config nodes was not complete yet with regards to the new layout. Removing nodes and trees used the wrong root path: getRootPath() refers to the most specific peer config, m_root to the part without the peer path. SyncConfig must distinguish between a view with peer-specific properties and one without, which is done by setting the m_peerPath only if a peer was selected. Copying properties must know whether writing per-specific properties ("unshared") is wanted, because trying to do it for a view without those properties would trigger the DevNullConfigNode exception. SyncConfig::removeSyncSource() removes source properties both in the shared part of the config and in *all* peers. This is used by Session.SetConfig() for the case that the caller is a) setting instead of updating the config and b) not providing any properties for the source. This is clearly a risky operation which should not be done when there are other peers which still use the source. We might have a problem in our D-Bus API definition for "removing a peer configuration" (MB #8059) because it always has an effect on other peers. The property registries were initialized implicitly before. With the recent changes it happened that SyncContext was initialized to analyze a SyncML message without initializing the registry, which caused getRemoteDevID() to use a property where the hidden flag had not been set yet. Moving all of these additional flags into the property constructors is awkward (which is why they are in the getRegistry() methods), so this was fixed by initializing the properties in the SyncConfig constructors by asking for the registries. Because there is no way to access them except via the registry and SyncConfig instances (*), this should ensure that the properties are valid when used. (*) Exception are some properties which are declared publicly to have access to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
SyncContext::SyncContext()
{
init();
}
SyncContext::SyncContext(const string &server,
config: share properties between peers, configuration view without peer This patch makes the configuration layout with per-source and per-peer properties the default for new configurations. Migrating old configurations is not implemented. The command line has not been updated at all (MB #8048). The D-Bus API is fairly complete, only listing sessions independently of a peer is missing (MB #8049). The key concept of this patch is that a pseudo-node implemented by MultiplexConfigNode provides a view on all user-visible or hidden properties. Based on the property name, it looks up the property definition, picks one of the underlying nodes based on the property visibility and sharing attributes, then reads and writes the property via that node. Clearing properties is not needed and not implemented, because of the uncertain semantic (really remove shared properties?!). The "sync" property must be available both in the per-source config (to pick a backend independently of a specific peer) and in the per-peer configuration (to select a specific data format). This is solved by making the property special (SHARED_AND_UNSHARED flag) and then writing it into two nodes. Reading is done from the more specific per-peer node, with the other node acting as fallback. The MultiplexConfigNode has to implement the FilterConfigNode API because it is used as one by the code which sets passwords in the filter. For this to work, the base FilterConfigNode implementation must use virtual method calls. The TestDBusSessionConfig.testUpdateConfigError checks that the error generated for an incorrect "sync" property contains the path of the config.ini file. The meaning of the error message in this case is that the wrong value is *for* that file, not that the property is already wrong *in* the file, but that's okay. The MultiplexConfigNode::getName() can only return a fixed name. To satisfy the test and because it is the right choice at the moment for all properties which might trigger such an error, it now is configured so that it returns the most specific path of the non-shared properties. "syncevolution --print-config" shows errors that are in files. Wrong command line parameters are rejected with a message that refers to the command line parameter ("--source-property sync=foo"). A future enhancement would be to make the name depend on the property (MB#8037). Because an empty string is now a valid configuration name (referencing the source properties without the per-peer properties) several checks for such empty strings were removed. The corresponding tests were updated resp. removed. Instead of talking about "server not found", the more neutral name "configuration" is used. The new TestMultipleConfigs.testSharing() covers the semantic of sharing properties between multiple configs. Access to non-existant nodes is routed into the new DevNullConfigNode. It always returns an empty string when reading and throws an error when trying to write into it. Unintentionally writing into a config.ini file therefore became harder, compared with the previous instantiation of SyncContext() with empty config name. The parsing of incoming messages uses a SyncContext which is bound to a VolatileConfigNode. This allows reading and writing of properties without any risk of touching files on disk. The patch which introduced the new config nodes was not complete yet with regards to the new layout. Removing nodes and trees used the wrong root path: getRootPath() refers to the most specific peer config, m_root to the part without the peer path. SyncConfig must distinguish between a view with peer-specific properties and one without, which is done by setting the m_peerPath only if a peer was selected. Copying properties must know whether writing per-specific properties ("unshared") is wanted, because trying to do it for a view without those properties would trigger the DevNullConfigNode exception. SyncConfig::removeSyncSource() removes source properties both in the shared part of the config and in *all* peers. This is used by Session.SetConfig() for the case that the caller is a) setting instead of updating the config and b) not providing any properties for the source. This is clearly a risky operation which should not be done when there are other peers which still use the source. We might have a problem in our D-Bus API definition for "removing a peer configuration" (MB #8059) because it always has an effect on other peers. The property registries were initialized implicitly before. With the recent changes it happened that SyncContext was initialized to analyze a SyncML message without initializing the registry, which caused getRemoteDevID() to use a property where the hidden flag had not been set yet. Moving all of these additional flags into the property constructors is awkward (which is why they are in the getRegistry() methods), so this was fixed by initializing the properties in the SyncConfig constructors by asking for the registries. Because there is no way to access them except via the registry and SyncConfig instances (*), this should ensure that the properties are valid when used. (*) Exception are some properties which are declared publicly to have access to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
bool doLogging) :
SyncConfig(server),
config: share properties between peers, configuration view without peer This patch makes the configuration layout with per-source and per-peer properties the default for new configurations. Migrating old configurations is not implemented. The command line has not been updated at all (MB #8048). The D-Bus API is fairly complete, only listing sessions independently of a peer is missing (MB #8049). The key concept of this patch is that a pseudo-node implemented by MultiplexConfigNode provides a view on all user-visible or hidden properties. Based on the property name, it looks up the property definition, picks one of the underlying nodes based on the property visibility and sharing attributes, then reads and writes the property via that node. Clearing properties is not needed and not implemented, because of the uncertain semantic (really remove shared properties?!). The "sync" property must be available both in the per-source config (to pick a backend independently of a specific peer) and in the per-peer configuration (to select a specific data format). This is solved by making the property special (SHARED_AND_UNSHARED flag) and then writing it into two nodes. Reading is done from the more specific per-peer node, with the other node acting as fallback. The MultiplexConfigNode has to implement the FilterConfigNode API because it is used as one by the code which sets passwords in the filter. For this to work, the base FilterConfigNode implementation must use virtual method calls. The TestDBusSessionConfig.testUpdateConfigError checks that the error generated for an incorrect "sync" property contains the path of the config.ini file. The meaning of the error message in this case is that the wrong value is *for* that file, not that the property is already wrong *in* the file, but that's okay. The MultiplexConfigNode::getName() can only return a fixed name. To satisfy the test and because it is the right choice at the moment for all properties which might trigger such an error, it now is configured so that it returns the most specific path of the non-shared properties. "syncevolution --print-config" shows errors that are in files. Wrong command line parameters are rejected with a message that refers to the command line parameter ("--source-property sync=foo"). A future enhancement would be to make the name depend on the property (MB#8037). Because an empty string is now a valid configuration name (referencing the source properties without the per-peer properties) several checks for such empty strings were removed. The corresponding tests were updated resp. removed. Instead of talking about "server not found", the more neutral name "configuration" is used. The new TestMultipleConfigs.testSharing() covers the semantic of sharing properties between multiple configs. Access to non-existant nodes is routed into the new DevNullConfigNode. It always returns an empty string when reading and throws an error when trying to write into it. Unintentionally writing into a config.ini file therefore became harder, compared with the previous instantiation of SyncContext() with empty config name. The parsing of incoming messages uses a SyncContext which is bound to a VolatileConfigNode. This allows reading and writing of properties without any risk of touching files on disk. The patch which introduced the new config nodes was not complete yet with regards to the new layout. Removing nodes and trees used the wrong root path: getRootPath() refers to the most specific peer config, m_root to the part without the peer path. SyncConfig must distinguish between a view with peer-specific properties and one without, which is done by setting the m_peerPath only if a peer was selected. Copying properties must know whether writing per-specific properties ("unshared") is wanted, because trying to do it for a view without those properties would trigger the DevNullConfigNode exception. SyncConfig::removeSyncSource() removes source properties both in the shared part of the config and in *all* peers. This is used by Session.SetConfig() for the case that the caller is a) setting instead of updating the config and b) not providing any properties for the source. This is clearly a risky operation which should not be done when there are other peers which still use the source. We might have a problem in our D-Bus API definition for "removing a peer configuration" (MB #8059) because it always has an effect on other peers. The property registries were initialized implicitly before. With the recent changes it happened that SyncContext was initialized to analyze a SyncML message without initializing the registry, which caused getRemoteDevID() to use a property where the hidden flag had not been set yet. Moving all of these additional flags into the property constructors is awkward (which is why they are in the getRegistry() methods), so this was fixed by initializing the properties in the SyncConfig constructors by asking for the registries. Because there is no way to access them except via the registry and SyncConfig instances (*), this should ensure that the properties are valid when used. (*) Exception are some properties which are declared publicly to have access to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
m_server(server)
{
init();
m_doLogging = doLogging;
}
local sync: avoid confusion about what data is changed In local sync the terms "local" and "remote" (in SyncReport, "Data modified locally") do not always apply and can be confusing. Replaced with explicitly mentioning the context. The source name also no longer is unique. Extended in the local sync case (and only in that case) by adding a <context>/ prefix to the source name. Here is an example of the modified output: $ syncevolution google [INFO] @default/itodo20: inactive [INFO] @default/addressbook: inactive [INFO] @default/calendar+todo: inactive [INFO] @default/memo: inactive [INFO] @default/ical20: inactive [INFO] @default/todo: inactive [INFO] @default/file_calendar+todo: inactive [INFO] @default/file_vcard21: inactive [INFO] @default/vcard30: inactive [INFO] @default/text: inactive [INFO] @default/file_itodo20: inactive [INFO] @default/vcard21: inactive [INFO] @default/file_ical20: inactive [INFO] @default/file_vcard30: inactive [INFO] @google/addressbook: inactive [INFO] @google/memo: inactive [INFO] @google/todo: inactive [INFO] @google/calendar: starting normal sync, two-way Local data changes to be applied remotely during synchronization: *** @google/calendar *** after last sync | current data removed since last sync < > added since last sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- [INFO] @google/calendar: sent 1/2 [INFO] @google/calendar: sent 2/2 Local data changes to be applied remotely during synchronization: *** @default/calendar *** no changes [INFO] @default/calendar: started [INFO] @default/calendar: updating "created in Google, online" [INFO] @default/calendar: updating "created in Google - mod2, online" [INFO] @google/calendar: started [INFO] @default/calendar: inactive [INFO] @google/calendar: normal sync done successfully Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @default | @google | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | disabled, 0 KB sent by client, 2 KB received | | item(s) in database backup: 3 before sync, 3 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @default during synchronization: *** @default/calendar *** before sync | after sync removed during sync < > added during sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR VERSION:2.0 VERSION:2.0 ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- pohly@pohly-mobl1:/tmp/syncevolution/src$ Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @google | @default | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | | two-way, 2 KB sent by client, 0 KB received | | item(s) in database backup: 2 before sync, 2 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @google during synchronization: *** @google/calendar *** no changes
2010-10-25 10:34:23 +02:00
SyncContext::SyncContext(const string &client,
const string &server,
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
const string &rootPath,
const std::shared_ptr<TransportAgent> &agent,
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
bool doLogging) :
local sync: avoid confusion about what data is changed In local sync the terms "local" and "remote" (in SyncReport, "Data modified locally") do not always apply and can be confusing. Replaced with explicitly mentioning the context. The source name also no longer is unique. Extended in the local sync case (and only in that case) by adding a <context>/ prefix to the source name. Here is an example of the modified output: $ syncevolution google [INFO] @default/itodo20: inactive [INFO] @default/addressbook: inactive [INFO] @default/calendar+todo: inactive [INFO] @default/memo: inactive [INFO] @default/ical20: inactive [INFO] @default/todo: inactive [INFO] @default/file_calendar+todo: inactive [INFO] @default/file_vcard21: inactive [INFO] @default/vcard30: inactive [INFO] @default/text: inactive [INFO] @default/file_itodo20: inactive [INFO] @default/vcard21: inactive [INFO] @default/file_ical20: inactive [INFO] @default/file_vcard30: inactive [INFO] @google/addressbook: inactive [INFO] @google/memo: inactive [INFO] @google/todo: inactive [INFO] @google/calendar: starting normal sync, two-way Local data changes to be applied remotely during synchronization: *** @google/calendar *** after last sync | current data removed since last sync < > added since last sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- [INFO] @google/calendar: sent 1/2 [INFO] @google/calendar: sent 2/2 Local data changes to be applied remotely during synchronization: *** @default/calendar *** no changes [INFO] @default/calendar: started [INFO] @default/calendar: updating "created in Google, online" [INFO] @default/calendar: updating "created in Google - mod2, online" [INFO] @google/calendar: started [INFO] @default/calendar: inactive [INFO] @google/calendar: normal sync done successfully Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @default | @google | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | disabled, 0 KB sent by client, 2 KB received | | item(s) in database backup: 3 before sync, 3 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @default during synchronization: *** @default/calendar *** before sync | after sync removed during sync < > added during sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR VERSION:2.0 VERSION:2.0 ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- pohly@pohly-mobl1:/tmp/syncevolution/src$ Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @google | @default | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | | two-way, 2 KB sent by client, 0 KB received | | item(s) in database backup: 2 before sync, 2 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @google during synchronization: *** @google/calendar *** no changes
2010-10-25 10:34:23 +02:00
SyncConfig(client,
std::shared_ptr<ConfigTree>(),
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
rootPath),
local sync: avoid confusion about what data is changed In local sync the terms "local" and "remote" (in SyncReport, "Data modified locally") do not always apply and can be confusing. Replaced with explicitly mentioning the context. The source name also no longer is unique. Extended in the local sync case (and only in that case) by adding a <context>/ prefix to the source name. Here is an example of the modified output: $ syncevolution google [INFO] @default/itodo20: inactive [INFO] @default/addressbook: inactive [INFO] @default/calendar+todo: inactive [INFO] @default/memo: inactive [INFO] @default/ical20: inactive [INFO] @default/todo: inactive [INFO] @default/file_calendar+todo: inactive [INFO] @default/file_vcard21: inactive [INFO] @default/vcard30: inactive [INFO] @default/text: inactive [INFO] @default/file_itodo20: inactive [INFO] @default/vcard21: inactive [INFO] @default/file_ical20: inactive [INFO] @default/file_vcard30: inactive [INFO] @google/addressbook: inactive [INFO] @google/memo: inactive [INFO] @google/todo: inactive [INFO] @google/calendar: starting normal sync, two-way Local data changes to be applied remotely during synchronization: *** @google/calendar *** after last sync | current data removed since last sync < > added since last sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- [INFO] @google/calendar: sent 1/2 [INFO] @google/calendar: sent 2/2 Local data changes to be applied remotely during synchronization: *** @default/calendar *** no changes [INFO] @default/calendar: started [INFO] @default/calendar: updating "created in Google, online" [INFO] @default/calendar: updating "created in Google - mod2, online" [INFO] @google/calendar: started [INFO] @default/calendar: inactive [INFO] @google/calendar: normal sync done successfully Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @default | @google | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | disabled, 0 KB sent by client, 2 KB received | | item(s) in database backup: 3 before sync, 3 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @default during synchronization: *** @default/calendar *** before sync | after sync removed during sync < > added during sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR VERSION:2.0 VERSION:2.0 ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- pohly@pohly-mobl1:/tmp/syncevolution/src$ Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @google | @default | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | | two-way, 2 KB sent by client, 0 KB received | | item(s) in database backup: 2 before sync, 2 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @google during synchronization: *** @google/calendar *** no changes
2010-10-25 10:34:23 +02:00
m_server(client),
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
m_localClientRootPath(rootPath),
m_agent(agent)
{
init();
local sync: avoid confusion about what data is changed In local sync the terms "local" and "remote" (in SyncReport, "Data modified locally") do not always apply and can be confusing. Replaced with explicitly mentioning the context. The source name also no longer is unique. Extended in the local sync case (and only in that case) by adding a <context>/ prefix to the source name. Here is an example of the modified output: $ syncevolution google [INFO] @default/itodo20: inactive [INFO] @default/addressbook: inactive [INFO] @default/calendar+todo: inactive [INFO] @default/memo: inactive [INFO] @default/ical20: inactive [INFO] @default/todo: inactive [INFO] @default/file_calendar+todo: inactive [INFO] @default/file_vcard21: inactive [INFO] @default/vcard30: inactive [INFO] @default/text: inactive [INFO] @default/file_itodo20: inactive [INFO] @default/vcard21: inactive [INFO] @default/file_ical20: inactive [INFO] @default/file_vcard30: inactive [INFO] @google/addressbook: inactive [INFO] @google/memo: inactive [INFO] @google/todo: inactive [INFO] @google/calendar: starting normal sync, two-way Local data changes to be applied remotely during synchronization: *** @google/calendar *** after last sync | current data removed since last sync < > added since last sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- [INFO] @google/calendar: sent 1/2 [INFO] @google/calendar: sent 2/2 Local data changes to be applied remotely during synchronization: *** @default/calendar *** no changes [INFO] @default/calendar: started [INFO] @default/calendar: updating "created in Google, online" [INFO] @default/calendar: updating "created in Google - mod2, online" [INFO] @google/calendar: started [INFO] @default/calendar: inactive [INFO] @google/calendar: normal sync done successfully Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @default | @google | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | disabled, 0 KB sent by client, 2 KB received | | item(s) in database backup: 3 before sync, 3 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @default during synchronization: *** @default/calendar *** before sync | after sync removed during sync < > added during sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR VERSION:2.0 VERSION:2.0 ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- pohly@pohly-mobl1:/tmp/syncevolution/src$ Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @google | @default | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | | two-way, 2 KB sent by client, 0 KB received | | item(s) in database backup: 2 before sync, 2 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @google during synchronization: *** @google/calendar *** no changes
2010-10-25 10:34:23 +02:00
initLocalSync(server);
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
m_doLogging = doLogging;
}
local sync: avoid confusion about what data is changed In local sync the terms "local" and "remote" (in SyncReport, "Data modified locally") do not always apply and can be confusing. Replaced with explicitly mentioning the context. The source name also no longer is unique. Extended in the local sync case (and only in that case) by adding a <context>/ prefix to the source name. Here is an example of the modified output: $ syncevolution google [INFO] @default/itodo20: inactive [INFO] @default/addressbook: inactive [INFO] @default/calendar+todo: inactive [INFO] @default/memo: inactive [INFO] @default/ical20: inactive [INFO] @default/todo: inactive [INFO] @default/file_calendar+todo: inactive [INFO] @default/file_vcard21: inactive [INFO] @default/vcard30: inactive [INFO] @default/text: inactive [INFO] @default/file_itodo20: inactive [INFO] @default/vcard21: inactive [INFO] @default/file_ical20: inactive [INFO] @default/file_vcard30: inactive [INFO] @google/addressbook: inactive [INFO] @google/memo: inactive [INFO] @google/todo: inactive [INFO] @google/calendar: starting normal sync, two-way Local data changes to be applied remotely during synchronization: *** @google/calendar *** after last sync | current data removed since last sync < > added since last sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- [INFO] @google/calendar: sent 1/2 [INFO] @google/calendar: sent 2/2 Local data changes to be applied remotely during synchronization: *** @default/calendar *** no changes [INFO] @default/calendar: started [INFO] @default/calendar: updating "created in Google, online" [INFO] @default/calendar: updating "created in Google - mod2, online" [INFO] @google/calendar: started [INFO] @default/calendar: inactive [INFO] @google/calendar: normal sync done successfully Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @default | @google | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | disabled, 0 KB sent by client, 2 KB received | | item(s) in database backup: 3 before sync, 3 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @default during synchronization: *** @default/calendar *** before sync | after sync removed during sync < > added during sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR VERSION:2.0 VERSION:2.0 ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- pohly@pohly-mobl1:/tmp/syncevolution/src$ Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @google | @default | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | | two-way, 2 KB sent by client, 0 KB received | | item(s) in database backup: 2 before sync, 2 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @google during synchronization: *** @google/calendar *** no changes
2010-10-25 10:34:23 +02:00
void SyncContext::initLocalSync(const string &config)
{
m_localSync = true;
string tmp;
splitConfigString(config, tmp, m_localPeerContext);
m_localPeerContext.insert(0, "@");
}
config: share properties between peers, configuration view without peer This patch makes the configuration layout with per-source and per-peer properties the default for new configurations. Migrating old configurations is not implemented. The command line has not been updated at all (MB #8048). The D-Bus API is fairly complete, only listing sessions independently of a peer is missing (MB #8049). The key concept of this patch is that a pseudo-node implemented by MultiplexConfigNode provides a view on all user-visible or hidden properties. Based on the property name, it looks up the property definition, picks one of the underlying nodes based on the property visibility and sharing attributes, then reads and writes the property via that node. Clearing properties is not needed and not implemented, because of the uncertain semantic (really remove shared properties?!). The "sync" property must be available both in the per-source config (to pick a backend independently of a specific peer) and in the per-peer configuration (to select a specific data format). This is solved by making the property special (SHARED_AND_UNSHARED flag) and then writing it into two nodes. Reading is done from the more specific per-peer node, with the other node acting as fallback. The MultiplexConfigNode has to implement the FilterConfigNode API because it is used as one by the code which sets passwords in the filter. For this to work, the base FilterConfigNode implementation must use virtual method calls. The TestDBusSessionConfig.testUpdateConfigError checks that the error generated for an incorrect "sync" property contains the path of the config.ini file. The meaning of the error message in this case is that the wrong value is *for* that file, not that the property is already wrong *in* the file, but that's okay. The MultiplexConfigNode::getName() can only return a fixed name. To satisfy the test and because it is the right choice at the moment for all properties which might trigger such an error, it now is configured so that it returns the most specific path of the non-shared properties. "syncevolution --print-config" shows errors that are in files. Wrong command line parameters are rejected with a message that refers to the command line parameter ("--source-property sync=foo"). A future enhancement would be to make the name depend on the property (MB#8037). Because an empty string is now a valid configuration name (referencing the source properties without the per-peer properties) several checks for such empty strings were removed. The corresponding tests were updated resp. removed. Instead of talking about "server not found", the more neutral name "configuration" is used. The new TestMultipleConfigs.testSharing() covers the semantic of sharing properties between multiple configs. Access to non-existant nodes is routed into the new DevNullConfigNode. It always returns an empty string when reading and throws an error when trying to write into it. Unintentionally writing into a config.ini file therefore became harder, compared with the previous instantiation of SyncContext() with empty config name. The parsing of incoming messages uses a SyncContext which is bound to a VolatileConfigNode. This allows reading and writing of properties without any risk of touching files on disk. The patch which introduced the new config nodes was not complete yet with regards to the new layout. Removing nodes and trees used the wrong root path: getRootPath() refers to the most specific peer config, m_root to the part without the peer path. SyncConfig must distinguish between a view with peer-specific properties and one without, which is done by setting the m_peerPath only if a peer was selected. Copying properties must know whether writing per-specific properties ("unshared") is wanted, because trying to do it for a view without those properties would trigger the DevNullConfigNode exception. SyncConfig::removeSyncSource() removes source properties both in the shared part of the config and in *all* peers. This is used by Session.SetConfig() for the case that the caller is a) setting instead of updating the config and b) not providing any properties for the source. This is clearly a risky operation which should not be done when there are other peers which still use the source. We might have a problem in our D-Bus API definition for "removing a peer configuration" (MB #8059) because it always has an effect on other peers. The property registries were initialized implicitly before. With the recent changes it happened that SyncContext was initialized to analyze a SyncML message without initializing the registry, which caused getRemoteDevID() to use a property where the hidden flag had not been set yet. Moving all of these additional flags into the property constructors is awkward (which is why they are in the getRegistry() methods), so this was fixed by initializing the properties in the SyncConfig constructors by asking for the registries. Because there is no way to access them except via the registry and SyncConfig instances (*), this should ensure that the properties are valid when used. (*) Exception are some properties which are declared publicly to have access to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
void SyncContext::init()
{
config: share properties between peers, configuration view without peer This patch makes the configuration layout with per-source and per-peer properties the default for new configurations. Migrating old configurations is not implemented. The command line has not been updated at all (MB #8048). The D-Bus API is fairly complete, only listing sessions independently of a peer is missing (MB #8049). The key concept of this patch is that a pseudo-node implemented by MultiplexConfigNode provides a view on all user-visible or hidden properties. Based on the property name, it looks up the property definition, picks one of the underlying nodes based on the property visibility and sharing attributes, then reads and writes the property via that node. Clearing properties is not needed and not implemented, because of the uncertain semantic (really remove shared properties?!). The "sync" property must be available both in the per-source config (to pick a backend independently of a specific peer) and in the per-peer configuration (to select a specific data format). This is solved by making the property special (SHARED_AND_UNSHARED flag) and then writing it into two nodes. Reading is done from the more specific per-peer node, with the other node acting as fallback. The MultiplexConfigNode has to implement the FilterConfigNode API because it is used as one by the code which sets passwords in the filter. For this to work, the base FilterConfigNode implementation must use virtual method calls. The TestDBusSessionConfig.testUpdateConfigError checks that the error generated for an incorrect "sync" property contains the path of the config.ini file. The meaning of the error message in this case is that the wrong value is *for* that file, not that the property is already wrong *in* the file, but that's okay. The MultiplexConfigNode::getName() can only return a fixed name. To satisfy the test and because it is the right choice at the moment for all properties which might trigger such an error, it now is configured so that it returns the most specific path of the non-shared properties. "syncevolution --print-config" shows errors that are in files. Wrong command line parameters are rejected with a message that refers to the command line parameter ("--source-property sync=foo"). A future enhancement would be to make the name depend on the property (MB#8037). Because an empty string is now a valid configuration name (referencing the source properties without the per-peer properties) several checks for such empty strings were removed. The corresponding tests were updated resp. removed. Instead of talking about "server not found", the more neutral name "configuration" is used. The new TestMultipleConfigs.testSharing() covers the semantic of sharing properties between multiple configs. Access to non-existant nodes is routed into the new DevNullConfigNode. It always returns an empty string when reading and throws an error when trying to write into it. Unintentionally writing into a config.ini file therefore became harder, compared with the previous instantiation of SyncContext() with empty config name. The parsing of incoming messages uses a SyncContext which is bound to a VolatileConfigNode. This allows reading and writing of properties without any risk of touching files on disk. The patch which introduced the new config nodes was not complete yet with regards to the new layout. Removing nodes and trees used the wrong root path: getRootPath() refers to the most specific peer config, m_root to the part without the peer path. SyncConfig must distinguish between a view with peer-specific properties and one without, which is done by setting the m_peerPath only if a peer was selected. Copying properties must know whether writing per-specific properties ("unshared") is wanted, because trying to do it for a view without those properties would trigger the DevNullConfigNode exception. SyncConfig::removeSyncSource() removes source properties both in the shared part of the config and in *all* peers. This is used by Session.SetConfig() for the case that the caller is a) setting instead of updating the config and b) not providing any properties for the source. This is clearly a risky operation which should not be done when there are other peers which still use the source. We might have a problem in our D-Bus API definition for "removing a peer configuration" (MB #8059) because it always has an effect on other peers. The property registries were initialized implicitly before. With the recent changes it happened that SyncContext was initialized to analyze a SyncML message without initializing the registry, which caused getRemoteDevID() to use a property where the hidden flag had not been set yet. Moving all of these additional flags into the property constructors is awkward (which is why they are in the getRegistry() methods), so this was fixed by initializing the properties in the SyncConfig constructors by asking for the registries. Because there is no way to access them except via the registry and SyncConfig instances (*), this should ensure that the properties are valid when used. (*) Exception are some properties which are declared publicly to have access to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
m_doLogging = false;
m_quiet = false;
m_dryrun = false;
m_keepPhotoData = false;
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
m_localSync = false;
config: share properties between peers, configuration view without peer This patch makes the configuration layout with per-source and per-peer properties the default for new configurations. Migrating old configurations is not implemented. The command line has not been updated at all (MB #8048). The D-Bus API is fairly complete, only listing sessions independently of a peer is missing (MB #8049). The key concept of this patch is that a pseudo-node implemented by MultiplexConfigNode provides a view on all user-visible or hidden properties. Based on the property name, it looks up the property definition, picks one of the underlying nodes based on the property visibility and sharing attributes, then reads and writes the property via that node. Clearing properties is not needed and not implemented, because of the uncertain semantic (really remove shared properties?!). The "sync" property must be available both in the per-source config (to pick a backend independently of a specific peer) and in the per-peer configuration (to select a specific data format). This is solved by making the property special (SHARED_AND_UNSHARED flag) and then writing it into two nodes. Reading is done from the more specific per-peer node, with the other node acting as fallback. The MultiplexConfigNode has to implement the FilterConfigNode API because it is used as one by the code which sets passwords in the filter. For this to work, the base FilterConfigNode implementation must use virtual method calls. The TestDBusSessionConfig.testUpdateConfigError checks that the error generated for an incorrect "sync" property contains the path of the config.ini file. The meaning of the error message in this case is that the wrong value is *for* that file, not that the property is already wrong *in* the file, but that's okay. The MultiplexConfigNode::getName() can only return a fixed name. To satisfy the test and because it is the right choice at the moment for all properties which might trigger such an error, it now is configured so that it returns the most specific path of the non-shared properties. "syncevolution --print-config" shows errors that are in files. Wrong command line parameters are rejected with a message that refers to the command line parameter ("--source-property sync=foo"). A future enhancement would be to make the name depend on the property (MB#8037). Because an empty string is now a valid configuration name (referencing the source properties without the per-peer properties) several checks for such empty strings were removed. The corresponding tests were updated resp. removed. Instead of talking about "server not found", the more neutral name "configuration" is used. The new TestMultipleConfigs.testSharing() covers the semantic of sharing properties between multiple configs. Access to non-existant nodes is routed into the new DevNullConfigNode. It always returns an empty string when reading and throws an error when trying to write into it. Unintentionally writing into a config.ini file therefore became harder, compared with the previous instantiation of SyncContext() with empty config name. The parsing of incoming messages uses a SyncContext which is bound to a VolatileConfigNode. This allows reading and writing of properties without any risk of touching files on disk. The patch which introduced the new config nodes was not complete yet with regards to the new layout. Removing nodes and trees used the wrong root path: getRootPath() refers to the most specific peer config, m_root to the part without the peer path. SyncConfig must distinguish between a view with peer-specific properties and one without, which is done by setting the m_peerPath only if a peer was selected. Copying properties must know whether writing per-specific properties ("unshared") is wanted, because trying to do it for a view without those properties would trigger the DevNullConfigNode exception. SyncConfig::removeSyncSource() removes source properties both in the shared part of the config and in *all* peers. This is used by Session.SetConfig() for the case that the caller is a) setting instead of updating the config and b) not providing any properties for the source. This is clearly a risky operation which should not be done when there are other peers which still use the source. We might have a problem in our D-Bus API definition for "removing a peer configuration" (MB #8059) because it always has an effect on other peers. The property registries were initialized implicitly before. With the recent changes it happened that SyncContext was initialized to analyze a SyncML message without initializing the registry, which caused getRemoteDevID() to use a property where the hidden flag had not been set yet. Moving all of these additional flags into the property constructors is awkward (which is why they are in the getRegistry() methods), so this was fixed by initializing the properties in the SyncConfig constructors by asking for the registries. Because there is no way to access them except via the registry and SyncConfig instances (*), this should ensure that the properties are valid when used. (*) Exception are some properties which are declared publicly to have access to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
m_serverMode = false;
m_serverAlerted = false;
m_configNeeded = true;
m_firstSourceAccess = true;
m_quitSync = false;
m_remoteInitiated = false;
m_sourceListPtr = nullptr;
m_syncFreeze = SYNC_FREEZE_NONE;
}
SyncContext::~SyncContext()
{
}
/**
* Utility code for parsing and comparing
* log dir names. Also a binary predicate for
* sorting them.
*/
class LogDirNames {
public:
// internal prefix for backup directory name: "SyncEvolution-"
static const char* const DIR_PREFIX;
/**
* Compare two directory by its creation time encoded
* in the directory name sort them in ascending order
*/
bool operator() (const string &str1, const string &str2) {
string iDirPath1, iStr1;
string iDirPath2, iStr2;
parseLogDir(str1, iDirPath1, iStr1);
parseLogDir(str2, iDirPath2, iStr2);
string dirPrefix1, peerName1, dateTime1;
parseDirName(iStr1, dirPrefix1, peerName1, dateTime1);
string dirPrefix2, peerName2, dateTime2;
parseDirName(iStr2, dirPrefix2, peerName2, dateTime2);
return dateTime1 < dateTime2;
}
/**
* extract backup directory name from a full backup path
* for example, a full path "/home/xxx/.cache/syncevolution/default/funambol-2009-12-08-14-05"
* is parsed as "/home/xxx/.cache/syncevolution/default" and "funambol-2009-12-08-14-05"
*/
static void parseLogDir(const string &fullpath, string &dirPath, string &dirName) {
string iFullpath = boost::trim_right_copy_if(fullpath, boost::is_any_of("/"));
size_t off = iFullpath.find_last_of('/');
if(off != iFullpath.npos) {
dirPath = iFullpath.substr(0, off);
dirName = iFullpath.substr(off+1);
} else {
dirPath = "";
dirName = iFullpath;
}
}
// escape '-' and '_' for peer name
static string escapePeer(const string &prefix) {
string escaped = prefix;
boost::replace_all(escaped, "_", "__");
boost::replace_all(escaped, "-", "_+");
return escaped;
}
// un-escape '_+' and '__' for peer name
static string unescapePeer(const string &escaped) {
string prefix = escaped;
boost::replace_all(prefix, "_+", "-");
boost::replace_all(prefix, "__", "_");
return prefix;
}
/**
* parse a directory name into dirPrefix(empty or DIR_PREFIX), peerName, dateTime.
* peerName must be unescaped by the caller to get the real string.
* If directory name is in the format of '[DIR_PREFIX]-peer[@context]-year-month-day-hour-min'
* then parsing is sucessful and these 3 strings are correctly set and true is returned.
* Otherwise, false is returned.
* Here we don't check whether the dir name is matching peer name
*/
static bool parseDirName(const string &dir, string &dirPrefix, string &config, string &dateTime) {
string iDir = dir;
if (!boost::starts_with(iDir, DIR_PREFIX)) {
dirPrefix = "";
} else {
dirPrefix = DIR_PREFIX;
boost::erase_first(iDir, DIR_PREFIX);
}
size_t off = iDir.find('-');
if (off != iDir.npos) {
config = iDir.substr(0, off);
dateTime = iDir.substr(off);
// m_prefix doesn't contain peer name or it equals with dirPrefix plus peerName
return checkDirName(dateTime);
}
return false;
}
// check the dir name is conforming to what format we write
static bool checkDirName(const string& value) {
const char* str = value.c_str();
/** need check whether string after prefix is a valid date-time we wrote, format
* should be -YYYY-MM-DD-HH-MM and optional sequence number */
static char table[] = {'-','9','9','9','9', //year
'-','1','9', //month
'-','3','9', //date
'-','2','9', //hour
'-','5','9' //minute
};
for(size_t i = 0; i < sizeof(table)/sizeof(table[0]) && *str; i++,str++) {
switch(table[i]) {
case '-':
if(*str != '-')
return false;
break;
case '1':
if(*str < '0' || *str > '1')
return false;
break;
case '2':
if(*str < '0' || *str > '2')
return false;
break;
case '3':
if(*str < '0' || *str > '3')
return false;
break;
case '5':
if(*str < '0' || *str > '5')
return false;
break;
case '9':
if(*str < '0' || *str > '9')
return false;
break;
default:
return false;
};
}
return true;
}
};
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
class LogDir;
/**
* Helper class for LogDir: acts as proxy for logging into
* the LogDir's reports and log file.
*/
class LogDirLogger : public Logger
{
Logger::Handle m_parentLogger; /**< the logger which was active before we started to intercept messages */
std::weak_ptr<LogDir> m_logdir; /**< grants access to report and Synthesis engine */
#ifdef USE_DLT
bool m_useDLT; /**< SyncEvolution and libsynthesis are logging to DLT */
#endif
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
public:
LogDirLogger(const std::weak_ptr<LogDir> &logdir);
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
virtual void remove() throw ();
virtual void messagev(const MessageOptions &options,
const char *format,
va_list args);
};
// This class owns the logging directory. It is responsible
// for redirecting output at the start and end of sync (even
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
// in case of exceptions thrown!).
class LogDir : private boost::noncopyable, private LogDirNames, public enable_weak_from_this<LogDir> {
SyncContext &m_client;
string m_logdir; /**< configured backup root dir */
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, empty if not writing one.
The file is no longer written by this class, nor
does it control the basename of it. Writing the
log file is enabled by the XML configuration that
we prepare for the Synthesis engine; the base name
of the file is hard-coded in the engine. Despite
that this class still is the central point to ask
for the name of the log file. */
boost::scoped_ptr<SafeConfigNode> m_info; /**< key/value representation of sync information */
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
// Access to m_report and m_client must be thread-safe as soon as
// LogDirLogger is active, because they are shared between main
// thread and any thread which might log errors.
friend class LogDirLogger;
bool m_readonly; /**< m_info is not to be written to */
SyncReport *m_report; /**< record start/end times here */
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
PushLogger<LogDirLogger> m_logger; /**< active logger */
LogDir(SyncContext &client) : m_client(client), m_info(nullptr), m_readonly(false), m_report(nullptr)
{
// Set default log directory. This will be overwritten with a user-specified
// location later on, if one was selected by the user. SyncEvolution >= 0.9 alpha
// and < 0.9 beta 2 used XDG_DATA_HOME because the logs and data base dumps
// were not considered "non-essential data files". Because XDG_DATA_HOME is
// searched for .desktop files and creating large amounts of other files there
// slows down that search, the default was changed to XDG_CACHE_DIR.
//
// To migrate old installations seamlessly, this code here renames the old
// default directory to the new one. Errors (like not found) are silently ignored.
mkdir_p(SubstEnvironment("${XDG_CACHE_HOME}").c_str());
rename(SubstEnvironment("${XDG_DATA_HOME}/applications/syncevolution").c_str(),
SubstEnvironment("${XDG_CACHE_HOME}/syncevolution").c_str());
string path = m_client.getLogDir();
if (path.empty()) {
path = "${XDG_CACHE_HOME}/syncevolution";
}
setLogdir(path);
}
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
public:
// Construct via make_weak_shared.
friend make_weak_shared;
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
/**
* Finds previous log directories for context. Reports errors via exceptions.
*
* @retval dirs vector of full path names, oldest first
*/
void previousLogdirs(vector<string> &dirs) {
dirs.clear();
getLogdirs(dirs);
}
/**
* Finds previous log directory. Returns empty string if anything went wrong.
*
* @param path path to configured backup directy, nullptr if defaulting to /tmp, "none" if not creating log file
* @return full path of previous log directory, empty string if not found
*/
string previousLogdir() throw() {
try {
vector<string> dirs;
previousLogdirs(dirs);
return dirs.empty() ? "" : dirs.back();
} catch (...) {
Exception::handle();
return "";
}
}
/**
* Set log dir and base name used for searching and creating sessions.
* Default if not called is the getLogDir() value of the context.
*
* @param logdir "none" to disable sessions, "" for default, may contain ${}
* for environment variables
*/
void setLogdir(const string &logdir) {
if (logdir.empty()) {
return;
}
m_logdir = SubstEnvironment(logdir);
m_logdir = boost::trim_right_copy_if(m_logdir, boost::is_any_of("/"));
if (m_logdir == "none") {
return;
}
// Resolve symbolic links in path now, in case that they change later
// while the session runs. Relies on being allowed to pass nullptr. If that's
// not allowed, we ignore the error and continue to use the known path.
errno = 0;
m_logdir = RealPath(m_logdir);
SE_LOG_DEBUG(NULL, "log path -> %s, %s",
m_logdir.c_str(),
errno ? strerror(errno) : "<okay>");
// the config name has been normalized
string peer = m_client.getConfigName();
// escape "_" and "-" the peer name
peer = escapePeer(peer);
if (boost::iends_with(m_logdir, "syncevolution")) {
// use just the server name as prefix
m_prefix = peer;
} else {
// SyncEvolution-<server>-<yyyy>-<mm>-<dd>-<hh>-<mm>
m_prefix = DIR_PREFIX;
m_prefix += peer;
}
}
/**
* access existing log directory to extract status information
*/
void openLogdir(const string &dir) {
auto filenode = std::make_shared<IniFileConfigNode>(dir, "status.ini", true);
m_info.reset(new SafeConfigNode(std::static_pointer_cast<ConfigNode>(filenode)));
m_info->setMode(false);
m_readonly = true;
}
/*
* get the corresponding peer name encoded in the logging dir name.
* The dir name must match the format(see startSession). Otherwise,
* empty string is returned.
*/
string getPeerNameFromLogdir(const string &dir) {
// extract the dir name from the fullpath
string iDirPath, iDirName;
parseLogDir(dir, iDirPath, iDirName);
// extract the peer name from the dir name
string dirPrefix, peerName, dateTime;
if(parseDirName(iDirName, dirPrefix, peerName, dateTime)) {
return unescapePeer(peerName);
}
return "";
}
/**
* read sync report for session selected with openLogdir()
*/
void readReport(SyncReport &report) {
report.clear();
if (!m_info) {
return;
}
*m_info >> report;
}
/**
* write sync report for current session
*/
void writeReport(SyncReport &report) {
if (m_info) {
*m_info << report;
/* write in slightly different format and flush at the end */
writeTimestamp("start", report.getStart(), false);
writeTimestamp("end", report.getEnd(), true);
}
}
enum SessionMode {
SESSION_USE_PATH, /**< write directly into path, don't create and manage subdirectories */
SESSION_READ_ONLY, /**< access an existing session directory identified with path */
SESSION_CREATE /**< create a new session directory inside the given path */
};
// setup log directory and redirect logging into it
// @param path path to configured backup directy, empty for using default, "none" if not creating log file
// @param mode determines how path is interpreted and which session is accessed
// @param maxlogdirs number of backup dirs to preserve in path, 0 if unlimited
// @param logLevel 0 = default, 1 = ERROR, 2 = INFO, 3 = DEBUG
// @param report record information about session here (may be nullptr)
void startSession(const string &path, SessionMode mode, int maxlogdirs, int logLevel, SyncReport *report) {
m_maxlogdirs = maxlogdirs;
m_report = report;
m_logfile = "";
if (boost::iequals(path, "none")) {
m_path = "";
} else {
setLogdir(path);
SE_LOG_DEBUG(NULL, "checking log dir %s", m_logdir.c_str());
if (mode == SESSION_CREATE) {
// create unique directory name in the given directory
time_t ts = time(nullptr);
struct tm tmbuffer;
struct tm *tm = localtime_r(&ts, &tmbuffer);
if (!tm) {
SE_THROW("localtime_r() failed");
}
stringstream base;
base << "-"
<< setfill('0')
<< setw(4) << tm->tm_year + 1900 << "-"
<< setw(2) << tm->tm_mon + 1 << "-"
<< setw(2) << tm->tm_mday << "-"
<< setw(2) << tm->tm_hour << "-"
<< setw(2) << tm->tm_min;
// If other sessions, regardless of which peer, have
// the same date and time, then append a sequence
// number to ensure correct sorting. Solve this by
// finding the maximum sequence number for any kind of
// date time. Backwards running clocks or changing the
// local time will still screw our ordering, though.
typedef std::map<string, int> SeqMap_t;
SeqMap_t dateTimes2Seq;
if (isDir(m_logdir)) {
ReadDir dir(m_logdir);
for (const string &entry: dir) {
string dirPrefix, peerName, dateTime;
if (parseDirName(entry, dirPrefix, peerName, dateTime)) {
// dateTime = -2010-01-31-12-00[-rev]
size_t off = 0;
for (int i = 0; off != dateTime.npos && i < 5; i++) {
off = dateTime.find('-', off + 1);
}
int sequence;
if (off != dateTime.npos) {
sequence = dateTime[off + 1] - 'a';
dateTime.resize(off);
} else {
sequence = -1;
}
pair <SeqMap_t::iterator, bool> entry = dateTimes2Seq.insert(make_pair(dateTime, sequence));
if (sequence > entry.first->second) {
entry.first->second = sequence;
}
}
}
}
stringstream path;
path << base.str();
auto it = dateTimes2Seq.find(path.str());
if (it != dateTimes2Seq.end()) {
path << "-" << (char)('a' + it->second + 1);
}
m_path = m_logdir + "/";
m_path += m_prefix;
m_path += path.str();
mkdir_p(m_path);
} else {
m_path = m_logdir;
if (mkdir(m_path.c_str(), S_IRWXU) &&
errno != EEXIST) {
SE_LOG_DEBUG(NULL, "%s: %s", m_path.c_str(), strerror(errno));
Exception::throwError(SE_HERE, m_path, errno);
}
}
m_logfile = m_path + "/" + LogfileBasename + ".html";
SE_LOG_DEBUG(NULL, "logfile: %s", m_logfile.c_str());
}
// update log level of default logger and our own replacement
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
Logger::Level level;
switch (logLevel) {
case 0:
// default for console output
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
level = Logger::INFO;
break;
case 1:
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
level = Logger::ERROR;
break;
case 2:
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
level = Logger::INFO;
break;
default:
if (m_logfile.empty() || getenv("SYNCEVOLUTION_DEBUG")) {
// no log file or user wants to see everything:
// print all information to the console
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
level = Logger::DEBUG;
} else {
// have log file: avoid excessive output to the console,
// full information is in the log file
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
level = Logger::INFO;
}
break;
}
if (mode != SESSION_USE_PATH) {
Logger::instance().setLevel(level);
}
auto logger = std::make_shared<LogDirLogger>(weak_from_this());
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
logger->setLevel(level);
m_logger.reset(logger);
time_t start = time(nullptr);
if (m_report) {
m_report->setStart(start);
}
m_readonly = mode == SESSION_READ_ONLY;
if (!m_path.empty()) {
auto filenode = std::make_shared<IniFileConfigNode>(m_path, "status.ini", m_readonly);
m_info.reset(new SafeConfigNode(std::static_pointer_cast<ConfigNode>(filenode)));
m_info->setMode(false);
if (mode != SESSION_READ_ONLY) {
// Create a status.ini which contains an error.
// Will be overwritten later on, unless we crash.
m_info->setProperty("status", STATUS_DIED_PREMATURELY);
m_info->setProperty("error", InitStateString("synchronization process died prematurely", true));
writeTimestamp("start", start);
}
}
}
/** sets a fixed directory for database files without redirecting logging */
void setPath(const string &path) { m_path = RealPath(path); SE_LOG_DEBUG(NULL, "setPath(%s) -> %s", path.c_str(), m_path.c_str()); }
// 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 backup dir(s) if exceeding limit
*
* Assign a priority to each session dir, with lower
* meaning "less important". Then sort by priority and (if
* equal) creation time (aka index) in ascending
* order. The sessions at the beginning of the sorted
* vector are then removed first.
*
* DUMPS = any kind of database dump was made
* ERROR = session failed
* CHANGES = local data modified since previous dump (based on dumps
* of the current peer, for simplicity reasons),
* dump created for the first time,
* changes made during sync (detected with dumps and statistics)
*
* The goal is to preserve as many database dumps as possible
* and ideally those where something happened.
*
* Some criteria veto the removal of a session:
* - it is the only one holding a dump of a specific source
* - it is the last session
*/
void expire() {
if (m_logdir.size() && m_maxlogdirs > 0 ) {
vector<string> dirs;
getLogdirs(dirs);
/** stores priority and index in "dirs"; after sorting, delete from the start */
vector< pair<Priority, size_t> > victims;
/** maps from source name to list of information about dump, oldest first */
typedef map< string, list<DumpInfo> > Dumps_t;
Dumps_t dumps;
for (size_t i = 0;
i < dirs.size();
i++) {
bool changes = false;
bool havedumps = false;
bool errors = false;
LogDir logdir(m_client);
logdir.openLogdir(dirs[i]);
SyncReport report;
logdir.readReport(report);
SyncMLStatus status = report.getStatus();
if (status != STATUS_OK && status != STATUS_HTTP_OK) {
errors = true;
}
for (SyncReport::SourceReport_t source: report) {
string &sourcename = source.first;
SyncSourceReport &sourcereport = source.second;
list<DumpInfo> &dumplist = dumps[sourcename];
if (sourcereport.m_backupBefore.isAvailable() ||
sourcereport.m_backupAfter.isAvailable()) {
// yes, we have backup dumps
havedumps = true;
DumpInfo info(i,
sourcereport.m_backupBefore.getNumItems(),
sourcereport.m_backupAfter.getNumItems());
// now check for changes, if none found yet
if (!changes) {
if (dumplist.empty()) {
// new backup dump
changes = true;
} else {
DumpInfo &previous = dumplist.back();
changes =
// item count changed -> items changed
previous.m_itemsDumpedAfter != info.m_itemsDumpedBefore ||
sourcereport.wasChanged(SyncSourceReport::ITEM_LOCAL) ||
sourcereport.wasChanged(SyncSourceReport::ITEM_REMOTE) ||
haveDifferentContent(sourcename,
dirs[previous.m_dirIndex], "after",
dirs[i], "before");
}
}
dumplist.push_back(info);
}
}
Priority pri =
havedumps ?
(changes ?
HAS_DUMPS_WITH_CHANGES :
errors ?
HAS_DUMPS_NO_CHANGES_WITH_ERRORS :
HAS_DUMPS_NO_CHANGES) :
(changes ?
NO_DUMPS_WITH_CHANGES :
errors ?
NO_DUMPS_WITH_ERRORS :
NO_DUMPS_NO_ERRORS);
victims.push_back(make_pair(pri, i));
}
sort(victims.begin(), victims.end());
int deleted = 0;
for (size_t e = 0;
e < victims.size() && (int)dirs.size() - deleted > m_maxlogdirs;
++e) {
size_t index = victims[e].second;
string &path = dirs[index];
// preserve latest session
if (index != dirs.size() - 1) {
bool mustkeep = false;
// also check whether it holds the only backup of a source
for (auto dump: dumps) {
if (dump.second.size() == 1 &&
dump.second.front().m_dirIndex == index) {
mustkeep = true;
break;
}
}
if (!mustkeep) {
SE_LOG_DEBUG(NULL, "removing %s", path.c_str());
rm_r(path);
++deleted;
}
}
}
}
}
// finalize session
void endSession()
{
time_t end = time(nullptr);
if (m_report) {
m_report->setEnd(end);
}
if (m_info) {
if (!m_readonly) {
writeTimestamp("end", end);
if (m_report) {
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
RecMutex::Guard guard = Logger::lock();
writeReport(*m_report);
}
m_info->flush();
}
m_info.reset();
}
}
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
// Remove redirection of logging.
void restore() {
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
m_logger.reset();
}
~LogDir() {
restore();
}
#if 0
/**
* A quick check for level = SHOW text dumps whether the text dump
* starts with the [ERROR] prefix; used to detect error messages
* from forked process which go through this instance but are not
* already tagged as error messages and thus would not show up as
* "first error" in sync reports.
*
* Example for the problem:
* [ERROR] onConnect not implemented [from child process]
* [ERROR] child process quit with return code 1 [from parent]
* ...
* Changes applied during synchronization:
* ...
* First ERROR encountered: child process quit with return code 1
*/
static bool isErrorString(const char *format,
va_list args)
{
const char *text;
if (!strcmp(format, "%s")) {
va_list argscopy;
va_copy(argscopy, args);
text = va_arg(argscopy, const char *);
va_end(argscopy);
} else {
text = format;
}
return boost::starts_with(text, "[ERROR");
}
#endif
/**
* Compare two database dumps just based on their inodes.
* @return true if inodes differ
*/
static bool haveDifferentContent(const string &sourceName,
const string &firstDir,
const string &firstSuffix,
const string &secondDir,
const string &secondSuffix)
{
string first = firstDir + "/" + sourceName + "." + firstSuffix;
string second = secondDir + "/" + sourceName + "." + secondSuffix;
ReadDir firstContent(first);
ReadDir secondContent(second);
set<ino_t> firstInodes;
for (const string &name: firstContent) {
struct stat buf;
string fullpath = first + "/" + name;
if (stat(fullpath.c_str(), &buf)) {
Exception::throwError(SE_HERE, fullpath, errno);
}
firstInodes.insert(buf.st_ino);
}
for (const string &name: secondContent) {
struct stat buf;
string fullpath = second + "/" + name;
if (stat(fullpath.c_str(), &buf)) {
Exception::throwError(SE_HERE, fullpath, errno);
}
auto it = firstInodes.find(buf.st_ino);
if (it == firstInodes.end()) {
// second dir has different file
return true;
} else {
firstInodes.erase(it);
}
}
if (!firstInodes.empty()) {
// first dir has different file
return true;
}
// exact match of inodes
return false;
}
private:
enum Priority {
NO_DUMPS_NO_ERRORS,
NO_DUMPS_WITH_ERRORS,
NO_DUMPS_WITH_CHANGES,
HAS_DUMPS_NO_CHANGES,
HAS_DUMPS_NO_CHANGES_WITH_ERRORS,
HAS_DUMPS_WITH_CHANGES
};
struct DumpInfo {
size_t m_dirIndex;
int m_itemsDumpedBefore;
int m_itemsDumpedAfter;
DumpInfo(size_t dirIndex,
int itemsDumpedBefore,
int itemsDumpedAfter) :
m_dirIndex(dirIndex),
m_itemsDumpedBefore(itemsDumpedBefore),
m_itemsDumpedAfter(itemsDumpedAfter)
{}
};
/**
* Find all entries in a given directory, return as sorted array of full paths in ascending order.
* If m_prefix doesn't contain peer name information, then all log dirs for different peers in the
* logdir are returned.
*/
void getLogdirs(vector<string> &dirs) {
if (m_logdir != "none" && !isDir(m_logdir)) {
return;
}
string peer = m_client.getConfigName();
string peerName, context;
SyncConfig::splitConfigString(peer, peerName, context);
ReadDir dir(m_logdir);
for (const string &entry: dir) {
string tmpDirPrefix, tmpPeer, tmpDateTime;
// if directory name could not be parsed, ignore it
if(parseDirName(entry, tmpDirPrefix, tmpPeer, tmpDateTime)) {
if(!peerName.empty() && (m_prefix == (tmpDirPrefix + tmpPeer))) {
// if peer name exists, match the logs for the given peer
dirs.push_back(m_logdir + "/" + entry);
} else if(peerName.empty()) {
// if no peer name and only context, match for all logs under the given context
string tmpName, tmpContext;
SyncConfig::splitConfigString(unescapePeer(tmpPeer), tmpName, tmpContext);
if( context == tmpContext && boost::starts_with(m_prefix, tmpDirPrefix)) {
dirs.push_back(m_logdir + "/" + entry);
}
}
}
}
// sort vector in ascending order
// if no peer name
if(peerName.empty()){
sort(dirs.begin(), dirs.end(), LogDirNames());
} else {
sort(dirs.begin(), dirs.end());
}
}
// store time stamp in session info
void writeTimestamp(const string &key, time_t val, bool flush = true) {
if (m_info) {
char buffer[160];
struct tm tmbuffer, *tm;
// be nice and store a human-readable date in addition the seconds since the epoch
tm = localtime_r(&val, &tmbuffer);
if (tm) {
strftime(buffer, sizeof(buffer), "%s, %Y-%m-%d %H:%M:%S %z", tm);
} else {
// Less suitable fallback. Won't work correctly for 32
// bit long beyond 2038.
sprintf(buffer, "%lu", (long unsigned)val);
}
m_info->setProperty(key, buffer);
if (flush) {
m_info->flush();
}
}
}
};
LogDirLogger::LogDirLogger(const std::weak_ptr<LogDir> &logdir) :
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
m_parentLogger(Logger::instance()),
m_logdir(logdir)
#ifdef USE_DLT
, m_useDLT(getenv("SYNCEVOLUTION_USE_DLT") != nullptr)
#endif
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
{
}
void LogDirLogger::remove() throw ()
{
// Forget reference to LogDir. This prevents accessing it in
// future messagev() calls.
RecMutex::Guard guard = Logger::lock();
m_logdir.reset();
}
void LogDirLogger::messagev(const MessageOptions &options,
const char *format,
va_list args)
{
// Protects ordering of log messages and access to shared
// variables like m_report and m_engine.
RecMutex::Guard guard = Logger::lock();
// always to parent first (usually stdout):
// if the parent is a LogRedirect instance, then
// it'll flush its own output first, which ensures
// that the new output comes later (as desired)
va_list argscopy;
va_copy(argscopy, args);
m_parentLogger.messagev(options, format, argscopy);
va_end(argscopy);
// Special handling of our own messages: include in sync report
// (always, because that is how we did it traditionally) and write
// to our own syncevolution-log.html (if not already logged).
//
// The TestLocalSync.testServerFailure and some others check that
// we record the child's error message in our sync report. If we
// don't then it shows up later marked as an "error on the target
// side", which is probably not what we want.
std::shared_ptr<LogDir> logdir;
if ((bool)(logdir = m_logdir.lock())) {
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
if (logdir->m_report &&
options.m_level <= ERROR &&
logdir->m_report->getError().empty()) {
va_list argscopy;
va_copy(argscopy, args);
string error = StringPrintfV(format, argscopy);
va_end(argscopy);
logdir->m_report->setError(error);
}
if (!(options.m_flags & MessageOptions::ALREADY_LOGGED) &&
#ifdef USE_DLT
// Don't send to libsynthesis if using DLT,
// because then it would end up getting logged
// in DLT twice.
!m_useDLT &&
#endif
logdir->m_client.getEngine().get()) {
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
va_list argscopy;
va_copy(argscopy, args);
// Once to Synthesis log, with full debugging.
// The API does not support a process name, so
// put it into the prefix as "<procname> <prefix>".
std::string prefix;
if (options.m_processName) {
prefix += *options.m_processName;
}
if (options.m_prefix) {
if (!prefix.empty()) {
prefix += " ";
}
prefix += *options.m_prefix;
}
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
logdir->m_client.getEngine().doDebug(options.m_level,
prefix.empty() ? nullptr : prefix.c_str(),
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
options.m_file,
options.m_line,
options.m_function,
format,
argscopy);
va_end(argscopy);
}
}
}
const char* const LogDirNames::DIR_PREFIX = "SyncEvolution-";
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
/**
* This class owns the sync sources. For historic reasons (required
* by Funambol) SyncSource instances are stored as plain pointers
* deleted by this class. Virtual sync sources were added later
* and are stored as shared pointers which are freed automatically.
* It is possible to iterate over the two classes of sources
* separately.
*
* The SourceList ensures that all sources (normal and virtual) have
* a valid and unique integer ID as needed for Synthesis. Traditionally
* this used to be a simple hash of the source name (which is unique
* by design), without checking for hash collisions. Now the ID is assigned
* the first time a source is added here and doesn't have one yet.
* For backward compatibility (the ID is stored in the .synthesis dir),
* the same Hash() value is tested first. Assuming that there were no
* hash conflicts, the same IDs will be generated as before.
*
* Together with a logdir, the SourceList
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
* handles writing of per-sync files as well as the final report.
* It is not stateless. The expectation is that it is instantiated
* together with a SyncContext for one particular operation (sync
* session, status check, restore). In contrast to a SyncContext,
* this class has to be recreated for another operation.
*
* When running as client, only the active sources get added. They can
* be dumped one after the other before running a sync.
*
* As a server, all sources get added, regardless whether they are
* active. This implies that at least their "type" must be valid. Then
* later when a client really starts using them, they are opened() and
* database dumps are made.
*
* Virtual datastores are stored here when they get initialized
* together with the normal sources by the user of SourceList.
*
*
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
*/
class SourceList : private vector<SyncSource *> {
typedef vector<SyncSource *> inherited;
public:
enum LogLevel {
LOGGING_QUIET, /**< avoid all extra output */
LOGGING_SUMMARY, /**< sync report, but no database comparison */
LOGGING_FULL /**< everything */
};
typedef std::vector< std::shared_ptr<VirtualSyncSource> > VirtualSyncSources_t;
/** reading our set of virtual sources is okay, modifying it is not */
const VirtualSyncSources_t &getVirtualSources() { return m_virtualSources; }
void addSource(const std::shared_ptr<VirtualSyncSource> &source) { checkSource(source.get()); m_virtualSources.push_back(source); }
using inherited::iterator;
using inherited::const_iterator;
using inherited::empty;
using inherited::begin;
using inherited::end;
using inherited::rbegin;
using inherited::rend;
SyncSource: optional support for asynchronous insert/update/delete The wrapper around the actual operation checks if the operation returned an error or result code (traditional behavior). If not, it expects a ContinueOperation instance, remembers it and calls it when the same operation gets called again for the same item. For add/insert, "same item" is detected based on the KeyH address, which must not change. For delete, the item local ID is used. Pre- and post-signals are called exactly once, before the first call and after the last call of the item. ContinueOperation is a simple boost::function pointer for now. The Synthesis engine itself is not able to force completion of the operation, it just polls. This can lead to many empty messages with just an Alert inside, thus triggering the "endless loop" protection, which aborts the sync. We overcome this limitation in the SyncEvolution layer above the Synthesis engine: first, we flush pending operations before starting network IO. This is a good place to batch together all pending operations. Second, to overcome the "endless loop" problem, we force a waiting for completion if the last message already was empty. If that happened, we are done with items and should start sending our responses. Binding a function which returns the traditional TSyError still works because it gets copied transparently into the boost::variant that the wrapper expects, so no other code in SyncSource or backends needs to be adapted. Enabling the use of LOCERR_AGAIN in the utility classes and backends will follow in the next patches.
2013-06-05 17:22:00 +02:00
using inherited::size;
/** transfers ownership */
void addSource(std::unique_ptr<SyncSource> source) { checkSource(source.get()); push_back(source.release()); }
private:
VirtualSyncSources_t m_virtualSources; /**< all configured virtual data sources (aka Synthesis <superdatastore>) */
std::shared_ptr<LogDir> m_logdir; /**< our logging directory */
SyncContext &m_client; /**< the context in which we were instantiated */
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
set<string> m_prepared; /**< remember for which source we dumped databases successfully */
string m_intro; /**< remembers the dumpLocalChanges() intro and only prints it again
when different from last dumpLocalChanges() call */
bool m_doLogging; /**< true iff the normal logdir handling is enabled
(creating and expiring directoties, before/after comparison) */
bool m_reportTodo; /**< true if syncDone() shall print a final report */
LogLevel m_logLevel; /**< chooses how much information is printed */
string m_previousLogdir; /**< remember previous log dir before creating the new one */
/** create name in current (if set) or previous logdir */
string databaseName(SyncSource &source, const string &suffix, string logdir = "") {
if (!logdir.size()) {
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
logdir = m_logdir->getLogdir();
}
return logdir + "/" +
source.getName() + "." + suffix;
}
/** ensure that Synthesis ID is set and unique */
void checkSource(SyncSource *source) {
if (source->getSynthesisID()) {
return;
}
int id = Hash(source->getName()) % INT_MAX;
while (true) {
// avoid negative values
if (id < 0) {
id = -id;
}
// avoid zero, it means unset
if (!id) {
id = 1;
}
// check for collisions
bool collision = false;
for (const string &other: m_client.getSyncSources()) {
std::shared_ptr<PersistentSyncSourceConfig> sc(m_client.getSyncSourceConfig(other));
int other_id = sc->getSynthesisID();
if (other_id == id) {
++id;
collision = true;
break;
}
}
if (!collision) {
source->setSynthesisID(id);
return;
}
}
}
public:
/** allow iterating over sources */
const inherited *getSourceSet() const { return this; }
LogLevel getLogLevel() const { return m_logLevel; }
void setLogLevel(LogLevel logLevel) { m_logLevel = logLevel; }
/**
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
* Dump into files with a certain suffix, optionally store report
* in member of SyncSourceReport. Remembers which sources were
* dumped before a sync and only dumps those again afterward.
*
* @param suffix "before/after/current" - before sync, after sync, during status check
* @param excludeSource when not empty, only dump that source
*/
void dumpDatabases(const string &suffix,
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
BackupReport SyncSourceReport::*report,
const string &excludeSource = "") {
// Identify all logdirs of current context, of any peer. Used
// to search for previous backups of each source, if
// necessary.
SyncContext context(m_client.getContextName());
auto logdir = make_weak_shared::make<LogDir>(context);
vector<string> dirs;
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
logdir->previousLogdirs(dirs);
for (SyncSource *source: *this) {
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
if ((!excludeSource.empty() && excludeSource != source->getName()) ||
(suffix == "after" && m_prepared.find(source->getName()) == m_prepared.end())) {
continue;
}
string dir = databaseName(*source, suffix);
std::shared_ptr<ConfigNode> node = ConfigNode::createFileNode(dir + ".ini");
SE_LOG_DEBUG(NULL, "creating %s", dir.c_str());
rm_r(dir);
BackupReport dummy;
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
if (source->getOperations().m_backupData) {
SyncSource::Operations::ConstBackupInfo oldBackup;
// Now look for a backup of the current source,
// starting with the most recent one.
for (const string &sessiondir: reverse(dirs)) {
string oldBackupDir;
2010-02-16 17:43:41 +01:00
SyncSource::Operations::BackupInfo::Mode mode =
SyncSource::Operations::BackupInfo::BACKUP_AFTER;
oldBackupDir = databaseName(*source, "after", sessiondir);
if (!isDir(oldBackupDir)) {
2010-02-16 17:43:41 +01:00
mode = SyncSource::Operations::BackupInfo::BACKUP_BEFORE;
oldBackupDir = databaseName(*source, "before", sessiondir);
if (!isDir(oldBackupDir)) {
// try next session
continue;
}
}
2010-02-16 17:43:41 +01:00
oldBackup.m_mode = mode;
oldBackup.m_dirname = oldBackupDir;
oldBackup.m_node = ConfigNode::createFileNode(oldBackupDir + ".ini");
break;
}
mkdir_p(dir);
2010-02-16 17:43:41 +01:00
SyncSource::Operations::BackupInfo newBackup(suffix == "before" ?
SyncSource::Operations::BackupInfo::BACKUP_BEFORE :
suffix == "after" ?
SyncSource::Operations::BackupInfo::BACKUP_AFTER :
SyncSource::Operations::BackupInfo::BACKUP_OTHER,
dir, node);
source->getOperations().m_backupData(oldBackup, newBackup,
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
report ? source->*report : dummy);
SE_LOG_DEBUG(NULL, "%s created", dir.c_str());
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
// remember that we have dumped at the beginning of a sync
if (suffix == "before") {
m_prepared.insert(source->getName());
}
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
}
}
}
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
void restoreDatabase(SyncSource &source, const string &suffix, bool dryrun, SyncSourceReport &report)
{
string dir = databaseName(source, suffix);
std::shared_ptr<ConfigNode> node = ConfigNode::createFileNode(dir + ".ini");
if (!node->exists()) {
Exception::throwError(SE_HERE, dir + ": no such database backup found");
}
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
if (source.getOperations().m_restoreData) {
2010-02-16 17:43:41 +01:00
source.getOperations().m_restoreData(SyncSource::Operations::ConstBackupInfo(SyncSource::Operations::BackupInfo::BACKUP_OTHER, dir, node),
dryrun, report);
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
}
}
SourceList(SyncContext &client, bool doLogging) :
m_logdir(make_weak_shared::make<LogDir>(client)),
m_client(client),
m_doLogging(doLogging),
m_reportTodo(true),
m_logLevel(LOGGING_FULL)
{
}
// call as soon as logdir settings are known
void startSession(const string &logDirPath, int maxlogdirs, int logLevel, SyncReport *report) {
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
m_logdir->setLogdir(logDirPath);
m_previousLogdir = m_logdir->previousLogdir();
if (m_doLogging) {
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
m_logdir->startSession(logDirPath, LogDir::SESSION_CREATE, maxlogdirs, logLevel, report);
} else {
// Run debug session without paying attention to
// the normal logdir handling. The log level here
// refers to stdout. The log file will be as complete
// as possible.
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
m_logdir->startSession(logDirPath, LogDir::SESSION_USE_PATH, 0, 1, report);
}
}
/** read-only access to existing session, identified in logDirPath */
void accessSession(const string &logDirPath) {
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
m_logdir->setLogdir(logDirPath);
m_previousLogdir = m_logdir->previousLogdir();
m_logdir->startSession(logDirPath, LogDir::SESSION_READ_ONLY, 0, 0, nullptr);
}
/** return log directory, empty if not enabled */
const string &getLogdir() {
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
return m_logdir->getLogdir();
}
/** return previous log dir found in startSession() */
const string &getPrevLogdir() const { return m_previousLogdir; }
/** set directory for database files without actually redirecting the logging */
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
void setPath(const string &path) { m_logdir->setPath(path); }
/**
* If possible (directory to compare against available) and enabled,
* then dump changes applied locally.
*
* @param oldSession directory to compare against; "" searches in sessions of current peer
* as selected by context for the lastest one involving each source
* @param oldSuffix suffix of old database dump: usually "after"
* @param currentSuffix the current database dump suffix: "current"
* when not doing a sync, otherwise "before"
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
* @param excludeSource when not empty, only dump that source
*/
bool dumpLocalChanges(const string &oldSession,
const string &oldSuffix, const string &newSuffix,
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
const string &excludeSource,
const string &intro = "Local data changes to be applied remotely during synchronization:\n",
const string &config = "CLIENT_TEST_LEFT_NAME='after last sync' CLIENT_TEST_RIGHT_NAME='current data' CLIENT_TEST_REMOVED='removed since last sync' CLIENT_TEST_ADDED='added since last sync'") {
if (m_logLevel <= LOGGING_SUMMARY) {
return false;
}
vector<string> dirs;
if (oldSession.empty()) {
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
m_logdir->previousLogdirs(dirs);
}
for (SyncSource *source: *this) {
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
if ((!excludeSource.empty() && excludeSource != source->getName()) ||
(newSuffix == "after" && m_prepared.find(source->getName()) == m_prepared.end())) {
continue;
}
// dump only if not done before or changed
if (m_intro != intro) {
SE_LOG_SHOW(NULL, "%s", intro.c_str());
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
m_intro = intro;
}
string oldDir;
if (oldSession.empty()) {
// Now look for the latest session involving the current source,
// starting with the most recent one.
for (const string &sessiondir: reverse(dirs)) {
auto oldsession = make_weak_shared::make<LogDir>(m_client);
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
oldsession->openLogdir(sessiondir);
SyncReport report;
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
oldsession->readReport(report);
if (report.find(source->getName()) != report.end()) {
// source was active in that session, use dump
// made there
oldDir = databaseName(*source, oldSuffix, sessiondir);
break;
}
}
} else {
oldDir = databaseName(*source, oldSuffix, oldSession);
}
string newDir = databaseName(*source, newSuffix);
SE_LOG_SHOW(NULL, "*** %s ***", source->getDisplayName().c_str());
string cmd = string("env CLIENT_TEST_COMPARISON_FAILED=10 " + config + " synccompare '" ) +
oldDir + "' '" + newDir + "'";
int ret = Execute(cmd, EXECUTE_NO_STDERR);
switch (ret == -1 ? ret :
WIFEXITED(ret) ? WEXITSTATUS(ret) :
-1) {
case 0:
SE_LOG_SHOW(NULL, "no changes");
break;
case 10:
break;
default:
SE_LOG_SHOW(NULL, "Comparison was impossible.");
break;
}
}
SE_LOG_SHOW(NULL, "\n");
return true;
}
// call when all sync sources are ready to dump
// pre-sync databases
// @param sourceName limit preparation to that source
void syncPrepare(const string &sourceName) {
if (m_prepared.find(sourceName) != m_prepared.end()) {
// data dump was already done (can happen when running multiple
// SyncML sessions)
return;
}
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
if (m_logdir->getLogfile().size() &&
m_doLogging &&
(m_client.getDumpData() || m_client.getPrintChanges())) {
// dump initial databases
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
SE_LOG_INFO(NULL, "creating complete data backup of datastore %s before sync (%s)",
sourceName.c_str(),
(m_client.getDumpData() && m_client.getPrintChanges()) ? "enabled with dumpData and needed for printChanges" :
m_client.getDumpData() ? "because it was enabled with dumpData" :
m_client.getPrintChanges() ? "needed for printChanges" :
"???");
dumpDatabases("before", &SyncSourceReport::m_backupBefore, sourceName);
if (m_client.getPrintChanges()) {
// compare against the old "after" database dump
dumpLocalChanges("", "after", "before", sourceName,
StringPrintf("%s data changes to be applied during synchronization:\n",
m_client.isLocalSync() ? m_client.getContextName().c_str() : "Local"));
}
}
}
// call at the end of a sync with success == true
// if all went well to print report
void syncDone(SyncMLStatus status, SyncReport *report) {
// record status - failures from now only affect post-processing
// and thus do no longer change that result
if (report) {
report->setStatus(status == 0 ? STATUS_HTTP_OK : status);
}
// dump database after sync if explicitly enabled or
// needed for comparison;
// in the latter case only if dumping it at the beginning completed
if (m_doLogging &&
(m_client.getDumpData() ||
(m_client.getPrintChanges() && m_reportTodo && !m_prepared.empty()))) {
try {
SE_LOG_INFO(NULL, "creating complete data backup after sync (%s)",
(m_client.getDumpData() && m_client.getPrintChanges()) ? "enabled with dumpData and needed for printChanges" :
m_client.getDumpData() ? "because it was enabled with dumpData" :
m_client.getPrintChanges() ? "needed for printChanges" :
"???");
dumpDatabases("after", &SyncSourceReport::m_backupAfter);
} catch (...) {
Exception::handle();
// not exactly sure what the problem was, but don't
// try it again
m_prepared.clear();
}
}
if (m_doLogging) {
if (m_reportTodo && !m_prepared.empty() && report) {
// update report with more recent information about m_backupAfter
updateSyncReport(*report);
}
// ensure that stderr is seen again
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
m_logdir->restore();
// write out session status
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
m_logdir->endSession();
if (m_reportTodo) {
// haven't looked at result of sync yet;
// don't do it again
m_reportTodo = false;
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
string logfile = m_logdir->getLogfile();
if (status == STATUS_OK) {
SE_LOG_SHOW(NULL, "\nSynchronization successful.");
} else if (logfile.size()) {
SE_LOG_SHOW(NULL, "\nSynchronization failed, see %s for details.",
command line: cleaned up output The user-visible part of this change is that command line output now uses the same [ERROR/INFO] prefixes like the rest of SyncEvolution, instead of "Error:". Several messages were split into [ERROR] and [INFO] parts on seperate lines. Multi-line messages with such a prefix now have the prefix at the start of each line. Full sentences start with captital letters. All usage errors related to the synopsis of the command line now include the synopsis, without the detailed documentation of all options. Some of those errors dumped the full documentation, which was way too much information and pushed the actual synopsis off the screen. Some other errors did not include usage information at all. All output still goes to stdout, stderr is not used at all. Should be changed in a seperate patch, because currently error messages during operations like "--export -" get mixed with the result of the operation. Technically the output handling was simplified. All output is printed via the logging system, instead of using a mixture of logging and streaming into std::cout. The advantage is that it will be easier to redirect all regular output inside the syncevo-dbus-helper to the parent. In particular, the following code could be removed: - the somewhat hacky std::streambuf->logging bridge code (CmdlineStreamBuf) - SyncContext set/getOutput() - ostream constructor parameters for Cmdline and derived classes The new code uses SE_LOG_SHOW() to produce output without prefix. Each call ends at the next line, regardless whether the string ends in a newline or not. The LoggerStdout was adapted to behave according to that expectation, and it inserts the line prefix at the start of each line - probably didn't matter before, because hardly any (no?!) message had line breaks. Because of this implicit newline in the logging code, some newlines become redundant; SE_LOG_SHOW("") is used to insert an empty line where needed. Calls to the logging system are minimized if possible by assembling output in buffers first, to reduce overhead and to adhere to the "one call per message" guideline. Testing was adapted accordingly. It's a bit stricter now, too, because it checks the entire error output instead of just the last line. The previous use of Cmdline ostreams to capture output from the class was replaced with loggers which hook into the logging system while the test runs and store the output. Same with SyncContext testing. Conflicts: src/dbus/server/cmdline-wrapper.h
2012-04-11 10:22:57 +02:00
logfile.c_str());
} else {
SE_LOG_SHOW(NULL, "\nSynchronization failed.");
}
// pretty-print report
if (m_logLevel > LOGGING_QUIET) {
local sync: improved target side output Added a "target side of local sync ready" INFO message to introduce the output which has the target context in the [INFO] tag. The sync report from the target side now has the target context embedded in brackets after the "Changes applied during synchronization" header, to avoid ambiguities. Output from the target side of a local sync was passed through stderr redirection as chunks of text to the frontends. This had several drawbacks: - forwarding only happened when the local sync parent was processing the output redirection, which (due to limitations of the implementation) only happens when it needs to print something itself - debug messages were not forwarded - message boundaries might have been lost In particular INFO messages about delays on the target side are relevant while the sync runs and need to be shown immediately. Now the output is passed through D-Bus, which happens immediately, preserves message boundaries and is done for all output. The frontend can decide separately whether it shows debug messages (not currently supported by the command line tool). Implementing this required extending the D-Bus API. The Server.LogOutput signal now has an additional "process name" parameter. Normally it is empty. For messages originating from the target side, it carries that extra target context string. This D-Bus API change is backward compatible. Older clients can still subscribe to and decode the LogOutput messages, they'll simply ignore the extra parameter. Newer clients expecting that extra parameter won't work with an older D-Bus daemon: they'll fail to decode the D-Bus message. This revealed that the last error messages in a session was incorrectly attributed to the syncevo-dbus-server. Might also have happened with several other error messages. Now everything happening in the server while working on code related to a session is logged as coming from that sessions. It's not perfect either (some of the output could be from unrelated events encountered indirectly while running that code), but it should be better than before. The patch changes the handling or errors in the local sync parent slightly: because it logs real ERROR messages now instead of plain text, it'll record the child's errors in its own sync report. That's okay, user's typically shouldn't have to care about where the error occurred. The D-Bus tests need to be adapted for this, because it removes the "failure in local sync child" from the sync report. Another, more internal change is that the syncevo-local-sync helper does its own output redirection instead of relying on the stderr handling of the parent. That way libneon debug output ends up in the log file of the child side (where it belongs) and not in the parent's log.
2012-06-28 14:58:05 +02:00
std::string procname = Logger::getProcessName();
SE_LOG_SHOW(NULL, "\nChanges applied during synchronization%s%s%s:",
local sync: improved target side output Added a "target side of local sync ready" INFO message to introduce the output which has the target context in the [INFO] tag. The sync report from the target side now has the target context embedded in brackets after the "Changes applied during synchronization" header, to avoid ambiguities. Output from the target side of a local sync was passed through stderr redirection as chunks of text to the frontends. This had several drawbacks: - forwarding only happened when the local sync parent was processing the output redirection, which (due to limitations of the implementation) only happens when it needs to print something itself - debug messages were not forwarded - message boundaries might have been lost In particular INFO messages about delays on the target side are relevant while the sync runs and need to be shown immediately. Now the output is passed through D-Bus, which happens immediately, preserves message boundaries and is done for all output. The frontend can decide separately whether it shows debug messages (not currently supported by the command line tool). Implementing this required extending the D-Bus API. The Server.LogOutput signal now has an additional "process name" parameter. Normally it is empty. For messages originating from the target side, it carries that extra target context string. This D-Bus API change is backward compatible. Older clients can still subscribe to and decode the LogOutput messages, they'll simply ignore the extra parameter. Newer clients expecting that extra parameter won't work with an older D-Bus daemon: they'll fail to decode the D-Bus message. This revealed that the last error messages in a session was incorrectly attributed to the syncevo-dbus-server. Might also have happened with several other error messages. Now everything happening in the server while working on code related to a session is logged as coming from that sessions. It's not perfect either (some of the output could be from unrelated events encountered indirectly while running that code), but it should be better than before. The patch changes the handling or errors in the local sync parent slightly: because it logs real ERROR messages now instead of plain text, it'll record the child's errors in its own sync report. That's okay, user's typically shouldn't have to care about where the error occurred. The D-Bus tests need to be adapted for this, because it removes the "failure in local sync child" from the sync report. Another, more internal change is that the syncevo-local-sync helper does its own output redirection instead of relying on the stderr handling of the parent. That way libneon debug output ends up in the log file of the child side (where it belongs) and not in the parent's log.
2012-06-28 14:58:05 +02:00
procname.empty() ? "" : " (",
procname.c_str(),
procname.empty() ? "" : ")");
}
if (m_logLevel > LOGGING_QUIET && report) {
command line: cleaned up output The user-visible part of this change is that command line output now uses the same [ERROR/INFO] prefixes like the rest of SyncEvolution, instead of "Error:". Several messages were split into [ERROR] and [INFO] parts on seperate lines. Multi-line messages with such a prefix now have the prefix at the start of each line. Full sentences start with captital letters. All usage errors related to the synopsis of the command line now include the synopsis, without the detailed documentation of all options. Some of those errors dumped the full documentation, which was way too much information and pushed the actual synopsis off the screen. Some other errors did not include usage information at all. All output still goes to stdout, stderr is not used at all. Should be changed in a seperate patch, because currently error messages during operations like "--export -" get mixed with the result of the operation. Technically the output handling was simplified. All output is printed via the logging system, instead of using a mixture of logging and streaming into std::cout. The advantage is that it will be easier to redirect all regular output inside the syncevo-dbus-helper to the parent. In particular, the following code could be removed: - the somewhat hacky std::streambuf->logging bridge code (CmdlineStreamBuf) - SyncContext set/getOutput() - ostream constructor parameters for Cmdline and derived classes The new code uses SE_LOG_SHOW() to produce output without prefix. Each call ends at the next line, regardless whether the string ends in a newline or not. The LoggerStdout was adapted to behave according to that expectation, and it inserts the line prefix at the start of each line - probably didn't matter before, because hardly any (no?!) message had line breaks. Because of this implicit newline in the logging code, some newlines become redundant; SE_LOG_SHOW("") is used to insert an empty line where needed. Calls to the logging system are minimized if possible by assembling output in buffers first, to reduce overhead and to adhere to the "one call per message" guideline. Testing was adapted accordingly. It's a bit stricter now, too, because it checks the entire error output instead of just the last line. The previous use of Cmdline ostreams to capture output from the class was replaced with loggers which hook into the logging system while the test runs and store the output. Same with SyncContext testing. Conflicts: src/dbus/server/cmdline-wrapper.h
2012-04-11 10:22:57 +02:00
ostringstream out;
out << *report;
std::string slowSync = report->slowSyncExplanation(m_client.getPeer());
if (!slowSync.empty()) {
out << endl << slowSync;
}
SE_LOG_SHOW(NULL, "%s", out.str().c_str());
}
// compare databases?
if (m_client.getPrintChanges()) {
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
dumpLocalChanges(m_logdir->getLogdir(),
"before", "after", "",
StringPrintf("\nData modified %s during synchronization:\n",
m_client.isLocalSync() ? m_client.getContextName().c_str() : "locally"),
"CLIENT_TEST_LEFT_NAME='before sync' CLIENT_TEST_RIGHT_NAME='after sync' CLIENT_TEST_REMOVED='removed during sync' CLIENT_TEST_ADDED='added during sync'");
}
// now remove some old logdirs
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
m_logdir->expire();
}
} else {
// finish debug session
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
m_logdir->restore();
m_logdir->endSession();
}
}
/** copies information about sources into sync report */
void updateSyncReport(SyncReport &report) {
for (SyncSource *source: *this) {
report.addSyncSourceReport(source->getName(), *source);
}
}
/** returns names of active sources */
set<string> getSources() {
set<string> res;
for (SyncSource *source: *this) {
res.insert(source->getName());
}
return res;
}
~SourceList() {
// free sync sources
for (SyncSource *source: *this) {
delete source;
}
}
/** find sync source by name (both normal and virtual sources) */
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
SyncSource *operator [] (const string &name) {
for (SyncSource *source: *this) {
if (name == source->getName()) {
return source;
}
}
for (std::shared_ptr<VirtualSyncSource> &source: m_virtualSources) {
if (name == source->getName()) {
return source.get();
}
}
return nullptr;
}
/** find by XML <dbtypeid> (the ID used by Synthesis to identify sources in progress events) */
SyncSource *lookupBySynthesisID(int synthesisid) {
for (SyncSource *source: *this) {
if (source->getSynthesisID() == synthesisid) {
return source;
}
}
for (std::shared_ptr<VirtualSyncSource> &source: m_virtualSources) {
if (source->getSynthesisID() == synthesisid) {
return source.get();
}
}
return nullptr;
}
std::list<std::string> getSourceNames() const;
};
std::list<std::string> SourceList::getSourceNames() const
{
std::list<std::string> sourceNames;
for (SyncSource *source: *this) {
sourceNames.push_back(source->getName());
}
return sourceNames;
}
void unref(SourceList *sourceList)
{
delete sourceList;
}
UserInterface &SyncContext::getUserInterfaceNonNull()
{
if (m_userInterface) {
return *m_userInterface;
} else {
// Doesn't use keyring.
static SimpleUserInterface dummy("0");
return dummy;
}
}
void SyncContext::requestAnotherSync()
{
if (m_activeContext &&
m_activeContext->m_engine.get() &&
m_activeContext->m_session) {
SharedKey sessionKey =
m_activeContext->m_engine.OpenSessionKey(m_activeContext->m_session);
m_activeContext->m_engine.SetInt32Value(sessionKey,
"restartsync",
true);
}
}
const std::vector<SyncSource *> *SyncContext::getSources() const
{
return m_sourceListPtr ?
m_sourceListPtr->getSourceSet() :
nullptr;
}
string SyncContext::getUsedSyncURL() {
vector<string> urls = getSyncURL();
for (string url: urls) {
if (boost::starts_with(url, "http://") ||
boost::starts_with(url, "https://")) {
#ifdef ENABLE_LIBSOUP
return url;
#elif defined(ENABLE_LIBCURL)
return url;
#endif
} else if (url.find("obex-bt://") ==0) {
#ifdef ENABLE_BLUETOOTH
return url;
#endif
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
} else if (boost::starts_with(url, "local://")) {
return url;
}
}
return "";
}
rewrote signal handling Having the signal handling code in SyncContext created an unnecessary dependency of some classes (in particular the transports) on SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files. Cleaning up when the caller is done with signal handling is now part of the utility class (removed automatically when guard instance is freed). The signal handlers now push one byte for each caught signal into a pipe. That byte tells the rest of the code which message it needs to print, which cannot be done in the signal handlers (because the logging code is not reentrant and thus not safe to call from a signal handler). Compared to the previous solution, this solves several problems: - no more race condition between setting and printing the message - the pipe can be watched in a glib event loop, thus removing the need to poll at regular intervals; polling is still possible (and necessary) in those transports which do not integrate with the event loop (CurlTransport) while it can be removed from others (SoupTransport, OBEXTransport) A boost::signal is emitted when the global SuspendFlags change. Automatic connection management is used to disconnect instances which are managed by boost::shared_ptr. For example, the current transport's cancel() method is called when the state changes to "aborted". The early connection phase of the OBEX transport now also can be aborted (required cleaning up that transport!). Currently watching for aborts via the event loop only works for real Unix signals, but not for "abort" flags set in derived SyncContext instances. The plan is to change that by allowing a "set abort" on SuspendFlags and thus making SyncContext::checkForSuspend/checkForAbort() redundant. The new class is used as follows: - syncevolution command line without daemon uses it to control suspend/abort directly - syncevolution command line as client of syncevo-dbus-server connects to the state change signal and relays it to the syncevo-dbus-server session via D-Bus; now all operations are protected like that, not just syncing - syncevo-dbus-server installs its own handlers for SIGINT and SIGTERM and tries to shut down when either of them is received. SuspendFlags then doesn't activate its own handler. Instead that handler is invoked by the syncevo-dbus-server niam() handler, to suspend or abort a running sync. Once syncs run in a separate process, the syncevo-dbus-server should request that these processes suspend or abort before shutting down itself. - The syncevo-local-sync helper ignores SIGINT after a sync has started. It would receive that signal when forked by syncevolution in non-daemon mode and the user presses CTRL-C. Now the signal is only handled in the parent process, which suspends as part of its own side of the SyncML session and aborts by sending a SIGTERM+SIGINT to syncevo-local-sync. SIGTERM in syncevo-local-sync is handled by SuspendFlags and is meant to abort whatever is going on there at the moment (see below). Aborting long-running operations like import/export or communication via CardDAV or ActiveSync still needs further work. The backends need to check the abort state and return early instead of continuing.
2012-01-19 16:11:22 +01:00
static void CancelTransport(TransportAgent *agent, SuspendFlags &flags)
{
if (flags.getState() == SuspendFlags::ABORT) {
SE_LOG_DEBUG(NULL, "CancelTransport: cancelling because of SuspendFlags::ABORT");
rewrote signal handling Having the signal handling code in SyncContext created an unnecessary dependency of some classes (in particular the transports) on SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files. Cleaning up when the caller is done with signal handling is now part of the utility class (removed automatically when guard instance is freed). The signal handlers now push one byte for each caught signal into a pipe. That byte tells the rest of the code which message it needs to print, which cannot be done in the signal handlers (because the logging code is not reentrant and thus not safe to call from a signal handler). Compared to the previous solution, this solves several problems: - no more race condition between setting and printing the message - the pipe can be watched in a glib event loop, thus removing the need to poll at regular intervals; polling is still possible (and necessary) in those transports which do not integrate with the event loop (CurlTransport) while it can be removed from others (SoupTransport, OBEXTransport) A boost::signal is emitted when the global SuspendFlags change. Automatic connection management is used to disconnect instances which are managed by boost::shared_ptr. For example, the current transport's cancel() method is called when the state changes to "aborted". The early connection phase of the OBEX transport now also can be aborted (required cleaning up that transport!). Currently watching for aborts via the event loop only works for real Unix signals, but not for "abort" flags set in derived SyncContext instances. The plan is to change that by allowing a "set abort" on SuspendFlags and thus making SyncContext::checkForSuspend/checkForAbort() redundant. The new class is used as follows: - syncevolution command line without daemon uses it to control suspend/abort directly - syncevolution command line as client of syncevo-dbus-server connects to the state change signal and relays it to the syncevo-dbus-server session via D-Bus; now all operations are protected like that, not just syncing - syncevo-dbus-server installs its own handlers for SIGINT and SIGTERM and tries to shut down when either of them is received. SuspendFlags then doesn't activate its own handler. Instead that handler is invoked by the syncevo-dbus-server niam() handler, to suspend or abort a running sync. Once syncs run in a separate process, the syncevo-dbus-server should request that these processes suspend or abort before shutting down itself. - The syncevo-local-sync helper ignores SIGINT after a sync has started. It would receive that signal when forked by syncevolution in non-daemon mode and the user presses CTRL-C. Now the signal is only handled in the parent process, which suspends as part of its own side of the SyncML session and aborts by sending a SIGTERM+SIGINT to syncevo-local-sync. SIGTERM in syncevo-local-sync is handled by SuspendFlags and is meant to abort whatever is going on there at the moment (see below). Aborting long-running operations like import/export or communication via CardDAV or ActiveSync still needs further work. The backends need to check the abort state and return early instead of continuing.
2012-01-19 16:11:22 +01:00
agent->cancel();
}
}
/**
* common initialization for all kinds of transports, to be called
* before using them
*/
static void InitializeTransport(const std::shared_ptr<TransportAgent> &agent,
rewrote signal handling Having the signal handling code in SyncContext created an unnecessary dependency of some classes (in particular the transports) on SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files. Cleaning up when the caller is done with signal handling is now part of the utility class (removed automatically when guard instance is freed). The signal handlers now push one byte for each caught signal into a pipe. That byte tells the rest of the code which message it needs to print, which cannot be done in the signal handlers (because the logging code is not reentrant and thus not safe to call from a signal handler). Compared to the previous solution, this solves several problems: - no more race condition between setting and printing the message - the pipe can be watched in a glib event loop, thus removing the need to poll at regular intervals; polling is still possible (and necessary) in those transports which do not integrate with the event loop (CurlTransport) while it can be removed from others (SoupTransport, OBEXTransport) A boost::signal is emitted when the global SuspendFlags change. Automatic connection management is used to disconnect instances which are managed by boost::shared_ptr. For example, the current transport's cancel() method is called when the state changes to "aborted". The early connection phase of the OBEX transport now also can be aborted (required cleaning up that transport!). Currently watching for aborts via the event loop only works for real Unix signals, but not for "abort" flags set in derived SyncContext instances. The plan is to change that by allowing a "set abort" on SuspendFlags and thus making SyncContext::checkForSuspend/checkForAbort() redundant. The new class is used as follows: - syncevolution command line without daemon uses it to control suspend/abort directly - syncevolution command line as client of syncevo-dbus-server connects to the state change signal and relays it to the syncevo-dbus-server session via D-Bus; now all operations are protected like that, not just syncing - syncevo-dbus-server installs its own handlers for SIGINT and SIGTERM and tries to shut down when either of them is received. SuspendFlags then doesn't activate its own handler. Instead that handler is invoked by the syncevo-dbus-server niam() handler, to suspend or abort a running sync. Once syncs run in a separate process, the syncevo-dbus-server should request that these processes suspend or abort before shutting down itself. - The syncevo-local-sync helper ignores SIGINT after a sync has started. It would receive that signal when forked by syncevolution in non-daemon mode and the user presses CTRL-C. Now the signal is only handled in the parent process, which suspends as part of its own side of the SyncML session and aborts by sending a SIGTERM+SIGINT to syncevo-local-sync. SIGTERM in syncevo-local-sync is handled by SuspendFlags and is meant to abort whatever is going on there at the moment (see below). Aborting long-running operations like import/export or communication via CardDAV or ActiveSync still needs further work. The backends need to check the abort state and return early instead of continuing.
2012-01-19 16:11:22 +01:00
int timeout)
{
agent->setTimeout(timeout);
// Automatically call cancel() when we an abort request
// is detected. Relies of automatic connection management
// to disconnect when agent is deconstructed.
SuspendFlags &flags(SuspendFlags::getSuspendFlags());
flags.m_stateChanged.connect(SuspendFlags::StateChanged_t::slot_type(CancelTransport, agent.get(), boost::placeholders::_1).track_foreign(agent));
rewrote signal handling Having the signal handling code in SyncContext created an unnecessary dependency of some classes (in particular the transports) on SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files. Cleaning up when the caller is done with signal handling is now part of the utility class (removed automatically when guard instance is freed). The signal handlers now push one byte for each caught signal into a pipe. That byte tells the rest of the code which message it needs to print, which cannot be done in the signal handlers (because the logging code is not reentrant and thus not safe to call from a signal handler). Compared to the previous solution, this solves several problems: - no more race condition between setting and printing the message - the pipe can be watched in a glib event loop, thus removing the need to poll at regular intervals; polling is still possible (and necessary) in those transports which do not integrate with the event loop (CurlTransport) while it can be removed from others (SoupTransport, OBEXTransport) A boost::signal is emitted when the global SuspendFlags change. Automatic connection management is used to disconnect instances which are managed by boost::shared_ptr. For example, the current transport's cancel() method is called when the state changes to "aborted". The early connection phase of the OBEX transport now also can be aborted (required cleaning up that transport!). Currently watching for aborts via the event loop only works for real Unix signals, but not for "abort" flags set in derived SyncContext instances. The plan is to change that by allowing a "set abort" on SuspendFlags and thus making SyncContext::checkForSuspend/checkForAbort() redundant. The new class is used as follows: - syncevolution command line without daemon uses it to control suspend/abort directly - syncevolution command line as client of syncevo-dbus-server connects to the state change signal and relays it to the syncevo-dbus-server session via D-Bus; now all operations are protected like that, not just syncing - syncevo-dbus-server installs its own handlers for SIGINT and SIGTERM and tries to shut down when either of them is received. SuspendFlags then doesn't activate its own handler. Instead that handler is invoked by the syncevo-dbus-server niam() handler, to suspend or abort a running sync. Once syncs run in a separate process, the syncevo-dbus-server should request that these processes suspend or abort before shutting down itself. - The syncevo-local-sync helper ignores SIGINT after a sync has started. It would receive that signal when forked by syncevolution in non-daemon mode and the user presses CTRL-C. Now the signal is only handled in the parent process, which suspends as part of its own side of the SyncML session and aborts by sending a SIGTERM+SIGINT to syncevo-local-sync. SIGTERM in syncevo-local-sync is handled by SuspendFlags and is meant to abort whatever is going on there at the moment (see below). Aborting long-running operations like import/export or communication via CardDAV or ActiveSync still needs further work. The backends need to check the abort state and return early instead of continuing.
2012-01-19 16:11:22 +01:00
}
std::shared_ptr<TransportAgent> SyncContext::createTransportAgent(void *gmainloop)
{
string url = getUsedSyncURL();
m_retryInterval = getRetryInterval();
m_retryDuration = getRetryDuration();
int timeout = m_serverMode ? m_retryDuration : min(m_retryInterval, m_retryDuration);
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
if (m_localSync) {
string peer = url.substr(strlen("local://"));
auto agent = make_weak_shared::make<LocalTransportAgent>(this, peer, gmainloop);
rewrote signal handling Having the signal handling code in SyncContext created an unnecessary dependency of some classes (in particular the transports) on SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files. Cleaning up when the caller is done with signal handling is now part of the utility class (removed automatically when guard instance is freed). The signal handlers now push one byte for each caught signal into a pipe. That byte tells the rest of the code which message it needs to print, which cannot be done in the signal handlers (because the logging code is not reentrant and thus not safe to call from a signal handler). Compared to the previous solution, this solves several problems: - no more race condition between setting and printing the message - the pipe can be watched in a glib event loop, thus removing the need to poll at regular intervals; polling is still possible (and necessary) in those transports which do not integrate with the event loop (CurlTransport) while it can be removed from others (SoupTransport, OBEXTransport) A boost::signal is emitted when the global SuspendFlags change. Automatic connection management is used to disconnect instances which are managed by boost::shared_ptr. For example, the current transport's cancel() method is called when the state changes to "aborted". The early connection phase of the OBEX transport now also can be aborted (required cleaning up that transport!). Currently watching for aborts via the event loop only works for real Unix signals, but not for "abort" flags set in derived SyncContext instances. The plan is to change that by allowing a "set abort" on SuspendFlags and thus making SyncContext::checkForSuspend/checkForAbort() redundant. The new class is used as follows: - syncevolution command line without daemon uses it to control suspend/abort directly - syncevolution command line as client of syncevo-dbus-server connects to the state change signal and relays it to the syncevo-dbus-server session via D-Bus; now all operations are protected like that, not just syncing - syncevo-dbus-server installs its own handlers for SIGINT and SIGTERM and tries to shut down when either of them is received. SuspendFlags then doesn't activate its own handler. Instead that handler is invoked by the syncevo-dbus-server niam() handler, to suspend or abort a running sync. Once syncs run in a separate process, the syncevo-dbus-server should request that these processes suspend or abort before shutting down itself. - The syncevo-local-sync helper ignores SIGINT after a sync has started. It would receive that signal when forked by syncevolution in non-daemon mode and the user presses CTRL-C. Now the signal is only handled in the parent process, which suspends as part of its own side of the SyncML session and aborts by sending a SIGTERM+SIGINT to syncevo-local-sync. SIGTERM in syncevo-local-sync is handled by SuspendFlags and is meant to abort whatever is going on there at the moment (see below). Aborting long-running operations like import/export or communication via CardDAV or ActiveSync still needs further work. The backends need to check the abort state and return early instead of continuing.
2012-01-19 16:11:22 +01:00
InitializeTransport(agent, timeout);
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
agent->start();
return agent;
} else if (boost::starts_with(url, "http://") ||
boost::starts_with(url, "https://")) {
#ifdef ENABLE_LIBSOUP
auto agent = make_weak_shared::make<SoupTransportAgent>(static_cast<GMainLoop *>(gmainloop));
OBEX Client Transport: in-process OBEX client (binding over Bluetooth, #5188) Outgoing OBEX connection implementation, only binds over Bluetooth now. Integrates with gmainloop so that the opertaions in the transport will not block the whole application. It uses Bluetooth sdp to automatically discovery the corresponding service channel providing SyncML service; the process is asynchronous. Callback sdp_source_cb and sdp_callback are used for this purpose. sdp_source_cb is a GIOChannel watch event callback which poll the underlying sdp socket, the sdp_callback is invoked by Bluez during processing sdp packets. Callback obex_fd_source and obex_callback are related to the OBEX processing (Connect, Put, Get, Disconnect). obex_fd_source is a GIOChannel event source callback which poll the underlying OBEX interface, the obex_callback is invoked by libopenobex when it needs to delivering events to the application. Connect is splited by several steps, see CONNECT_STATUS for more detail. Disconnect will be invoked when shutDown is called or processing in obex_fd_source_cb is failed, timeout occurs or user suspention. It will first try to send a "Disconnect" command to server and waiting for response. If such opertaion is failed it will disconnect anyway. It is important to call wait after shutdown to ensure the transport is properly cleaned up. Each callback function is protected by a "Try-catch" block to ensure no exception is thrown in the C stack. This is important otherwise the application will abort if an exception is really thrown. Using several smart pointers to avoid potential resource leak. After initialized the resource is held by ObexTransportAgent. Copy the smart pointer to the local stack entering a function and return to ObexTransportAgent if the whole process is correct and we want to continue. First, it ensures the resource is released at least during ObexTransportAgent destructing; Second, it can also try to release the resource as early as possible. For example cxxptr<ObexEvent> will release the resource during each wait() so that the underlying poll will not be processed if no transport activity is expected by the application. "SyncURL" is used consistently for the address of the remote peer to contact with.
2009-11-13 06:13:12 +01:00
agent->setConfig(*this);
rewrote signal handling Having the signal handling code in SyncContext created an unnecessary dependency of some classes (in particular the transports) on SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files. Cleaning up when the caller is done with signal handling is now part of the utility class (removed automatically when guard instance is freed). The signal handlers now push one byte for each caught signal into a pipe. That byte tells the rest of the code which message it needs to print, which cannot be done in the signal handlers (because the logging code is not reentrant and thus not safe to call from a signal handler). Compared to the previous solution, this solves several problems: - no more race condition between setting and printing the message - the pipe can be watched in a glib event loop, thus removing the need to poll at regular intervals; polling is still possible (and necessary) in those transports which do not integrate with the event loop (CurlTransport) while it can be removed from others (SoupTransport, OBEXTransport) A boost::signal is emitted when the global SuspendFlags change. Automatic connection management is used to disconnect instances which are managed by boost::shared_ptr. For example, the current transport's cancel() method is called when the state changes to "aborted". The early connection phase of the OBEX transport now also can be aborted (required cleaning up that transport!). Currently watching for aborts via the event loop only works for real Unix signals, but not for "abort" flags set in derived SyncContext instances. The plan is to change that by allowing a "set abort" on SuspendFlags and thus making SyncContext::checkForSuspend/checkForAbort() redundant. The new class is used as follows: - syncevolution command line without daemon uses it to control suspend/abort directly - syncevolution command line as client of syncevo-dbus-server connects to the state change signal and relays it to the syncevo-dbus-server session via D-Bus; now all operations are protected like that, not just syncing - syncevo-dbus-server installs its own handlers for SIGINT and SIGTERM and tries to shut down when either of them is received. SuspendFlags then doesn't activate its own handler. Instead that handler is invoked by the syncevo-dbus-server niam() handler, to suspend or abort a running sync. Once syncs run in a separate process, the syncevo-dbus-server should request that these processes suspend or abort before shutting down itself. - The syncevo-local-sync helper ignores SIGINT after a sync has started. It would receive that signal when forked by syncevolution in non-daemon mode and the user presses CTRL-C. Now the signal is only handled in the parent process, which suspends as part of its own side of the SyncML session and aborts by sending a SIGTERM+SIGINT to syncevo-local-sync. SIGTERM in syncevo-local-sync is handled by SuspendFlags and is meant to abort whatever is going on there at the moment (see below). Aborting long-running operations like import/export or communication via CardDAV or ActiveSync still needs further work. The backends need to check the abort state and return early instead of continuing.
2012-01-19 16:11:22 +01:00
InitializeTransport(agent, timeout);
OBEX Client Transport: in-process OBEX client (binding over Bluetooth, #5188) Outgoing OBEX connection implementation, only binds over Bluetooth now. Integrates with gmainloop so that the opertaions in the transport will not block the whole application. It uses Bluetooth sdp to automatically discovery the corresponding service channel providing SyncML service; the process is asynchronous. Callback sdp_source_cb and sdp_callback are used for this purpose. sdp_source_cb is a GIOChannel watch event callback which poll the underlying sdp socket, the sdp_callback is invoked by Bluez during processing sdp packets. Callback obex_fd_source and obex_callback are related to the OBEX processing (Connect, Put, Get, Disconnect). obex_fd_source is a GIOChannel event source callback which poll the underlying OBEX interface, the obex_callback is invoked by libopenobex when it needs to delivering events to the application. Connect is splited by several steps, see CONNECT_STATUS for more detail. Disconnect will be invoked when shutDown is called or processing in obex_fd_source_cb is failed, timeout occurs or user suspention. It will first try to send a "Disconnect" command to server and waiting for response. If such opertaion is failed it will disconnect anyway. It is important to call wait after shutdown to ensure the transport is properly cleaned up. Each callback function is protected by a "Try-catch" block to ensure no exception is thrown in the C stack. This is important otherwise the application will abort if an exception is really thrown. Using several smart pointers to avoid potential resource leak. After initialized the resource is held by ObexTransportAgent. Copy the smart pointer to the local stack entering a function and return to ObexTransportAgent if the whole process is correct and we want to continue. First, it ensures the resource is released at least during ObexTransportAgent destructing; Second, it can also try to release the resource as early as possible. For example cxxptr<ObexEvent> will release the resource during each wait() so that the underlying poll will not be processed if no transport activity is expected by the application. "SyncURL" is used consistently for the address of the remote peer to contact with.
2009-11-13 06:13:12 +01:00
return agent;
#elif defined(ENABLE_LIBCURL)
auto agent = std::make_shared<CurlTransportAgent>();
agent->setConfig(*this);
InitializeTransport(agent, timeout);
return agent;
#endif
} else if (boost::starts_with(url, "obex-bt://")) {
OBEX Client Transport: in-process OBEX client (binding over Bluetooth, #5188) Outgoing OBEX connection implementation, only binds over Bluetooth now. Integrates with gmainloop so that the opertaions in the transport will not block the whole application. It uses Bluetooth sdp to automatically discovery the corresponding service channel providing SyncML service; the process is asynchronous. Callback sdp_source_cb and sdp_callback are used for this purpose. sdp_source_cb is a GIOChannel watch event callback which poll the underlying sdp socket, the sdp_callback is invoked by Bluez during processing sdp packets. Callback obex_fd_source and obex_callback are related to the OBEX processing (Connect, Put, Get, Disconnect). obex_fd_source is a GIOChannel event source callback which poll the underlying OBEX interface, the obex_callback is invoked by libopenobex when it needs to delivering events to the application. Connect is splited by several steps, see CONNECT_STATUS for more detail. Disconnect will be invoked when shutDown is called or processing in obex_fd_source_cb is failed, timeout occurs or user suspention. It will first try to send a "Disconnect" command to server and waiting for response. If such opertaion is failed it will disconnect anyway. It is important to call wait after shutdown to ensure the transport is properly cleaned up. Each callback function is protected by a "Try-catch" block to ensure no exception is thrown in the C stack. This is important otherwise the application will abort if an exception is really thrown. Using several smart pointers to avoid potential resource leak. After initialized the resource is held by ObexTransportAgent. Copy the smart pointer to the local stack entering a function and return to ObexTransportAgent if the whole process is correct and we want to continue. First, it ensures the resource is released at least during ObexTransportAgent destructing; Second, it can also try to release the resource as early as possible. For example cxxptr<ObexEvent> will release the resource during each wait() so that the underlying poll will not be processed if no transport activity is expected by the application. "SyncURL" is used consistently for the address of the remote peer to contact with.
2009-11-13 06:13:12 +01:00
#ifdef ENABLE_BLUETOOTH
std::string btUrl = url.substr (strlen ("obex-bt://"), std::string::npos);
auto agent = std::make_shared<ObexTransportAgent>(ObexTransportAgent::OBEX_BLUETOOTH,
static_cast<GMainLoop *>(gmainloop));
OBEX Client Transport: in-process OBEX client (binding over Bluetooth, #5188) Outgoing OBEX connection implementation, only binds over Bluetooth now. Integrates with gmainloop so that the opertaions in the transport will not block the whole application. It uses Bluetooth sdp to automatically discovery the corresponding service channel providing SyncML service; the process is asynchronous. Callback sdp_source_cb and sdp_callback are used for this purpose. sdp_source_cb is a GIOChannel watch event callback which poll the underlying sdp socket, the sdp_callback is invoked by Bluez during processing sdp packets. Callback obex_fd_source and obex_callback are related to the OBEX processing (Connect, Put, Get, Disconnect). obex_fd_source is a GIOChannel event source callback which poll the underlying OBEX interface, the obex_callback is invoked by libopenobex when it needs to delivering events to the application. Connect is splited by several steps, see CONNECT_STATUS for more detail. Disconnect will be invoked when shutDown is called or processing in obex_fd_source_cb is failed, timeout occurs or user suspention. It will first try to send a "Disconnect" command to server and waiting for response. If such opertaion is failed it will disconnect anyway. It is important to call wait after shutdown to ensure the transport is properly cleaned up. Each callback function is protected by a "Try-catch" block to ensure no exception is thrown in the C stack. This is important otherwise the application will abort if an exception is really thrown. Using several smart pointers to avoid potential resource leak. After initialized the resource is held by ObexTransportAgent. Copy the smart pointer to the local stack entering a function and return to ObexTransportAgent if the whole process is correct and we want to continue. First, it ensures the resource is released at least during ObexTransportAgent destructing; Second, it can also try to release the resource as early as possible. For example cxxptr<ObexEvent> will release the resource during each wait() so that the underlying poll will not be processed if no transport activity is expected by the application. "SyncURL" is used consistently for the address of the remote peer to contact with.
2009-11-13 06:13:12 +01:00
agent->setURL (btUrl);
rewrote signal handling Having the signal handling code in SyncContext created an unnecessary dependency of some classes (in particular the transports) on SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files. Cleaning up when the caller is done with signal handling is now part of the utility class (removed automatically when guard instance is freed). The signal handlers now push one byte for each caught signal into a pipe. That byte tells the rest of the code which message it needs to print, which cannot be done in the signal handlers (because the logging code is not reentrant and thus not safe to call from a signal handler). Compared to the previous solution, this solves several problems: - no more race condition between setting and printing the message - the pipe can be watched in a glib event loop, thus removing the need to poll at regular intervals; polling is still possible (and necessary) in those transports which do not integrate with the event loop (CurlTransport) while it can be removed from others (SoupTransport, OBEXTransport) A boost::signal is emitted when the global SuspendFlags change. Automatic connection management is used to disconnect instances which are managed by boost::shared_ptr. For example, the current transport's cancel() method is called when the state changes to "aborted". The early connection phase of the OBEX transport now also can be aborted (required cleaning up that transport!). Currently watching for aborts via the event loop only works for real Unix signals, but not for "abort" flags set in derived SyncContext instances. The plan is to change that by allowing a "set abort" on SuspendFlags and thus making SyncContext::checkForSuspend/checkForAbort() redundant. The new class is used as follows: - syncevolution command line without daemon uses it to control suspend/abort directly - syncevolution command line as client of syncevo-dbus-server connects to the state change signal and relays it to the syncevo-dbus-server session via D-Bus; now all operations are protected like that, not just syncing - syncevo-dbus-server installs its own handlers for SIGINT and SIGTERM and tries to shut down when either of them is received. SuspendFlags then doesn't activate its own handler. Instead that handler is invoked by the syncevo-dbus-server niam() handler, to suspend or abort a running sync. Once syncs run in a separate process, the syncevo-dbus-server should request that these processes suspend or abort before shutting down itself. - The syncevo-local-sync helper ignores SIGINT after a sync has started. It would receive that signal when forked by syncevolution in non-daemon mode and the user presses CTRL-C. Now the signal is only handled in the parent process, which suspends as part of its own side of the SyncML session and aborts by sending a SIGTERM+SIGINT to syncevo-local-sync. SIGTERM in syncevo-local-sync is handled by SuspendFlags and is meant to abort whatever is going on there at the moment (see below). Aborting long-running operations like import/export or communication via CardDAV or ActiveSync still needs further work. The backends need to check the abort state and return early instead of continuing.
2012-01-19 16:11:22 +01:00
InitializeTransport(agent, timeout);
// this will block already
OBEX Client Transport: in-process OBEX client (binding over Bluetooth, #5188) Outgoing OBEX connection implementation, only binds over Bluetooth now. Integrates with gmainloop so that the opertaions in the transport will not block the whole application. It uses Bluetooth sdp to automatically discovery the corresponding service channel providing SyncML service; the process is asynchronous. Callback sdp_source_cb and sdp_callback are used for this purpose. sdp_source_cb is a GIOChannel watch event callback which poll the underlying sdp socket, the sdp_callback is invoked by Bluez during processing sdp packets. Callback obex_fd_source and obex_callback are related to the OBEX processing (Connect, Put, Get, Disconnect). obex_fd_source is a GIOChannel event source callback which poll the underlying OBEX interface, the obex_callback is invoked by libopenobex when it needs to delivering events to the application. Connect is splited by several steps, see CONNECT_STATUS for more detail. Disconnect will be invoked when shutDown is called or processing in obex_fd_source_cb is failed, timeout occurs or user suspention. It will first try to send a "Disconnect" command to server and waiting for response. If such opertaion is failed it will disconnect anyway. It is important to call wait after shutdown to ensure the transport is properly cleaned up. Each callback function is protected by a "Try-catch" block to ensure no exception is thrown in the C stack. This is important otherwise the application will abort if an exception is really thrown. Using several smart pointers to avoid potential resource leak. After initialized the resource is held by ObexTransportAgent. Copy the smart pointer to the local stack entering a function and return to ObexTransportAgent if the whole process is correct and we want to continue. First, it ensures the resource is released at least during ObexTransportAgent destructing; Second, it can also try to release the resource as early as possible. For example cxxptr<ObexEvent> will release the resource during each wait() so that the underlying poll will not be processed if no transport activity is expected by the application. "SyncURL" is used consistently for the address of the remote peer to contact with.
2009-11-13 06:13:12 +01:00
agent->connect();
return agent;
#endif
}
SE_THROW("unsupported transport type is specified in the configuration");
}
void SyncContext::displayServerMessage(const string &message)
{
SE_LOG_INFO(NULL, "message from server: %s", message.c_str());
}
void SyncContext::displaySyncProgress(sysync::TProgressEventEnum type,
int32_t extra1, int32_t extra2, int32_t extra3)
{
}
bool SyncContext::displaySourceProgress(SyncSource &source,
const SyncSourceEvent &event,
bool flush)
{
if (!flush) {
// Certain events do not need to be printed immediately.
// For example, instead of multiple PEV_ITEMRECEIVED events
// foo: received 1/100
// foo: received 2/100
// foo: ...
// foo: received 100/100
// it is better to just print one:
// foo: received 100/100
switch (event.m_type) {
case sysync::PEV_ITEMPROCESSED:
// Ignore this one completely. There is one such event
// after each PEV_ITEMRECEIVED, so processing
// PEV_ITEMPROCESSED would break the merging of
// PEV_ITEMRECEIVED, at least the way it is implemented
// now. PEV_ITEMPROCESSED also doesn't add much
// information.
return true;
case sysync::PEV_DELETING:
case sysync::PEV_ITEMRECEIVED:
case sysync::PEV_ITEMSENT:
// Flush when switching to a different event type or source.
if (m_sourceEvent.m_type != sysync::PEV_NOP &&
(m_sourceEvent.m_type != event.m_type ||
m_sourceProgress != &source)) {
displaySourceProgress(*m_sourceProgress, m_sourceEvent, true);
}
m_sourceEvent.m_type = event.m_type;
m_sourceEvent.m_extra1 = event.m_extra1;
m_sourceEvent.m_extra2 = event.m_extra2;
m_sourceEvent.m_extra3 = event.m_extra3;
m_sourceProgress = &source;
return true;
break;
default:
if (m_sourceEvent.m_type != sysync::PEV_NOP) {
displaySourceProgress(*m_sourceProgress, m_sourceEvent, true);
m_sourceEvent.m_type = sysync::PEV_NOP;
}
break;
}
}
switch(event.m_type) {
case sysync::PEV_PREPARING:
/* preparing (e.g. preflight in some clients), extra1=progress, extra2=total */
/* extra2 might be zero */
/*
* At the moment, preparing items doesn't do any real work.
* Printing this progress just increases the output and slows
* us down. Disabled.
*/
if (true || source.getFinalSyncMode() == SYNC_NONE) {
// not active, suppress output
} else if (event.m_extra2) {
SE_LOG_INFO(NULL, "%s: preparing %d/%d",
source.getDisplayName().c_str(), event.m_extra1, event.m_extra2);
} else {
SE_LOG_INFO(NULL, "%s: preparing %d",
source.getDisplayName().c_str(), event.m_extra1);
}
break;
case sysync::PEV_DELETING:
/* deleting (zapping datastore), extra1=progress, extra2=total */
if (event.m_extra2) {
SE_LOG_INFO(NULL, "%s: deleting %d/%d",
source.getDisplayName().c_str(), event.m_extra1, event.m_extra2);
} else {
SE_LOG_INFO(NULL, "%s: deleting %d",
source.getDisplayName().c_str(), event.m_extra1);
}
break;
case sysync::PEV_ALERTED: {
/* datastore alerted (extra1=0 for normal, 1 for slow, 2 for first time slow,
extra2=1 for resumed session,
extra3 0=twoway, 1=fromserver, 2=fromclient */
// -1 is used for alerting a restore from backup. Synthesis won't use this
bool peerIsClient = getPeerIsClient();
if (event.m_extra1 != -1) {
SE_LOG_INFO(NULL, "%s: %s %s sync%s (%s)",
source.getDisplayName().c_str(),
event.m_extra2 ? "resuming" : "starting",
event.m_extra1 == 0 ? "normal" :
event.m_extra1 == 1 ? "slow" :
event.m_extra1 == 2 ? "first time" :
"unknown",
event.m_extra3 == 0 ? ", two-way" :
event.m_extra3 == 1 ? " from server" :
event.m_extra3 == 2 ? " from client" :
", unknown direction",
peerIsClient ? "peer is client" : "peer is server");
SimpleSyncMode mode = SIMPLE_SYNC_NONE;
SyncMode sync = StringToSyncMode(source.getSync());
switch (event.m_extra1) {
case 0:
switch (event.m_extra3) {
case 0:
mode = SIMPLE_SYNC_TWO_WAY;
if (m_serverMode &&
engine: local cache sync mode This patch introduces support for true one-way syncing ("caching"): the local datastore is meant to be an exact copy of the data on the remote side. The assumption is that no modifications are ever made locally outside of syncing. This is different from one-way sync modes, which allows local changes and only temporarily disables sending them to the remote side. Another goal of the new mode is to avoid data writes as much as possible. This new mode only works on the server side of a sync, where the engine has enough control over the data flow. Most of the changes are in libsynthesis. SyncEvolution only needs to enable the new mode, which is done via an extension of the "sync" property: - "local-cache-incremental" will do an incremental sync (if possible) or a slow sync (otherwise). This is usually the right mode to use, and thus has "local-cache" as alias. - "local-cache-slow" will always do a slow sync. Useful for debugging or after (accidentally) making changes on the server side. An incremental sync will ignore such changes because they are not meant to happen and thus leave client and sync out-of-sync! Both modes are recorded in the sync report of the local side. The target side is the client and records the normal "two-way" or "slow" sync modes. With the current SyncEvolution contact field list, first, middle and last name are used to find matches during any kind of slow sync. The organization field is ignored for matching during the initial slow sync and used in all following ones. That's okay, the difference won't matter in practice because the initial slow sync in PBAP caching will be done with no local data. The test achieve the same result in both cases by keeping the organization set in the reduced data set. It's also okay to include the property in the comparison, because it might help to distinguish between "John Doe" in different companies. It might be worthwhile to add more fields as match criteria, for example the birthday. Currently they are excluded, probably because they are not trusted to be supported by SyncML peers. In caching mode the situation is different, because all our data came from the peer. The downside is that in cases where matching has to be done all the time because change detection is not supported (PBAP), including the birthday as criteria will cause unnecessary contact removed/added events (and thus disk IO) when a contact was originally created without birthday locally and then a birthday gets added on the phone. Testing is done as part of the D-Bus testing framework, because usually this functionality will be used as part of the D-Bus server and writing tests in Python is easier. A new test class "TestLocalCache" contains the new tests. They include tests for removing extra items during a slow sync (testItemRemoval), adding new client items under various conditions (testItemAdd*) and updating/removing an item during incremental syncing (testItemUpdate/Delete*). Doing these changes during a slow sync could also be tested (not currently covered). The tests for removing properties (testPropertyRemoval*) cover removing almost all contact properties during an initial slow sync, a second slow sync (which is treated differently in libsynthesis, see merge=always and merge=slowsync), and an incremental sync.
2012-08-23 14:25:55 +02:00
m_serverAlerted) {
if (sync == SYNC_ONE_WAY_FROM_SERVER ||
sync == SYNC_ONE_WAY_FROM_LOCAL) {
// As in the slow/refresh-from-server case below,
// pretending to do a two-way incremental sync
// is a correct way of executing the requested
// one-way sync, as long as the client doesn't
// send any of its own changes. The Synthesis
// engine does that.
mode = SIMPLE_SYNC_ONE_WAY_FROM_LOCAL;
} else if (sync == SYNC_LOCAL_CACHE_SLOW ||
sync == SYNC_LOCAL_CACHE_INCREMENTAL) {
mode = SIMPLE_SYNC_LOCAL_CACHE_INCREMENTAL;
}
}
break;
case 1:
mode = peerIsClient ? SIMPLE_SYNC_ONE_WAY_FROM_LOCAL : SIMPLE_SYNC_ONE_WAY_FROM_REMOTE;
break;
case 2:
mode = peerIsClient ? SIMPLE_SYNC_ONE_WAY_FROM_REMOTE : SIMPLE_SYNC_ONE_WAY_FROM_LOCAL;
break;
}
break;
case 1:
case 2:
switch (event.m_extra3) {
case 0:
mode = SIMPLE_SYNC_SLOW;
if (m_serverMode &&
engine: local cache sync mode This patch introduces support for true one-way syncing ("caching"): the local datastore is meant to be an exact copy of the data on the remote side. The assumption is that no modifications are ever made locally outside of syncing. This is different from one-way sync modes, which allows local changes and only temporarily disables sending them to the remote side. Another goal of the new mode is to avoid data writes as much as possible. This new mode only works on the server side of a sync, where the engine has enough control over the data flow. Most of the changes are in libsynthesis. SyncEvolution only needs to enable the new mode, which is done via an extension of the "sync" property: - "local-cache-incremental" will do an incremental sync (if possible) or a slow sync (otherwise). This is usually the right mode to use, and thus has "local-cache" as alias. - "local-cache-slow" will always do a slow sync. Useful for debugging or after (accidentally) making changes on the server side. An incremental sync will ignore such changes because they are not meant to happen and thus leave client and sync out-of-sync! Both modes are recorded in the sync report of the local side. The target side is the client and records the normal "two-way" or "slow" sync modes. With the current SyncEvolution contact field list, first, middle and last name are used to find matches during any kind of slow sync. The organization field is ignored for matching during the initial slow sync and used in all following ones. That's okay, the difference won't matter in practice because the initial slow sync in PBAP caching will be done with no local data. The test achieve the same result in both cases by keeping the organization set in the reduced data set. It's also okay to include the property in the comparison, because it might help to distinguish between "John Doe" in different companies. It might be worthwhile to add more fields as match criteria, for example the birthday. Currently they are excluded, probably because they are not trusted to be supported by SyncML peers. In caching mode the situation is different, because all our data came from the peer. The downside is that in cases where matching has to be done all the time because change detection is not supported (PBAP), including the birthday as criteria will cause unnecessary contact removed/added events (and thus disk IO) when a contact was originally created without birthday locally and then a birthday gets added on the phone. Testing is done as part of the D-Bus testing framework, because usually this functionality will be used as part of the D-Bus server and writing tests in Python is easier. A new test class "TestLocalCache" contains the new tests. They include tests for removing extra items during a slow sync (testItemRemoval), adding new client items under various conditions (testItemAdd*) and updating/removing an item during incremental syncing (testItemUpdate/Delete*). Doing these changes during a slow sync could also be tested (not currently covered). The tests for removing properties (testPropertyRemoval*) cover removing almost all contact properties during an initial slow sync, a second slow sync (which is treated differently in libsynthesis, see merge=always and merge=slowsync), and an incremental sync.
2012-08-23 14:25:55 +02:00
m_serverAlerted) {
if (sync == SYNC_REFRESH_FROM_SERVER ||
sync == SYNC_REFRESH_FROM_LOCAL) {
// We run as server and told the client to refresh
// its data. A slow sync is how some clients (the
// Synthesis engine included) execute that sync mode;
// let's be optimistic and assume that the client
// did as it was told and deleted its data.
mode = SIMPLE_SYNC_REFRESH_FROM_LOCAL;
} else if (sync == SYNC_LOCAL_CACHE_SLOW ||
sync == SYNC_LOCAL_CACHE_INCREMENTAL) {
mode = SIMPLE_SYNC_LOCAL_CACHE_SLOW;
}
}
break;
case 1:
mode = peerIsClient ? SIMPLE_SYNC_REFRESH_FROM_LOCAL : SIMPLE_SYNC_REFRESH_FROM_REMOTE;
break;
case 2:
mode = peerIsClient ? SIMPLE_SYNC_REFRESH_FROM_REMOTE : SIMPLE_SYNC_REFRESH_FROM_LOCAL;
break;
}
break;
}
if (SyncMode(mode) != SYNC_NONE) {
SE_LOG_DEBUG(NULL, "reading: set read-ahead based on sync mode %s",
PrettyPrintSyncMode(SyncMode(mode)).c_str());
switch (mode) {
case SIMPLE_SYNC_NONE:
case SIMPLE_SYNC_INVALID:
case SIMPLE_SYNC_RESTORE_FROM_BACKUP:
case SIMPLE_SYNC_ONE_WAY_FROM_REMOTE:
case SIMPLE_SYNC_REFRESH_FROM_REMOTE:
case SIMPLE_SYNC_LOCAL_CACHE_INCREMENTAL:
source.setReadAheadOrder(SyncSourceBase::READ_NONE);
break;
case SIMPLE_SYNC_TWO_WAY:
case SIMPLE_SYNC_ONE_WAY_FROM_LOCAL:
source.setReadAheadOrder(SyncSourceBase::READ_CHANGED_ITEMS);
break;
case SIMPLE_SYNC_SLOW:
case SIMPLE_SYNC_REFRESH_FROM_LOCAL:
case SIMPLE_SYNC_LOCAL_CACHE_SLOW:
source.setReadAheadOrder(SyncSourceBase::READ_ALL_ITEMS);
break;
}
}
SyncContext: increase "restart" counter in SyncSourceReport Don't overwrite the initially recorded sync mode when the source is restarted again later. Instead bump the "restart" counter. To the user this is shown as "number of cycles" in a sync session in the sync report. "Restart" is the process of entering a new cycle; hopefully users will understand the concept. The cycles are also visible in the command line output as multiple INFO lines: [INFO] eds_contact: starting first time sync from client (peer is server) [INFO] creating complete data backup of source eds_contact before sync (enabled with dumpData and needed for printChanges) Local data changes to be applied during synchronization: *** eds_contact *** no changes [INFO] eds_contact: sent 1/1 [INFO] eds_contact: started [INFO] eds_contact: first time sync done successfully [INFO] eds_contact: starting normal sync from client (peer is server) <=== [INFO] eds_contact: started <=== [INFO] eds_contact: normal sync done successfully <=== [INFO] creating complete data backup after sync (enabled with dumpData and needed for printChanges) Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | eds_contact | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | | refresh-from-local, 2 cycles, 0 KB sent by client, 0 KB received | | item(s) in database backup: 1 before sync, 1 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Tue Feb 7 17:07:49 2012, duration 0:03min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
2012-02-03 17:41:54 +01:00
if (source.getFinalSyncMode() == SYNC_NONE) {
source.recordFinalSyncMode(SyncMode(mode));
source.recordFirstSync(event.m_extra1 == 2);
source.recordResumeSync(event.m_extra2 == 1);
SyncContext: increase "restart" counter in SyncSourceReport Don't overwrite the initially recorded sync mode when the source is restarted again later. Instead bump the "restart" counter. To the user this is shown as "number of cycles" in a sync session in the sync report. "Restart" is the process of entering a new cycle; hopefully users will understand the concept. The cycles are also visible in the command line output as multiple INFO lines: [INFO] eds_contact: starting first time sync from client (peer is server) [INFO] creating complete data backup of source eds_contact before sync (enabled with dumpData and needed for printChanges) Local data changes to be applied during synchronization: *** eds_contact *** no changes [INFO] eds_contact: sent 1/1 [INFO] eds_contact: started [INFO] eds_contact: first time sync done successfully [INFO] eds_contact: starting normal sync from client (peer is server) <=== [INFO] eds_contact: started <=== [INFO] eds_contact: normal sync done successfully <=== [INFO] creating complete data backup after sync (enabled with dumpData and needed for printChanges) Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | eds_contact | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | | refresh-from-local, 2 cycles, 0 KB sent by client, 0 KB received | | item(s) in database backup: 1 before sync, 1 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Tue Feb 7 17:07:49 2012, duration 0:03min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
2012-02-03 17:41:54 +01:00
} else if (SyncMode(mode) != SYNC_NONE) {
// Broadcast statistics before moving into next cycle.
m_sourceSyncedSignal(source.getName(), source);
SyncContext: increase "restart" counter in SyncSourceReport Don't overwrite the initially recorded sync mode when the source is restarted again later. Instead bump the "restart" counter. To the user this is shown as "number of cycles" in a sync session in the sync report. "Restart" is the process of entering a new cycle; hopefully users will understand the concept. The cycles are also visible in the command line output as multiple INFO lines: [INFO] eds_contact: starting first time sync from client (peer is server) [INFO] creating complete data backup of source eds_contact before sync (enabled with dumpData and needed for printChanges) Local data changes to be applied during synchronization: *** eds_contact *** no changes [INFO] eds_contact: sent 1/1 [INFO] eds_contact: started [INFO] eds_contact: first time sync done successfully [INFO] eds_contact: starting normal sync from client (peer is server) <=== [INFO] eds_contact: started <=== [INFO] eds_contact: normal sync done successfully <=== [INFO] creating complete data backup after sync (enabled with dumpData and needed for printChanges) Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | eds_contact | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | | refresh-from-local, 2 cycles, 0 KB sent by client, 0 KB received | | item(s) in database backup: 1 before sync, 1 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Tue Feb 7 17:07:49 2012, duration 0:03min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
2012-02-03 17:41:54 +01:00
// may happen when the source is used in multiple
// SyncML sessions; only remember the initial sync
// mode in that case and count all following syncs
// (they should only finish the work of the initial
// one)
source.recordRestart();
PBAP: incremental sync (FDO #59551) Depending on the SYNCEVOLUTION_PBAP_SYNC env variable, syncing reads all properties as configured ("all"), excludes photos ("text") or first text, then all ("incremental"). When excluding photos, only known properties get requested. This avoids issues with phones which reject the request when enabling properties via the bit flags. This also helps with "databaseFormat=^PHOTO". When excluding photos, the vcard merge script as used by EDS ensures that existing photo data is preserved. This only works during a slow sync (merge script not called otherwise, okay for PBAP because it always syncs in slow sync) and EDS (other backends do not use the merge script, okay at the moment because PIM Manager is hard-coded to use EDS). The PBAP backend must be aware of the PBAP sync mode and request a second cycle, which again must be a slow sync. This only works because the sync engine is aware of the special mode and sets a new session variable "keepPhotoData". It would be better to have the PBAP backend send CTCap with PHOTO marked as not supported for text-only syncs and enabled when sending PHOTO data, but that is considerably harder to implement (CTCap cannot be adjusted at runtime). beginSync() may only ask for a slow sync when not already called for one. That's what the command line tool does when accessing items. It fails when getting the 508 status. The original goal of overlapping syncing with download has not been achieved yet. It turned out that all item IDs get requested before syncing starts, which thus depends on downloading all items in the current implementation. Can be fixed by making up IDs based on the number of existing items (see GetSize() in PBAP) and then downloading later when the data is needed.
2013-07-05 10:39:21 +02:00
if (m_serverMode) {
// Done with first cycle, revert to normal photo
// handling if it was disabled.
SharedKey sessionKey = m_engine.OpenSessionKey(m_session);
SharedKey contextKey = m_engine.OpenKeyByPath(sessionKey, "/sessionvars");
m_engine.SetInt32Value(contextKey, "keepPhotoData", false);
}
PIM: enhanced progress notifications (FDO #72114) This adds GetPeerStatus() and "progress" events. To detect DB changes as they happen, the SyncSource operations are monitored. Upon entry, a counter gets increased and transmitted through to the PIM manager in syncevo-dbus-server using extended SourceProgress structs (only visible internally - public API must not be extended!). This will count operations which fail and count those twice which get resumed, so the counts will be too high occasionally. That is in line with the API definition; because it is not exact, the API only exposes a "modified" flag. Progress is reported based on the "item received" Synthesis event and the total item count. A modified libsynthesis is needed where the SyncML binfile client on the target side of the local sync actually sends the total item count (via NumberOfChanges). This cannot be done yet right at the start of the sync, only the second SyncML message will have it. That is acceptable, because completion is reached very quickly anyway for syncs involving only one message. At the moment, SyncContext::displaySourceProgress() holds back "item received" events until a different event needs to be emitted. Progress reporting might get more fine-grained when adding allowing held back events to be emitted at a fixed rate, every 0.1s. This is not done yet because it seems to work well enough already. For testing and demonstration purposes, sync.py gets command line arguments for setting progress frequency and showing progress either via listening to signals or polling.
2014-01-30 21:02:10 +01:00
// Reset "started" flags for PEV_SYNCSTART.
m_sourceStarted.clear();
SyncContext: increase "restart" counter in SyncSourceReport Don't overwrite the initially recorded sync mode when the source is restarted again later. Instead bump the "restart" counter. To the user this is shown as "number of cycles" in a sync session in the sync report. "Restart" is the process of entering a new cycle; hopefully users will understand the concept. The cycles are also visible in the command line output as multiple INFO lines: [INFO] eds_contact: starting first time sync from client (peer is server) [INFO] creating complete data backup of source eds_contact before sync (enabled with dumpData and needed for printChanges) Local data changes to be applied during synchronization: *** eds_contact *** no changes [INFO] eds_contact: sent 1/1 [INFO] eds_contact: started [INFO] eds_contact: first time sync done successfully [INFO] eds_contact: starting normal sync from client (peer is server) <=== [INFO] eds_contact: started <=== [INFO] eds_contact: normal sync done successfully <=== [INFO] creating complete data backup after sync (enabled with dumpData and needed for printChanges) Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | eds_contact | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | | refresh-from-local, 2 cycles, 0 KB sent by client, 0 KB received | | item(s) in database backup: 1 before sync, 1 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Tue Feb 7 17:07:49 2012, duration 0:03min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
2012-02-03 17:41:54 +01:00
}
} else {
SE_LOG_INFO(NULL, "%s: restore from backup", source.getDisplayName().c_str());
source.recordFinalSyncMode(SYNC_RESTORE_FROM_BACKUP);
}
break;
}
case sysync::PEV_SYNCSTART:
/* sync started */
PIM: enhanced progress notifications (FDO #72114) This adds GetPeerStatus() and "progress" events. To detect DB changes as they happen, the SyncSource operations are monitored. Upon entry, a counter gets increased and transmitted through to the PIM manager in syncevo-dbus-server using extended SourceProgress structs (only visible internally - public API must not be extended!). This will count operations which fail and count those twice which get resumed, so the counts will be too high occasionally. That is in line with the API definition; because it is not exact, the API only exposes a "modified" flag. Progress is reported based on the "item received" Synthesis event and the total item count. A modified libsynthesis is needed where the SyncML binfile client on the target side of the local sync actually sends the total item count (via NumberOfChanges). This cannot be done yet right at the start of the sync, only the second SyncML message will have it. That is acceptable, because completion is reached very quickly anyway for syncs involving only one message. At the moment, SyncContext::displaySourceProgress() holds back "item received" events until a different event needs to be emitted. Progress reporting might get more fine-grained when adding allowing held back events to be emitted at a fixed rate, every 0.1s. This is not done yet because it seems to work well enough already. For testing and demonstration purposes, sync.py gets command line arguments for setting progress frequency and showing progress either via listening to signals or polling.
2014-01-30 21:02:10 +01:00
/* Get's triggered by libsynthesis frequently. Limit it to once per sync cycle. */
if (m_sourceStarted.find(source.getName()) == m_sourceStarted.end()) {
SE_LOG_INFO(NULL, "%s: started",
source.getDisplayName().c_str());
m_sourceStarted.insert(source.getName());
}
break;
case sysync::PEV_ITEMRECEIVED:
/* item received, extra1=current item count,
extra2=number of expected changes (if >= 0) */
if (source.getFinalSyncMode() == SYNC_NONE) {
} else if (event.m_extra2 > 0) {
SE_LOG_INFO(NULL, "%s: received %d/%d",
source.getDisplayName().c_str(), event.m_extra1, event.m_extra2);
} else {
SE_LOG_INFO(NULL, "%s: received %d",
source.getDisplayName().c_str(), event.m_extra1);
}
source.recordTotalNumItemsReceived(event.m_extra1);
break;
case sysync::PEV_ITEMSENT:
/* item sent, extra1=current item count,
extra2=number of expected items to be sent (if >=0) */
if (source.getFinalSyncMode() == SYNC_NONE) {
} else if (event.m_extra2 > 0) {
SE_LOG_INFO(NULL, "%s: sent %d/%d",
source.getDisplayName().c_str(), event.m_extra1, event.m_extra2);
} else {
SE_LOG_INFO(NULL, "%s: sent %d",
source.getDisplayName().c_str(), event.m_extra1);
}
source.recordTotalNumItemsSent(event.m_extra1);
break;
// Not reached, see above.
case sysync::PEV_ITEMPROCESSED:
/* item locally processed, extra1=# added,
extra2=# updated,
extra3=# deleted */
if (source.getFinalSyncMode() == SYNC_NONE) {
} else if (source.getFinalSyncMode() != SYNC_NONE) {
SE_LOG_INFO(NULL, "%s: added %d, updated %d, removed %d",
source.getDisplayName().c_str(), event.m_extra1, event.m_extra2, event.m_extra3);
}
break;
case sysync::PEV_SYNCEND: {
/* sync finished, probably with error in extra1 (0=ok),
syncmode in extra2 (0=normal, 1=slow, 2=first time),
extra3=1 for resumed session) */
if (source.getFinalSyncMode() == SYNC_NONE) {
SE_LOG_INFO(NULL, "%s: inactive", source.getDisplayName().c_str());
} else if(source.getFinalSyncMode() == SYNC_RESTORE_FROM_BACKUP) {
SE_LOG_INFO(NULL, "%s: restore done %s",
source.getDisplayName().c_str(),
event.m_extra1 ? "unsuccessfully" : "successfully" );
} else {
SE_LOG_INFO(NULL, "%s: %s%s sync done %s",
source.getDisplayName().c_str(),
event.m_extra3 ? "resumed " : "",
event.m_extra2 == 0 ? "normal" :
event.m_extra2 == 1 ? "slow" :
event.m_extra2 == 2 ? "first time" :
"unknown",
event.m_extra1 ? "unsuccessfully" : "successfully");
}
int32_t extra1 = event.m_extra1;
switch (extra1) {
case 401:
// TODO: reset cached password
SE_LOG_INFO(NULL, "authorization failed, check username '%s' and password", getSyncUser().toString().c_str());
break;
case 403:
SE_LOG_INFO(source.getDisplayName(), "log in succeeded, but server refuses access - contact server operator");
break;
case 407:
SE_LOG_INFO(NULL, "proxy authorization failed, check proxy username and password");
break;
case 404:
SE_LOG_INFO(source.getDisplayName(), "server database not found, check URI '%s'", source.getURINonEmpty().c_str());
break;
case 0:
break;
case sysync::LOCERR_DATASTORE_ABORT:
// this can mean only one thing in SyncEvolution: unexpected slow sync
extra1 = STATUS_UNEXPECTED_SLOW_SYNC;
// no break!
default:
// Printing unknown status codes here is of somewhat questionable value,
// because even "good" sources will get a bad status when the overall
// session turns bad. We also don't have good explanations for the
// status here.
SE_LOG_ERROR(source.getDisplayName(), "%s", Status2String(SyncMLStatus(event.m_extra1)).c_str());
break;
}
source.recordStatus(SyncMLStatus(extra1));
break;
}
case sysync::PEV_DSSTATS_L:
/* datastore statistics for local (extra1=# added,
extra2=# updated,
extra3=# deleted) */
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_LOCAL,
SyncSource::ITEM_ADDED,
SyncSource::ITEM_TOTAL,
event.m_extra1);
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_LOCAL,
SyncSource::ITEM_UPDATED,
SyncSource::ITEM_TOTAL,
event.m_extra2);
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_LOCAL,
SyncSource::ITEM_REMOVED,
SyncSource::ITEM_TOTAL,
// Synthesis engine doesn't count locally
// deleted items during
// refresh-from-server/client. That's a matter of
// taste. In SyncEvolution we'd like these
// items to show up, so add it here.
(source.getFinalSyncMode() == (m_serverMode ? SYNC_REFRESH_FROM_CLIENT : SYNC_REFRESH_FROM_SERVER) ||
source.getFinalSyncMode() == SYNC_REFRESH_FROM_REMOTE) ?
source.getNumDeleted() :
event.m_extra3);
break;
case sysync::PEV_DSSTATS_R:
/* datastore statistics for remote (extra1=# added,
extra2=# updated,
extra3=# deleted) */
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_REMOTE,
SyncSource::ITEM_ADDED,
SyncSource::ITEM_TOTAL,
event.m_extra1);
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_REMOTE,
SyncSource::ITEM_UPDATED,
SyncSource::ITEM_TOTAL,
event.m_extra2);
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_REMOTE,
SyncSource::ITEM_REMOVED,
SyncSource::ITEM_TOTAL,
event.m_extra3);
break;
case sysync::PEV_DSSTATS_E:
/* datastore statistics for local/remote rejects (extra1=# locally rejected,
extra2=# remotely rejected) */
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_LOCAL,
SyncSource::ITEM_ANY,
SyncSource::ITEM_REJECT,
event.m_extra1);
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_REMOTE,
SyncSource::ITEM_ANY,
SyncSource::ITEM_REJECT,
event.m_extra2);
break;
case sysync::PEV_DSSTATS_S:
/* datastore statistics for server slowsync (extra1=# slowsync matches) */
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_REMOTE,
SyncSource::ITEM_ANY,
SyncSource::ITEM_MATCH,
event.m_extra1);
break;
case sysync::PEV_DSSTATS_C:
/* datastore statistics for server conflicts (extra1=# server won,
extra2=# client won,
extra3=# duplicated) */
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_REMOTE,
SyncSource::ITEM_ANY,
SyncSource::ITEM_CONFLICT_SERVER_WON,
event.m_extra1);
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_REMOTE,
SyncSource::ITEM_ANY,
SyncSource::ITEM_CONFLICT_CLIENT_WON,
event.m_extra2);
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_REMOTE,
SyncSource::ITEM_ANY,
SyncSource::ITEM_CONFLICT_DUPLICATED,
event.m_extra3);
break;
case sysync::PEV_DSSTATS_D:
/* datastore statistics for data volume (extra1=outgoing bytes,
extra2=incoming bytes) */
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_LOCAL,
SyncSource::ITEM_ANY,
SyncSource::ITEM_SENT_BYTES,
event.m_extra1);
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
source.setItemStat(SyncSource::ITEM_LOCAL,
SyncSource::ITEM_ANY,
SyncSource::ITEM_RECEIVED_BYTES,
event.m_extra2);
break;
case sysync::PEV_NOP:
// Handled, do not process further.
return true;
break;
default:
SE_LOG_DEBUG(NULL, "%s: progress event %d, extra %d/%d/%d",
source.getDisplayName().c_str(),
event.m_type, event.m_extra1, event.m_extra2, event.m_extra3);
}
return false;
}
/*
* There have been segfaults inside glib in the background
* thread which ran the second event loop. Disabled it again,
* even though the synchronous EDS API calls will block then
* when EDS dies.
*/
#if 0 && defined(HAVE_GLIB) && defined(HAVE_EDS)
# define RUN_GLIB_LOOP
#endif
#ifdef RUN_GLIB_LOOP
static void *mainLoopThread(void *)
{
// The test framework uses SIGALRM for timeouts.
// Block the signal here because a) the signal handler
// prints a stack back trace when called and we are not
// interessted in the background thread's stack and b)
// it seems to have confused glib/libebook enough to
// access invalid memory and segfault when it gets the SIGALRM.
sigset_t blocked;
sigemptyset(&blocked);
sigaddset(&blocked, SIGALRM);
pthread_sigmask(SIG_BLOCK, &blocked, nullptr);
GMainLoop *mainloop = g_main_loop_new(nullptr, TRUE);
if (mainloop) {
g_main_loop_run(mainloop);
g_main_loop_unref(mainloop);
}
return nullptr;
}
#endif
void SyncContext::startLoopThread()
{
#ifdef RUN_GLIB_LOOP
// when using Evolution we must have a running main loop,
// otherwise loss of connection won't be reported to us
static pthread_t loopthread;
static bool loopthreadrunning;
if (!loopthreadrunning) {
loopthreadrunning = !pthread_create(&loopthread, nullptr, mainLoopThread, nullptr);
}
#endif
}
SyncSource *SyncContext::findSource(const std::string &name)
{
if (!m_activeContext || !m_activeContext->m_sourceListPtr) {
return nullptr;
}
const char *realname = strrchr(name.c_str(), m_findSourceSeparator);
if (realname) {
realname++;
} else {
realname = name.c_str();
}
return (*m_activeContext->m_sourceListPtr)[realname];
}
SyncContext *SyncContext::findContext(const char *sessionName)
{
return m_activeContext;
}
void SyncContext::initSources(SourceList &sourceList)
{
list<string> configuredSources = getSyncSources();
map<string, string> subSources;
// Disambiguate source names because we have multiple with the same
// name active?
string contextName;
if (m_localSync) {
contextName = getContextName();
}
// Phase 1, check all virtual sync soruces
for (const string &name: configuredSources) {
std::shared_ptr<PersistentSyncSourceConfig> sc(getSyncSourceConfig(name));
SyncSourceNodes source = getSyncSourceNodes (name);
std::string sync = sc->getSync();
SyncMode mode = StringToSyncMode(sync);
if (mode != SYNC_NONE) {
SourceType sourceType = SyncSource::getSourceType(source);
if (sourceType.m_backend == "virtual") {
//This is a virtual sync source, check and enable the referenced
//sub syncsources here
SyncSourceParams params(name, source, std::shared_ptr<SyncConfig>(this, SyncConfigNOP()), contextName);
std::shared_ptr<VirtualSyncSource> vSource = std::shared_ptr<VirtualSyncSource>(new VirtualSyncSource (params));
std::vector<std::string> mappedSources = vSource->getMappedSources();
for (std::string source: mappedSources) {
//check whether the mapped source is really available
std::shared_ptr<PersistentSyncSourceConfig> source_config
= getSyncSourceConfig(source);
if (!source_config || !source_config->exists()) {
Exception::throwError(SE_HERE,
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
StringPrintf("Virtual datastore \"%s\" references a nonexistent datasource \"%s\".", name.c_str(), source.c_str()));
}
pair< map<string, string>::iterator, bool > res = subSources.insert(make_pair(source, name));
if (!res.second) {
Exception::throwError(SE_HERE,
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
StringPrintf("Datastore \"%s\" included in the virtual datastores \"%s\" and \"%s\". It can only be included in one virtual datastore at a time.",
source.c_str(), res.first->second.c_str(), name.c_str()));
}
}
FilterConfigNode::ConfigFilter vFilter;
vFilter["sync"] = sync;
if (!m_serverMode) {
// must set special URI for clients so that
// engine knows about superdatastore and its
// URI
vFilter["uri"] = string("<") + vSource->getName() + ">" + vSource->getURINonEmpty();
}
for (std::string source: mappedSources) {
setConfigFilter (false, source, vFilter);
}
sourceList.addSource(vSource);
}
}
}
for (const string &name: configuredSources) {
std::shared_ptr<PersistentSyncSourceConfig> sc(getSyncSourceConfig(name));
SyncSourceNodes source = getSyncSourceNodes (name);
if (!sc->isDisabled()) {
SourceType sourceType = SyncSource::getSourceType(source);
if (sourceType.m_backend != "virtual") {
SyncSourceParams params(name,
source,
std::shared_ptr<SyncConfig>(this, SyncConfigNOP()),
contextName);
auto syncSource = SyncSource::createSource(params);
if (!syncSource) {
Exception::throwError(SE_HERE, name + ": type unknown" );
}
if (subSources.find(name) != subSources.end()) {
syncSource->recordVirtualSource(subSources[name]);
}
sourceList.addSource(std::move(syncSource));
}
} else {
// the Synthesis engine is never going to see this source,
// therefore we have to mark it as 100% complete and
// "done"
class DummySyncSource source(name, contextName);
source.recordFinalSyncMode(SYNC_NONE);
displaySourceProgress(source,
SyncSourceEvent(sysync::PEV_PREPARING, 0, 0, 0),
true);
displaySourceProgress(source,
SyncSourceEvent(sysync::PEV_ITEMPROCESSED, 0, 0, 0),
true);
displaySourceProgress(source,
SyncSourceEvent(sysync::PEV_ITEMRECEIVED, 0, 0, 0),
true);
displaySourceProgress(source,
SyncSourceEvent(sysync::PEV_ITEMSENT, 0, 0, 0),
true);
displaySourceProgress(source,
SyncSourceEvent(sysync::PEV_SYNCEND, 0, 0, 0),
true);
}
}
}
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
// XML configuration converted to C string constants
extern "C" {
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
// including all known fragments for a client
extern const char *SyncEvolutionXMLClient;
// the remote rules for a client
extern const char *SyncEvolutionXMLClientRules;
}
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
/**
* helper class which scans directories for
* XML config files
*/
class XMLFiles
{
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
public:
enum Category {
MAIN, /**< files directly under searched directories */
DATATYPES, /**< inside datatypes and datatypes/<mode> */
SCRIPTING, /**< inside scripting and scripting/<mode> */
REMOTERULES, /**< inside remoterules and remoterules/<mode> */
MAX_CATEGORY
};
/** search file system for XML config fragments */
void scan(const string &mode);
/** datatypes, scripts and rules concatenated, empty if none found */
string get(Category category);
/** main file, typically "syncevolution.xml", empty if not found */
string get(const string &file);
static const string m_syncevolutionXML;
private:
/* base name as sort key + full file path, iterating is done in lexical order */
StringMap m_files[MAX_CATEGORY];
/**
* scan a specific directory for main files directly inside it
* and inside datatypes, scripting, remoterules;
* it is not an error when it does not exist or is not a directory
*/
void scanRoot(const string &mode, const string &dir);
/**
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
* scan a datatypes/scripting/remoterules sub directory,
* including the <mode> sub-directory
*/
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
void scanFragments(const string &mode, const string &dir, Category category);
/**
* add all .xml files to the right hash, overwriting old entries
*/
void addFragments(const string &dir, Category category);
};
const string XMLFiles::m_syncevolutionXML("syncevolution.xml");
void XMLFiles::scan(const string &mode)
{
const char *dir = getenv("SYNCEVOLUTION_XML_CONFIG_DIR");
/*
* read either one or the other, so that testing can run without
* accidentally reading installed files
*/
if (dir) {
scanRoot(mode, dir);
} else {
scanRoot(mode, XML_CONFIG_DIR);
scanRoot(mode, SubstEnvironment("${XDG_CONFIG_HOME}/syncevolution-xml"));
}
}
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
void XMLFiles::scanRoot(const string &mode, const string &dir)
{
addFragments(dir, MAIN);
scanFragments(mode, dir + "/scripting", SCRIPTING);
scanFragments(mode, dir + "/datatypes", DATATYPES);
scanFragments(mode, dir + "/remoterules", REMOTERULES);
}
void XMLFiles::scanFragments(const string &mode, const string &dir, Category category)
{
addFragments(dir, category);
addFragments(dir + "/" + mode, category);
}
void XMLFiles::addFragments(const string &dir, Category category)
{
if (!isDir(dir)) {
return;
}
ReadDir content(dir);
for (const string &file: content) {
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
if (boost::ends_with(file, ".xml")) {
m_files[category][file] = dir + "/" + file;
}
}
}
string XMLFiles::get(Category category)
{
string res;
for (const StringPair &entry: m_files[category]) {
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
string content;
ReadFile(entry.second, content);
res += content;
}
return res;
}
string XMLFiles::get(const string &file)
{
string res;
auto entry = m_files[MAIN].find(file);
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
if (entry != m_files[MAIN].end()) {
ReadFile(entry->second, res);
}
return res;
}
static void substTag(string &xml, const string &tagname, const string &replacement, bool replaceElement = false)
{
string tag;
size_t index;
tag.reserve(tagname.size() + 3);
tag += "<";
tag += tagname;
tag += "/>";
index = xml.find(tag);
if (index != xml.npos) {
string tmp;
tmp.reserve(tagname.size() * 2 + 2 + 3 + replacement.size());
if (!replaceElement) {
tmp += "<";
tmp += tagname;
tmp += ">";
}
tmp += replacement;
if (!replaceElement) {
tmp += "</";
tmp += tagname;
tmp += ">";
}
xml.replace(index, tag.size(), tmp);
}
}
static void substTag(string &xml, const string &tagname, const char *replacement, bool replaceElement = false)
{
substTag(xml, tagname, std::string(replacement), replaceElement);
}
template <class T> void substTag(string &xml, const string &tagname, const T replacement, bool replaceElement = false)
{
stringstream str;
str << replacement;
substTag(xml, tagname, str.str(), replaceElement);
}
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
void SyncContext::getConfigTemplateXML(const string &mode,
string &xml,
string &rules,
string &configname)
{
XMLFiles files;
files.scan(mode);
xml = files.get(files.m_syncevolutionXML);
if (xml.empty()) {
if (mode != "client") {
SE_THROW(files.m_syncevolutionXML + " not found");
}
configname = "builtin XML configuration";
xml = SyncEvolutionXMLClient;
rules = SyncEvolutionXMLClientRules;
} else {
configname = "XML configuration files";
rules = files.get(XMLFiles::REMOTERULES);
substTag(xml, "datatypes",
files.get(XMLFiles::DATATYPES) +
" <fieldlists/>\n <profiles/>\n <datatypedefs/>\n");
substTag(xml, "scripting", files.get(XMLFiles::SCRIPTING));
}
}
void SyncContext::getConfigXML(bool isSync, string &xml, string &configname)
{
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
string rules;
getConfigTemplateXML(m_serverMode ? "server" : "client",
xml,
rules,
configname);
string tag;
size_t index;
unsigned long hash = 0;
SyncML: workarounds for broken peers, attempt 2 Some peers have problems with meta data (CtCap, old Nokia phones) and the sync mode extensions required for advertising the restart capability (Oracle Beehive). Because the problem occurs when SyncEvolution contacts the peers before it gets the device information from the peer, dynamic rules based on the peer identifiers cannot be used. Instead the local config must already disable these extra features in advance. The "SyncMLVersion" property gets extended for this. Instead of just "SyncMLVersion = 1.0" (as before) it now becomes possible to say "SyncMLVersion = 1.0, noctcap, norestart". "noctcap" disables sending CtCap. "norestart" disables the sync mode extensions and thus doing multiple sync cycles in the same session (used between SyncEvolution instances in some cases to get client and server into sync in one session). Both keywords are case-insensitive. There's no error checking for typos, so beware! The "SyncMLVersion" property was chosen because it was already in use for configuring SyncML compatibility aspects and adding a new property would have been harder. In the previous attempt (commit a0375e) setting the <syncmodeextensions> option was only done on the client side, thus breaking multi-cycle syncing with SyncEvolution as server. On the server side the option shouldn't be needed (the server never sends the extensions unless the client did first), but for symmetry and testing reasons it makes sense to also offer the options there.
2012-11-06 10:49:26 +01:00
std::set<std::string> flags = getSyncMLFlags();
bool noctcap = flags.find("noctcap") != flags.end();
bool norestart = flags.find("norestart") != flags.end();
PBAP: incremental sync (FDO #59551) Depending on the SYNCEVOLUTION_PBAP_SYNC env variable, syncing reads all properties as configured ("all"), excludes photos ("text") or first text, then all ("incremental"). When excluding photos, only known properties get requested. This avoids issues with phones which reject the request when enabling properties via the bit flags. This also helps with "databaseFormat=^PHOTO". When excluding photos, the vcard merge script as used by EDS ensures that existing photo data is preserved. This only works during a slow sync (merge script not called otherwise, okay for PBAP because it always syncs in slow sync) and EDS (other backends do not use the merge script, okay at the moment because PIM Manager is hard-coded to use EDS). The PBAP backend must be aware of the PBAP sync mode and request a second cycle, which again must be a slow sync. This only works because the sync engine is aware of the special mode and sets a new session variable "keepPhotoData". It would be better to have the PBAP backend send CTCap with PHOTO marked as not supported for text-only syncs and enabled when sending PHOTO data, but that is considerably harder to implement (CTCap cannot be adjusted at runtime). beginSync() may only ask for a slow sync when not already called for one. That's what the command line tool does when accessing items. It fails when getting the 508 status. The original goal of overlapping syncing with download has not been achieved yet. It turned out that all item IDs get requested before syncing starts, which thus depends on downloading all items in the current implementation. Can be fixed by making up IDs based on the number of existing items (see GetSize() in PBAP) and then downloading later when the data is needed.
2013-07-05 10:39:21 +02:00
const char *PBAPSyncMode = getenv("SYNCEVOLUTION_PBAP_SYNC");
bool keepPhotoData = PBAPSyncMode &&
(boost::iequals(PBAPSyncMode, "incremental") ||
boost::iequals(PBAPSyncMode, "text"));
std::string sessioninitscript =
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
" <sessioninitscript><![CDATA[\n"
" // these variables are possibly modified by rule scripts\n"
" TIMESTAMP mindate; // earliest date remote party can handle\n"
" INTEGER retransfer_body; // if set to true, body is re-sent to client when message is moved from outbox to sent\n"
" mindate=EMPTY; // no limit by default\n"
" retransfer_body=FALSE; // normally, do not retransfer email body (and attachments) when moving items to sent box\n"
" INTEGER delayedabort;\n"
" delayedabort = FALSE;\n"
" INTEGER alarmTimeToUTC;\n"
" alarmTimeToUTC = FALSE;\n"
" INTEGER addInternetEmail;\n"
" addInternetEmail = FALSE;\n"
" INTEGER stripUID;\n"
" stripUID = FALSE;\n"
PBAP: incremental sync (FDO #59551) Depending on the SYNCEVOLUTION_PBAP_SYNC env variable, syncing reads all properties as configured ("all"), excludes photos ("text") or first text, then all ("incremental"). When excluding photos, only known properties get requested. This avoids issues with phones which reject the request when enabling properties via the bit flags. This also helps with "databaseFormat=^PHOTO". When excluding photos, the vcard merge script as used by EDS ensures that existing photo data is preserved. This only works during a slow sync (merge script not called otherwise, okay for PBAP because it always syncs in slow sync) and EDS (other backends do not use the merge script, okay at the moment because PIM Manager is hard-coded to use EDS). The PBAP backend must be aware of the PBAP sync mode and request a second cycle, which again must be a slow sync. This only works because the sync engine is aware of the special mode and sets a new session variable "keepPhotoData". It would be better to have the PBAP backend send CTCap with PHOTO marked as not supported for text-only syncs and enabled when sending PHOTO data, but that is considerably harder to implement (CTCap cannot be adjusted at runtime). beginSync() may only ask for a slow sync when not already called for one. That's what the command line tool does when accessing items. It fails when getting the 508 status. The original goal of overlapping syncing with download has not been achieved yet. It turned out that all item IDs get requested before syncing starts, which thus depends on downloading all items in the current implementation. Can be fixed by making up IDs based on the number of existing items (see GetSize() in PBAP) and then downloading later when the data is needed.
2013-07-05 10:39:21 +02:00
" INTEGER keepPhotoData;\n"
" keepPhotoData = "
// Keep local photos in first cycle when using special sync
// mode for PBAP. PBAP source will request second cycle if it
// has contacts whose photo data was not donwloaded. Then we
// will disable this special handling for that cycle and photo
// can be updated and removed normally.
+ std::string(keepPhotoData ? "TRUE" : "FALSE") + ";\n"
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
" ]]></sessioninitscript>\n";
ostringstream clientorserver;
if (m_serverMode) {
clientorserver <<
" <server type='plugin'>\n"
" <plugin_module>SyncEvolution</plugin_module>\n"
" <plugin_sessionauth>yes</plugin_sessionauth>\n"
" <plugin_deviceadmin>yes</plugin_deviceadmin>\n";
InitState<unsigned int> configrequestmaxtime = getRequestMaxTime();
unsigned int requestmaxtime;
if (configrequestmaxtime.wasSet()) {
// Explicitly set, use it regardless of the kind of sync.
// We allow this even if thread support was not available,
// because if a user enables it explicitly, it's probably
// for a good reason (= failing client), in which case
// risking multithreading issues is preferable.
requestmaxtime = configrequestmaxtime.get();
} else if (m_remoteInitiated || m_localSync) {
// We initiated the sync (local sync, Bluetooth). The client
// should not time out, so there is no need for intermediate
// message sending.
//
// To avoid potential problems and get a single log file,
// avoid it and multithreading by default.
requestmaxtime = 0;
} else {
// We were contacted by an HTTP client. Reply to client
// not later than 120 seconds while storage initializes
// in a background thread.
#ifdef HAVE_THREAD_SUPPORT
requestmaxtime = 120; // default in seconds
#else
requestmaxtime = 0;
#endif
}
if (requestmaxtime) {
clientorserver <<
" <multithread>yes</multithread>\n"
" <requestmaxtime>" << requestmaxtime << "</requestmaxtime>\n";
} else {
clientorserver <<
" <multithread>no</multithread>\n";
}
clientorserver <<
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
"\n" <<
sessioninitscript <<
" <sessiontimeout>300</sessiontimeout>\n"
"\n";
//do not send respuri if over bluetooth
if (boost::starts_with (getUsedSyncURL(), "obex-bt://")) {
clientorserver << " <sendrespuri>no</sendrespuri>\n"
"\n";
}
SyncML: workarounds for broken peers, attempt 2 Some peers have problems with meta data (CtCap, old Nokia phones) and the sync mode extensions required for advertising the restart capability (Oracle Beehive). Because the problem occurs when SyncEvolution contacts the peers before it gets the device information from the peer, dynamic rules based on the peer identifiers cannot be used. Instead the local config must already disable these extra features in advance. The "SyncMLVersion" property gets extended for this. Instead of just "SyncMLVersion = 1.0" (as before) it now becomes possible to say "SyncMLVersion = 1.0, noctcap, norestart". "noctcap" disables sending CtCap. "norestart" disables the sync mode extensions and thus doing multiple sync cycles in the same session (used between SyncEvolution instances in some cases to get client and server into sync in one session). Both keywords are case-insensitive. There's no error checking for typos, so beware! The "SyncMLVersion" property was chosen because it was already in use for configuring SyncML compatibility aspects and adding a new property would have been harder. In the previous attempt (commit a0375e) setting the <syncmodeextensions> option was only done on the client side, thus breaking multi-cycle syncing with SyncEvolution as server. On the server side the option shouldn't be needed (the server never sends the extensions unless the client did first), but for symmetry and testing reasons it makes sense to also offer the options there.
2012-11-06 10:49:26 +01:00
clientorserver << " <syncmodeextensions>" << (norestart ? "no" : "yes" ) << "</syncmodeextensions>\n";
if (noctcap) {
clientorserver << " <showctcapproperties>no</showctcapproperties>\n"
"\n";
}
clientorserver<<" <defaultauth/>\n"
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
"\n"
" <datastore/>\n"
"\n"
" <remoterules/>\n"
" </server>\n";
} else {
clientorserver <<
" <client type='plugin'>\n"
" <binfilespath>$(binfilepath)</binfilespath>\n"
" <multithread>no</multithread>\n"
" <defaultauth/>\n";
if (getRefreshSync()) {
clientorserver <<
" <preferslowsync>no</preferslowsync>\n";
}
clientorserver <<
"\n" ;
string syncMLVersion (getSyncMLVersion());
if (!syncMLVersion.empty()) {
clientorserver << "<defaultsyncmlversion>"
<<syncMLVersion.c_str()<<"</defaultsyncmlversion>\n";
}
SyncML: workarounds for broken peers, attempt 2 Some peers have problems with meta data (CtCap, old Nokia phones) and the sync mode extensions required for advertising the restart capability (Oracle Beehive). Because the problem occurs when SyncEvolution contacts the peers before it gets the device information from the peer, dynamic rules based on the peer identifiers cannot be used. Instead the local config must already disable these extra features in advance. The "SyncMLVersion" property gets extended for this. Instead of just "SyncMLVersion = 1.0" (as before) it now becomes possible to say "SyncMLVersion = 1.0, noctcap, norestart". "noctcap" disables sending CtCap. "norestart" disables the sync mode extensions and thus doing multiple sync cycles in the same session (used between SyncEvolution instances in some cases to get client and server into sync in one session). Both keywords are case-insensitive. There's no error checking for typos, so beware! The "SyncMLVersion" property was chosen because it was already in use for configuring SyncML compatibility aspects and adding a new property would have been harder. In the previous attempt (commit a0375e) setting the <syncmodeextensions> option was only done on the client side, thus breaking multi-cycle syncing with SyncEvolution as server. On the server side the option shouldn't be needed (the server never sends the extensions unless the client did first), but for symmetry and testing reasons it makes sense to also offer the options there.
2012-11-06 10:49:26 +01:00
clientorserver << " <syncmodeextensions>" << (norestart ? "no" : "yes" ) << "</syncmodeextensions>\n";
if (noctcap) {
clientorserver << " <showctcapproperties>no</showctcapproperties>\n"
"\n";
}
clientorserver << sessioninitscript <<
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
// SyncEvolution has traditionally not folded long lines in
// vCard. Testing showed that servers still have problems with
// it, so avoid it by default
" <donotfoldcontent>yes</donotfoldcontent>\n"
"\n"
" <fakedeviceid/>\n"
"\n"
" <datastore/>\n"
"\n"
" <remoterules/>\n"
" </client>\n";
}
substTag(xml,
"clientorserver",
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
clientorserver.str(),
true);
tag = "<debug/>";
index = xml.find(tag);
if (index != xml.npos) {
stringstream debug;
bool logging = !m_sourceListPtr->getLogdir().empty();
int loglevel = getLogLevel();
#ifdef USE_DLT
const char *useDLT = getenv("SYNCEVOLUTION_USE_DLT");
#else
static const char *useDLT = nullptr;
#endif
debug <<
" <debug>\n"
// logpath is a config variable set by SyncContext::doSync()
" <logpath>$(logpath)</logpath>\n"
" <filename>" << (useDLT ? "" : LogfileBasename) << "</filename>" <<
" <logflushmode>flush</logflushmode>\n"
" <logformat>" << (useDLT ? "dlt" : "html") << "</logformat>\n"
" <folding>auto</folding>\n" <<
(useDLT ?
" <timestamp>no</timestamp>\n"
" <timestampall>no</timestampall>\n" :
" <timestamp>yes</timestamp>\n"
" <timestampall>yes</timestampall>\n") <<
" <timedsessionlognames>no</timedsessionlognames>\n"
" <subthreadmode>separate</subthreadmode>\n"
" <logsessionstoglobal>yes</logsessionstoglobal>\n"
" <singlegloballog>yes</singlegloballog>\n";
#ifdef USE_DLT
if (useDLT) {
debug <<
// We have to enable all logging inside libsynthesis.
// The actual filtering then takes place inside DLT.
// Message logging is not supported.
" <enable option=\"all\"/>\n"
// Allow logging outside of sessions.
" <globallogs>yes</globallogs>\n"
// Don't try per-session logging, it all goes to DLT anyway.
" <sessionlogs>yes</sessionlogs>\n"
;
// Be extra verbose if currently enabled. Cannot be changed later on.
if (atoi(useDLT) > DLT_LOG_DEBUG) {
debug <<
" <enable option=\"userdata\"/>\n"
" <enable option=\"scripts\"/>\n";
}
if (atoi(useDLT) > DLT_LOG_DEBUG) {
debug <<
" <enable option=\"exotic\"/>\n";
}
} else
#endif // USE_DLT
if (logging) {
debug <<
" <sessionlogs>yes</sessionlogs>\n"
" <globallogs>yes</globallogs>\n";
debug << "<msgdump>" << (loglevel >= 5 ? "yes" : "no") << "</msgdump>\n";
debug << "<xmltranslate>" << (loglevel >= 4 ? "yes" : "no") << "</xmltranslate>\n";
if (loglevel >= 3) {
debug <<
" <sourcelink>doxygen</sourcelink>\n"
" <enable option=\"all\"/>\n"
" <enable option=\"userdata\"/>\n"
" <enable option=\"scripts\"/>\n"
" <enable option=\"exotic\"/>\n";
}
} else {
debug <<
" <sessionlogs>no</sessionlogs>\n"
" <globallogs>no</globallogs>\n"
" <msgdump>no</msgdump>\n"
" <xmltranslate>no</xmltranslate>\n"
" <disable option=\"all\"/>";
}
debug <<
" </debug>\n";
xml.replace(index, tag.size(), debug.str());
}
XMLConfigFragments fragments;
tag = "<datastore/>";
index = xml.find(tag);
if (index != xml.npos) {
stringstream datastores;
for (SyncSource *source: *m_sourceListPtr) {
string fragment;
source->getDatastoreXML(fragment, fragments);
string name;
// Make sure that sub-datastores do not interfere with the global URI namespace
// by adding a <superdatastore>/ prefix. That way we can have a "calendar"
// alias for "calendar+todo" without conflicting with the underlying
// "calendar", which will be called "calendar+todo/calendar" in the XML config.
name = source->getVirtualSource();
if (!name.empty()) {
name += m_findSourceSeparator;
}
name += source->getName();
datastores << " <datastore name='" << name << "' type='plugin'>\n" <<
" <dbtypeid>" << source->getSynthesisID() << "</dbtypeid>\n" <<
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
fragment;
datastores << " <resumesupport>on</resumesupport>\n";
if (source->getOperations().m_writeBlob) {
// BLOB support is essential for caching partially received items.
datastores << " <resumeitemsupport>on</resumeitemsupport>\n";
}
SyncMode mode = StringToSyncMode(source->getSync());
if (source->getForceSlowSync()) {
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
// we *want* a slow sync, but couldn't tell the client -> force it server-side
datastores << " <alertscript> FORCESLOWSYNC(); </alertscript>\n";
engine: local cache sync mode This patch introduces support for true one-way syncing ("caching"): the local datastore is meant to be an exact copy of the data on the remote side. The assumption is that no modifications are ever made locally outside of syncing. This is different from one-way sync modes, which allows local changes and only temporarily disables sending them to the remote side. Another goal of the new mode is to avoid data writes as much as possible. This new mode only works on the server side of a sync, where the engine has enough control over the data flow. Most of the changes are in libsynthesis. SyncEvolution only needs to enable the new mode, which is done via an extension of the "sync" property: - "local-cache-incremental" will do an incremental sync (if possible) or a slow sync (otherwise). This is usually the right mode to use, and thus has "local-cache" as alias. - "local-cache-slow" will always do a slow sync. Useful for debugging or after (accidentally) making changes on the server side. An incremental sync will ignore such changes because they are not meant to happen and thus leave client and sync out-of-sync! Both modes are recorded in the sync report of the local side. The target side is the client and records the normal "two-way" or "slow" sync modes. With the current SyncEvolution contact field list, first, middle and last name are used to find matches during any kind of slow sync. The organization field is ignored for matching during the initial slow sync and used in all following ones. That's okay, the difference won't matter in practice because the initial slow sync in PBAP caching will be done with no local data. The test achieve the same result in both cases by keeping the organization set in the reduced data set. It's also okay to include the property in the comparison, because it might help to distinguish between "John Doe" in different companies. It might be worthwhile to add more fields as match criteria, for example the birthday. Currently they are excluded, probably because they are not trusted to be supported by SyncML peers. In caching mode the situation is different, because all our data came from the peer. The downside is that in cases where matching has to be done all the time because change detection is not supported (PBAP), including the birthday as criteria will cause unnecessary contact removed/added events (and thus disk IO) when a contact was originally created without birthday locally and then a birthday gets added on the phone. Testing is done as part of the D-Bus testing framework, because usually this functionality will be used as part of the D-Bus server and writing tests in Python is easier. A new test class "TestLocalCache" contains the new tests. They include tests for removing extra items during a slow sync (testItemRemoval), adding new client items under various conditions (testItemAdd*) and updating/removing an item during incremental syncing (testItemUpdate/Delete*). Doing these changes during a slow sync could also be tested (not currently covered). The tests for removing properties (testPropertyRemoval*) cover removing almost all contact properties during an initial slow sync, a second slow sync (which is treated differently in libsynthesis, see merge=always and merge=slowsync), and an incremental sync.
2012-08-23 14:25:55 +02:00
} else if (mode == SYNC_LOCAL_CACHE_SLOW ||
mode == SYNC_LOCAL_CACHE_INCREMENTAL) {
if (!m_serverMode) {
SE_THROW("sync modes 'local-cache-*' are only supported on the server side");
}
datastores << " <alertscript>SETREFRESHONLY(1); SETCACHEDATA(1);</alertscript>\n";
// datastores << " <datastoreinitscript>REFRESHONLY(); CACHEDATA(); SLOWSYNC(); ALERTCODE();</datastoreinitscript>\n";
} else if (mode != SYNC_SLOW &&
// slow-sync detection not implemented when running as server,
// not even when initiating the sync (direct sync with phone)
!m_serverMode &&
// is implemented as "delete local data" + "slow sync",
// so a slow sync is acceptable in this case
mode != SYNC_REFRESH_FROM_SERVER &&
mode != SYNC_REFRESH_FROM_REMOTE &&
// The forceSlow should be disabled if the sync session is
// initiated by a remote peer (eg. Server Alerted Sync)
!m_remoteInitiated &&
getPreventSlowSync() &&
(!source->getOperations().m_isEmpty || // check is only relevant if we have local data;
!source->getOperations().m_isEmpty())) { // if we cannot check, assume we have data
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
// We are not expecting a slow sync => refuse to execute one.
// This is the client check for this, server must be handled
// differently (TODO, MB #2416).
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
datastores <<
" <datastoreinitscript><![CDATA[\n"
" if (SLOWSYNC() && ALERTCODE() != 203) {\n" // SLOWSYNC() is true for acceptable refresh-from-client, check for that
" DEBUGMESSAGE(\"slow sync not expected by SyncEvolution, disabling datastore\");\n"
" ABORTDATASTORE(" << sysync::LOCERR_DATASTORE_ABORT << ");\n"
" // tell UI to abort instead of sending the next message\n"
" SETSESSIONVAR(\"delayedabort\", 1);\n"
" }\n"
" ]]></datastoreinitscript>\n";
}
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
if (m_serverMode && !m_localSync) {
string uri = source->getURI();
if (!uri.empty()) {
datastores << " <alias name='" << uri << "'/>";
}
}
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
datastores << " </datastore>\n\n";
}
/*If there is super datastore, add it here*/
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
//TODO generate specific superdatastore contents (MB #8753)
//Now only works for synthesis built-in events+tasks
for (std::shared_ptr<VirtualSyncSource> vSource: m_sourceListPtr->getVirtualSources()) {
std::string superType = vSource->getSourceType().m_format;
std::string evoSyncSource = vSource->getDatabaseID();
std::vector<std::string> mappedSources = unescapeJoinedString (evoSyncSource, ',');
// always check for a consistent config
SourceType sourceType = vSource->getSourceType();
for (std::string source: mappedSources) {
//check the data type
SyncSource *subSource = (*m_sourceListPtr)[source];
SourceType subType = subSource->getSourceType();
//If there is no format explictly selected in sub SyncSource, we
//have no way to determine whether it works with the format
//specific in the virtual SyncSource, thus no warning in this
//case.
if (!subType.m_format.empty() && (
sourceType.m_format != subType.m_format ||
sourceType.m_forceFormat != subType.m_forceFormat)) {
SE_LOG_WARNING(NULL,
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
"Virtual datastore \"%s\" and sub datastore \"%s\" have different data format. Will use the format in virtual datastore.",
vSource->getDisplayName().c_str(), source.c_str());
}
}
if (mappedSources.size() !=2) {
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
vSource->throwError(SE_HERE, "virtual datastore currently only supports events+tasks combinations");
}
string name = vSource->getName();
datastores << " <superdatastore name= '" << name << "'> \n";
datastores << " <contains datastore = '" << name << m_findSourceSeparator << mappedSources[0] <<"'>\n"
<< " <dispatchfilter>F.ISEVENT:=1</dispatchfilter>\n"
<< " <guidprefix>e</guidprefix>\n"
<< " </contains>\n"
<< "\n <contains datastore = '" << name << m_findSourceSeparator << mappedSources[1] <<"'>\n"
<< " <dispatchfilter>F.ISEVENT:=0</dispatchfilter>\n"
<< " <guidprefix>t</guidprefix>\n"
<<" </contains>\n" ;
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
if (m_serverMode && !m_localSync) {
string uri = vSource->getURI();
if (!uri.empty()) {
datastores << " <alias name='" << uri << "'/>";
}
}
2010-03-03 07:47:40 +01:00
if (vSource->getForceSlowSync()) {
// we *want* a slow sync, but couldn't tell the client -> force it server-side
datastores << " <alertscript> FORCESLOWSYNC(); </alertscript>\n";
}
std::string typesupport;
typesupport = vSource->getDataTypeSupport();
datastores << " <typesupport>\n"
<< typesupport
<< " </typesupport>\n";
datastores <<"\n </superdatastore>";
}
if (datastores.str().empty()) {
// Add dummy datastore, the engine needs it. sync()
// checks that we have a valid configuration if it is
// really needed.
#if 0
datastores << "<datastore name=\"____dummy____\" type=\"plugin\">"
"<plugin_module>SyncEvolution</plugin_module>"
"<fieldmap fieldlist=\"contacts\"/>"
"<typesupport>"
"<use datatype=\"vCard30\"/>"
"</typesupport>"
"</datastore>";
#endif
}
xml.replace(index, tag.size(), datastores.str());
}
substTag(xml, "fieldlists", fragments.m_fieldlists.join(), true);
substTag(xml, "profiles", fragments.m_profiles.join(), true);
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
substTag(xml, "datatypedefs", fragments.m_datatypes.join(), true);
substTag(xml, "remoterules",
XML config: use configuration composed from fragments (MB #7712) This patch replaces src/syncclient_sample_config.xml with a combination of src/syncevo/configs/syncevolution.xml and the config fragments that are shared with Synthesis upstream. These fragments are installed in /usr/share/syncevolution/xml (or the corresponding data path). From there they are read at runtime to compose the final XML configuration. Users can copy individual files into the corresponding directory hierarchy rooted at $XDG_CONFIG_HOME/syncevolution-xml to replace individual fragments. New fragments can be added there or in /usr/share. For testing, these two directories can be overridden with the SYNCEVOLUTION_XML_CONFIG_DIR env variable. No tests have been added for this yet. There's also no documentation about it except this commit message - add something to the HACKING guide once this new concept stabilizes. Developers can add new fragments in the source tree, invoke make and run the resulting binary in client mode. As before, a complete config is included in the binary. However, it is only sufficient for SyncML client mode. For server mode, the files are expected to be installed (no need to maintain a list of files in a Makefile for that) or SYNCEVOLUTION_XML_CONFIG_DIR must be set. At the moment, the following sub-directories are scanned for .xml files: - the root directory to find syncevolution.xml - datatypes, datatypes/client, datatypes/server - scripting, scripting/client, scripting/server - remoterules, remoterules/client, remoterules/server Files inside "client" or "server" sub-directories are only used when assembling a config for the corresponding mode of operation. The goal of this patch is to simplify config sharing with Synthesis (individual files are easier to manage than the monolitithic one), to share files between client and server with the possibility to add mode-specific files, and to allow users to extend the XML configuration. The most likely use case for the latter is support for more devices. Previously, remote rules for the different devices listed in syncserv_sample_config.xml were not used by SyncEvolution. This patch moves the ZYB remote rule into a client-specific remote rule, thus removing a complaint from libsynthesis about the unknown <client> element when running as server. Because we are using the unified upstream config, some parts of the config have changed: - There is a SYNCLVL field in all field list. This is currently unused by SyncEvolution, but doesn't hurt either. - A new iCalendar 2.0 all-day sanity check was added (for older Oracle servers?). - The CATEGORIES defition in vBookmark was extended. - some comment and white space changes Because this is such fundamental change, extra care was taken to minimize and verify the config changes. Here's the command which compares old and new config for clients plus its output: $ update-samples.pl syncevolution.xml client | diff -c -b syncclient_sample_config.xml - *************** *** 31,42 **** <scripting> <looptimeout>5</looptimeout> - <function><![CDATA[ - // create a UID - string newuid() { - return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); - } - ]]></function> <macro name="VCARD_BEFOREWRITE_SCRIPT_EVOLUTION"><![CDATA[ // a wordaround for cellphone in evolution. for incoming contacts, if there is only one CELL, // strip the HOME or WORK flag from it. Evolution then should show it. */ --- 30,35 ---- *************** *** 118,123 **** --- 111,124 ---- } ]]></macro> + <function><![CDATA[ + // create a UID + string newuid() { + return "syuid" + NUMFORMAT(RANDOM(1000000),6,"0") + "." + (string)MILLISECONDS(NOW()); + } + ]]></function> + + <!-- define script macros for scripts that are used by both vCalendar 1.0 and iCalendar 2.0 --> <macro name="VCALENDAR_INCOMING_SCRIPT"><![CDATA[ *************** *** 145,150 **** --- 146,158 ---- DTSTART = CONVERTTOUSERZONE(DTSTART); MAKEALLDAY(DTSTART,DTEND,i); } + else { + // iCalendar 2.0 - only if DTSTART is a date-only value this really is an allday + if (ISDATEONLY(DTSTART)) { + // reshape to make sure we don't have invalid zero-duration alldays (old OCS 9 servers) + MAKEALLDAY(DTSTART,DTEND,i); + } + } // Make sure that all EXDATE times are in the same timezone as the start // time. Some servers send them as UTC, which is all fine and well, but *************** *** 265,275 **** </scripting> - <datatypes> - <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> --- 274,283 ---- </scripting> <datatypes> <!-- list of internal fields representing vCard data --> <fieldlist name="contacts"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="REV" type="timestamp" compare="never" age="yes"/> <!-- Name elements --> *************** *** 680,689 **** $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> - - <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> --- 688,696 ---- $VCARD_INCOMING_NAMECHANGE_SCRIPT ]]></incomingscript> </datatype> <!-- common field list for events and todos (both represented by vCalendar/iCalendar) --> <fieldlist name="calendar"> + <field name="SYNCLVL" type="integer" compare="never"/> <field name="ISEVENT" type="integer" compare="always"/> <field name="DMODIFIED" type="timestamp" compare="never" age="yes"/> *************** *** 787,793 **** <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for todoz --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> --- 792,798 ---- <subprofile onlyformode="standard" name="VTIMEZONE" mode="vtimezones"/> ! <!-- sub-profile for tasks --> <subprofile name="VTODO" nummandatory="1" showifselectedonly="yes" field="ISEVENT" value="0"> <property name="LAST-MODIFIED" suppressempty="yes"> *************** *** 1394,1401 **** <!-- non-standard properties --> ! <property name="CATEGORIES"> ! <value field="CATEGORIES"/> </property> <property name="CLASS" suppressempty="yes"> --- 1394,1402 ---- <!-- non-standard properties --> ! <!-- inherit CATEGORIES from vCard 3.0, i.e. comma separated --> ! <property name="CATEGORIES" values="list" valueseparator="," altvalueseparator=";"> ! <value field="CATEGORIES" combine=","/> </property> <property name="CLASS" suppressempty="yes"> *************** *** 1416,1435 **** <use profile="vBookmark"/> </datatype> ! <fieldlists/> ! <profiles/> ! <datatypes/> </datatypes> <clientorserver/> - - <client type="plugin"> - <remoterule name="ZYB"> - <manufacturer>ZYB</manufacturer> - <model>ZYB</model> - <!-- information to disable anchors checking --> - <lenientmode>yes</lenientmode> - </remoterule> - </client> - </sysync_config> --- 1417,1424 ---- <use profile="vBookmark"/> </datatype> ! </datatypes> <clientorserver/> </sysync_config>
2010-02-02 21:29:53 +01:00
rules +
fragments.m_remoterules.join(),
true);
if (m_serverMode) {
// TODO: set the device ID for an OBEX server
} else {
substTag(xml, "fakedeviceid", getDevID());
}
substTag(xml, "model", getMod());
substTag(xml, "manufacturer", getMan());
substTag(xml, "hardwareversion", getHwv());
// abuse (?) the firmware version to store the SyncEvolution version number
substTag(xml, "firmwareversion", getSwv());
substTag(xml, "devicetype", getDevType());
substTag(xml, "maxmsgsize", getMaxMsgSize().get());
substTag(xml, "maxobjsize", getMaxObjSize().get());
if (m_serverMode) {
UserIdentity id = getSyncUser();
/*
* Do not check username/pwd if this local sync or over
* bluetooth transport. Need credentials for checking,
* and IdentityProviderCredentials() throws an error when
* called for a provider which does not support plain
* credentials.
*/
bool withauth = !m_localSync && !boost::starts_with(getUsedSyncURL(), "obex-bt");
if (withauth) {
Credentials cred = IdentityProviderCredentials(id, getSyncPassword());
const string &user = cred.m_username;
const string &password = cred.m_password;
if (user.empty() && password.empty()) {
withauth = false;
}
}
if (withauth) {
// require authentication with the configured password
substTag(xml, "defaultauth",
"<requestedauth>md5</requestedauth>\n"
"<requiredauth>basic</requiredauth>\n"
"<autononce>yes</autononce>\n",
true);
} else {
if (id.wasSet()) {
SE_LOG_WARNING(getConfigName(), "ignoring username %s, it is not needed",
id.toString().c_str());
}
// no authentication required
substTag(xml, "defaultauth",
"<logininitscript>return TRUE</logininitscript>\n"
"<requestedauth>none</requestedauth>\n"
"<requiredauth>none</requiredauth>\n"
"<autononce>yes</autononce>\n",
true);
}
} else {
substTag(xml, "defaultauth", getClientAuthType());
}
if (isSync ||
!getConfigDate().wasSet()) {
// If the hash code of the main sync XML config is changed, that
// means the content of the config has changed. Save the new hash
// and regen the configdate. Also necessary when no config data
// has ever been set.
hash = Hash(xml.c_str());
if (getHashCode() != hash) {
setConfigDate();
setHashCode(hash);
flush();
}
}
substTag(xml, "configdate", getConfigDate().c_str());
}
SharedEngine SyncContext::createEngine()
{
SharedEngine engine(new sysync::TEngineModuleBridge);
// This instance of the engine is used outside of the sync session
// itself for logging. doSync() then reinitializes it with a full
// datastore configuration.
engine.Connect(m_serverMode ?
#ifdef ENABLE_SYNCML_LINKED
// use Synthesis client or server engine that we were linked against
"[server:]" : "[]",
#else
// load engine dynamically
"server:libsynthesis.so.0" : "libsynthesis.so.0",
#endif
0,
sysync::DBG_PLUGIN_NONE|
sysync::DBG_PLUGIN_INT|
sysync::DBG_PLUGIN_DB|
sysync::DBG_PLUGIN_EXOT);
SharedKey configvars = engine.OpenKeyByPath(SharedKey(), "/configvars");
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
string logdir;
if (m_sourceListPtr) {
logdir = m_sourceListPtr->getLogdir();
}
engine.SetStrValue(configvars, "defout_path",
logdir.size() ? logdir : "/dev/null");
engine.SetStrValue(configvars, "conferrpath", "console");
engine.SetStrValue(configvars, "binfilepath", getSynthesisDatadir().c_str());
configvars.reset();
return engine;
}
namespace {
void GnutlsLogFunction(int level, const char *str)
{
SE_LOG_DEBUG("GNUTLS", "level %d: %s", level, str);
}
}
void SyncContext::initServer(const std::string &sessionID,
SharedBuffer data,
const std::string &messageType)
{
m_serverMode = true;
m_sessionID = sessionID;
m_initialMessage = data;
m_initialMessageType = messageType;
}
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
struct SyncContext::SyncMLMessageInfo
SyncContext::analyzeSyncMLMessage(const char *data, size_t len,
const std::string &messageType)
{
config: share properties between peers, configuration view without peer This patch makes the configuration layout with per-source and per-peer properties the default for new configurations. Migrating old configurations is not implemented. The command line has not been updated at all (MB #8048). The D-Bus API is fairly complete, only listing sessions independently of a peer is missing (MB #8049). The key concept of this patch is that a pseudo-node implemented by MultiplexConfigNode provides a view on all user-visible or hidden properties. Based on the property name, it looks up the property definition, picks one of the underlying nodes based on the property visibility and sharing attributes, then reads and writes the property via that node. Clearing properties is not needed and not implemented, because of the uncertain semantic (really remove shared properties?!). The "sync" property must be available both in the per-source config (to pick a backend independently of a specific peer) and in the per-peer configuration (to select a specific data format). This is solved by making the property special (SHARED_AND_UNSHARED flag) and then writing it into two nodes. Reading is done from the more specific per-peer node, with the other node acting as fallback. The MultiplexConfigNode has to implement the FilterConfigNode API because it is used as one by the code which sets passwords in the filter. For this to work, the base FilterConfigNode implementation must use virtual method calls. The TestDBusSessionConfig.testUpdateConfigError checks that the error generated for an incorrect "sync" property contains the path of the config.ini file. The meaning of the error message in this case is that the wrong value is *for* that file, not that the property is already wrong *in* the file, but that's okay. The MultiplexConfigNode::getName() can only return a fixed name. To satisfy the test and because it is the right choice at the moment for all properties which might trigger such an error, it now is configured so that it returns the most specific path of the non-shared properties. "syncevolution --print-config" shows errors that are in files. Wrong command line parameters are rejected with a message that refers to the command line parameter ("--source-property sync=foo"). A future enhancement would be to make the name depend on the property (MB#8037). Because an empty string is now a valid configuration name (referencing the source properties without the per-peer properties) several checks for such empty strings were removed. The corresponding tests were updated resp. removed. Instead of talking about "server not found", the more neutral name "configuration" is used. The new TestMultipleConfigs.testSharing() covers the semantic of sharing properties between multiple configs. Access to non-existant nodes is routed into the new DevNullConfigNode. It always returns an empty string when reading and throws an error when trying to write into it. Unintentionally writing into a config.ini file therefore became harder, compared with the previous instantiation of SyncContext() with empty config name. The parsing of incoming messages uses a SyncContext which is bound to a VolatileConfigNode. This allows reading and writing of properties without any risk of touching files on disk. The patch which introduced the new config nodes was not complete yet with regards to the new layout. Removing nodes and trees used the wrong root path: getRootPath() refers to the most specific peer config, m_root to the part without the peer path. SyncConfig must distinguish between a view with peer-specific properties and one without, which is done by setting the m_peerPath only if a peer was selected. Copying properties must know whether writing per-specific properties ("unshared") is wanted, because trying to do it for a view without those properties would trigger the DevNullConfigNode exception. SyncConfig::removeSyncSource() removes source properties both in the shared part of the config and in *all* peers. This is used by Session.SetConfig() for the case that the caller is a) setting instead of updating the config and b) not providing any properties for the source. This is clearly a risky operation which should not be done when there are other peers which still use the source. We might have a problem in our D-Bus API definition for "removing a peer configuration" (MB #8059) because it always has an effect on other peers. The property registries were initialized implicitly before. With the recent changes it happened that SyncContext was initialized to analyze a SyncML message without initializing the registry, which caused getRemoteDevID() to use a property where the hidden flag had not been set yet. Moving all of these additional flags into the property constructors is awkward (which is why they are in the getRegistry() methods), so this was fixed by initializing the properties in the SyncConfig constructors by asking for the registries. Because there is no way to access them except via the registry and SyncConfig instances (*), this should ensure that the properties are valid when used. (*) Exception are some properties which are declared publicly to have access to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
SyncContext sync;
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
SourceList sourceList(sync, false);
sourceList.setLogLevel(SourceList::LOGGING_SUMMARY);
sync.m_sourceListPtr = &sourceList;
SwapContext syncSentinel(&sync);
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
sync.initServer("", SharedBuffer(), "");
SwapEngine swapengine(sync);
sync.initEngine(false);
sysync::TEngineProgressInfo progressInfo;
sysync::uInt16 stepCmd = sysync::STEPCMD_GOTDATA;
SharedSession session = sync.m_engine.OpenSession(sync.m_sessionID);
SessionSentinel sessionSentinel(sync, session);
sync.m_engine.WriteSyncMLBuffer(session, data, len);
SharedKey sessionKey = sync.m_engine.OpenSessionKey(session);
sync.m_engine.SetStrValue(sessionKey,
"contenttype",
messageType);
// analyze main loop: runs until SessionStep() signals reply or error.
// Will call our SynthesisDBPlugin callbacks, most importantly
// SyncEvolution_Session_CheckDevice(), which records the device ID
// for us.
do {
sync.m_engine.SessionStep(session, stepCmd, &progressInfo);
switch (stepCmd) {
case sysync::STEPCMD_OK:
case sysync::STEPCMD_PROGRESS:
stepCmd = sysync::STEPCMD_STEP;
break;
default:
// whatever it is, cannot proceed
break;
}
} while (stepCmd == sysync::STEPCMD_STEP);
SyncMLMessageInfo info;
info.m_deviceID = sync.getSyncDeviceID();
return info;
}
void SyncContext::initEngine(bool isSync)
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
{
string xml, configname;
getConfigXML(isSync, xml, configname);
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
try {
m_engine.InitEngineXML(xml.c_str());
} catch (const BadSynthesisResult &ex) {
SE_LOG_ERROR(NULL,
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
"internal error, invalid XML configuration (%s):\n%s",
m_sourceListPtr && !m_sourceListPtr->empty() ?
"with datastores" :
"without datastores",
xml.c_str());
throw;
}
if (isSync &&
getLogLevel() >= 5) {
SE_LOG_DEV(NULL, "Full XML configuration:\n%s", xml.c_str());
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
}
}
Merge branch 'HARMATTAN-1-3-1' Fetched the code and its history from the 1.3.1 archives at: http://people.debian.org/~ovek/maemo/ http://people.debian.org/~ovek/harmattan/ Merged almost everything, except for Maemo/Harmattan specific build files: autogen-maemo.sh builddeb buildsrc debian The following changes were also removed, because they are either local workarounds or merge artifacts which probably also don't belong into the Maemo/Harmattan branch: diff --git a/configure.ac b/configure.ac index cb66617..2c4403c 100644 --- a/configure.ac +++ b/configure.ac @@ -44,7 +44,7 @@ if test "$enable_release_mode" = "yes"; then AC_DEFINE(SYNCEVOLUTION_STABLE_RELEASE, 1, [binary is meant for end-users]) fi -AM_INIT_AUTOMAKE([1.11.1 tar-ustar silent-rules subdir-objects -Wno-portability]) +AM_INIT_AUTOMAKE([subdir-objects -Wno-portability]) AM_PROG_CC_C_O diff --git a/src/backends/webdav/CalDAVSource.cpp b/src/backends/webdav/CalDAVSource.cpp index decd170..7d338ac 100644 --- a/src/backends/webdav/CalDAVSource.cpp +++ b/src/backends/webdav/CalDAVSource.cpp @@ -1282,6 +1282,7 @@ void CalDAVSource::Event::fixIncomingCalendar(icalcomponent *calendar) // time. bool ridInUTC = false; const icaltimezone *zone = NULL; + icalcomponent *parent = NULL; for (icalcomponent *comp = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT); comp; @@ -1295,6 +1296,7 @@ void CalDAVSource::Event::fixIncomingCalendar(icalcomponent *calendar) // is parent event? -> remember time zone unless it is UTC static const struct icaltimetype null = { 0 }; if (!memcmp(&rid, &null, sizeof(null))) { + parent = comp; struct icaltimetype dtstart = icalcomponent_get_dtstart(comp); if (!icaltime_is_utc(dtstart)) { zone = icaltime_get_timezone(dtstart); diff --git a/src/backends/webdav/CalDAVSource.h b/src/backends/webdav/CalDAVSource.h index 517ac2f..fa7c2ca 100644 --- a/src/backends/webdav/CalDAVSource.h +++ b/src/backends/webdav/CalDAVSource.h @@ -45,6 +45,10 @@ class CalDAVSource : public WebDAVSource, virtual void removeMergedItem(const std::string &luid); virtual void flushItem(const string &uid); virtual std::string getSubDescription(const string &uid, const string &subid); + virtual void updateSynthesisInfo(SynthesisInfo &info, + XMLConfigFragments &fragments) { + info.m_backendRule = "HAVE-SYNCEVOLUTION-EXDATE-DETACHED"; + } // implementation of SyncSourceLogging callback virtual std::string getDescription(const string &luid); Making SySync_ConsolePrintf a real instance inside SyncEvolution leads to link errors in other configurations. It really has to be extern. Added a comment to the master branch to make that more obvious: -extern "C" { // without curly braces, g++ 4.2 thinks the variable is extern - int (*SySync_ConsolePrintf)(FILE *stream, const char *format, ...); -} +// This is just the declaration. The actual function pointer instance +// is inside libsynthesis, which, for historic purposes, doesn't define +// it in its header files (yet). +extern "C" int (*SySync_ConsolePrintf)(FILE *stream, const char *format, ...);
2012-11-01 14:39:31 +01:00
// This is just the declaration. The actual function pointer instance
// is inside libsynthesis, which, for historic purposes, doesn't define
// it in its header files (yet).
extern "C" int (*SySync_ConsolePrintf)(FILE *stream, const char *format, ...);
static int nopPrintf(FILE *stream, const char *format, ...) { return 0; }
extern "C"
{
extern int (*SySync_CondTimedWait)(pthread_cond_t *cond, pthread_mutex_t *mutex, bool &aTerminated, long aMilliSecondsToWait);
}
#ifdef HAVE_GLIB
static gboolean timeout(gpointer data)
{
// Call me again...
return true;
}
static int CondTimedWaitGLib(pthread_cond_t * /* cond */, pthread_mutex_t *mutex,
bool &terminated, long milliSecondsToWait)
{
int result = 0;
// return abstime ? pthread_cond_timedwait(cond, mutex, abstime) : pthread_cond_wait(cond, mutex);
try {
pthread_mutex_unlock(mutex);
SE_LOG_DEBUG(NULL, "wait for background thread: %lds", milliSecondsToWait);
SuspendFlags &flags = SuspendFlags::getSuspendFlags();
Timespec now = Timespec::system();
Timespec wait(milliSecondsToWait / 1000, milliSecondsToWait % 1000);
Timespec deadline = now + wait;
// We don't need to react to thread shutdown immediately (only
// called once per sync), so a relatively long check interval of
// one second is okay.
GLibEvent id(g_timeout_add_seconds(1, timeout, nullptr), "timeout");
auto condTimedWaitContinue = [mutex, &terminated, milliSecondsToWait, &deadline, &flags, &result] () {
// Thread has terminated?
pthread_mutex_lock(mutex);
if (terminated) {
pthread_mutex_unlock(mutex);
SE_LOG_DEBUG(NULL, "background thread completed");
return false;
}
pthread_mutex_unlock(mutex);
// Abort? Ignore when waiting for final thread shutdown, because
// in that case we just get called again.
if (milliSecondsToWait > 0 && flags.isAborted()) {
SE_LOG_DEBUG(NULL, "give up waiting for background thread, aborted");
// Signal error. libsynthesis then assumes that the thread still
// runs and enters its parallel message sending, which eventually
// returns control to us.
result = 1;
return false;
}
// Timeout?
if (!milliSecondsToWait ||
(milliSecondsToWait > 0 && deadline <= Timespec::system())) {
SE_LOG_DEBUG(NULL, "give up waiting for background thread, timeout");
result = 1;
return false;
}
return true;
};
GRunWhile(condTimedWaitContinue);
} catch (...) {
Exception::handle(HANDLE_EXCEPTION_FATAL);
}
pthread_mutex_lock(mutex);
return result;
}
#endif
void SyncContext::initMain(const char *appname)
{
#if defined(HAVE_GLIB)
// this is required when using glib directly or indirectly
#if !GLIB_CHECK_VERSION(2,36,0)
g_type_init();
#endif
#if !GLIB_CHECK_VERSION(2,32,0)
g_thread_init(nullptr);
#endif
g_set_prgname(appname);
shutdown: fix memory allocation deadlock When the SuspendFlags::handleSignal() signal handler got called while the program was allocating memory in libc, a deadlock occurred when the mutex locking code in RecMutex::lock() tried to allocate the guard instance (see backtrace below). This could be fixed with new mutex code which gives up the guard concept, a guard concept using C11 mechanisms (copy semantic of a stack-allocated guard) or not locking at all in getSuspendFlags(). This commit uses the latter. It is simpler and can still be done correctly because locking is not necessary except when the singleton has not been allocated yet. That part gets moved into the process startup phase via SyncContext::initMain(). Thread 1 (Thread 0x7fec389e6840 (LWP 16592)): at ../nptl/sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:95 from /lib/x86_64-linux-gnu/libc.so.6 from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 d=<optimized out>, p=0x7fec38196f60 <SyncEvo::suspendRecMutex>, this=<synthetic pointer>) at /usr/include/boost/smart_ptr/detail/shared_count.hpp:167 p=0x7fec38196f60 <SyncEvo::suspendRecMutex>, this=<synthetic pointer>) at /usr/include/boost/smart_ptr/shared_ptr.hpp:363 this=<synthetic pointer>) at /data/runtests/work/sources/syncevolution/src/syncevo/ThreadSupport.h:60 at /data/runtests/work/sources/syncevolution/src/syncevo/ThreadSupport.h:81 at /data/runtests/work/sources/syncevolution/src/syncevo/SuspendFlags.cpp:58 at /data/runtests/work/sources/syncevolution/src/syncevo/SuspendFlags.cpp:280 oldp=0x115b7d0, oldsize=80, nb=48) at malloc.c:4208 at malloc.c:3029 n_bytes=n_bytes@entry=32)
2015-03-02 11:32:15 +01:00
// Initialize SuspendFlags singleton.
SuspendFlags::getSuspendFlags();
// redirect glib logging into our own logging
g_log_set_default_handler(Logger::glogFunc, nullptr);
engine: event processing when using multithreading Only one thread may handle events in the default context at any point in time. If a second thread calls g_main_context_iteration() or g_main_loop_run(), it blocks until the other thread releases ownership of the context. In that case, the first thread may wake up because of an event that the second thread waits for, in which case the second thread may never wake up. See https://mail.gnome.org/archives/gtk-list/2013-April/msg00040.html This means that SyncEvolution can no longer rely on these functions outside of the main thread. This affects Sleep() and the EDS backend. As an interim solution, take over permanent ownership of the default context in the main thread. This prevents fights over the ownership when the main thread enters and leaves the main loop repeatedly. Utility code using the main context must check for ownership first and fall back to some other means when not the owner. The assumption for the fallback is that the main thread will drive the event loop, so polling with small delays for the expected status change (like "view complete" in the EDS backend) is going to succeed eventually. A better solution would be to have one thread running the event loop permanently and push all event handling into that thread. There is C++ utility code for such things in: http://cxx-gtk-utils.sourceforge.net/2.0/index.html See in particular the TaskManager class and its make_task_when()/make_task_compose()/make_task_when_full() functions for executing asynchronous results via a glib main loop, also the Future::when() method and a number of other similar things in the library.
2013-04-30 11:31:07 +02:00
// Only the main thread may use the default GMainContext.
// Anything else is unsafe, see https://mail.gnome.org/archives/gtk-list/2013-April/msg00040.html
// util.cpp:Sleep() checks this and uses the default context
// when called by the main thread, otherwise falls back to
// select(). GRunWhile() is always safe to use.
g_main_context_acquire(nullptr);
SySync_CondTimedWait = CondTimedWaitGLib;
#endif
if (atoi(getEnv("SYNCEVOLUTION_DEBUG", "0")) > 3) {
SySync_ConsolePrintf = Logger::sysyncPrintf;
} else {
SySync_ConsolePrintf = nopPrintf;
}
// Load backends.
SyncSource::backendsInit();
// invoke optional init parts, for example KDE KApplication init
// in KDE backend
GetInitMainSignal()(appname);
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, nullptr);
// Initializing a potential use of EDS early is necessary for
// libsynthesis when compiled with
// --enable-evolution-compatibility: in that mode libical will
// only be found by libsynthesis after EDSAbiWrapperInit()
// pulls it into the process by loading libecal.
EDSAbiWrapperInit();
if (const char *gnutlsdbg = getenv("SYNCEVOLUTION_GNUTLS_DEBUG")) {
// Enable libgnutls debugging without creating a hard dependency on it,
// because we don't call it directly and might not even be linked against
// it. Therefore check for the relevant symbols via dlsym().
void (*set_log_level)(int);
typedef void (*LogFunc_t)(int level, const char *str);
void (*set_log_function)(LogFunc_t func);
set_log_level = (decltype(set_log_level))dlsym(RTLD_DEFAULT, "gnutls_global_set_log_level");
set_log_function = (decltype(set_log_function))dlsym(RTLD_DEFAULT, "gnutls_global_set_log_function");
if (set_log_level && set_log_function) {
set_log_level(atoi(gnutlsdbg));
set_log_function(GnutlsLogFunction);
} else {
SE_LOG_ERROR(NULL, "SYNCEVOLUTION_GNUTLS_DEBUG debugging not possible, log functions not found");
}
}
}
SyncContext::InitMainSignal &SyncContext::GetInitMainSignal()
{
static InitMainSignal initMainSignal;
return initMainSignal;
}
static bool IsStableRelease =
#ifdef SYNCEVOLUTION_STABLE_RELEASE
true
#else
false
#endif
;
bool SyncContext::isStableRelease()
{
return IsStableRelease;
}
void SyncContext::setStableRelease(bool isStableRelease)
{
IsStableRelease = isStableRelease;
}
void SyncContext::checkConfig(const std::string &operation) const
{
std::string peer, context;
splitConfigString(m_server, peer, context);
if (isConfigNeeded() &&
(!exists() || peer.empty())) {
if (peer.empty()) {
SE_LOG_INFO(NULL, "Configuration \"%s\" does not refer to a sync peer.", m_server.c_str());
} else {
SE_LOG_INFO(NULL, "Configuration \"%s\" does not exist.", m_server.c_str());
}
Exception::throwError(SE_HERE, StringPrintf("Cannot proceed with %s without a configuration.", operation.c_str()));
}
}
SyncMLStatus SyncContext::sync(SyncReport *report)
{
SyncMLStatus status = STATUS_OK;
checkConfig("sync");
if (getenv("SYNCEVOLUTION_EPHEMERAL")) {
SE_LOG_INFO(NULL, "turning on ephemeral sync mode as requested by SYNCEVOLUTION_EPHEMERAL variable");
makeEphemeral();
}
// redirect logging as soon as possible
SourceList sourceList(*this, m_doLogging);
sourceList.setLogLevel(m_quiet ? SourceList::LOGGING_QUIET :
getPrintChanges() ? SourceList::LOGGING_FULL :
SourceList::LOGGING_SUMMARY);
// careful about scope, this is needed for writing the
// report below
SyncReport buffer;
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
SwapContext syncSentinel(this);
try {
m_sourceListPtr = &sourceList;
local sync: avoid confusion about what data is changed In local sync the terms "local" and "remote" (in SyncReport, "Data modified locally") do not always apply and can be confusing. Replaced with explicitly mentioning the context. The source name also no longer is unique. Extended in the local sync case (and only in that case) by adding a <context>/ prefix to the source name. Here is an example of the modified output: $ syncevolution google [INFO] @default/itodo20: inactive [INFO] @default/addressbook: inactive [INFO] @default/calendar+todo: inactive [INFO] @default/memo: inactive [INFO] @default/ical20: inactive [INFO] @default/todo: inactive [INFO] @default/file_calendar+todo: inactive [INFO] @default/file_vcard21: inactive [INFO] @default/vcard30: inactive [INFO] @default/text: inactive [INFO] @default/file_itodo20: inactive [INFO] @default/vcard21: inactive [INFO] @default/file_ical20: inactive [INFO] @default/file_vcard30: inactive [INFO] @google/addressbook: inactive [INFO] @google/memo: inactive [INFO] @google/todo: inactive [INFO] @google/calendar: starting normal sync, two-way Local data changes to be applied remotely during synchronization: *** @google/calendar *** after last sync | current data removed since last sync < > added since last sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- [INFO] @google/calendar: sent 1/2 [INFO] @google/calendar: sent 2/2 Local data changes to be applied remotely during synchronization: *** @default/calendar *** no changes [INFO] @default/calendar: started [INFO] @default/calendar: updating "created in Google, online" [INFO] @default/calendar: updating "created in Google - mod2, online" [INFO] @google/calendar: started [INFO] @default/calendar: inactive [INFO] @google/calendar: normal sync done successfully Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @default | @google | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | disabled, 0 KB sent by client, 2 KB received | | item(s) in database backup: 3 before sync, 3 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @default during synchronization: *** @default/calendar *** before sync | after sync removed during sync < > added during sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR VERSION:2.0 VERSION:2.0 ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- pohly@pohly-mobl1:/tmp/syncevolution/src$ Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @google | @default | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | | two-way, 2 KB sent by client, 0 KB received | | item(s) in database backup: 2 before sync, 2 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @google during synchronization: *** @google/calendar *** no changes
2010-10-25 10:34:23 +02:00
string url = getUsedSyncURL();
if (boost::starts_with(url, "local://")) {
initLocalSync(url.substr(strlen("local://")));
}
if (!report) {
report = &buffer;
}
report->clear();
local sync: avoid confusion about what data is changed In local sync the terms "local" and "remote" (in SyncReport, "Data modified locally") do not always apply and can be confusing. Replaced with explicitly mentioning the context. The source name also no longer is unique. Extended in the local sync case (and only in that case) by adding a <context>/ prefix to the source name. Here is an example of the modified output: $ syncevolution google [INFO] @default/itodo20: inactive [INFO] @default/addressbook: inactive [INFO] @default/calendar+todo: inactive [INFO] @default/memo: inactive [INFO] @default/ical20: inactive [INFO] @default/todo: inactive [INFO] @default/file_calendar+todo: inactive [INFO] @default/file_vcard21: inactive [INFO] @default/vcard30: inactive [INFO] @default/text: inactive [INFO] @default/file_itodo20: inactive [INFO] @default/vcard21: inactive [INFO] @default/file_ical20: inactive [INFO] @default/file_vcard30: inactive [INFO] @google/addressbook: inactive [INFO] @google/memo: inactive [INFO] @google/todo: inactive [INFO] @google/calendar: starting normal sync, two-way Local data changes to be applied remotely during synchronization: *** @google/calendar *** after last sync | current data removed since last sync < > added since last sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- [INFO] @google/calendar: sent 1/2 [INFO] @google/calendar: sent 2/2 Local data changes to be applied remotely during synchronization: *** @default/calendar *** no changes [INFO] @default/calendar: started [INFO] @default/calendar: updating "created in Google, online" [INFO] @default/calendar: updating "created in Google - mod2, online" [INFO] @google/calendar: started [INFO] @default/calendar: inactive [INFO] @google/calendar: normal sync done successfully Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @default | @google | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | disabled, 0 KB sent by client, 2 KB received | | item(s) in database backup: 3 before sync, 3 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @default during synchronization: *** @default/calendar *** before sync | after sync removed during sync < > added during sync ------------------------------------------------------------------------------- BEGIN:VCALENDAR BEGIN:VCALENDAR VERSION:2.0 VERSION:2.0 ... END:VCALENDAR END:VCALENDAR ------------------------------------------------------------------------------- pohly@pohly-mobl1:/tmp/syncevolution/src$ Synchronization successful. Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | @google | @default | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | | two-way, 2 KB sent by client, 0 KB received | | item(s) in database backup: 2 before sync, 2 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Mon Oct 25 10:03:24 2010, duration 0:13min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified @google during synchronization: *** @google/calendar *** no changes
2010-10-25 10:34:23 +02:00
if (m_localSync) {
report->setRemoteName(m_localPeerContext);
report->setLocalName(getContextName());
}
// let derived classes override settings, like the log dir
prepare();
// choose log directory
sourceList.startSession(getLogDir(),
getMaxLogDirs(),
getLogLevel(),
report);
/* Must detect server or client session before creating the
* underlying SynthesisEngine
* */
if ( getPeerIsClient()) {
m_serverMode = true;
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
} else if (m_localSync && !m_agent) {
Exception::throwError(SE_HERE, "configuration error, syncURL = local can only be used in combination with peerIsClient = 1");
}
// create a Synthesis engine, used purely for logging purposes
// at this time
SwapEngine swapengine(*this);
initEngine(false);
try {
// dump some summary information at the beginning of the log
SE_LOG_DEV(NULL, "SyncML server account: %s", getSyncUser().toString().c_str());
SE_LOG_DEV(NULL, "client: SyncEvolution %s for %s", getSwv().c_str(), getDevType().c_str());
SE_LOG_DEV(NULL, "device ID: %s", getDevID().c_str());
SE_LOG_DEV(NULL, "%s", EDSAbiWrapperDebug());
SE_LOG_DEV(NULL, "%s", SyncSource::backendsDebug().c_str());
// ensure that config can be modified (might have to be migrated first)
prepareConfigForWrite();
// instantiate backends, but do not open them yet
initSources(sourceList);
if (sourceList.empty()) {
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
Exception::throwError(SE_HERE, "no datastores active, check configuration");
}
// request all config properties once: throwing exceptions
// now is okay, whereas later it would lead to leaks in the
// not exception safe client library
SyncConfig dummy;
set<string> activeSources = sourceList.getSources();
dummy.copy(*this, &activeSources);
// start background thread if not running yet:
// necessary to catch problems with Evolution backend
startLoopThread();
// ask for passwords now
PasswordConfigProperty::checkPasswords(getUserInterfaceNonNull(), *this,
PasswordConfigProperty::CHECK_PASSWORD_ALL,
sourceList.getSourceNames());
// open each source - failing now is still safe
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
// in clients; in servers we wait until the source
// is really needed
auto startSourceAccess = [this] (SyncEvo::SyncSource &source, const char *, const char *) {
if(m_firstSourceAccess) {
syncSuccessStart();
m_firstSourceAccess = false;
}
if (m_serverMode) {
// When using the source as cache, change tracking
// is not required. Disabling it can make item
// changes faster.
SyncMode mode = StringToSyncMode(source.getSync());
if (mode == SYNC_LOCAL_CACHE_SLOW ||
mode == SYNC_LOCAL_CACHE_INCREMENTAL) {
source.setNeedChanges(false);
}
// source is active in sync, now open it
source.open();
}
// database dumping is delayed in both client and server
m_sourceListPtr->syncPrepare(source.getName());
return STATUS_OK;
};
for (SyncSource *source: sourceList) {
if (m_serverMode) {
source->enableServerMode();
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
} else {
source->open();
}
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
// request callback when starting to use source
source->getOperations().m_startDataRead.getPreSignal().connect(startSourceAccess);
}
// ready to go
status = doSync();
} catch (...) {
// handle the exception here while the engine (and logging!) is still alive
Exception::handle(&status);
goto report;
}
} catch (...) {
Exception::handle(&status);
}
report:
if (status == SyncMLStatus(sysync::LOCERR_DATASTORE_ABORT)) {
// this can mean only one thing in SyncEvolution: unexpected slow sync
status = STATUS_UNEXPECTED_SLOW_SYNC;
}
try {
// Print final report before cleaning up.
// Status was okay only if all sources succeeded.
// When a source or the overall sync was successful,
// but some items failed, we report a "partial failure"
// status.
for (SyncSource *source: sourceList) {
m_sourceSyncedSignal(source->getName(), *source);
if (source->getStatus() == STATUS_OK &&
(source->getItemStat(SyncSource::ITEM_LOCAL,
SyncSource::ITEM_ANY,
SyncSource::ITEM_REJECT) ||
source->getItemStat(SyncSource::ITEM_REMOTE,
SyncSource::ITEM_ANY,
SyncSource::ITEM_REJECT))) {
source->recordStatus(STATUS_PARTIAL_FAILURE);
}
if (source->getStatus() != STATUS_OK &&
status == STATUS_OK) {
status = source->getStatus();
break;
}
}
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
// Also take into account result of client side in local sync,
// if any existed. A non-success status code in the client's report
// was already propagated to the parent via a TransportStatusException
// in LocalTransportAgent::checkChildReport(). What we can do here
// is updating the individual's sources status.
if (m_localSync && m_agent && getPeerIsClient()) {
std::shared_ptr<LocalTransportAgent> agent = std::static_pointer_cast<LocalTransportAgent>(m_agent);
SyncReport childReport;
agent->getClientSyncReport(childReport);
for (SyncSource *source: sourceList) {
const SyncSourceReport *childSourceReport = childReport.findSyncSourceReport(source->getURINonEmpty());
if (childSourceReport) {
SyncMLStatus parentSourceStatus = source->getStatus();
SyncMLStatus childSourceStatus = childSourceReport->getStatus();
// child source had an error *and*
// parent error is either unspecific (USERABORT) or
// is a remote error (HTTP error range)
if (childSourceStatus != STATUS_OK && childSourceStatus != STATUS_HTTP_OK &&
(parentSourceStatus == SyncMLStatus(sysync::LOCERR_USERABORT) ||
parentSourceStatus < SyncMLStatus(sysync::LOCAL_STATUS_CODE))) {
source->recordStatus(childSourceStatus);
}
}
}
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
}
sourceList.updateSyncReport(*report);
sourceList.syncDone(status, report);
} catch(...) {
Exception::handle(&status);
}
m_agent.reset();
m_sourceListPtr = nullptr;
return status;
}
bool SyncContext::sendSAN(uint16_t version)
{
sysync::SanPackage san;
bool legacy = version < 12;
/* Should be nonce sent by the server in the preceeding sync session */
string nonce = "SyncEvolution";
UserIdentity id = getSyncUser();
Credentials cred = IdentityProviderCredentials(id, getSyncPassword());
const std::string &user = cred.m_username;
const std::string &password = cred.m_password;
string uauthb64 = san.B64_H(user, password);
/* Client is expected to conduct the sync in the backgroud */
sysync::UI_Mode mode = sysync::UI_not_specified;
uint16_t sessionId = 1;
string serverId = getRemoteIdentifier();
if(serverId.empty()) {
serverId = getDevID();
}
SE_LOG_DEBUG(NULL, "starting SAN %u auth %s nonce %s session %u server %s",
version,
uauthb64.c_str(),
nonce.c_str(),
sessionId,
serverId.c_str());
san.PreparePackage( uauthb64, nonce, version, mode,
sysync::Initiator_Server, sessionId, serverId);
san.CreateEmptyNotificationBody();
bool hasSource = false;
std::set<std::string> dataSources = m_sourceListPtr->getSources();
/* For each virtual datasoruce, generate the SAN accoring to it and ignoring
* sub datasource in the later phase*/
for (std::shared_ptr<VirtualSyncSource> vSource: m_sourceListPtr->getVirtualSources()) {
std::string evoSyncSource = vSource->getDatabaseID();
std::string sync = vSource->getSync();
SANSyncMode mode = AlertSyncMode(StringToSyncMode(sync, true), getPeerIsClient());
std::vector<std::string> mappedSources = unescapeJoinedString (evoSyncSource, ',');
for (std::string source: mappedSources) {
dataSources.erase (source);
if (mode == SA_SLOW) {
// We force a source which the client is not expected to use into slow mode.
// Shouldn't we rather reject attempts to synchronize it?
(*m_sourceListPtr)[source]->setForceSlowSync(true);
}
}
dataSources.insert (vSource->getName());
}
SANSyncMode syncMode = SA_INVALID;
vector<pair <string, string> > alertedSources;
/* For each source to be notified do the following: */
for (string name: dataSources) {
std::shared_ptr<PersistentSyncSourceConfig> sc(getSyncSourceConfig(name));
string sync = sc->getSync();
SANSyncMode mode = AlertSyncMode(StringToSyncMode(sync, true), getPeerIsClient());
if (mode == SA_SLOW) {
(*m_sourceListPtr)[name]->setForceSlowSync(true);
mode = SA_TWO_WAY;
}
if (mode < SA_FIRST || mode > SA_LAST) {
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
SE_LOG_DEV(NULL, "Ignoring datastore %s with an invalid sync mode", name.c_str());
continue;
}
syncMode = mode;
hasSource = true;
string uri = sc->getURINonEmpty();
SourceType sourceType = sc->getSourceType();
/*If the type is not set by user explictly, let's use backend default
* value*/
if(sourceType.m_format.empty()) {
sourceType.m_format = (*m_sourceListPtr)[name]->getPeerMimeType();
}
if (!legacy) {
/*If user did not use force type, we will always use the older type as
* this is what most phones support*/
int contentTypeB = StringToContentType (sourceType.m_format, sourceType.m_forceFormat);
if (contentTypeB == WSPCTC_UNKNOWN) {
contentTypeB = 0;
SE_LOG_DEBUG(NULL, "Unknown datasource mimetype, use 0 as default");
}
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
SE_LOG_DEBUG(NULL, "SAN datastore %s uri %s type %u mode %d",
name.c_str(),
uri.c_str(),
contentTypeB,
mode);
if ( san.AddSync(mode, (uInt32) contentTypeB, uri.c_str())) {
SE_LOG_ERROR(NULL, "SAN: adding server alerted sync element failed");
};
} else {
string mimetype = GetLegacyMIMEType(sourceType.m_format, sourceType.m_forceFormat);
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
SE_LOG_DEBUG(NULL, "SAN datastore %s uri %s type %s",
name.c_str(),
uri.c_str(),
mimetype.c_str());
alertedSources.push_back(std::make_pair(mimetype, uri));
}
}
if (!hasSource) {
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
SE_THROW ("No datastore enabled for server alerted sync!");
}
/* Generate the SAN Package */
void *buffer;
size_t sanSize;
if (!legacy) {
if (san.GetPackage(buffer, sanSize)){
SE_LOG_ERROR(NULL, "SAN package generating failed");
return false;
}
//TODO log the binary SAN content
} else {
SE_LOG_DEBUG(NULL, "SAN with overall sync mode %d", syncMode);
if (san.GetPackageLegacy(buffer, sanSize, alertedSources, syncMode, getWBXML())){
SE_LOG_ERROR(NULL, "SAN package generating failed");
return false;
}
//SE_LOG_DEBUG(NULL, "SAN package content: %s", (char*)buffer);
}
m_agent = createTransportAgent();
SE_LOG_INFO(NULL, "Server sending SAN");
m_serverAlerted = true;
m_agent->setContentType(!legacy ?
TransportAgent::m_contentTypeServerAlertedNotificationDS
: (getWBXML() ? TransportAgent::m_contentTypeSyncWBXML :
TransportAgent::m_contentTypeSyncML));
m_agent->send(reinterpret_cast <char *> (buffer), sanSize);
//change content type
m_agent->setContentType(getWBXML() ? TransportAgent::m_contentTypeSyncWBXML :
TransportAgent::m_contentTypeSyncML);
TransportAgent::Status status;
do {
status = m_agent->wait();
} while(status == TransportAgent::ACTIVE);
if (status == TransportAgent::GOT_REPLY) {
const char *reply;
size_t replyLen;
string contentType;
m_agent->getReply (reply, replyLen, contentType);
//sanity check for the reply
if (contentType.empty() ||
contentType.find(TransportAgent::m_contentTypeSyncML) != contentType.npos ||
contentType.find(TransportAgent::m_contentTypeSyncWBXML) != contentType.npos) {
SharedBuffer request (reply, replyLen);
//TODO should generate more reasonable sessionId here
string sessionId ="";
initServer (sessionId, request, contentType);
return true;
}
}
return false;
}
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
static string Step2String(sysync::uInt16 stepcmd)
{
switch (stepcmd) {
case sysync::STEPCMD_CLIENTSTART: return "STEPCMD_CLIENTSTART";
case sysync::STEPCMD_CLIENTAUTOSTART: return "STEPCMD_CLIENTAUTOSTART";
case sysync::STEPCMD_STEP: return "STEPCMD_STEP";
case sysync::STEPCMD_GOTDATA: return "STEPCMD_GOTDATA";
case sysync::STEPCMD_SENTDATA: return "STEPCMD_SENTDATA";
case sysync::STEPCMD_SUSPEND: return "STEPCMD_SUSPEND";
case sysync::STEPCMD_ABORT: return "STEPCMD_ABORT";
case sysync::STEPCMD_TRANSPFAIL: return "STEPCMD_TRANSPFAIL";
case sysync::STEPCMD_TIMEOUT: return "STEPCMD_TIMEOUT";
case sysync::STEPCMD_SAN_CHECK: return "STEPCMD_SAN_CHECK";
case sysync::STEPCMD_AUTOSYNC_CHECK: return "STEPCMD_AUTOSYNC_CHECK";
case sysync::STEPCMD_OK: return "STEPCMD_OK";
case sysync::STEPCMD_PROGRESS: return "STEPCMD_PROGRESS";
case sysync::STEPCMD_ERROR: return "STEPCMD_ERROR";
case sysync::STEPCMD_SENDDATA: return "STEPCMD_SENDDATA";
case sysync::STEPCMD_NEEDDATA: return "STEPCMD_NEEDDATA";
case sysync::STEPCMD_RESENDDATA: return "STEPCMD_RESENDDATA";
case sysync::STEPCMD_DONE: return "STEPCMD_DONE";
case sysync::STEPCMD_RESTART: return "STEPCMD_RESTART";
case sysync::STEPCMD_NEEDSYNC: return "STEPCMD_NEEDSYNC";
default: return StringPrintf("STEPCMD %d", stepcmd);
}
}
const char *SyncContext::SyncFreezeName(SyncFreeze syncFreeze)
{
switch (syncFreeze) {
case SYNC_FREEZE_NONE: return "none";
case SYNC_FREEZE_RUNNING: return "running";
case SYNC_FREEZE_FROZEN: return "frozen";
}
return "???";
}
bool SyncContext::setFreeze(bool freeze)
{
SyncFreeze newSyncFreeze = freeze ? SYNC_FREEZE_FROZEN : SYNC_FREEZE_RUNNING;
if (m_syncFreeze == SYNC_FREEZE_NONE ||
newSyncFreeze == m_syncFreeze) {
SE_LOG_DEBUG(NULL, "SyncContext::setFreeze(%s): not changing freeze state: %s",
freeze ? "freeze" : "thaw",
SyncFreezeName(m_syncFreeze));
return false;
} else {
SE_LOG_DEBUG(NULL, "SyncContext::setFreeze(%s): changing freeze state: %s -> %s",
freeze ? "freeze" : "thaw",
SyncFreezeName(m_syncFreeze),
SyncFreezeName(newSyncFreeze));
if (m_agent) {
SE_LOG_DEBUG(NULL, "SyncContext::setFreeze(): transport agent");
m_agent->setFreeze(freeze);
}
if (m_sourceListPtr) {
for (SyncSource *source: *m_sourceListPtr) {
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
SE_LOG_DEBUG(NULL, "SyncContext::setFreeze(): datastore %s", source->getDisplayName().c_str());
source->setFreeze(freeze);
}
}
m_syncFreeze = newSyncFreeze;
return true;
}
}
SharedSession *keepSession;
SyncMLStatus SyncContext::doSync()
{
std::shared_ptr<SuspendFlags::Guard> signalGuard;
rewrote signal handling Having the signal handling code in SyncContext created an unnecessary dependency of some classes (in particular the transports) on SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files. Cleaning up when the caller is done with signal handling is now part of the utility class (removed automatically when guard instance is freed). The signal handlers now push one byte for each caught signal into a pipe. That byte tells the rest of the code which message it needs to print, which cannot be done in the signal handlers (because the logging code is not reentrant and thus not safe to call from a signal handler). Compared to the previous solution, this solves several problems: - no more race condition between setting and printing the message - the pipe can be watched in a glib event loop, thus removing the need to poll at regular intervals; polling is still possible (and necessary) in those transports which do not integrate with the event loop (CurlTransport) while it can be removed from others (SoupTransport, OBEXTransport) A boost::signal is emitted when the global SuspendFlags change. Automatic connection management is used to disconnect instances which are managed by boost::shared_ptr. For example, the current transport's cancel() method is called when the state changes to "aborted". The early connection phase of the OBEX transport now also can be aborted (required cleaning up that transport!). Currently watching for aborts via the event loop only works for real Unix signals, but not for "abort" flags set in derived SyncContext instances. The plan is to change that by allowing a "set abort" on SuspendFlags and thus making SyncContext::checkForSuspend/checkForAbort() redundant. The new class is used as follows: - syncevolution command line without daemon uses it to control suspend/abort directly - syncevolution command line as client of syncevo-dbus-server connects to the state change signal and relays it to the syncevo-dbus-server session via D-Bus; now all operations are protected like that, not just syncing - syncevo-dbus-server installs its own handlers for SIGINT and SIGTERM and tries to shut down when either of them is received. SuspendFlags then doesn't activate its own handler. Instead that handler is invoked by the syncevo-dbus-server niam() handler, to suspend or abort a running sync. Once syncs run in a separate process, the syncevo-dbus-server should request that these processes suspend or abort before shutting down itself. - The syncevo-local-sync helper ignores SIGINT after a sync has started. It would receive that signal when forked by syncevolution in non-daemon mode and the user presses CTRL-C. Now the signal is only handled in the parent process, which suspends as part of its own side of the SyncML session and aborts by sending a SIGTERM+SIGINT to syncevo-local-sync. SIGTERM in syncevo-local-sync is handled by SuspendFlags and is meant to abort whatever is going on there at the moment (see below). Aborting long-running operations like import/export or communication via CardDAV or ActiveSync still needs further work. The backends need to check the abort state and return early instead of continuing.
2012-01-19 16:11:22 +01:00
// install signal handlers unless this was explicitly disabled
bool catchSignals = getenv("SYNCEVOLUTION_NO_SYNC_SIGNALS") == nullptr;
rewrote signal handling Having the signal handling code in SyncContext created an unnecessary dependency of some classes (in particular the transports) on SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files. Cleaning up when the caller is done with signal handling is now part of the utility class (removed automatically when guard instance is freed). The signal handlers now push one byte for each caught signal into a pipe. That byte tells the rest of the code which message it needs to print, which cannot be done in the signal handlers (because the logging code is not reentrant and thus not safe to call from a signal handler). Compared to the previous solution, this solves several problems: - no more race condition between setting and printing the message - the pipe can be watched in a glib event loop, thus removing the need to poll at regular intervals; polling is still possible (and necessary) in those transports which do not integrate with the event loop (CurlTransport) while it can be removed from others (SoupTransport, OBEXTransport) A boost::signal is emitted when the global SuspendFlags change. Automatic connection management is used to disconnect instances which are managed by boost::shared_ptr. For example, the current transport's cancel() method is called when the state changes to "aborted". The early connection phase of the OBEX transport now also can be aborted (required cleaning up that transport!). Currently watching for aborts via the event loop only works for real Unix signals, but not for "abort" flags set in derived SyncContext instances. The plan is to change that by allowing a "set abort" on SuspendFlags and thus making SyncContext::checkForSuspend/checkForAbort() redundant. The new class is used as follows: - syncevolution command line without daemon uses it to control suspend/abort directly - syncevolution command line as client of syncevo-dbus-server connects to the state change signal and relays it to the syncevo-dbus-server session via D-Bus; now all operations are protected like that, not just syncing - syncevo-dbus-server installs its own handlers for SIGINT and SIGTERM and tries to shut down when either of them is received. SuspendFlags then doesn't activate its own handler. Instead that handler is invoked by the syncevo-dbus-server niam() handler, to suspend or abort a running sync. Once syncs run in a separate process, the syncevo-dbus-server should request that these processes suspend or abort before shutting down itself. - The syncevo-local-sync helper ignores SIGINT after a sync has started. It would receive that signal when forked by syncevolution in non-daemon mode and the user presses CTRL-C. Now the signal is only handled in the parent process, which suspends as part of its own side of the SyncML session and aborts by sending a SIGTERM+SIGINT to syncevo-local-sync. SIGTERM in syncevo-local-sync is handled by SuspendFlags and is meant to abort whatever is going on there at the moment (see below). Aborting long-running operations like import/export or communication via CardDAV or ActiveSync still needs further work. The backends need to check the abort state and return early instead of continuing.
2012-01-19 16:11:22 +01:00
if (catchSignals) {
SE_LOG_DEBUG(NULL, "sync is starting, catch signals");
rewrote signal handling Having the signal handling code in SyncContext created an unnecessary dependency of some classes (in particular the transports) on SyncContext.h. Now the code is in its own SuspendFlags.cpp/h files. Cleaning up when the caller is done with signal handling is now part of the utility class (removed automatically when guard instance is freed). The signal handlers now push one byte for each caught signal into a pipe. That byte tells the rest of the code which message it needs to print, which cannot be done in the signal handlers (because the logging code is not reentrant and thus not safe to call from a signal handler). Compared to the previous solution, this solves several problems: - no more race condition between setting and printing the message - the pipe can be watched in a glib event loop, thus removing the need to poll at regular intervals; polling is still possible (and necessary) in those transports which do not integrate with the event loop (CurlTransport) while it can be removed from others (SoupTransport, OBEXTransport) A boost::signal is emitted when the global SuspendFlags change. Automatic connection management is used to disconnect instances which are managed by boost::shared_ptr. For example, the current transport's cancel() method is called when the state changes to "aborted". The early connection phase of the OBEX transport now also can be aborted (required cleaning up that transport!). Currently watching for aborts via the event loop only works for real Unix signals, but not for "abort" flags set in derived SyncContext instances. The plan is to change that by allowing a "set abort" on SuspendFlags and thus making SyncContext::checkForSuspend/checkForAbort() redundant. The new class is used as follows: - syncevolution command line without daemon uses it to control suspend/abort directly - syncevolution command line as client of syncevo-dbus-server connects to the state change signal and relays it to the syncevo-dbus-server session via D-Bus; now all operations are protected like that, not just syncing - syncevo-dbus-server installs its own handlers for SIGINT and SIGTERM and tries to shut down when either of them is received. SuspendFlags then doesn't activate its own handler. Instead that handler is invoked by the syncevo-dbus-server niam() handler, to suspend or abort a running sync. Once syncs run in a separate process, the syncevo-dbus-server should request that these processes suspend or abort before shutting down itself. - The syncevo-local-sync helper ignores SIGINT after a sync has started. It would receive that signal when forked by syncevolution in non-daemon mode and the user presses CTRL-C. Now the signal is only handled in the parent process, which suspends as part of its own side of the SyncML session and aborts by sending a SIGTERM+SIGINT to syncevo-local-sync. SIGTERM in syncevo-local-sync is handled by SuspendFlags and is meant to abort whatever is going on there at the moment (see below). Aborting long-running operations like import/export or communication via CardDAV or ActiveSync still needs further work. The backends need to check the abort state and return early instead of continuing.
2012-01-19 16:11:22 +01:00
signalGuard = SuspendFlags::getSuspendFlags().activate();
syncevo-dbus-server + syncevolution: fixed signal handling and D-Bus suspend/abort/shutdown (MB#7555) This patch fixes signal handling and shutdown of syncevo-dbus-server when signals were received. This problems were found in combination with automated tests via test-dbus.py. Signals handled by the signal handlers in the core had no effect on syncevo-dbus-server, which called its own event loop once the suspended or aborted sync returned. Signals received while outside of a sync and outside of an event loop also had no effect. Now signals are always caught by the niam() function in syncevo-dbus-server. It uses the new SyncContext::handleSignal() API to tell the core engine about it and in addition, remembers that syncevo-dbus-server is meant to shut down (suspendRequested). This is then checked by the syncevo-dbus-server event loop (DBusServer::run()) before blocking again. The implementation of Session.Abort() and Session.Suspend() didn't work. isSuspend() and isAbort() mixed up the state. They also did not wake up DBusTransportAgent, so the syncevo-dbus-server might get stuck although an abort was already requested. Partially fixed by adding g_main_loop_quit(). It's not entirely sure whether g_main_loop_quit() is reentrant (see below). There is other code which uses it in signal handlers, but that doesn't mean that this is correct. The right long term solution would be: - avoid entering and leaving the event loop - feed signals into the event loop as normal events SoupTransportAgent solves this by polling for a signal once per second. This also should be improved. Because such code was used in several places for a connection, that common code was moved into Connection::wakeupSession(). Things which went in unnoticed when the original signal handling was merged: - SyncContext::getSuspendFlags() should not allow the caller to modify the SyncContext state. Made the returned SuspendFlags const. - A signal handler may only call re-entrant functions (Stevens, "Advanced Programming in a Unix Environment", 10.6). All printing therefore has to be done outside of the signal handlers. Now the signal handlers just set a message and the regular code checking for abort/suspend prints it. There's a slight race condition (a message might get overwritten before it is printed), but printing just the last message might actually make more sense. There might be a slight delay between receiving the signal and printing now.
2009-10-30 07:25:53 +01:00
}
// From now on it is possible to freeze the sync.
m_syncFreeze = SYNC_FREEZE_RUNNING;
// delay the sync for debugging purposes
SE_LOG_DEBUG(NULL, "ready to sync");
const char *delay = getenv("SYNCEVOLUTION_SYNC_DELAY");
if (delay) {
Sleep(atoi(delay));
}
SuspendFlags &flags = SuspendFlags::getSuspendFlags();
if (!flags.isNormal()) {
return (SyncMLStatus)sysync::LOCERR_USERABORT;
}
SyncMLStatus status = STATUS_OK;
std::string s;
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
if (m_serverMode &&
!m_initialMessage.size() &&
!m_localSync) {
//This is a server alerted sync !
string sanFormat (getSyncMLVersion());
uint16_t version = 12;
if (boost::iequals (sanFormat, "1.2") ||
sanFormat == "") {
version = 12;
} else if (boost::iequals (sanFormat, "1.1")) {
version = 11;
} else {
version = 10;
}
bool status = true;
try {
status = sendSAN (version);
} catch (const TransportException &e) {
if (!sanFormat.empty()){
throw;
}
status = false;
//by pass the exception if we will try again with legacy SANFormat
}
if (!flags.isNormal()) {
return (SyncMLStatus)sysync::LOCERR_USERABORT;
}
if (! status) {
if (sanFormat.empty()) {
SE_LOG_DEBUG(NULL, "Server Alerted Sync init with SANFormat %d failed, trying with legacy format", version);
version = 11;
if (!sendSAN (version)) {
// return a proper error code
Exception::throwError(SE_HERE, "Server Alerted Sync init failed");
}
} else {
// return a proper error code
Exception::throwError(SE_HERE, "Server Alerted Sync init failed");
}
}
}
if (!flags.isNormal()) {
return (SyncMLStatus)sysync::LOCERR_USERABORT;
}
// re-init engine with all sources configured
string xml, configname;
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
initEngine(true);
SharedKey targets;
SharedKey target;
if (m_serverMode) {
// Server engine has no profiles. All settings have be done
// via the XML configuration or function parameters (session ID
// in OpenSession()).
} else {
// check the settings status (MUST BE DONE TO MAKE SETTINGS READY)
SharedKey profiles = m_engine.OpenKeyByPath(SharedKey(), "/profiles");
m_engine.GetStrValue(profiles, "settingsstatus");
// allow creating new settings when existing settings are not up/downgradeable
m_engine.SetStrValue(profiles, "overwrite", "1");
// check status again
m_engine.GetStrValue(profiles, "settingsstatus");
// open first profile
SharedKey profile;
profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_FIRST, true);
if (!profile) {
// no profile exists yet, create default profile
profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_NEW_DEFAULT);
}
if (!m_localSync) {
// Not needed for local sync and might even be
// impossible/wrong because username could refer to an
// identity provider which cannot return a plain string.
SE_LOG_DEBUG(NULL, "copying syncURL, username, password to Synthesis engine");
m_engine.SetStrValue(profile, "serverURI", getUsedSyncURL());
UserIdentity syncUser = getSyncUser();
InitStateString syncPassword = getSyncPassword();
std::shared_ptr<AuthProvider> provider = AuthProvider::create(syncUser, syncPassword);
Credentials cred = provider->getCredentials();
const std::string &user = cred.m_username;
const std::string &password = cred.m_password;
m_engine.SetStrValue(profile, "serverUser", user);
m_engine.SetStrValue(profile, "serverPassword", password);
}
m_engine.SetInt32Value(profile, "encoding",
getWBXML() ? 1 /* WBXML */ : 2 /* XML */);
// Iterate over all data stores in the XML config
// and match them with sync sources.
// TODO: let sync sources provide their own
// XML snippets (inside <client> and inside <datatypes>).
targets = m_engine.OpenKeyByPath(profile, "targets");
for(target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_FIRST, true);
target;
target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_NEXT, true)) {
s = m_engine.GetStrValue(target, "dbname");
SyncSource *source = findSource(s);
if (source) {
m_engine.SetInt32Value(target, "enabled", 1);
int slow = 0;
int direction = 0;
string sync = source->getSync();
// this code only runs when we are the client,
// take that into account for the "from-local/remote" modes
SimpleSyncMode mode = SimplifySyncMode(StringToSyncMode(sync), false);
if (mode == SIMPLE_SYNC_SLOW) {
slow = 1;
direction = 0;
} else if (mode == SIMPLE_SYNC_TWO_WAY) {
slow = 0;
direction = 0;
} else if (mode == SIMPLE_SYNC_REFRESH_FROM_REMOTE) {
slow = 1;
direction = 1;
} else if (mode == SIMPLE_SYNC_REFRESH_FROM_LOCAL) {
slow = 1;
direction = 2;
} else if (mode == SIMPLE_SYNC_ONE_WAY_FROM_REMOTE) {
slow = 0;
direction = 1;
} else if (mode == SIMPLE_SYNC_ONE_WAY_FROM_LOCAL) {
slow = 0;
direction = 2;
} else {
source->throwError(SE_HERE, string("invalid sync mode: ") + sync);
}
m_engine.SetInt32Value(target, "forceslow", slow);
m_engine.SetInt32Value(target, "syncmode", direction);
string uri = source->getURINonEmpty();
m_engine.SetStrValue(target, "remotepath", uri);
} else {
m_engine.SetInt32Value(target, "enabled", 0);
}
}
// Close all keys so that engine can flush the modified config.
// Otherwise the session reads the unmodified values from the
// created files while the updated values are still in memory.
target.reset();
targets.reset();
profile.reset();
profiles.reset();
// reopen profile keys
profiles = m_engine.OpenKeyByPath(SharedKey(), "/profiles");
m_engine.GetStrValue(profiles, "settingsstatus");
profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_FIRST);
targets = m_engine.OpenKeyByPath(profile, "targets");
}
m_retries = 0;
//Create the transport agent if not already created
if(!m_agent) {
m_agent = createTransportAgent();
}
// server in local sync initiates sync by passing data to forked process
if (m_serverMode && m_localSync) {
m_serverAlerted = true;
}
sysync::TEngineProgressInfo progressInfo;
sysync::uInt16 stepCmd =
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
(m_localSync && m_serverMode) ? sysync::STEPCMD_NEEDDATA :
m_serverMode ?
sysync::STEPCMD_GOTDATA :
sysync::STEPCMD_CLIENTSTART;
SharedSession session = m_engine.OpenSession(m_sessionID);
SharedBuffer sendBuffer;
std::unique_ptr<SessionSentinel> sessionSentinel(new SessionSentinel(*this, session));
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
if (m_serverMode && !m_localSync) {
m_engine.WriteSyncMLBuffer(session,
m_initialMessage.get(),
m_initialMessage.size());
SharedKey sessionKey = m_engine.OpenSessionKey(session);
m_engine.SetStrValue(sessionKey,
"contenttype",
m_initialMessageType);
m_initialMessage.reset();
// TODO: set "sendrespuri" session key to control
// whether the generated messages contain a respURI
// (not needed for OBEX)
}
// Special case local sync when nothing changed: we can be sure
// that we can do another sync from exactly the same state (nonce,
// source change tracking meta data, etc.) and be successful
// again. In such a case we can avoid unnecessary updates of the
// .ini and .bfi files.
//
// To detect this, the server side hooks into the SaveAdminData
// operation and replaces it with just returning an "aborted by
// user" error.
if (m_serverMode &&
m_localSync &&
m_sourceListPtr->size() == 1) {
SyncSource *source = *(*m_sourceListPtr).begin();
auto preSaveAdminData = [this] (SyncSource &source, const char *adminData) {
if (!source.getTotalNumItemsReceived() &&
!source.getTotalNumItemsSent() &&
source.getFinalSyncMode() == SYNC_TWO_WAY &&
!source.isFirstSync()) {
SE_LOG_DEBUG(NULL, "requesting end of two-way sync with one source early because nothing changed");
m_quitSync = true;
return STATUS_SYNC_END_SHORTCUT;
} else {
return STATUS_OK;
}
};
source->getOperations().m_saveAdminData.getPreSignal().connect(preSaveAdminData);
}
// Sync main loop: runs until SessionStep() signals end or error.
// Exceptions are caught and lead to a call of SessionStep() with
// parameter STEPCMD_ABORT -> abort session as soon as possible.
bool aborting = false;
int suspending = 0;
Timespec sendStart, resendStart;
int requestNum = 0;
sysync::uInt16 previousStepCmd = stepCmd;
SyncSource: optional support for asynchronous insert/update/delete The wrapper around the actual operation checks if the operation returned an error or result code (traditional behavior). If not, it expects a ContinueOperation instance, remembers it and calls it when the same operation gets called again for the same item. For add/insert, "same item" is detected based on the KeyH address, which must not change. For delete, the item local ID is used. Pre- and post-signals are called exactly once, before the first call and after the last call of the item. ContinueOperation is a simple boost::function pointer for now. The Synthesis engine itself is not able to force completion of the operation, it just polls. This can lead to many empty messages with just an Alert inside, thus triggering the "endless loop" protection, which aborts the sync. We overcome this limitation in the SyncEvolution layer above the Synthesis engine: first, we flush pending operations before starting network IO. This is a good place to batch together all pending operations. Second, to overcome the "endless loop" problem, we force a waiting for completion if the last message already was empty. If that happened, we are done with items and should start sending our responses. Binding a function which returns the traditional TSyError still works because it gets copied transparently into the boost::variant that the wrapper expects, so no other code in SyncSource or backends needs to be adapted. Enabling the use of LOCERR_AGAIN in the utility classes and backends will follow in the next patches.
2013-06-05 17:22:00 +02:00
std::vector<int> numItemsReceived; // source->getTotalNumItemsReceived() for each source, see STEPCMD_SENDDATA
m_quitSync = false;
do {
try {
if (m_quitSync &&
!m_serverMode) {
SE_LOG_DEBUG(NULL, "ending sync early as requested");
// Intentionally prevent destructing the Synthesis
// engine and session destruction by keeping a
// reference to it around forever, because destroying
// the session would cause undesired disk writes.
keepSession = new SharedSession(session);
break;
}
// check for suspend, if so, modify step command for next step
// Since the suspend will actually be committed until it is
// sending out a message, we can safely delay the suspend to
// GOTDATA state.
// After exception occurs, stepCmd will be set to abort to force
// aborting, must avoid to change it back to suspend cmd.
if (flags.isSuspended() && stepCmd == sysync::STEPCMD_GOTDATA) {
SE_LOG_DEBUG(NULL, "suspending before SessionStep() in STEPCMD_GOTDATA as requested by user");
2009-03-11 12:59:25 +01:00
stepCmd = sysync::STEPCMD_SUSPEND;
}
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
// Aborting is useful while waiting for a reply and before
// sending a message (which will just lead to us waiting
// for the reply, but possibly after doing some slow network
// IO for setting up the message send).
//
// While processing a message we let the engine run, because
// that is a) likely to be done soon and b) may reduce the
// breakage caused by aborting a running sync.
//
// This check here covers the "waiting for reply" case.
if ((stepCmd == sysync::STEPCMD_RESENDDATA ||
stepCmd == sysync::STEPCMD_SENTDATA ||
stepCmd == sysync::STEPCMD_NEEDDATA) &&
flags.isAborted()) {
SE_LOG_DEBUG(NULL, "aborting before SessionStep() in %s as requested by script",
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
Step2String(stepCmd).c_str());
2009-03-11 12:59:25 +01:00
stepCmd = sysync::STEPCMD_ABORT;
}
// take next step, but don't abort twice: instead
// let engine contine with its shutdown
if (stepCmd == sysync::STEPCMD_ABORT) {
if (aborting) {
SE_LOG_DEBUG(NULL, "engine already notified of abort request, reverting to %s",
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
Step2String(previousStepCmd).c_str());
stepCmd = previousStepCmd;
} else {
aborting = true;
}
}
2009-03-11 12:59:25 +01:00
// same for suspending
if (stepCmd == sysync::STEPCMD_SUSPEND) {
if (suspending) {
SE_LOG_DEBUG(NULL, "engine already notified of suspend request, reverting to %s",
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
Step2String(previousStepCmd).c_str());
2009-03-11 12:59:25 +01:00
stepCmd = previousStepCmd;
suspending++;
2009-03-11 12:59:25 +01:00
} else {
suspending++;
2009-03-11 12:59:25 +01:00
}
}
// Need to wait for setFrozen(false) or suspend/abort request.
// Such a call can come in via a D-Bus interface that we keep
// active by servicing the event loop inside GRunWhile().
//
// We freeze without notifying our peer. It will freeze itself
// eventually because we stop exchanging SyncML messages.
if (!aborting && !suspending && m_syncFreeze == SYNC_FREEZE_FROZEN) {
SE_LOG_DEBUG(NULL, "freezing sync");
GRunWhile([this, &flags] () { return this->m_syncFreeze == SYNC_FREEZE_FROZEN && flags.isNormal(); });
}
if (stepCmd == sysync::STEPCMD_NEEDDATA) {
// Engine already notified. Don't call it twice
// with this state, because it doesn't know how
// to handle this. Skip the SessionStep() call
// and wait for response.
} else {
if (getLogLevel() > 4) {
SE_LOG_DEBUG(NULL, "before SessionStep: %s", Step2String(stepCmd).c_str());
}
m_engine.SessionStep(session, stepCmd, &progressInfo);
if (getLogLevel() > 4) {
SE_LOG_DEBUG(NULL, "after SessionStep: %s", Step2String(stepCmd).c_str());
}
reportStepCmd(stepCmd);
}
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
if (stepCmd == sysync::STEPCMD_SENDDATA &&
checkForScriptAbort(session)) {
SE_LOG_DEBUG(NULL, "aborting after SessionStep() in STEPCMD_SENDDATA as requested by script");
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
// Catch outgoing message and abort if requested by script.
// Report which sources are affected, based on their status code.
set<string> sources;
for (SyncSource *source: *m_sourceListPtr) {
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
if (source->getStatus() == STATUS_UNEXPECTED_SLOW_SYNC) {
string name = source->getVirtualSource();
if (name.empty()) {
name = source->getName();
}
sources.insert(name);
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
}
}
string explanation = SyncReport::slowSyncExplanation(m_server,
sources);
if (!explanation.empty()) {
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
string sourceparam = boost::join(sources, " ");
SE_LOG_ERROR(NULL,
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
"Aborting because of unexpected slow sync for datastore(s): %s",
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
sourceparam.c_str());
SE_LOG_INFO(NULL, "%s", explanation.c_str());
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
} else {
// we should not get here, but if we do, at least log something
SE_LOG_ERROR(NULL, "aborting as requested by script");
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
}
stepCmd = sysync::STEPCMD_ABORT;
continue;
} else if (stepCmd == sysync::STEPCMD_SENDDATA &&
flags.isAborted()) {
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
// Catch outgoing message and abort if requested by user.
SE_LOG_DEBUG(NULL, "aborting after SessionStep() in STEPCMD_SENDDATA as requested by user");
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
stepCmd = sysync::STEPCMD_ABORT;
continue;
} else if (suspending == 1) {
//During suspention we actually insert a STEPCMD_SUSPEND cmd
//Should restore to the original step here
stepCmd = previousStepCmd;
continue;
}
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
switch (stepCmd) {
case sysync::STEPCMD_OK:
// no progress info, call step again
stepCmd = sysync::STEPCMD_STEP;
break;
case sysync::STEPCMD_PROGRESS:
// new progress info to show
// Check special case of interactive display alert
if (progressInfo.eventtype == sysync::PEV_DISPLAY100) {
// alert 100 received from remote, message text is in
// SessionKey's "displayalert" field
SharedKey sessionKey = m_engine.OpenSessionKey(session);
// get message from server to display
s = m_engine.GetStrValue(sessionKey,
"displayalert");
displayServerMessage(s);
} else {
switch (progressInfo.targetID) {
case sysync::KEYVAL_ID_UNKNOWN:
case 0 /* used with PEV_SESSIONSTART?! */:
displaySyncProgress(sysync::TProgressEventEnum(progressInfo.eventtype),
progressInfo.extra1,
progressInfo.extra2,
progressInfo.extra3);
if (progressInfo.eventtype == sysync::PEV_SESSIONEND &&
!status) {
// remember sync result
status = SyncMLStatus(progressInfo.extra1);
}
break;
default: {
// specific for a certain sync source:
// find it...
SyncSource *source = m_sourceListPtr->lookupBySynthesisID(progressInfo.targetID);
if (source) {
displaySourceProgress(*source,
SyncSourceEvent(sysync::TProgressEventEnum(progressInfo.eventtype),
progressInfo.extra1,
progressInfo.extra2,
progressInfo.extra3),
false);
} else {
Exception::throwError(SE_HERE, std::string("unknown target ") + s);
}
target.reset();
break;
}
}
}
stepCmd = sysync::STEPCMD_STEP;
break;
case sysync::STEPCMD_ERROR:
// error, terminate (should not happen, as status is
// already checked above)
break;
case sysync::STEPCMD_RESTART:
// make sure connection is closed and will be re-opened for next request
// tbd: close communication channel if still open to make sure it is
// re-opened for the next request
stepCmd = sysync::STEPCMD_STEP;
m_retries = 0;
break;
case sysync::STEPCMD_SENDDATA: {
SyncSource: optional support for asynchronous insert/update/delete The wrapper around the actual operation checks if the operation returned an error or result code (traditional behavior). If not, it expects a ContinueOperation instance, remembers it and calls it when the same operation gets called again for the same item. For add/insert, "same item" is detected based on the KeyH address, which must not change. For delete, the item local ID is used. Pre- and post-signals are called exactly once, before the first call and after the last call of the item. ContinueOperation is a simple boost::function pointer for now. The Synthesis engine itself is not able to force completion of the operation, it just polls. This can lead to many empty messages with just an Alert inside, thus triggering the "endless loop" protection, which aborts the sync. We overcome this limitation in the SyncEvolution layer above the Synthesis engine: first, we flush pending operations before starting network IO. This is a good place to batch together all pending operations. Second, to overcome the "endless loop" problem, we force a waiting for completion if the last message already was empty. If that happened, we are done with items and should start sending our responses. Binding a function which returns the traditional TSyError still works because it gets copied transparently into the boost::variant that the wrapper expects, so no other code in SyncSource or backends needs to be adapted. Enabling the use of LOCERR_AGAIN in the utility classes and backends will follow in the next patches.
2013-06-05 17:22:00 +02:00
// We'll be busy for a while with network IO, so give
// sources a chance to do some work in parallel and
// flush pending progress notifications.
SyncSource: optional support for asynchronous insert/update/delete The wrapper around the actual operation checks if the operation returned an error or result code (traditional behavior). If not, it expects a ContinueOperation instance, remembers it and calls it when the same operation gets called again for the same item. For add/insert, "same item" is detected based on the KeyH address, which must not change. For delete, the item local ID is used. Pre- and post-signals are called exactly once, before the first call and after the last call of the item. ContinueOperation is a simple boost::function pointer for now. The Synthesis engine itself is not able to force completion of the operation, it just polls. This can lead to many empty messages with just an Alert inside, thus triggering the "endless loop" protection, which aborts the sync. We overcome this limitation in the SyncEvolution layer above the Synthesis engine: first, we flush pending operations before starting network IO. This is a good place to batch together all pending operations. Second, to overcome the "endless loop" problem, we force a waiting for completion if the last message already was empty. If that happened, we are done with items and should start sending our responses. Binding a function which returns the traditional TSyError still works because it gets copied transparently into the boost::variant that the wrapper expects, so no other code in SyncSource or backends needs to be adapted. Enabling the use of LOCERR_AGAIN in the utility classes and backends will follow in the next patches.
2013-06-05 17:22:00 +02:00
if (m_sourceListPtr) {
bool needResults = true;
if (numItemsReceived.size() < m_sourceListPtr->size()) {
numItemsReceived.insert(numItemsReceived.end(),
m_sourceListPtr->size() - numItemsReceived.size(),
0);
}
for (size_t i = 0; i < numItemsReceived.size(); i++) {
SyncSource *source = (*m_sourceListPtr->getSourceSet())[i];
int received = source->getTotalNumItemsReceived();
SE_LOG_DEBUG(source->getDisplayName(), "total number of items received %d",
received);
if (numItemsReceived[i] != received) {
numItemsReceived[i] = received;
needResults = false;
}
}
for (SyncSource *source: *m_sourceListPtr) {
source->flushItemChanges();
SyncSource: optional support for asynchronous insert/update/delete The wrapper around the actual operation checks if the operation returned an error or result code (traditional behavior). If not, it expects a ContinueOperation instance, remembers it and calls it when the same operation gets called again for the same item. For add/insert, "same item" is detected based on the KeyH address, which must not change. For delete, the item local ID is used. Pre- and post-signals are called exactly once, before the first call and after the last call of the item. ContinueOperation is a simple boost::function pointer for now. The Synthesis engine itself is not able to force completion of the operation, it just polls. This can lead to many empty messages with just an Alert inside, thus triggering the "endless loop" protection, which aborts the sync. We overcome this limitation in the SyncEvolution layer above the Synthesis engine: first, we flush pending operations before starting network IO. This is a good place to batch together all pending operations. Second, to overcome the "endless loop" problem, we force a waiting for completion if the last message already was empty. If that happened, we are done with items and should start sending our responses. Binding a function which returns the traditional TSyError still works because it gets copied transparently into the boost::variant that the wrapper expects, so no other code in SyncSource or backends needs to be adapted. Enabling the use of LOCERR_AGAIN in the utility classes and backends will follow in the next patches.
2013-06-05 17:22:00 +02:00
if (needResults) {
source->finishItemChanges();
SyncSource: optional support for asynchronous insert/update/delete The wrapper around the actual operation checks if the operation returned an error or result code (traditional behavior). If not, it expects a ContinueOperation instance, remembers it and calls it when the same operation gets called again for the same item. For add/insert, "same item" is detected based on the KeyH address, which must not change. For delete, the item local ID is used. Pre- and post-signals are called exactly once, before the first call and after the last call of the item. ContinueOperation is a simple boost::function pointer for now. The Synthesis engine itself is not able to force completion of the operation, it just polls. This can lead to many empty messages with just an Alert inside, thus triggering the "endless loop" protection, which aborts the sync. We overcome this limitation in the SyncEvolution layer above the Synthesis engine: first, we flush pending operations before starting network IO. This is a good place to batch together all pending operations. Second, to overcome the "endless loop" problem, we force a waiting for completion if the last message already was empty. If that happened, we are done with items and should start sending our responses. Binding a function which returns the traditional TSyError still works because it gets copied transparently into the boost::variant that the wrapper expects, so no other code in SyncSource or backends needs to be adapted. Enabling the use of LOCERR_AGAIN in the utility classes and backends will follow in the next patches.
2013-06-05 17:22:00 +02:00
}
displaySourceProgress(*source, SyncSourceEvent(), false);
SyncSource: optional support for asynchronous insert/update/delete The wrapper around the actual operation checks if the operation returned an error or result code (traditional behavior). If not, it expects a ContinueOperation instance, remembers it and calls it when the same operation gets called again for the same item. For add/insert, "same item" is detected based on the KeyH address, which must not change. For delete, the item local ID is used. Pre- and post-signals are called exactly once, before the first call and after the last call of the item. ContinueOperation is a simple boost::function pointer for now. The Synthesis engine itself is not able to force completion of the operation, it just polls. This can lead to many empty messages with just an Alert inside, thus triggering the "endless loop" protection, which aborts the sync. We overcome this limitation in the SyncEvolution layer above the Synthesis engine: first, we flush pending operations before starting network IO. This is a good place to batch together all pending operations. Second, to overcome the "endless loop" problem, we force a waiting for completion if the last message already was empty. If that happened, we are done with items and should start sending our responses. Binding a function which returns the traditional TSyError still works because it gets copied transparently into the boost::variant that the wrapper expects, so no other code in SyncSource or backends needs to be adapted. Enabling the use of LOCERR_AGAIN in the utility classes and backends will follow in the next patches.
2013-06-05 17:22:00 +02:00
}
}
// send data to remote
SharedKey sessionKey = m_engine.OpenSessionKey(session);
if (m_serverMode) {
m_agent->setURL("");
} else {
// use OpenSessionKey() and GetValue() to retrieve "connectURI"
// and "contenttype" to be used to send data to the server
s = m_engine.GetStrValue(sessionKey,
"connectURI");
m_agent->setURL(s);
}
s = m_engine.GetStrValue(sessionKey,
"contenttype");
m_agent->setContentType(s);
sessionKey.reset();
sendStart = resendStart = Timespec::monotonic();
requestNum ++;
// use GetSyncMLBuffer()/RetSyncMLBuffer() to access the data to be
// sent or have it copied into caller's buffer using
// ReadSyncMLBuffer(), then send it to the server
sendBuffer = m_engine.GetSyncMLBuffer(session, true);
if (m_serverMode && m_quitSync) {
// When aborting prematurely, skip the server's
// last reply message and instead tell the client
// to quit.
m_agent->setContentType("quitsync");
m_agent->send(nullptr, 0);
} else {
m_agent->send(sendBuffer.get(), sendBuffer.size());
}
stepCmd = sysync::STEPCMD_SENTDATA; // we have sent the data
break;
}
case sysync::STEPCMD_RESENDDATA: {
SE_LOG_INFO(NULL, "resend previous message, retry #%d", m_retries);
resendStart = Timespec::monotonic();
/* We are resending previous message, just read from the
* previous buffer */
m_agent->send(sendBuffer.get(), sendBuffer.size());
stepCmd = sysync::STEPCMD_SENTDATA; // we have sent the data
break;
}
case sysync::STEPCMD_NEEDDATA:
if (!sendStart) {
// no message sent yet, record start of wait for data
sendStart = Timespec::monotonic();
}
switch (m_agent->wait()) {
case TransportAgent::ACTIVE:
// Still sending the data?! Don't change anything,
// skip SessionStep() above.
break;
case TransportAgent::TIME_OUT: {
double duration = (Timespec::monotonic() - sendStart).duration();
// HTTP SyncML servers cannot resend a HTTP POST
// reply. Other server transports could in theory
// resend, but don't have the necessary D-Bus APIs
// (MB #6370).
// Same if() as below for FAILED.
if (m_serverMode ||
!m_retryInterval || duration + 0.9 >= m_retryDuration || requestNum == 1) {
SE_LOG_INFO(NULL,
"Transport giving up after %d retries and %ld:%02ldmin",
m_retries,
(long)duration / 60,
(long)duration % 60);
SE_THROW_EXCEPTION(TransportException, "timeout, retry period exceeded");
}else {
// Timeout must have been due to retryInterval having passed, resend
// immediately.
m_retries ++;
stepCmd = sysync::STEPCMD_RESENDDATA;
}
break;
}
case TransportAgent::GOT_REPLY: {
const char *reply;
size_t replylen;
string contentType;
m_agent->getReply(reply, replylen, contentType);
// sanity check for reply: if known at all, it must be either XML or WBXML
if (contentType.empty() ||
contentType.find("application/vnd.syncml+wbxml") != contentType.npos ||
contentType.find("application/vnd.syncml+xml") != contentType.npos) {
// put answer received earlier into SyncML engine's buffer
m_retries = 0;
sendBuffer.reset();
m_engine.WriteSyncMLBuffer(session,
reply,
replylen);
if (m_serverMode) {
SharedKey sessionKey = m_engine.OpenSessionKey(session);
m_engine.SetStrValue(sessionKey,
"contenttype",
contentType);
}
stepCmd = sysync::STEPCMD_GOTDATA; // we have received response data
break;
} else if (contentType == "quitsync") {
SE_LOG_DEBUG(NULL, "server is asking us to quit the sync session");
// Fake "done" events for each active source.
for (SyncSource *source: *m_sourceListPtr) {
if (source->getFinalSyncMode() != SYNC_NONE) {
displaySourceProgress(*source,
SyncSourceEvent(sysync::PEV_SYNCEND, 0, 0, 0),
true);
}
}
m_quitSync = true;
break;
} else {
SE_LOG_DEBUG(NULL, "unexpected content type '%s' in reply, %d bytes:\n%.*s",
contentType.c_str(), (int)replylen, (int)replylen, reply);
SE_LOG_ERROR(NULL, "unexpected reply from peer; might be a temporary problem, try again later");
} //fall through to network failure case
}
/* If this is a network error, it usually failed quickly, retry
* immediately has likely no effect. Manually sleep here to wait a while
* before retry. Sleep time will be calculated so that the
* message sending interval equals m_retryInterval.
*/
case TransportAgent::FAILED: {
// Send might have failed because of abort or
// suspend request.
if (flags.isSuspended()) {
SE_LOG_DEBUG(NULL, "suspending after TransportAgent::FAILED as requested by user");
stepCmd = sysync::STEPCMD_SUSPEND;
break;
} else if (flags.isAborted()) {
SE_LOG_DEBUG(NULL, "aborting after TransportAgent::FAILED as requested by user");
stepCmd = sysync::STEPCMD_ABORT;
break;
}
Timespec curTime = Timespec::monotonic();
double duration = (curTime - sendStart).duration();
double resendDelay = m_retryInterval - (curTime - resendStart).duration();
if (resendDelay < 0) {
resendDelay = 0;
}
// Similar if() as above for TIME_OUT. In addition, we must check that
// the next resend won't happen after the retryDuration, because then
// we might as well give up now immediately. Include some fuzz factor
// in case we woke up slightly too early.
if (m_serverMode ||
!m_retryInterval || duration + resendDelay + 0.9 >= m_retryDuration || requestNum == 1) {
SE_LOG_INFO(NULL,
"Transport giving up after %d retries and %ld:%02ldmin",
m_retries,
(long)duration / 60,
(long)duration % 60);
SE_THROW_EXCEPTION(TransportException, "transport failed, retry period exceeded");
} else {
// Resend after having ensured that the retryInterval is over.
if (resendDelay > 0) {
if (Sleep(resendDelay) > 0) {
if (flags.isSuspended()) {
SE_LOG_DEBUG(NULL, "suspending after premature exit from sleep() caused by user suspend");
stepCmd = sysync::STEPCMD_SUSPEND;
} else {
SE_LOG_DEBUG(NULL, "aborting after premature exit from sleep() caused by user abort");
stepCmd = sysync::STEPCMD_ABORT;
}
break;
}
}
m_retries ++;
stepCmd = sysync::STEPCMD_RESENDDATA;
}
break;
}
local sync: kill syncevo-local-sync with SIGTERM Shutting down syncevo-local-sync in a timely manner when aborting is hard: the process might be stuck in a blocking call which cannot be made to check the abort request (blocking libneon, activesyncd client library, ...). The best that can be done is to let the process be killed by the SIGTERM. To have some trace of that, catch the signal and log the signal; there's a slight risk that the logging system is in an inconsistent state, but overall that risk is minor. Because syncevo-local-sync catches SIGINT, ForkExec::stop() must send SIGTERM in addition to SIGINT. To suppress redundant and misleading ERROR messages when the bad child status is handled, the ForkExecParent remembers that itself asked the child to stop and only treats unexpected "killed by signal" results as error. The local transport must call that stop() in its cancel(). It enters the "canceled" state which prevents all further communication with the child, in particular waiting for the child sync report; doing that would produce another redundant error message about "child exited without sending report". Calling stop() in the local transport's shutdown() is no longer possible, because it would kill the child right away. Before it simply had no effect, because SIGINT was ignored. This points towards an unsolved problem: how long should the parent wait for the child after the sync is done? If the child gets stuck hard after sending its last message, the parent currently waits forever until the user aborts. In the sync event loop the caller of the transport must recognize CANCELED as something which might be desired and thus should not be logged as ERROR. That way the Synthesis engine is called one more time with STEPCMD_ABORT also in those cases where the transport itself detected the abort request first.
2012-01-20 15:28:54 +01:00
case TransportAgent::CANCELED:
// Send might have failed because of abort or
// suspend request.
if (flags.isSuspended()) {
SE_LOG_DEBUG(NULL, "suspending after TransportAgent::CANCELED as requested by user");
local sync: kill syncevo-local-sync with SIGTERM Shutting down syncevo-local-sync in a timely manner when aborting is hard: the process might be stuck in a blocking call which cannot be made to check the abort request (blocking libneon, activesyncd client library, ...). The best that can be done is to let the process be killed by the SIGTERM. To have some trace of that, catch the signal and log the signal; there's a slight risk that the logging system is in an inconsistent state, but overall that risk is minor. Because syncevo-local-sync catches SIGINT, ForkExec::stop() must send SIGTERM in addition to SIGINT. To suppress redundant and misleading ERROR messages when the bad child status is handled, the ForkExecParent remembers that itself asked the child to stop and only treats unexpected "killed by signal" results as error. The local transport must call that stop() in its cancel(). It enters the "canceled" state which prevents all further communication with the child, in particular waiting for the child sync report; doing that would produce another redundant error message about "child exited without sending report". Calling stop() in the local transport's shutdown() is no longer possible, because it would kill the child right away. Before it simply had no effect, because SIGINT was ignored. This points towards an unsolved problem: how long should the parent wait for the child after the sync is done? If the child gets stuck hard after sending its last message, the parent currently waits forever until the user aborts. In the sync event loop the caller of the transport must recognize CANCELED as something which might be desired and thus should not be logged as ERROR. That way the Synthesis engine is called one more time with STEPCMD_ABORT also in those cases where the transport itself detected the abort request first.
2012-01-20 15:28:54 +01:00
stepCmd = sysync::STEPCMD_SUSPEND;
break;
} else if (flags.isAborted()) {
SE_LOG_DEBUG(NULL, "aborting after TransportAgent::CANCELED as requested by user");
local sync: kill syncevo-local-sync with SIGTERM Shutting down syncevo-local-sync in a timely manner when aborting is hard: the process might be stuck in a blocking call which cannot be made to check the abort request (blocking libneon, activesyncd client library, ...). The best that can be done is to let the process be killed by the SIGTERM. To have some trace of that, catch the signal and log the signal; there's a slight risk that the logging system is in an inconsistent state, but overall that risk is minor. Because syncevo-local-sync catches SIGINT, ForkExec::stop() must send SIGTERM in addition to SIGINT. To suppress redundant and misleading ERROR messages when the bad child status is handled, the ForkExecParent remembers that itself asked the child to stop and only treats unexpected "killed by signal" results as error. The local transport must call that stop() in its cancel(). It enters the "canceled" state which prevents all further communication with the child, in particular waiting for the child sync report; doing that would produce another redundant error message about "child exited without sending report". Calling stop() in the local transport's shutdown() is no longer possible, because it would kill the child right away. Before it simply had no effect, because SIGINT was ignored. This points towards an unsolved problem: how long should the parent wait for the child after the sync is done? If the child gets stuck hard after sending its last message, the parent currently waits forever until the user aborts. In the sync event loop the caller of the transport must recognize CANCELED as something which might be desired and thus should not be logged as ERROR. That way the Synthesis engine is called one more time with STEPCMD_ABORT also in those cases where the transport itself detected the abort request first.
2012-01-20 15:28:54 +01:00
stepCmd = sysync::STEPCMD_ABORT;
break;
}
// not sure exactly why it is canceled
SE_THROW_EXCEPTION_STATUS(BadSynthesisResult,
"transport canceled",
sysync::LOCERR_USERABORT);
break;
default:
stepCmd = sysync::STEPCMD_TRANSPFAIL; // communication with server failed
break;
}
}
// Don't tell engine to abort when it already did.
if (aborting && stepCmd == sysync::STEPCMD_ABORT) {
stepCmd = sysync::STEPCMD_DONE;
}
2009-03-11 12:59:25 +01:00
previousStepCmd = stepCmd;
// loop until session done or aborted with error
} catch (const BadSynthesisResult &result) {
if (result.result() == sysync::LOCERR_USERABORT && aborting) {
SE_LOG_INFO(NULL, "Aborted as requested.");
stepCmd = sysync::STEPCMD_DONE;
} else if (result.result() == sysync::LOCERR_USERSUSPEND && suspending) {
SE_LOG_INFO(NULL, "Suspended as requested.");
stepCmd = sysync::STEPCMD_DONE;
} else if (aborting) {
// aborting very early can lead to results different from LOCERR_USERABORT
// => don't treat this as error
SE_LOG_INFO(NULL, "Aborted with unexpected result (%d)",
static_cast<int>(result.result()));
stepCmd = sysync::STEPCMD_DONE;
} else {
Exception::handle(&status);
SE_LOG_DEBUG(NULL, "aborting after catching fatal error");
// Don't tell engine to abort when it already did.
stepCmd = aborting ? sysync::STEPCMD_DONE : sysync::STEPCMD_ABORT;
}
} catch (...) {
Exception::handle(&status);
SE_LOG_DEBUG(NULL, "aborting after catching fatal error");
// Don't tell engine to abort when it already did.
stepCmd = aborting ? sysync::STEPCMD_DONE : sysync::STEPCMD_ABORT;
}
} while (stepCmd != sysync::STEPCMD_DONE && stepCmd != sysync::STEPCMD_ERROR);
// If we get here without error, then close down connection normally.
// Otherwise destruct the agent without further communication.
if (!status && !flags.isAborted()) {
try {
m_agent->shutdown();
// TODO: implement timeout for peers which fail to respond
while (!flags.isAborted() &&
m_agent->wait(true) == TransportAgent::ACTIVE) {
// TODO: allow aborting the sync here
}
} catch (...) {
status = handleException();
}
}
// Let session shut down before auto-destructing anything else
// (like our signal blocker). This may take a while, because it
// may involve shutting down the helper background thread which
// opened our local datastore.
SE_LOG_DEBUG(NULL, "closing session");
// setFreeze() no longer has an effect and returns false from now on.
m_syncFreeze = SYNC_FREEZE_NONE;
m_initialMessage.reset();
sessionSentinel.reset();
sendBuffer.reset();
session.reset();
SE_LOG_DEBUG(NULL, "session closed");
return status;
}
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
string SyncContext::getSynthesisDatadir()
{
if (isEphemeral() && m_sourceListPtr) {
// Suppress writing in libsynthesis binfile client.
return "/dev/null";
} else if (m_localSync && !m_serverMode) {
support local sync (BMC #712) Local sync is configured with a new syncURL = local://<context> where <context> identifies the set of databases to synchronize with. The URI of each source in the config identifies the source in that context to synchronize with. The databases in that context run a SyncML session as client. The config itself is for a server. Reversing these roles is possible by putting the config into the other context. A sync is started by the server side, via the new LocalTransportAgent. That agent forks, sets up the client side, then passes messages back and forth via stream sockets. Stream sockets are useful because unexpected peer shutdown can be detected. Running the server side requires a few changes: - do not send a SAN message, the client will start the message exchange based on the config - wait for that message before doing anything The client side is more difficult: - Per-peer config nodes do not exist in the target context. They are stored in a hidden .<context> directory inside the server config tree. This depends on the new "registering nodes in the tree" feature. All nodes are hidden, because users are not meant to edit any of them. Their name is intentionally chosen like traditional nodes so that removing the config also removes the new files. - All relevant per-peer properties must be copied from the server config (log level, printing changes, ...); they cannot be set differently. Because two separate SyncML sessions are used, we end up with two normal session directories and log files. The implementation is not complete yet: - no glib support, so cannot be used in syncevo-dbus-server - no support for CTRL-C and abort - no interactive password entry for target sources - unexpected slow syncs are detected on the client side, but not reported properly on the server side
2010-07-31 18:28:53 +02:00
return m_localClientRootPath + "/.synthesis";
} else {
return getRootPath() + "/.synthesis";
}
}
SyncMLStatus SyncContext::handleException()
{
SyncMLStatus res = Exception::handle();
return res;
}
void SyncContext::status()
{
checkConfig("status check");
SourceList sourceList(*this, false);
initSources(sourceList);
PasswordConfigProperty::checkPasswords(getUserInterfaceNonNull(), *this,
// Don't need sync passwords.
PasswordConfigProperty::CHECK_PASSWORD_ALL & ~PasswordConfigProperty::CHECK_PASSWORD_SYNC,
sourceList.getSourceNames());
for (SyncSource *source: sourceList) {
source->open();
}
SyncReport changes;
checkSourceChanges(sourceList, changes);
stringstream out;
changes.prettyPrint(out,
SyncReport::WITHOUT_SERVER|
SyncReport::WITHOUT_CONFLICTS|
SyncReport::WITHOUT_REJECTS|
SyncReport::WITH_TOTAL);
SE_LOG_INFO(NULL, "Local item changes:\n%s",
out.str().c_str());
sourceList.accessSession(getLogDir());
Logger::instance().setLevel(Logger::INFO);
string prevLogdir = sourceList.getPrevLogdir();
bool found = access(prevLogdir.c_str(), R_OK|X_OK) == 0;
if (found) {
if (!m_quiet && getPrintChanges()) {
try {
sourceList.setPath(prevLogdir);
sourceList.dumpDatabases("current", nullptr);
sourceList.dumpLocalChanges("", "after", "current", "");
} catch(...) {
Exception::handle();
}
}
} else {
SE_LOG_SHOW(NULL, "Previous log directory not found.");
if (getLogDir().empty()) {
SE_LOG_SHOW(NULL, "Enable the 'logdir' option and synchronize to use this feature.");
}
}
}
void SyncContext::checkStatus(SyncReport &report)
{
checkConfig("status check");
SourceList sourceList(*this, false);
initSources(sourceList);
PasswordConfigProperty::checkPasswords(getUserInterfaceNonNull(), *this,
// Don't need sync passwords.
PasswordConfigProperty::CHECK_PASSWORD_ALL & ~PasswordConfigProperty::CHECK_PASSWORD_SYNC,
sourceList.getSourceNames());
for (SyncSource *source: sourceList) {
source->open();
}
checkSourceChanges(sourceList, report);
}
static void logRestoreReport(const SyncReport &report, bool dryrun)
{
if (!report.empty()) {
stringstream out;
report.prettyPrint(out, SyncReport::WITHOUT_SERVER|SyncReport::WITHOUT_CONFLICTS|SyncReport::WITH_TOTAL);
SE_LOG_INFO(NULL, "Item changes %s applied locally during restore:\n%s",
dryrun ? "to be" : "that were",
out.str().c_str());
SE_LOG_INFO(NULL, "The same incremental changes will be applied to the server during the next sync.");
SE_LOG_INFO(NULL, "Use -sync refresh-from-client to replace the complete data on the server.");
}
}
void SyncContext::checkSourceChanges(SourceList &sourceList, SyncReport &changes)
{
changes.setStart(time(nullptr));
for (SyncSource *source: sourceList) {
SyncSourceReport local;
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
if (source->getOperations().m_checkStatus) {
source->getOperations().m_checkStatus(local);
} else {
// no information available
local.setItemStat(SyncSourceReport::ITEM_LOCAL,
SyncSourceReport::ITEM_ADDED,
SyncSourceReport::ITEM_TOTAL,
-1);
local.setItemStat(SyncSourceReport::ITEM_LOCAL,
SyncSourceReport::ITEM_UPDATED,
SyncSourceReport::ITEM_TOTAL,
-1);
local.setItemStat(SyncSourceReport::ITEM_LOCAL,
SyncSourceReport::ITEM_REMOVED,
SyncSourceReport::ITEM_TOTAL,
-1);
local.setItemStat(SyncSourceReport::ITEM_LOCAL,
SyncSourceReport::ITEM_ANY,
SyncSourceReport::ITEM_TOTAL,
-1);
}
changes.addSyncSourceReport(source->getName(), local);
}
changes.setEnd(time(nullptr));
}
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
bool SyncContext::checkForScriptAbort(SharedSession session)
{
try {
SharedKey sessionKey = m_engine.OpenSessionKey(session);
SharedKey contextKey = m_engine.OpenKeyByPath(sessionKey, "/sessionvars");
bool abort = m_engine.GetInt32Value(contextKey, "delayedabort");
return abort;
} catch (const NoSuchKey &) {
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
// this is necessary because the session might already have
// been closed, which removes the variable
return false;
} catch (const BadSynthesisResult &) {
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
return false;
}
}
void SyncContext::restore(const string &dirname, RestoreDatabase database)
{
checkConfig("restore");
SourceList sourceList(*this, false);
sourceList.accessSession(dirname.c_str());
Logger::instance().setLevel(Logger::INFO);
initSources(sourceList);
PasswordConfigProperty::checkPasswords(getUserInterfaceNonNull(), *this,
// Don't need sync passwords.
PasswordConfigProperty::CHECK_PASSWORD_ALL & ~PasswordConfigProperty::CHECK_PASSWORD_SYNC,
sourceList.getSourceNames());
string datadump = database == DATABASE_BEFORE_SYNC ? "before" : "after";
for (SyncSource *source: sourceList) {
// fake a source alert event
displaySourceProgress(*source, SyncSourceEvent(sysync::PEV_ALERTED, -1, 0, 0), true);
source->open();
}
if (!m_quiet && getPrintChanges()) {
sourceList.dumpDatabases("current", nullptr);
SyncML server: delayed checking of sources (MB #7710) With this patch, SyncML server sources are only opened() and their data dumped when a client really uses them. As before, sources are only enabled in the server if their sync mode is not "disabled". This tolerates sources which cannot be instantiated because their "type" is not supported. The patch changes the SourceList and its methods so that they can do the database dumps and comparisons for a single source at a time. SourceList tracks which of its sources were dumped before the sync and uses that information at the end to produce the "after sync" comparison. That "after sync" comparison was a reduced copy of the dumpLocalChanges() source code. The copy was replaced with a suitably parameterized call to dumpLocalChanges(), which became easy after adding the "oldSession" parameter in a recent patch. That output now is as follows: -------------------------> snip <----------------------------------- Changes applied during synchronization: +---------------|-----------------------|-----------------------|-CON-+ | | LOCAL | REMOTE | FLI | | Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | addressbook | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | calendar | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | two-way, 0 KB sent by client, 0 KB received | | item(s) in database backup: 20 before sync, 20 after it | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | start Wed Feb 10 16:38:15 2010, duration 0:02min | | synchronization completed successfully | +---------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ Data modified locally during sync: *** addressbook *** no changes *** calendar *** no changes -------------------------> snip <----------------------------------- Previously the last heading was "Changes applied to client during synchronization", which is wrong for the server (it is not a client) and did not properly distinguish between item and data changes (items may be changed without affecting the set of data, as in removing one item and adding it with the same content). In a server, the "*** <source> ***" part is only printed for active sources, whereas the table always contains all sources with sync mode != "disabled". If we had progress events for the server, it should be more obvious that some sources were not really used during the sync. Alternatively we could also remove them from the report. Also fixed several other such "to server/client" messages. They were written from the perspective of a client and were wrong when running as server. Using "remotely" and "locally" instead works on both client and server.
2010-02-10 17:47:24 +01:00
sourceList.dumpLocalChanges(dirname, "current", datadump, "",
"Data changes to be applied locally during restore:\n",
"CLIENT_TEST_LEFT_NAME='current data' "
"CLIENT_TEST_REMOVED='after restore' "
"CLIENT_TEST_REMOVED='to be removed' "
"CLIENT_TEST_ADDED='to be added'");
}
SyncReport report;
try {
for (SyncSource *source: sourceList) {
SyncSourceReport sourcereport;
try {
displaySourceProgress(*source, SyncSourceEvent(sysync::PEV_SYNCSTART, 0, 0, 0), true);
sourceList.restoreDatabase(*source,
datadump,
m_dryrun,
sourcereport);
displaySourceProgress(*source, SyncSourceEvent(sysync::PEV_SYNCEND, 0, 0, 0), true);
report.addSyncSourceReport(source->getName(), sourcereport);
} catch (...) {
sourcereport.recordStatus(STATUS_FATAL);
report.addSyncSourceReport(source->getName(), sourcereport);
throw;
}
}
} catch (...) {
logRestoreReport(report, m_dryrun);
throw;
}
logRestoreReport(report, m_dryrun);
}
void SyncContext::getSessions(vector<string> &dirs)
{
make_weak_shared::make<LogDir>(*this)->previousLogdirs(dirs);
}
string SyncContext::readSessionInfo(const string &dir, SyncReport &report)
{
auto logging = make_weak_shared::make<LogDir>(*this);
Logging: thread-safe Logging must be thread-safe, because the glib log callback may be called from arbitrary threads. This becomes more important with EDS 3.8, because it shifts the execution of synchronous calls into threads. Thread-safe logging will also be required for running the Synthesis engine multithreaded, to overlap SyncML client communication with preparing the sources. To achieve this, the core Logging module protects its global data with a recursive mutex. A recursive mutes is used because logging calls themselves may be recursive, so ensuring single-lock semantic would be hard. Ref-counted boost pointers are used to track usage of Logger instances. This allows removal of an instance from the logging stack while it may still be in use. Destruction then will be delayed until the last user of the instance drops it. The instance itself must be prepared to handle this. The Logging mutex is available to users of the Logging module. Code which holds the logging mutex should not lock any other mutex, to avoid deadlocks. The new code is a bit fuzzy on that, because it calls other modules (glib, Synthesis engine) while holding the mutex. If that becomes a problem, then the mutex can be unlocked, at the risk of leading to reordered log messages in different channels (see ServerLogger). Making all loggers follow the new rules uses different approaches. Loggers like the one in the local transport child which use a parent logger and an additional ref-counted class like the D-Bus helper keep a weak reference to the helper and lock it before use. If it is gone already, the second logging part is skipped. This is the recommended approach. In cases where introducing ref-counting for the second class would have been too intrusive (Server and SessionHelper), a fake boost::shared_ptr without a destructor is used as an intermediate step towards the recommended approach. To avoid race conditions while the instance these fake pointers refer to destructs, an explicit "remove()" method is necessary which must hold the Logging mutex. Using the potentially removed pointer must do the same. Such fake ref-counted Loggers cannot be used as parent logger of other loggers, because then remove() would not be able to drop the last reference to the fake boost::shared_ptr. Loggers with fake boost::shared_ptr must keep a strong reference, because no-one else does. The goal is to turn this into weak references eventually. LogDir must protect concurrent access to m_report and the Synthesis engine. The LogRedirectLogger assumes that it is still the active logger while disabling itself. The remove() callback method will always be invoked before removing a logger from the stack.
2013-04-09 21:32:35 +02:00
logging->openLogdir(dir);
logging->readReport(report);
return logging->getPeerNameFromLogdir(dir);
}
#ifdef ENABLE_UNIT_TESTS
/**
* This class works LogDirTest as scratch directory.
* LogDirTest/[file_event|file_contact]_[one|two|empty] contain different
* sets of items for use in a FileSyncSource.
*
* With that setup and a fake SyncContext it is possible to simulate
* sessions and test the resulting logdirs.
*/
class LogDirTest : public CppUnit::TestFixture
{
class LogContext : public SyncContext, public Logger
{
public:
LogContext() :
SyncContext("nosuchconfig@nosuchcontext")
{}
ostringstream m_out;
/** capture output produced while test ran */
void messagev(const MessageOptions &options,
const char *format,
va_list args)
{
std::string str = StringPrintfV(format, args);
m_out << '[' << levelToStr(options.m_level) << ']' << str;
if (!boost::ends_with(str, "\n")) {
m_out << std::endl;
}
}
};
std::shared_ptr<LogContext> m_logContext;
public:
LogDirTest() :
m_maxLogDirs(10)
{
command line: cleaned up output The user-visible part of this change is that command line output now uses the same [ERROR/INFO] prefixes like the rest of SyncEvolution, instead of "Error:". Several messages were split into [ERROR] and [INFO] parts on seperate lines. Multi-line messages with such a prefix now have the prefix at the start of each line. Full sentences start with captital letters. All usage errors related to the synopsis of the command line now include the synopsis, without the detailed documentation of all options. Some of those errors dumped the full documentation, which was way too much information and pushed the actual synopsis off the screen. Some other errors did not include usage information at all. All output still goes to stdout, stderr is not used at all. Should be changed in a seperate patch, because currently error messages during operations like "--export -" get mixed with the result of the operation. Technically the output handling was simplified. All output is printed via the logging system, instead of using a mixture of logging and streaming into std::cout. The advantage is that it will be easier to redirect all regular output inside the syncevo-dbus-helper to the parent. In particular, the following code could be removed: - the somewhat hacky std::streambuf->logging bridge code (CmdlineStreamBuf) - SyncContext set/getOutput() - ostream constructor parameters for Cmdline and derived classes The new code uses SE_LOG_SHOW() to produce output without prefix. Each call ends at the next line, regardless whether the string ends in a newline or not. The LoggerStdout was adapted to behave according to that expectation, and it inserts the line prefix at the start of each line - probably didn't matter before, because hardly any (no?!) message had line breaks. Because of this implicit newline in the logging code, some newlines become redundant; SE_LOG_SHOW("") is used to insert an empty line where needed. Calls to the logging system are minimized if possible by assembling output in buffers first, to reduce overhead and to adhere to the "one call per message" guideline. Testing was adapted accordingly. It's a bit stricter now, too, because it checks the entire error output instead of just the last line. The previous use of Cmdline ostreams to capture output from the class was replaced with loggers which hook into the logging system while the test runs and store the output. Same with SyncContext testing. Conflicts: src/dbus/server/cmdline-wrapper.h
2012-04-11 10:22:57 +02:00
}
~LogDirTest() {
}
void setUp() {
static const char *vcard_1 =
"BEGIN:VCARD\n"
"VERSION:2.1\n"
"TITLE:tester\n"
"FN:John Doe\n"
"N:Doe;John;;;\n"
"X-MOZILLA-HTML:FALSE\n"
"TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
"EMAIL:john.doe@work.com\n"
"X-AIM:AIM JOHN\n"
"END:VCARD\n";
static const char *vcard_2 =
"BEGIN:VCARD\n"
"VERSION:2.1\n"
"TITLE:developer\n"
"FN:John Doe\n"
"N:Doe;John;;;\n"
"X-MOZILLA-HTML:TRUE\n"
"BDAY:2006-01-08\n"
"END:VCARD\n";
static const char *ical_1 =
"BEGIN:VCALENDAR\n"
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
"VERSION:2.0\n"
"METHOD:PUBLISH\n"
"BEGIN:VEVENT\n"
"SUMMARY:phone meeting\n"
"DTEND:20060406T163000Z\n"
"DTSTART:20060406T160000Z\n"
"UID:1234567890!@#$%^&*()<>@dummy\n"
"DTSTAMP:20060406T211449Z\n"
"LAST-MODIFIED:20060409T213201\n"
"CREATED:20060409T213201\n"
"LOCATION:calling from home\n"
"DESCRIPTION:let's talk\n"
"CLASS:PUBLIC\n"
"TRANSP:OPAQUE\n"
"SEQUENCE:1\n"
"BEGIN:VALARM\n"
"DESCRIPTION:alarm\n"
"ACTION:DISPLAY\n"
"TRIGGER;VALUE=DURATION;RELATED=START:-PT15M\n"
"END:VALARM\n"
"END:VEVENT\n"
"END:VCALENDAR\n";
static const char *ical_2 =
"BEGIN:VCALENDAR\n"
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
"VERSION:2.0\n"
"METHOD:PUBLISH\n"
"BEGIN:VEVENT\n"
"SUMMARY:phone meeting\n"
"DTEND:20060406T163000Z\n"
"DTSTART:20060406T160000Z\n"
"UID:1234567890!@#$%^&*()<>@dummy\n"
"DTSTAMP:20060406T211449Z\n"
"LAST-MODIFIED:20060409T213201\n"
"CREATED:20060409T213201\n"
"LOCATION:my office\n"
"CATEGORIES:WORK\n"
"DESCRIPTION:what the heck\\, let's even shout a bit\n"
"CLASS:PUBLIC\n"
"TRANSP:OPAQUE\n"
"SEQUENCE:1\n"
"END:VEVENT\n"
"END:VCALENDAR\n";
rm_r("LogDirTest");
dump("file_event.one", "1", ical_1);
dump("file_event.two", "1", ical_1);
dump("file_event.two", "2", ical_2);
mkdir_p(getLogData() + "/file_event.empty");
dump("file_contact.one", "1", vcard_1);
dump("file_contact.two", "1", vcard_1);
dump("file_contact.two", "2", vcard_2);
mkdir_p(getLogData() + "/file_contact.empty");
mkdir_p(getLogDir());
m_maxLogDirs = 0;
// Suppress output by redirecting into LogContext::m_out.
// It's not tested at the moment.
m_logContext.reset(new LogContext);
Logger::addLogger(m_logContext);
}
void tearDown() {
Logger::removeLogger(m_logContext.get());
m_logContext.reset();
}
private:
string getLogData() { return "LogDirTest/data"; }
virtual InitStateString getLogDir() const { return "LogDirTest/cache/syncevolution"; }
int m_maxLogDirs;
void dump(const char *dir, const char *file, const char *data) {
string name = getLogData();
name += "/";
name += dir;
mkdir_p(name);
name += "/";
name += file;
ofstream out(name.c_str());
out << data;
}
CPPUNIT_TEST_SUITE(LogDirTest);
CPPUNIT_TEST(testQuickCompare);
CPPUNIT_TEST(testSessionNoChanges);
CPPUNIT_TEST(testSessionChanges);
CPPUNIT_TEST(testMultipleSessions);
CPPUNIT_TEST(testExpire);
CPPUNIT_TEST_SUITE_END();
/**
* Simulate a session involving one or more sources.
*
* @param changeServer pretend that peer got changed
* @param status result of session
* @param varargs sourcename ("file_event"),
* statebefore (nullptr for no dump, or suffix like "_one"),
* stateafter (nullptr for same as before), ..., nullptr
* @return logdir created for the session
*/
string session(bool changeServer, SyncMLStatus status, ...) {
Logger::Level level = Logger::instance().getLevel();
SourceList list(*m_logContext, true);
list.setLogLevel(SourceList::LOGGING_QUIET);
SyncReport report;
list.startSession("", m_maxLogDirs, 0, &report);
va_list ap;
va_start(ap, status);
while (true) {
const char *sourcename = va_arg(ap, const char *);
if (!sourcename) {
break;
}
const char *type = nullptr;
if (!strcmp(sourcename, "file_event")) {
type = "file:text/calendar:2.0";
} else if (!strcmp(sourcename, "file_contact")) {
type = "file:text/vcard:3.0";
}
CPPUNIT_ASSERT(type);
string datadir = getLogData() + "/";
auto source = SyncSource::createTestingSource(sourcename, type, true,
(string("file://") + datadir).c_str());
datadir += sourcename;
datadir += "_1";
source->open();
if (changeServer) {
// fake one added item on server
source->setItemStat(SyncSourceReport::ITEM_REMOTE,
SyncSourceReport::ITEM_ADDED,
SyncSourceReport::ITEM_TOTAL,
1);
}
list.addSource(std::move(source));
const char *before = va_arg(ap, const char *);
const char *after = va_arg(ap, const char *);
if (before) {
// do a "before" dump after directing the source towards the desired data
rm_r(datadir);
CPPUNIT_ASSERT_EQUAL(0, symlink((string(sourcename) + before).c_str(),
datadir.c_str()));
list.syncPrepare(sourcename);
if (after) {
rm_r(datadir);
CPPUNIT_ASSERT_EQUAL(0, symlink((string(sourcename) + after).c_str(),
datadir.c_str()));
}
}
}
list.syncDone(status, &report);
va_end(ap);
Logger::instance().setLevel(level);
return list.getLogdir();
}
typedef vector<string> Sessions_t;
// full paths to all sessions, sorted
Sessions_t listSessions() {
Sessions_t sessions;
string logdir = getLogDir();
ReadDir dirs(logdir);
for (const string &dir: dirs) {
sessions.push_back(RealPath(logdir + "/" + dir));
}
sort(sessions.begin(), sessions.end());
return sessions;
}
void testQuickCompare() {
// identical dirs => identical files
CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_event",
getLogData(), "empty",
getLogData(), "empty"));
CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_event",
getLogData(), "one",
getLogData(), "one"));
CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_event",
getLogData(), "two",
getLogData(), "two"));
// some files shared
CPPUNIT_ASSERT(!system("cp -l -r LogDirTest/data/file_event.two LogDirTest/data/file_event.copy && rm LogDirTest/data/file_event.copy/2"));
CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_event",
getLogData(), "two",
getLogData(), "copy"));
CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_event",
getLogData(), "copy",
getLogData(), "one"));
}
void testSessionNoChanges() {
ScopedEnvChange config("XDG_CONFIG_HOME", "LogDirTest/config");
ScopedEnvChange cache("XDG_CACHE_HOME", "LogDirTest/cache");
// simple session with no changes
string dir = session(false, STATUS_OK, "file_event", ".one", ".one", (char *)0);
Sessions_t sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)1, sessions.size());
CPPUNIT_ASSERT_EQUAL(dir, sessions[0]);
IniFileConfigNode status(dir, "status.ini", true);
CPPUNIT_ASSERT(status.exists());
CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__event-backup-before").get());
CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__event-backup-after").get());
CPPUNIT_ASSERT_EQUAL(string("200"), status.readProperty("status").get());
CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_event",
dir, "before",
dir, "after"));
}
void testSessionChanges() {
ScopedEnvChange config("XDG_CONFIG_HOME", "LogDirTest/config");
ScopedEnvChange cache("XDG_CACHE_HOME", "LogDirTest/cache");
// session with local changes
string dir = session(false, STATUS_OK, "file_event", ".one", ".two", (char *)0);
Sessions_t sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)1, sessions.size());
CPPUNIT_ASSERT_EQUAL(dir, sessions[0]);
IniFileConfigNode status(dir, "status.ini", true);
CPPUNIT_ASSERT(status.exists());
CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__event-backup-before").get());
CPPUNIT_ASSERT_EQUAL(string("2"), status.readProperty("source-file__event-backup-after").get());
CPPUNIT_ASSERT_EQUAL(string("200"), status.readProperty("status").get());
CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_event",
dir, "before",
dir, "after"));
}
void testMultipleSessions() {
ScopedEnvChange config("XDG_CONFIG_HOME", "LogDirTest/config");
ScopedEnvChange cache("XDG_CACHE_HOME", "LogDirTest/cache");
// two sessions, starting with 1 item, adding 1 during the sync, then
// removing it again during the second
string dir = session(false, STATUS_OK,
"file_event", ".one", ".two",
"file_contact", ".one", ".two",
(char *)0);
{
Sessions_t sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)1, sessions.size());
CPPUNIT_ASSERT_EQUAL(dir, sessions[0]);
IniFileConfigNode status(dir, "status.ini", true);
CPPUNIT_ASSERT(status.exists());
CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__event-backup-before").get());
CPPUNIT_ASSERT_EQUAL(string("2"), status.readProperty("source-file__event-backup-after").get());
CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__contact-backup-before").get());
CPPUNIT_ASSERT_EQUAL(string("2"), status.readProperty("source-file__contact-backup-after").get());
CPPUNIT_ASSERT_EQUAL(string("200"), status.readProperty("status").get());
CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_event",
dir, "before",
dir, "after"));
CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_contact",
dir, "before",
dir, "after"));
}
string seconddir = session(false, STATUS_OK,
"file_event", ".two", ".one",
"file_contact", ".two", ".one",
(char *)0);
{
Sessions_t sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size());
CPPUNIT_ASSERT_EQUAL(dir, sessions[0]);
CPPUNIT_ASSERT_EQUAL(seconddir, sessions[1]);
IniFileConfigNode status(seconddir, "status.ini", true);
CPPUNIT_ASSERT(status.exists());
CPPUNIT_ASSERT_EQUAL(string("2"), status.readProperty("source-file__event-backup-before").get());
CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__event-backup-after").get());
CPPUNIT_ASSERT_EQUAL(string("2"), status.readProperty("source-file__contact-backup-before").get());
CPPUNIT_ASSERT_EQUAL(string("1"), status.readProperty("source-file__contact-backup-after").get());
CPPUNIT_ASSERT_EQUAL(string("200"), status.readProperty("status").get());
CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_event",
seconddir, "before",
seconddir, "after"));
CPPUNIT_ASSERT(LogDir::haveDifferentContent("file_contact",
seconddir, "before",
seconddir, "after"));
}
CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_event",
dir, "after",
seconddir, "before"));
CPPUNIT_ASSERT(!LogDir::haveDifferentContent("file_contact",
dir, "after",
seconddir, "before"));
}
void testExpire() {
ScopedEnvChange config("XDG_CONFIG_HOME", "LogDirTest/config");
ScopedEnvChange cache("XDG_CACHE_HOME", "LogDirTest/cache");
string dirs[5];
Sessions_t sessions;
m_maxLogDirs = 1;
// The latest session always must be preserved, even if it
// is normally considered less important (no error in this case).
dirs[0] = session(false, STATUS_FATAL, (char *)0);
dirs[0] = session(false, STATUS_OK, (char *)0);
sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)1, sessions.size());
CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]);
// all things being equal, then expire the oldest session,
// leaving us with two here
m_maxLogDirs = 2;
dirs[0] = session(false, STATUS_OK, (char *)0);
dirs[1] = session(false, STATUS_OK, (char *)0);
sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size());
CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]);
CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]);
// When syncing first file_event, then file_contact, both sessions
// must be preserved despite m_maxLogDirs = 1, otherwise
// we would loose the only existent backup of file_event.
dirs[0] = session(false, STATUS_OK, "file_event", ".two", ".one", (char *)0);
dirs[1] = session(false, STATUS_OK, "file_contact", ".two", ".one", (char *)0);
sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size());
CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]);
CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]);
// after synchronizing both, we can expire both the old sessions
m_maxLogDirs = 1;
dirs[0] = session(false, STATUS_OK,
"file_event", ".two", ".one",
"file_contact", ".two", ".one",
(char *)0);
sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)1, sessions.size());
CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]);
// when doing multiple failed syncs without dumps, keep the sessions
// which have database dumps
m_maxLogDirs = 2;
dirs[1] = session(false, STATUS_FATAL, (char *)0);
dirs[1] = session(false, STATUS_FATAL, (char *)0);
sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size());
CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]);
CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]);
// when doing syncs which don't change data, keep the sessions which
// did change something: keep oldest backup because it created the
// backups for the first time
dirs[1] = session(false, STATUS_OK,
"file_event", ".one", ".one",
"file_contact", ".one", ".one",
(char *)0);
dirs[1] = session(false, STATUS_OK,
"file_event", ".one", ".one",
"file_contact", ".one", ".one",
(char *)0);
sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size());
CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]);
CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]);
// when making a change in each sync, we end up with the two
// most recent sessions eventually: first change server,
// then local
dirs[1] = session(true, STATUS_OK,
"file_event", ".one", ".one",
"file_contact", ".one", ".one",
(char *)0);
sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size());
CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]);
CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]);
dirs[0] = dirs[1];
dirs[1] = session(false, STATUS_OK,
"file_event", ".one", ".two",
"file_contact", ".one", ".two",
(char *)0);
sessions = listSessions();
CPPUNIT_ASSERT_EQUAL((size_t)2, sessions.size());
CPPUNIT_ASSERT_EQUAL(dirs[0], sessions[0]);
CPPUNIT_ASSERT_EQUAL(dirs[1], sessions[1]);
}
};
SYNCEVOLUTION_TEST_SUITE_REGISTRATION(LogDirTest);
#endif // ENABLE_UNIT_TESTS
SE_END_CXX