Merge remote branch 'origin/syncevolution-1-2-branch'

Conflicts:
	configure.ac
	test/ClientTest.cpp
	test/testcases/eds_event.ics.funambol.tem.patch

Conflicts because of version number and updated test cases resp. local
delete optimization.

ActiveSync backend had to be adapted to modified InsertItemResult: now
it requests a merge when it detects duplicates, like the CalDAV backend
already did on the 1.2 branch.
This commit is contained in:
Patrick Ohly 2011-10-17 12:39:44 +02:00
commit a7f7c8eacf
40 changed files with 1155 additions and 151 deletions

301
NEWS
View File

@ -1,3 +1,304 @@
SyncEvolution 1.1.1 -> 1.2, 13.10.2011
======================================
The major new feature of the 1.2 release is support for non-SyncML
protocols in general and CalDAV/CardDAV in particular. ActiveSync
support is in development and will be in 1.3. These protocols are
implemented as backends which are combined with other backends by
SyncEvolution in a so called "local sync". The GTK sync-ui does not
yet support configuring non-SyncML protocols. See the README.rst and
man page for more information on how to use the new feature via the
command line.
Properties not supported by SyncML servers can now be preserved
locally in two-way synchronization (BMC #15030). This depends on
information about what properties a SyncML server supports ("CtCap"),
which is typically not provided by servers. SyncEvolution contains a
copy of that information for Google Contacts (BMC #15029).
Akonadi backend and KWallet support were merged. They are not included
yet in syncevolution.org binaries. To use them compile from source.
The configuration format was updated to solve a conceptual problem
inherited with the legacy property names: the "type" property had
multiple, sometimes conflicting roles. For example, setting the
preferred data format for sync with one peer might have changed the
backend selection for some other peer (BMC #1023). Now
"backend/databaseFormat/syncFormat/forceSyncFormat" replace
"type". "type" is still accepted by the command line as alias.
Upgrading from releases before 1.2:
Old configurations can still be read. But writing, as it happens
during a sync, must migrate the configuration first. Release 1.2
automatically migrates configurations. The old configurations
will still be available (see "syncevolution --print-configs") but must
be renamed manually to use them again under their original names with
older SyncEvolution releases.
Other changes:
* Using the --sync-property and --source-property command line options is
optional, just specifying the property assignment is enough.
* syncevo-http-server was enhanced considerably. See http://syncevolution.org/wiki/http-server-howto
* support NetworkManager API >= 0.9 (BMC #19470)
* syncevolution.org binaries: now compatible with Debian Testing/libnotify.so.4 (BMC #22668)
libnotify is not linked directly into syncevo-dbus-server in the
syncevolution.org binaries. Instead libnotify.so.1 till .so.4
(current Debian Testing) are opened opened dynamically and the
necessary functions are looked up via dlsym(). Not finding the
libraries or the functions silently disables this notification
mechanism.
* Sync mode is recorded when running in SyncML server mode (BMC #2786).
* syncevo-dbus-server automatically stops when some of its libraries
are updated and restarts if auto-syncing is on (BMC #14955).
* Added code for Buteo, mKCal and QtContacts in MeeGo.
Buteo and mKCal were removed again from MeeGo, so the code
is obsolete. The QtContacts backend may be still be useful
to access items via that API, but for syncing on MeeGo
the normal EDS backend is used since MeeGo reverted back
to EDS as PIM storage.
* "databasePassword" source property: lookup failure in keyring (BMC #22937)
The databasePassword also wasn't looked up at all when doing item operations
via the command line.
When configuring sources for an HTTP server, the config name typically
is just the context (@foo). When using the config in the HTTP server,
the config name is the peer inside that context (client@foo). Because
the GNOME keyring lookup keys for the "databasePassword" (more
specifically, the object name) contained the full config name which
was different in both cases, looking up the saved password failed.
The solution is to normalize the config name (to accomodate for
different ways of spelling it) and use only the context, with @ as
before. This will break existing setups where the object name in the
keyring (incorrectly) includes the full config name. In that case just
configure the source again to set the password anew.
* Evolution Calendar: fixed detached recurrence support (BMC #22940)
When manipulating a meeting series with more than one detached
recurrence certain sequences of operations could incorrectly fail
with "UID already exists".
* iCalendar 2.0: must set VALUE in EXDATE (part of BMC #22940)
EXDATE has a VALUE parameter, which wasn't defined in the XML
profile. Didn't seem to matter at all in practice, but wasn't
standard-compliant.
* GTK sync-ui: wrap sync service descriptions (BMC #7199)
Descriptions of different sync services are not fully visible unless
word-wrapping gets enabled.
* CalDAV/CardDAV + local storage: avoid empty properties
The main motivation for this change is that a recent Apple Calendar
server rejects vCards with empty BDAY property. Another reason is that
keeping the data as small as possible is desirable by itself.
Sending an empty property serves as a hint for the peer that the
property is supported. This is not necessary when storing an item in a
backend. Therefore this commit disables empty properties for all
backends which do not themselves set the m_backendRule Synthesis info
value.
* Google Contacts: ensure that first/middle/name are set when storing in EDS (BMC #20864)
Evolution and the MeeGo UX assume that first/middle/last name are set.
That is not the case when a contact is created in the Google Contacts
web interface. Such contacts are sent by Google without the N
property.
SyncEvolution now tries to recreate the name components from the FN
string, by splitting at word boundaries and assuming "<first>
<middle> <last>" or "<last>, <first>" format. Obviously this
heuristic fails for some locales.
* Evolution Calendar: fixed error handling for broken TZIDs
* Sony Ericsson: use ISO-8859-1 for all devices (BMC #14414)
Passing invalid UTF-8 strings into libecal caused glib to
abort syncevo-dbus-server.
* auto sync: show all failed syncs except for temporary network errors (BMC #21888)
Notifications were meant to be shown for all errors except temporary
ones. This has never been implemented correctly since the feature was
introduced: instead of hiding known temporary errors, all errors except
500 (fatal error) were suppressed.
* vCard: inline local photo data (BMC #19661)
Some platforms (Maemo, MeeGo) store photos in separate files. Now SyncEvolution
efficiently includes that photo data in the generated vCard right before sending
it to a peer; previously it sent a useless local file:// URI. The Maemo port
has a less efficient workaround for that which now should be obsolete.
* syncevo-dbus-server: online status wrong without Network Manager or ConnMan (BMC #21543)
When neither Network Manager nor ConnMan are running, network presence was "not
online". This prevented running automatic syncs.
For developers:
* modified backend API
- ClientTestConfig modernized
- InsertItemResult::m_merged turned from boolean to enum
* testing and compilation changes; for example, the minimum version of
libsynthesis is now checked at configure time instead of failing at
runtime due to missing features in the Synthesis engine
SyncEvolution 1.1.99.7 -> 1.2, 13.10.2011
=========================================
Some more bug fixes and testing improvements.
* fixed potential invalid memory access in add<->add conflict handling
* fixed memory leak in workaround for EDS bug
* CalDAV/CardDAV: handle ETags without quotation marks (eGroupware)
* updated README: warning about sync direction moved to --sync option
SyncEvolution 1.1.99.6 -> 1.1.99.7, 15.09.2011
==============================================
Mostly bug fixes again. Some are a bit more intrusive, thus another
pre-release.
* syncevolution.org binaries: now compatible with Debian Testing/libnotify.so.4 (BMC #22668)
libnotify is not linked directly into syncevo-dbus-server in the
syncevolution.org binaries. Instead libnotify.so.1 till .so.4
(current Debian Testing) are opened opened dynamically and the
necessary functions are looked up via dlsym(). Not finding the
libraries or the functions silently disables this notification
mechanism.
* calendar sync: better handling for add<->add conflicts (partly fixes BMC #22783)
When both sides of a sync have added the same event, the sync must
determine which one is more recent instead of blindly overwriting
always the same side. Such conflicts are typically rare except for
enterprise scenarios where meeting invitiations are processed
automatically by a groupware (Exchange, Google Calendar/Mail, ...)
and then the attendee status is updated on one side.
SyncEvolution now does the necessary age comparison and preserves the more
recent data for most properties. In some properties the data from both
sides is preserved by concatenating the text (description, location, ...).
It remains to be seen whether that is really desirable. Also, sync statistics
are slightly off: the incoming item is counted as "added" even though it
gets turned into an update.
* item operations: authentication problem for WebDAV when using keyring (BMC #21311)
The password still wasn't looked up in the keyring when using
--import/export/delete-items.
* "databasePassword" source property: lookup failure in keyring (BMC #22937)
The databasePassword also wasn't looked up at all when doing item operations
via the command line.
When configuring sources for an HTTP server, the config name typically
is just the context (@foo). When using the config in the HTTP server,
the config name is the peer inside that context (client@foo). Because
the GNOME keyring lookup keys for the "databasePassword" (more
specifically, the object name) contained the full config name which
was different in both cases, looking up the saved password failed.
The solution is to normalize the config name (to accomodate for
different ways of spelling it) and use only the context, with @ as
before. This will break existing setups where the object name in the
keyring (incorrectly) includes the full config name. In that case just
configure the source again to set the password anew.
* Evolution Calendar: fixed detached recurrence support (BMC #22940)
When manipulating a meeting series with more than one detached
recurrence certain sequences of operations could incorrectly fail
with "UID already exists".
* iCalendar 2.0: must set VALUE in EXDATE (part of BMC #22940)
EXDATE has a VALUE parameter, which wasn't defined in the XML
profile. Didn't seem to matter at all in practice, but wasn't
standard-compliant.
* GTK sync-ui: wrap sync service descriptions (BMC #7199)
Descriptions of different sync services are not fully visible unless
word-wrapping gets enabled.
* source configs: don't check "backend" unless it is needed
When using a config which has sources with a backend type set which is
not currently available, an error was thrown even if those sources
weren't even part of the current operation (for example, syncing
another source which is currently supported).
* config migration: avoid name conflicts and auto syncing of old configs (BMC #22691)
When (auto-)migrating a config, it was possible that a name for the
peer, say foo.old, was chosen for the renamed config although there
was already such a config, for example foo.old in ~/.sync4j. Besides
being confusing for users, this also led to a bug in the code where it
copied from the older config with the foo.old name.
The main problem fixed is the disabling of auto syncing
in the old config. Otherwise it was still used by syncevo-dbus-server
for syncing, which triggered another auto-migration, ad infinitum...
* auto syncing: must check whether enabled when looking at unknown URLs (part of BMC #22691)
"syncURL = insert your URL here" with "autoSync = 0" did lead to auto
sync attempts although it wasn't enabled. A check for "auto syncing
enabled" was missing for the "unknown transport" case.
* CalDAV/CardDAV + local storage: avoid empty properties
The main motivation for this change is that a recent Apple Calendar
server rejects vCards with empty BDAY property. Another reason is that
keeping the data as small as possible is desirable by itself.
Sending an empty property serves as a hint for the peer that the
property is supported. This is not necessary when storing an item in a
backend. Therefore this commit disables empty properties for all
backends which do not themselves set the m_backendRule Synthesis info
value.
* Apple CardDAV: apply PHOTO import/export scripts by default
A recent Apple Calendar server (correctly) rejects the invalid
PHOTO;TYPE=unknown: property in a vCard. This internal representation
must be cleared before serializing the field list.
* for developers: modified backend API
- ClientTestConfig modernized
- InsertItemResult::m_merged turned from boolean to enum
* testing and compilation changes; for example, the minimum version of
libsynthesis is now checked at configure time instead of failing at
runtime due to missing features in the Synthesis engine
SyncEvolution 1.1.99.5 -> 1.1.99.6, 17.08.2011
==============================================

View File

@ -274,6 +274,15 @@ a list of valid values.
for a `refresh-from-server` or `refresh-from-client` sync which
clears all data at one end and copies all items from the other.
**Warning:** in local sync (CalDAV/CardDAV/ActiveSync, ...) and
direct sync with a phone, the sync is started by the side which acts
as server. Therefore the ``from-server`` variants
(``one-way-from-server``, ``refresh-from-server``) transfer data
from the sync config into the target config (see "Synchronization
beyond SyncML" below) resp. to a phone. The ``from-client`` variants
transfer in the other direction, even if the target config happens
to access data on a remote server.
--print-servers|--print-configs|--print-peers
Prints the names of all configured peers to stdout. There is no
difference between these options, the are just aliases.
@ -661,13 +670,6 @@ source in the target config. The ``sync`` property in the sync config
defines the direction of the data flow. It can be set temporarily when
starting a synchronzation with the sync config.
**Warning:** in local sync, the sync config side acts as
server. Therefore the ``from-server`` variants
(``one-way-from-server``, ``refresh-from-server``) transfer data
from the sync config into the target config. The ``from-client``
variants transfer in the other direction, even if the target config
happens to access data on a remote server.
**Warning:** because the client in the local sync starts the sync,
``preventSlowSync=0`` must be set in the target config to have an effect.

View File

@ -8,7 +8,7 @@ dnl Invoke autogen.sh to produce a configure script.
#
# Starting with the 1.1 release cycle, the rpm-style
# .99 pseudo-version number is used to mark a pre-release.
AC_INIT([syncevolution], [m4_esyscmd([build/gen-git-version.sh 1.1.99.6])])
AC_INIT([syncevolution], [m4_esyscmd([build/gen-git-version.sh 1.2])])
# STABLE_VERSION=1.0.1+
AC_SUBST(STABLE_VERSION)
@ -25,7 +25,7 @@ SE_CHECK_FOR_STABLE_RELEASE
# Minimum version of libsynthesis as defined in its
# configure script and thus .pc files:
define([SYNTHESIS_MIN_VERSION], [3.4.0.16.1])
define([SYNTHESIS_MIN_VERSION], [3.4.0.16.4])
# Line above is patched by gen-autotools.sh. Handle
# both "yes" and "no".

View File

@ -417,7 +417,7 @@ SyncSourceRaw::InsertItemResult ActiveSyncCalendarSource::insertItem(const std::
}
}
bool merged = false;
InsertItemResultState state;
if (easid.empty()) {
// New VEVENT; should not be part of an existing merged item
// ("meeting series").
@ -430,7 +430,7 @@ SyncSourceRaw::InsertItemResult ActiveSyncCalendarSource::insertItem(const std::
Event &event = loadItem(*it->second);
if (event.m_subids.find(subid) != event.m_subids.end()) {
// was already in that item but caller didn't seem to know
merged = true;
state = ITEM_MERGED;
} else {
// add to merged item
event.m_subids.insert(subid);
@ -452,10 +452,11 @@ SyncSourceRaw::InsertItemResult ActiveSyncCalendarSource::insertItem(const std::
Event &event = findItem(easid);
if (event.m_subids.size() == 1 &&
*event.m_subids.begin() == subid) {
// special case: no need to load old data, replace it outright
// special case: no need to load old data, replace or request merge immediately
event.m_calendar = newEvent->m_calendar;
if (easid != callerEasID) {
merged = true;
state = ITEM_NEEDS_MERGE;
goto done;
}
} else {
// populate event
@ -472,13 +473,13 @@ SyncSourceRaw::InsertItemResult ActiveSyncCalendarSource::insertItem(const std::
}
}
if (easid != callerEasID) {
// caller didn't know final UID: if found, then tell him that
// we merged the item for him, if not, then don't complain about
// caller didn't know final UID: if found, then tell him to
// merge the items, if not, then don't complain about
// it not being found (like we do when the item should exist
// but doesn't)
if (removeme) {
merged = true;
icalcomponent_remove_component(event.m_calendar, removeme);
state = ITEM_NEEDS_MERGE;
goto done;
} else {
event.m_subids.insert(subid);
}
@ -501,15 +502,16 @@ SyncSourceRaw::InsertItemResult ActiveSyncCalendarSource::insertItem(const std::
// TODO: avoid updating item on server immediately?
InsertItemResult res = ActiveSyncSource::insertItem(event.m_easid, data);
if (res.m_merged ||
if (res.m_state == ITEM_MERGED ||
res.m_luid != event.m_easid) {
// should not merge with anything, if so, our cache was invalid
SE_THROW("CalDAV item not updated as expected");
}
}
done:
return SyncSourceRaw::InsertItemResult(createLUID(easid, subid),
"", merged);
"", state);
}
void ActiveSyncCalendarSource::readItem(const std::string &luid, std::string &item)
@ -599,7 +601,7 @@ void ActiveSyncCalendarSource::deleteItem(const string &luid)
// TODO: avoid updating the item immediately
eptr<char> icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar)));
InsertItemResult res = ActiveSyncSource::insertItem(easid, icalstr.get());
if (res.m_merged ||
if (res.m_state != ITEM_OKAY ||
res.m_luid != easid) {
SE_THROW("unexpected result of removing sub event");
}

View File

@ -198,7 +198,7 @@ TrackingSyncSource::InsertItemResult AkonadiSyncSource::insertItem(const std::st
ItemCreateJob *createJob = new ItemCreateJob(item, m_collection);
if (!createJob->exec()) {
throwError(string("storing new item ") + luid);
return InsertItemResult("", "", false);
return InsertItemResult("", "", ITEM_OKAY);
}
item = createJob->item();
} else {
@ -215,7 +215,7 @@ TrackingSyncSource::InsertItemResult AkonadiSyncSource::insertItem(const std::st
// TODO: check that the item has not been updated in the meantime
if (!modifyJob->exec()) {
throwError(string("updating item ") + luid);
return InsertItemResult("", "", false);
return InsertItemResult("", "", ITEM_OKAY);
}
item = modifyJob->item();
}
@ -225,7 +225,7 @@ TrackingSyncSource::InsertItemResult AkonadiSyncSource::insertItem(const std::st
// above will take care of this
return InsertItemResult(QByteArray::number(item.id()).constData(),
QByteArray::number(item.revision()).constData(),
false);
ITEM_OKAY);
}
void AkonadiSyncSource::removeItem(const string &luid)

View File

@ -56,6 +56,32 @@ class unrefECalObjectList {
}
};
bool EvolutionCalendarSource::LUIDs::containsLUID(const ItemID &id) const
{
const_iterator it = findUID(id.m_uid);
return it != end() &&
it->second.find(id.m_rid) != it->second.end();
}
void EvolutionCalendarSource::LUIDs::insertLUID(const ItemID &id)
{
(*this)[id.m_uid].insert(id.m_rid);
}
void EvolutionCalendarSource::LUIDs::eraseLUID(const ItemID &id)
{
iterator it = find(id.m_uid);
if (it != end()) {
set<string>::iterator it2 = it->second.find(id.m_rid);
if (it2 != it->second.end()) {
it->second.erase(it2);
if (it->second.empty()) {
erase(it);
}
}
}
}
static int granularity()
{
// This long delay is necessary in combination
@ -254,7 +280,7 @@ void EvolutionCalendarSource::listAllItems(RevisionMap_t &revisions)
string luid = id.getLUID();
string modTime = getItemModTime(ecomp);
m_allLUIDs.insert(luid);
m_allLUIDs.insertLUID(id);
revisions[luid] = modTime;
nextItem = nextItem->next;
}
@ -274,7 +300,7 @@ void EvolutionCalendarSource::readItem(const string &luid, std::string &item, bo
EvolutionCalendarSource::InsertItemResult EvolutionCalendarSource::insertItem(const string &luid, const std::string &item, bool raw)
{
bool update = !luid.empty();
bool merged = false;
InsertItemResultState state = ITEM_OKAY;
bool detached = false;
string newluid = luid;
string data = item;
@ -389,14 +415,14 @@ EvolutionCalendarSource::InsertItemResult EvolutionCalendarSource::insertItem(co
// gets used twice during a sync (examples: add + add, delete + add),
// which should never happen.
newluid = id.getLUID();
if (m_allLUIDs.find(newluid) != m_allLUIDs.end()) {
merged = true;
if (m_allLUIDs.containsLUID(id)) {
state = ITEM_NEEDS_MERGE;
} else {
// if this is a detached recurrence, then we
// must use e_cal_modify_object() below if
// the parent already exists
// the parent or any other child already exists
if (!id.m_rid.empty() &&
m_allLUIDs.find(ItemID::getLUID(id.m_uid, "")) != m_allLUIDs.end()) {
m_allLUIDs.containsUID(id.m_uid)) {
detached = true;
} else {
// Creating the parent while children are already in
@ -420,7 +446,7 @@ EvolutionCalendarSource::InsertItemResult EvolutionCalendarSource::insertItem(co
ItemID newid(!id.m_uid.empty() ? id.m_uid : uid, id.m_rid);
newluid = newid.getLUID();
modTime = getItemModTime(newid);
m_allLUIDs.insert(newluid);
m_allLUIDs.insertLUID(newid);
} else {
throwError("storing new item", gerror);
}
@ -438,7 +464,7 @@ EvolutionCalendarSource::InsertItemResult EvolutionCalendarSource::insertItem(co
}
}
if (update || merged || detached) {
if (update || state != ITEM_NEEDS_MERGE || detached) {
ItemID id(newluid);
bool isParent = id.m_rid.empty();
@ -475,11 +501,13 @@ EvolutionCalendarSource::InsertItemResult EvolutionCalendarSource::insertItem(co
// Therefore we have to use CALOBJ_MOD_ALL, but that removes
// children.
bool hasChildren = false;
BOOST_FOREACH(ItemID existingId, m_allLUIDs) {
if (existingId.m_uid == id.m_uid &&
existingId.m_rid.size()) {
hasChildren = true;
break;
LUIDs::const_iterator it = m_allLUIDs.find(id.m_uid);
if (it != m_allLUIDs.end()) {
BOOST_FOREACH(const string &rid, it->second) {
if (!rid.empty()) {
hasChildren = true;
break;
}
}
}
@ -526,17 +554,17 @@ EvolutionCalendarSource::InsertItemResult EvolutionCalendarSource::insertItem(co
modTime = getItemModTime(newid);
}
return InsertItemResult(newluid, modTime, merged);
return InsertItemResult(newluid, modTime, state);
}
EvolutionCalendarSource::ICalComps_t EvolutionCalendarSource::removeEvents(const string &uid, bool returnOnlyChildren)
{
ICalComps_t events;
BOOST_FOREACH(const string &luid, m_allLUIDs) {
ItemID id(luid);
if (id.m_uid == uid) {
LUIDs::const_iterator it = m_allLUIDs.find(uid);
if (it != m_allLUIDs.end()) {
BOOST_FOREACH(const string &rid, it->second) {
ItemID id(uid, rid);
icalcomponent *icomp = retrieveItem(id);
if (icomp) {
if (id.m_rid.empty() && returnOnlyChildren) {
@ -582,11 +610,21 @@ void EvolutionCalendarSource::removeItem(const string &luid)
ICalComps_t children = removeEvents(id.m_uid, true);
// recreate children
bool first = true;
BOOST_FOREACH(boost::shared_ptr< eptr<icalcomponent> > &icalcomp, children) {
char *uid;
if (first) {
char *uid;
if (!e_cal_create_object(m_calendar, *icalcomp, &uid, &gerror)) {
throwError(string("recreating item ") + luid, gerror);
if (!e_cal_create_object(m_calendar, *icalcomp, &uid, &gerror)) {
throwError(string("recreating first item ") + luid, gerror);
}
first = false;
} else {
if (!e_cal_modify_object(m_calendar, *icalcomp,
CALOBJ_MOD_THIS,
&gerror)) {
throwError(string("recreating following item ") + luid, gerror);
}
}
}
} else if(!e_cal_remove_object_with_mod(m_calendar,
@ -603,7 +641,7 @@ void EvolutionCalendarSource::removeItem(const string &luid)
throwError(string("deleting item " ) + luid, gerror);
}
}
m_allLUIDs.erase(luid);
m_allLUIDs.eraseLUID(id);
if (!id.m_rid.empty()) {
// Removing the child may have modified the parent.
@ -642,8 +680,21 @@ icalcomponent *EvolutionCalendarSource::retrieveItem(const ItemID &id)
if (!comp) {
throwError(string("retrieving item: ") + id.getLUID());
}
eptr<icalcomponent> ptr(comp);
return comp;
/*
* EDS bug: if a parent doesn't exist while a child does, and we ask
* for the parent, we are sent the (first?) child. Detect this and
* turn it into a "not found" error.
*/
if (id.m_rid.empty()) {
struct icaltimetype rid = icalcomponent_get_recurrenceid(comp);
if (!icaltime_is_null_time(rid)) {
throwError(string("retrieving item: got child instead of parent: ") + id.m_uid);
}
}
return ptr.release();
}
string EvolutionCalendarSource::retrieveItemAsString(const ItemID &id)

View File

@ -179,7 +179,15 @@ class EvolutionCalendarSource : public EvolutionSyncSource,
* implemented without the troublesome querying of the EDS
* backend.
*/
set<string> m_allLUIDs;
class LUIDs : public map< string, set<string> > {
public:
bool containsUID(const std::string &uid) const { return findUID(uid) != end(); }
const_iterator findUID(const std::string &uid) const { return find(uid); }
bool containsLUID(const ItemID &id) const;
void insertLUID(const ItemID &id);
void eraseLUID(const ItemID &id);
} m_allLUIDs;
/**
* A list of ref-counted smart pointers to icalcomponents.

View File

@ -330,7 +330,7 @@ EvolutionContactSource::insertItem(const string &uid, const std::string &item, b
throwError("no UID for contact");
}
string newrev = getRevision(newuid);
return InsertItemResult(newuid, newrev, false);
return InsertItemResult(newuid, newrev, ITEM_OKAY);
} else {
throwError(uid.empty() ?
"storing new contact" :
@ -341,7 +341,7 @@ EvolutionContactSource::insertItem(const string &uid, const std::string &item, b
throwError(string("failure parsing vcard " ) + item);
}
// not reached!
return InsertItemResult("", "", false);
return InsertItemResult("", "", ITEM_OKAY);
}
void EvolutionContactSource::removeItem(const string &uid)

View File

@ -122,7 +122,7 @@ EvolutionCalendarSource::InsertItemResult EvolutionMemoSource::insertItem(const
}
bool update = !luid.empty();
bool merged = false;
InsertItemResultState state = ITEM_OKAY;
string newluid = luid;
string modTime;
@ -170,28 +170,31 @@ EvolutionCalendarSource::InsertItemResult EvolutionMemoSource::insertItem(const
}
GError *gerror = NULL;
char *uid = NULL;
if (!update) {
if(!e_cal_create_object(m_calendar, subcomp, &uid, &gerror)) {
const char *uid = NULL;
if(!e_cal_create_object(m_calendar, subcomp, (char **)&uid, &gerror)) {
if (gerror->domain == E_CALENDAR_ERROR &&
gerror->code == E_CALENDAR_STATUS_OBJECT_ID_ALREADY_EXISTS) {
// Deal with error due to adding already existing item.
// Should never happen for plain text journal entries because
// they have no embedded ID, but who knows...
merged = true;
state = ITEM_NEEDS_MERGE;
uid = icalcomponent_get_uid(subcomp);
if (!uid) {
throwError("storing new memo item, no UID set", gerror);
}
g_clear_error(&gerror);
} else {
throwError( "storing new memo item", gerror );
}
} else {
ItemID id(uid, "");
newluid = id.getLUID();
}
ItemID id(uid, "");
newluid = id.getLUID();
if (state != ITEM_NEEDS_MERGE) {
modTime = getItemModTime(id);
}
}
if (update || merged) {
} else {
ItemID id(newluid);
// ensure that the component has the right UID
@ -207,7 +210,7 @@ EvolutionCalendarSource::InsertItemResult EvolutionMemoSource::insertItem(const
modTime = getItemModTime(newid);
}
return InsertItemResult(newluid, modTime, merged);
return InsertItemResult(newluid, modTime, state);
}
bool EvolutionMemoSource::isNativeType(const char *type)

View File

@ -233,7 +233,7 @@ TrackingSyncSource::InsertItemResult FileSyncSource::insertItem(const string &ui
return InsertItemResult(newuid,
getATimeString(filename),
false /* true if adding item was turned into update */);
ITEM_OKAY);
}

View File

@ -433,8 +433,9 @@ SubSyncSource::SubItemResult CalDAVSource::insertSubItem(const std::string &luid
Event &event = loadItem(*it->second);
event.m_etag = res.m_revision;
if (event.m_subids.find(subid) != event.m_subids.end()) {
// was already in that item but caller didn't seem to know
subres.m_merged = true;
// was already in that item but caller didn't seem to know,
// and now we replaced the data on the CalDAV server
subres.m_state = ITEM_REPLACED;
} else {
// add to merged item
event.m_subids.insert(subid);
@ -548,14 +549,11 @@ SubSyncSource::SubItemResult CalDAVSource::insertSubItem(const std::string &luid
}
}
if (davLUID != luid) {
// caller didn't know final UID: if found, the tell him that
// we merged the item for him, if not, then don't complain about
// it not being found (like we do when the item should exist
// but doesn't)
// caller didn't know final UID: if found, then tell him to
// merge the data and try again
if (removeme) {
subres.m_merged = true;
icalcomponent_remove_component(event.m_calendar, removeme);
icalcomponent_free(removeme);
subres.m_state = ITEM_NEEDS_MERGE;
goto done;
} else {
event.m_subids.insert(subid);
}
@ -586,7 +584,7 @@ SubSyncSource::SubItemResult CalDAVSource::insertSubItem(const std::string &luid
try {
SE_LOG_DEBUG(this, NULL, "updating VEVENT");
InsertItemResult res = insertItem(event.m_DAVluid, data, true);
if (res.m_merged ||
if (res.m_state != ITEM_OKAY ||
res.m_luid != event.m_DAVluid) {
// should not merge with anything, if so, our cache was invalid
SE_THROW("CalDAV item not updated as expected");
@ -654,7 +652,7 @@ SubSyncSource::SubItemResult CalDAVSource::insertSubItem(const std::string &luid
eptr<char> icalstr(ical_strdup(icalcomponent_as_ical_string(event.m_calendar)));
std::string data = icalstr.get();
InsertItemResult res = insertItem(event.m_DAVluid, data, true);
if (res.m_merged ||
if (res.m_state != ITEM_OKAY ||
res.m_luid != event.m_DAVluid) {
// should not merge with anything, if so, our cache was invalid
SE_THROW("CalDAV item not updated as expected");
@ -667,6 +665,7 @@ SubSyncSource::SubItemResult CalDAVSource::insertSubItem(const std::string &luid
}
}
done:
return subres;
}
@ -850,7 +849,7 @@ std::string CalDAVSource::removeSubItem(const string &davLUID, const std::string
} else {
res = insertItem(davLUID, icalstr.get(), true);
}
if (res.m_merged ||
if (res.m_state != ITEM_OKAY ||
res.m_luid != davLUID) {
SE_THROW("unexpected result of removing sub event");
}

View File

@ -1020,7 +1020,7 @@ TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid,
{
std::string new_uid;
std::string rev;
bool update = false; /* true if adding item was turned into update */
InsertItemResultState state = ITEM_OKAY;
Timespec deadline = createDeadline(); // no resending if left empty
m_session->startOperation("PUT", deadline);
@ -1087,7 +1087,7 @@ TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid,
SE_LOG_DEBUG(NULL, NULL, "new item mapped to %s", real_luid.c_str());
new_uid = real_luid;
// TODO: find a better way of detecting unexpected updates.
// update = true;
// state = ...
} else if (!rev.empty()) {
// Yahoo Contacts returns an etag, but no href. For items
// that were really created as requested, that's okay. But
@ -1114,7 +1114,7 @@ TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid,
new_uid.c_str(),
revisions.begin()->first.c_str());
new_uid = revisions.begin()->first;
update = true;
state = ITEM_REPLACED;
}
}
} else {
@ -1182,7 +1182,7 @@ TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid,
}
}
return InsertItemResult(new_uid, rev, update);
return InsertItemResult(new_uid, rev, state);
}
std::string WebDAVSource::ETag2Rev(const std::string &etag)
@ -1191,7 +1191,9 @@ std::string WebDAVSource::ETag2Rev(const std::string &etag)
if (boost::starts_with(res, "W/")) {
res.erase(0, 2);
}
if (res.size() >= 2) {
if (res.size() >= 2 &&
res[0] == '"' &&
res[res.size() - 1] == '"') {
res = res.substr(1, res.size() - 2);
}
return res;

View File

@ -141,7 +141,7 @@ TrackingSyncSource::InsertItemResult XMLRPCSyncSource::insertItem(const string &
return InsertItemResult((*it).first,
xmlrpc_c::value_string((*it).second),
false);
ITEM_OKAY);
}

View File

@ -1969,6 +1969,7 @@ sync_config_widget_init (SyncConfigWidget *self)
self->description_label = gtk_label_new ("");
gtk_misc_set_alignment (GTK_MISC (self->description_label), 0.0, 0.5);
gtk_widget_set_size_request (self->description_label, 700, -1);
gtk_label_set_line_wrap (GTK_LABEL (self->description_label), TRUE);
gtk_box_pack_start (GTK_BOX (tmp_box), self->description_label, FALSE, FALSE, 0);
tmp_box = gtk_hbox_new (FALSE, 0);

View File

@ -1167,6 +1167,22 @@ bool Cmdline::run() {
sysync::TSyError err;
#define CHECK_ERROR(_op) if (err) { SE_THROW_EXCEPTION_STATUS(StatusException, string(source->getName()) + ": " + (_op), SyncMLStatus(err)); }
// acquire passwords before doing anything (interactive password
// access not supported for the command line)
{
ConfigPropertyRegistry& registry = SyncConfig::getRegistry();
BOOST_FOREACH(const ConfigProperty *prop, registry) {
prop->checkPassword(*context, m_server, *context->getProperties());
}
}
{
ConfigPropertyRegistry &registry = SyncSourceConfig::getRegistry();
BOOST_FOREACH(const ConfigProperty *prop, registry) {
prop->checkPassword(*context, m_server, *context->getProperties(),
source->getName(), sourceNodes.getProperties());
}
}
source->open();
const SyncSource::Operations &ops = source->getOperations();
if (m_printItems) {
@ -1176,11 +1192,6 @@ bool Cmdline::run() {
source->throwError("reading items not supported");
}
ConfigPropertyRegistry& registry = SyncConfig::getRegistry();
BOOST_FOREACH(const ConfigProperty *prop, registry) {
prop->checkPassword(*context, m_server, *context->getProperties());
}
err = ops.m_startDataRead("", "");
CHECK_ERROR("reading items");
list<string> luids;

View File

@ -288,12 +288,15 @@ SyncSourceRaw::InsertItemResult MapSyncSource::insertItem(const std::string &lui
{
StringPair ids = splitLUID(luid);
SubSyncSource::SubItemResult res = m_sub->insertSubItem(ids.first, ids.second, item);
SubRevisionEntry &entry = m_revisions[res.m_mainid];
entry.m_uid = res.m_uid;
entry.m_revision = res.m_revision;
entry.m_subids.insert(res.m_subid);
// anything changed?
if (res.m_state != ITEM_NEEDS_MERGE) {
SubRevisionEntry &entry = m_revisions[res.m_mainid];
entry.m_uid = res.m_uid;
entry.m_revision = res.m_revision;
entry.m_subids.insert(res.m_subid);
}
return SyncSourceRaw::InsertItemResult(createLUID(res.m_mainid, res.m_subid),
res.m_revision, res.m_merged);
res.m_revision, res.m_state);
}
void MapSyncSource::readItem(const std::string &luid, std::string &item)

View File

@ -66,7 +66,7 @@ class SubSyncSource : virtual public SyncSourceBase
class SubItemResult {
public:
SubItemResult() :
m_merged(false)
m_state(ITEM_OKAY)
{}
/**
@ -78,25 +78,25 @@ class SubSyncSource : virtual public SyncSourceBase
* @param uid an arbitrary string, stored, but not used by MapSyncSource;
* used in the CalDAV backend to associate mainid (= resource path)
* with UID (= part of the item content, but with special semantic)
* @param merged set this to true if an existing sub item was updated instead of adding it
* @param state report about what was done with the data
*/
SubItemResult(const string &mainid,
const string &subid,
const string &revision,
const string &uid,
bool merged) :
InsertItemResultState state) :
m_mainid(mainid),
m_subid(subid),
m_revision(revision),
m_uid(uid),
m_merged(merged)
m_state(state)
{}
string m_mainid;
string m_subid;
string m_revision;
string m_uid;
bool m_merged;
InsertItemResultState m_state;
};
SubSyncSource() : m_parent(NULL) {}

View File

@ -2587,7 +2587,11 @@ ConfigPasswordKey DatabasePasswordConfigProperty::getPasswordKey(const string &d
{
ConfigPasswordKey key;
key.user = sourcePropUser.getProperty(*sourceConfigNode);
key.object = serverName;
std::string configName = SyncConfig::normalizeConfigString(serverName, SyncConfig::NORMALIZE_LONG_FORMAT);
std::string peer, context;
SyncConfig::splitConfigString(configName, peer, context);
key.object = "@";
key.object += context;
key.object += " ";
key.object += sourceName;
key.object += " backend";

View File

@ -1950,11 +1950,11 @@ void SyncContext::initSources(SourceList &sourceList)
BOOST_FOREACH(const string &name, configuredSources) {
boost::shared_ptr<PersistentSyncSourceConfig> sc(getSyncSourceConfig(name));
SyncSourceNodes source = getSyncSourceNodes (name);
SourceType sourceType = SyncSource::getSourceType(source);
// is the source enabled?
string sync = sc->getSync();
bool enabled = sync != "disabled";
if (enabled) {
SourceType sourceType = SyncSource::getSourceType(source);
if (sourceType.m_backend == "virtual") {
//This is a virtual sync source, check and enable the referenced
//sub syncsources here
@ -1995,12 +1995,12 @@ void SyncContext::initSources(SourceList &sourceList)
boost::shared_ptr<PersistentSyncSourceConfig> sc(getSyncSourceConfig(name));
SyncSourceNodes source = getSyncSourceNodes (name);
SourceType sourceType = SyncSource::getSourceType(source);
// is the source enabled?
string sync = sc->getSync();
bool enabled = sync != "disabled";
if (enabled) {
SourceType sourceType = SyncSource::getSourceType(source);
if (sourceType.m_backend != "virtual") {
SyncSourceParams params(name,
source,

View File

@ -668,6 +668,19 @@ sysync::TSyError SyncSourceSerialize::insertItemAsKey(sysync::KeyH aItemKey, sys
InsertItemResult inserted =
insertItem(!aID ? "" : aID->item, data.get());
newID->item = StrAlloc(inserted.m_luid.c_str());
switch (inserted.m_state) {
case ITEM_OKAY:
break;
case ITEM_REPLACED:
res = sysync::DB_DataReplaced;
break;
case ITEM_MERGED:
res = sysync::DB_DataMerged;
break;
case ITEM_NEEDS_MERGE:
res = sysync::DB_Conflict;
break;
}
}
return res;

View File

@ -1420,6 +1420,53 @@ class SyncSourceDelete : virtual public SyncSourceBase {
sysync::TSyError deleteItemSynthesis(sysync::cItemID aID);
};
enum InsertItemResultState {
/**
* item added or updated as requested
*/
ITEM_OKAY,
/**
* When a backend is asked to add an item and recognizes
* that the item matches an already existing item, it may
* replace that item instead of creating a duplicate. In this
* case it must return ITEM_REPLACED and set the luid/revision
* of that updated item.
*
* This can happen when such an item was added concurrently to
* the running sync or, more likely, was reported as new by
* the backend and the engine failed to find the match because
* it doesn't know about some special semantic, like iCalendar
* 2.0 UID).
*
* Note that depending on the age of the items, the older data
* will replace the more recent one when always using item
* replacement.
*/
ITEM_REPLACED,
/**
* Same as ITEM_REPLACED, except that the backend did some
* modifications to the data that was sent to it before
* storing it, like merging it with the existing item. The
* engine will treat the updated item as modified and send
* back the update to the peer as soon as possible. In server
* mode that will be in the same sync session, in a client in
* the next session (client cannot send changes after having
* received data from the server).
*/
ITEM_MERGED,
/**
* As before, a match against an existing item was detected.
* By returning this state and the luid of the matched item
* (revision not needed) the engine is instructed to do the
* necessary data comparison and merging itself. Useful when a
* backend can't do the necessary merging itself.
*/
ITEM_NEEDS_MERGE
};
/**
* an interface for reading and writing items in the internal
* format; see SyncSourceSerialize for an explanation
@ -1429,26 +1476,26 @@ class SyncSourceRaw : virtual public SyncSourceBase {
class InsertItemResult {
public:
InsertItemResult() :
m_merged(false)
m_state(ITEM_OKAY)
{}
/**
* @param luid the LUID after the operation; during an update the LUID must
* not be changed, so return the original one here
* @param revision the revision string after the operation; leave empty if not used
* @param merged set this to true if an existing item was updated instead of adding it
* @param state report about what was done with the data
*/
InsertItemResult(const string &luid,
const string &revision,
bool merged) :
InsertItemResultState state) :
m_luid(luid),
m_revision(revision),
m_merged(merged)
m_state(state)
{}
string m_luid;
string m_revision;
bool m_merged;
InsertItemResultState m_state;
};
/** same as SyncSourceSerialize::insertItem(), but with internal format */

View File

@ -140,14 +140,18 @@ std::string TrackingSyncSource::endSync(bool success)
TrackingSyncSource::InsertItemResult TrackingSyncSource::insertItem(const std::string &luid, const std::string &item)
{
InsertItemResult res = insertItem(luid, item, false);
updateRevision(*m_trackingNode, luid, res.m_luid, res.m_revision);
if (res.m_state != ITEM_NEEDS_MERGE) {
updateRevision(*m_trackingNode, luid, res.m_luid, res.m_revision);
}
return res;
}
TrackingSyncSource::InsertItemResult TrackingSyncSource::insertItemRaw(const std::string &luid, const std::string &item)
{
InsertItemResult res = insertItem(luid, item, true);
updateRevision(*m_trackingNode, luid, res.m_luid, res.m_revision);
if (res.m_state != ITEM_NEEDS_MERGE) {
updateRevision(*m_trackingNode, luid, res.m_luid, res.m_revision);
}
return res;
}

View File

@ -384,6 +384,9 @@
<parameter name="TZID" default="no" show="yes">
<value field="EXDATES" conversion="TZID"/>
</parameter>
<parameter name="VALUE" default="no" show="yes">
<value field="EXDATES" conversion="VALUETYPE"/>
</parameter>
</property>
<property name="EXDATE" values="list" suppressempty="yes" onlyformode="old" delayedparsing="1" valueseparator=";" altvalueseparator=",">

View File

@ -133,12 +133,21 @@ class TestingSyncSourcePtr : public std::auto_ptr<TestingSyncSource>
typedef std::auto_ptr<TestingSyncSource> base_t;
static StringMap m_anchors;
static std::string m_testName;
public:
TestingSyncSourcePtr() {}
TestingSyncSourcePtr(TestingSyncSource *source) :
base_t(source)
{
// reset anchors each time a new test starts,
// because it avoids interactions between tests
std::string testName = getCurrentTest();
if (testName != m_testName) {
m_anchors.clear();
m_testName = testName;
}
CPPUNIT_ASSERT(source);
source->open();
string node = source->getTrackingNode()->getName();
@ -180,6 +189,7 @@ public:
};
StringMap TestingSyncSourcePtr::m_anchors;
std::string TestingSyncSourcePtr::m_testName;
bool SyncOptions::defaultWBXML()
{
@ -329,6 +339,16 @@ std::string LocalTests::insert(CreateSource createSource, const std::string &dat
SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw("", mangled));
CPPUNIT_ASSERT(!res.m_luid.empty());
bool updated = false;
if (res.m_state == ITEM_NEEDS_MERGE) {
// conflict detected, overwrite existing item as done in the past
std::string luid = res.m_luid;
SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw(luid, mangled));
CPPUNIT_ASSERT_EQUAL(luid, res.m_luid);
CPPUNIT_ASSERT(res.m_state == ITEM_OKAY);
updated = true;
}
// delete source again
CPPUNIT_ASSERT_NO_THROW(source.reset());
@ -337,7 +357,7 @@ std::string LocalTests::insert(CreateSource createSource, const std::string &dat
// - a new item was added
// - the item was matched against an existing one
CPPUNIT_ASSERT_NO_THROW(source.reset(createSource()));
CPPUNIT_ASSERT_EQUAL(numItems + (res.m_merged ? 0 : 1),
CPPUNIT_ASSERT_EQUAL(numItems + ((res.m_state == ITEM_REPLACED || res.m_state == ITEM_MERGED || updated) ? 0 : 1),
countItems(source.get()));
CPPUNIT_ASSERT(countNewItems(source.get()) == 0);
CPPUNIT_ASSERT(countUpdatedItems(source.get()) == 0);
@ -394,8 +414,11 @@ void LocalTests::update(CreateSource createSource, const std::string &data, bool
SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin());
CPPUNIT_ASSERT(it != source->getAllItems().end());
string luid = *it;
SOURCE_ASSERT_NO_FAILURE(source.get(), source->insertItemRaw(luid, config.m_mangleItem(data, true)));
SyncSourceRaw::InsertItemResult res;
SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw(luid, config.m_mangleItem(data, true)));
CPPUNIT_ASSERT_NO_THROW(source.reset());
CPPUNIT_ASSERT_EQUAL(luid, res.m_luid);
CPPUNIT_ASSERT_EQUAL(ITEM_OKAY, res.m_state);
if (!check) {
return;
@ -720,7 +743,8 @@ void LocalTests::testLocalDeleteAll() {
// clean database, then insert
void LocalTests::testComplexInsert() {
testLocalDeleteAll();
CPPUNIT_ASSERT(config.m_createSourceA);
CPPUNIT_ASSERT_NO_THROW(deleteAll(createSourceA));
testSimpleInsert();
testIterateTwice();
}
@ -729,8 +753,10 @@ void LocalTests::testComplexInsert() {
void LocalTests::testLocalUpdate() {
// check additional requirements
CPPUNIT_ASSERT(!config.m_updateItem.empty());
CPPUNIT_ASSERT(config.m_createSourceA);
CPPUNIT_ASSERT_NO_THROW(deleteAll(createSourceA));
testLocalDeleteAll();
testSimpleInsert();
CPPUNIT_ASSERT_NO_THROW(update(createSourceA, config.m_updateItem));
}
@ -741,8 +767,9 @@ void LocalTests::testChanges() {
// check additional requirements
CPPUNIT_ASSERT(config.m_createSourceB);
CPPUNIT_ASSERT(config.m_createSourceA);
testLocalDeleteAll();
CPPUNIT_ASSERT_NO_THROW(deleteAll(createSourceA));
testSimpleInsert();
// clean changes in sync source B by creating and closing it
@ -861,8 +888,9 @@ void LocalTests::testImport() {
CPPUNIT_ASSERT(config.m_dump);
CPPUNIT_ASSERT(config.m_compare);
CPPUNIT_ASSERT(!config.m_testcases.empty());
CPPUNIT_ASSERT(config.m_createSourceA);
testLocalDeleteAll();
CPPUNIT_ASSERT_NO_THROW(deleteAll(createSourceA));
// import via sync source A
TestingSyncSourcePtr source;
@ -892,7 +920,7 @@ void LocalTests::testImportDelete() {
// delete again, because it was observed that this did not
// work right with calendars in SyncEvolution
testLocalDeleteAll();
CPPUNIT_ASSERT_NO_THROW(deleteAll(createSourceA));
}
// test change tracking with large number of items
@ -1811,6 +1839,12 @@ void SyncTests::addTests(bool isFirstSource) {
ADD_TEST(SyncTests, testSlowSyncSemantic);
ADD_TEST(SyncTests, testComplexRefreshFromServerSemantic);
ADD_TEST(SyncTests, testDeleteBothSides);
if (config.m_updateItem.find("UID:") != std::string::npos &&
config.m_updateItem.find("LAST-MODIFIED:") != std::string::npos &&
sources.size() == 1) {
ADD_TEST(SyncTests, testAddBothSides);
ADD_TEST(SyncTests, testAddBothSidesRefresh);
}
// only add when testing individual source,
// test data not guaranteed to be available for all sources
@ -3072,6 +3106,207 @@ void SyncTests::testDeleteBothSides()
}
}
/**
* - clean A, server, B
* - create an item on A
* - sync A
* - create a modified version of the item on B
* - sync B
*
* Depends on UID and LAST-MODIFIED in item data, i.e., iCalendar 2.0.
* Uses the normal "insertItem" test case. Only works for a single source.
*
* The server must not duplicate the item *and* preserve the modified
* properties.
*
* Temporary: because conflict resolution is server-dependent, such a strict
* test fails. For example, with SyncEvolution 1.2 as server, DESCRIPTION and
* LOCATION end up being concatenated (merge=lines mode). The test now avoids
* using different data, with the expected outcome that only one item
* is present at the end and no unnecessary data transfers happen (only true
* for SyncEvolution server).
*
* A similar situation occurs on the client side, but it is harder to
* trigger: the updated item must be added to the client's database
* after it has reported its changes. Because if it happens earlier,
* it would send an Add to the server and the server would have to
* resolve the add<->add conflict, as in this test here.
*/
// using updated item data makes the test harder to pass:
// server must use exactly the right item, which currently
// is not the case for SyncEvolution
bool addBothSidesUsesUpdateItem = true;
// SyncEvolution passes with addBothSidesUsesUpdateItem == true
// if we avoid changes to those properties in the iCalendar test
// set which currently use merge=lines.
bool addBothSidesNoMergeLines=true;
// if true, relax expectations for updates from server:
// may or may not send one
bool addBothSidesMayUpdate = false;
// if true, then accept that the Synthesis server mode counts
// Add commands as "added items" even if they are turned into updates
bool addBothSidesAddStatsBroken = false;
void SyncTests::testAddBothSides()
{
deleteAll();
accessClientB->deleteAll();
std::string insertItem = sources[0].second->config.m_insertItem;
std::string updateItem = sources[0].second->config.m_updateItem;
if (addBothSidesNoMergeLines) {
boost::replace_all(updateItem, "LOCATION:big meeting room", "LOCATION:my office");
boost::replace_all(updateItem, "DESCRIPTION:nice to see you", "DESCRIPTION:let's talk<<REVISION>>");
}
CPPUNIT_ASSERT_NO_THROW(sources[0].second->insert(sources[0].second->createSourceA,
insertItem));
doSync("send-old",
SyncOptions(SYNC_TWO_WAY,
CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
// insert updated item data on B
std::string data;
CPPUNIT_ASSERT_NO_THROW(accessClientB->sources[0].second->insert(accessClientB->sources[0].second->createSourceA,
addBothSidesUsesUpdateItem ?
updateItem:
insertItem,
false,
&data));
// As far as the client knows, it is adding an item;
// server not expected to send back an update (our data was more recent
// and completely overwrites the server's data).
// When acting as server, we do the duplicate detection and thus know
// more about the actual outcome.
accessClientB->doSync("send-update",
SyncOptions(SYNC_TWO_WAY,
isServerMode() ?
CheckSyncReport(addBothSidesAddStatsBroken ? -1 : 0,0,0,
0,
addBothSidesMayUpdate ? -1 :
addBothSidesUsesUpdateItem ? 1 : 0,
0,
true, SYNC_TWO_WAY) :
CheckSyncReport(0,
addBothSidesMayUpdate ? -1 : 0,
0,
// client doesn't know that the add
// was an update, in contrast to server
1,0,0, true, SYNC_TWO_WAY)));
// update sent to client A
doSync("update",
SyncOptions(SYNC_TWO_WAY,
CheckSyncReport(0,
addBothSidesMayUpdate ? -1 :
addBothSidesUsesUpdateItem ? 1 : 0,
0,
0,0,0, true, SYNC_TWO_WAY)));
// nothing necessary for client B
accessClientB->doSync("nop",
SyncOptions(SYNC_TWO_WAY,
CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY)));
// now compare client A against reference data
TestingSyncSourcePtr copy;
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(sources[0].second->createSourceB()));
sources[0].second->compareDatabases(*copy, &data, (void *)NULL);
CPPUNIT_ASSERT_NO_THROW(copy.reset());
}
/**
* compared to testAddBothSides the age of the items is reversed now;
* a server which always copies the client's data passes testAddBothSides
* but fails here
*/
void SyncTests::testAddBothSidesRefresh()
{
deleteAll();
accessClientB->deleteAll();
std::string insertItem = sources[0].second->config.m_insertItem;
std::string updateItem = sources[0].second->config.m_updateItem;
if (addBothSidesNoMergeLines) {
boost::replace_all(updateItem, "LOCATION:big meeting room", "LOCATION:my office");
boost::replace_all(updateItem, "DESCRIPTION:nice to see you", "DESCRIPTION:let's talk<<REVISION>>");
}
// insert initial item data on B
CPPUNIT_ASSERT_NO_THROW(accessClientB->sources[0].second->insert(accessClientB->sources[0].second->createSourceA,
insertItem));
// sleep one second to ensure that it's mangled LAST-MODIFIED is older than
// the one from the next item, inserted on A
sleep(1);
// more recent data sent to server first
std::string data;
CPPUNIT_ASSERT_NO_THROW(sources[0].second->insert(sources[0].second->createSourceA,
addBothSidesUsesUpdateItem ?
updateItem :
insertItem,
false,
&data));
doSync("send-new",
SyncOptions(SYNC_TWO_WAY,
CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
// As far as the client knows, it is adding an item;
// server expected to send back an update (client's data was out-dated);
// When acting as server, we do the duplicate detection and thus
// know more about the actual outcome.
accessClientB->doSync("send-old",
SyncOptions(SYNC_TWO_WAY,
isServerMode() ?
CheckSyncReport(addBothSidesAddStatsBroken ? -1 : 0,
addBothSidesMayUpdate ? -1 :
addBothSidesUsesUpdateItem ? 1 : 0,
0,
0,
addBothSidesMayUpdate ? -1 : 0,
0,
true, SYNC_TWO_WAY) :
CheckSyncReport(0,
addBothSidesMayUpdate ? -1 :
addBothSidesUsesUpdateItem ? 1 : 0,
0,
// client doesn't know that add was
// an update
1,0,0, true, SYNC_TWO_WAY)));
// update sent to client A (result of merge)
doSync("nopA",
SyncOptions(SYNC_TWO_WAY,
CheckSyncReport(0,addBothSidesMayUpdate ? -1 : 0,0, 0,0,0, true, SYNC_TWO_WAY)));
// nothing necessary for client B (already synchronized completely above in one sync)
accessClientB->doSync("nopB",
SyncOptions(SYNC_TWO_WAY,
CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY)));
// now compare client A against reference data
TestingSyncSourcePtr copy;
SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(sources[0].second->createSourceB()));
sources[0].second->compareDatabases(*copy, &data, (void *)NULL);
CPPUNIT_ASSERT_NO_THROW(copy.reset());
}
/**
* - adds parent on client A
* - syncs A
@ -4586,7 +4821,7 @@ void ClientTest::getTestData(const char *type, Config &config)
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
"VERSION:2.0\n"
"BEGIN:VEVENT\n"
"SUMMARY:phone meeting\n"
"SUMMARY:phone meeting - old\n"
"DTEND:20060406T163000Z\n"
"DTSTART:20060406T160000Z\n"
"UID:1234567890!@#$%^&*()<>@dummy\n"
@ -4605,7 +4840,7 @@ void ClientTest::getTestData(const char *type, Config &config)
"PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
"VERSION:2.0\n"
"BEGIN:VEVENT\n"
"SUMMARY:meeting on site\n"
"SUMMARY:meeting on site - updated\n"
"DTEND:20060406T163000Z\n"
"DTSTART:20060406T160000Z\n"
"UID:1234567890!@#$%^&*()<>@dummy\n"

View File

@ -753,6 +753,8 @@ protected:
virtual void testSlowSyncSemantic();
virtual void testComplexRefreshFromServerSemantic();
virtual void testDeleteBothSides();
virtual void testAddBothSides();
virtual void testAddBothSidesRefresh();
virtual void testLinkedItemsParentChild();
virtual void testLinkedItemsChild();
virtual void testLinkedItemsChildParent();

View File

@ -821,6 +821,10 @@ test = SyncEvolutionTest("googlecalendar", compile,
"CLIENT_TEST_SIMPLE_UID=1 " # server gets confused by UID with special characters
"CLIENT_TEST_UNIQUE_UID=1 " # server keeps backups and restores old data unless UID is unieque
"CLIENT_TEST_MODE=server " # for Client::Sync
"CLIENT_TEST_FAILURES="
# http://code.google.com/p/google-caldav-issues/issues/detail?id=61 "cannot remove detached recurrence"
"Client::Source::google_caldav::LinkedItems_0::testLinkedItemsRemoveNormal,"
"Client::Source::google_caldav::LinkedItems_1::testLinkedItemsRemoveNormal,"
,
testPrefix=options.testprefix)
context.add(test)
@ -856,7 +860,6 @@ test = SyncEvolutionTest("apple", compile,
"CLIENT_TEST_WEBDAV='apple caldav carddav' "
"CLIENT_TEST_NUM_ITEMS=250 " # test is local, so we can afford a higher number
"CLIENT_TEST_ALARM=2400 " # but even with a local server does the test run a long time
"CLIENT_TEST_SIMPLE_UID=1 " # server gets confused by UID with special characters
"CLIENT_TEST_MODE=server " # for Client::Sync
,
testPrefix=options.testprefix)
@ -1002,6 +1005,12 @@ class FunambolTest(SyncEvolutionTest):
"eds_task",
"eds_memo" ],
"CLIENT_TEST_SKIP="
# server duplicates items in add<->add conflict because it
# does not check UID
"Client::Sync::eds_event::testAddBothSides,"
"Client::Sync::eds_event::testAddBothSidesRefresh,"
"Client::Sync::eds_task::testAddBothSides,"
"Client::Sync::eds_task::testAddBothSidesRefresh,"
# test cannot pass because we don't have CtCap info about
# the Funambol server
"Client::Sync::eds_contact::testExtensions,"
@ -1101,6 +1110,12 @@ mobicaltest = SyncEvolutionTest("mobical", compile,
"CLIENT_TEST_NOCHECK_SYNCMODE=1 "
"CLIENT_TEST_MAX_ITEMSIZE=2048 "
"CLIENT_TEST_SKIP="
# server duplicates items in add<->add conflict because it
# does not check UID
"Client::Sync::eds_event::testAddBothSides,"
"Client::Sync::eds_event::testAddBothSidesRefresh,"
"Client::Sync::eds_task::testAddBothSides,"
"Client::Sync::eds_task::testAddBothSidesRefresh,"
"Client::Sync::eds_contact::Retry,"
"Client::Sync::eds_contact::Suspend,"
"Client::Sync::eds_contact::Resend,"
@ -1178,6 +1193,12 @@ memotootest = SyncEvolutionTest("memotoo", compile,
"CLIENT_TEST_NOCHECK_SYNCMODE=1 "
"CLIENT_TEST_NUM_ITEMS=10 "
"CLIENT_TEST_SKIP="
# server duplicates items in add<->add conflict because it
# does not check UID
"Client::Sync::eds_event::testAddBothSides,"
"Client::Sync::eds_event::testAddBothSidesRefresh,"
"Client::Sync::eds_task::testAddBothSides,"
"Client::Sync::eds_task::testAddBothSidesRefresh,"
"Client::Sync::eds_contact::Retry,"
"Client::Sync::eds_contact::Suspend,"
# "Client::Sync::eds_contact::testRefreshFromClientSync,"

View File

@ -612,3 +612,17 @@
...
obj:*libgiognutls.so
}
{
gnutls + libneon: ASN buffer
Memcheck:Addr4
fun:asn1_der_coding
...
obj:*libneon*
}
{
gnutls certificate: ASN buffer
Memcheck:Addr4
fun:asn1_der_coding
...
fun:gnutls_certificate_set_x509_trust_file
}

View File

@ -3,7 +3,7 @@ VERSION:3.0
NICKNAME:user17
NOTE:triggers parser bug in Funambol 3.0: trailing = is mistaken for soft line break=
FN:parserbug=
N:parserbug=
N:parserbug=;;;;
X-EVOLUTION-FILE-AS:parserbug=
END:VCARD
@ -12,7 +12,7 @@ VERSION:3.0
NICKNAME:user16
NOTE:test case with empty email
FN:incomplete
N:incomplete
N:incomplete;;;;
EMAIL:
X-EVOLUTION-FILE-AS:incomplete
END:VCARD
@ -162,7 +162,7 @@ X-EVOLUTION-MANAGER:John Doe Senior
X-EVOLUTION-ASSISTANT:John Doe Junior
NICKNAME:user1
BDAY:2006-01-08
X-FOOBAR-EXTENSION;X-FOOBAR-PARAMETER=foobar:has to be stored internally by engine and preserved in testExtensions test; never sent to a peer
X-FOOBAR-EXTENSION;X-FOOBAR-PARAMETER=foobar:has to be stored internally by engine and preserved in testExtensions test\; never sent to a peer
X-TEST;PARAMETER1=nonquoted;PARAMETER2="quoted because of spaces":Content with\nMultiple\nText lines\nand national chars: äöü
X-EVOLUTION-ANNIVERSARY:2006-01-09
X-EVOLUTION-SPOUSE:Joan Doe

View File

@ -0,0 +1,9 @@
@@ -163,7 +163,7 @@
NICKNAME:user1
BDAY:2006-01-08
X-FOOBAR-EXTENSION;X-FOOBAR-PARAMETER=foobar:has to be stored internally by engine and preserved in testExtensions test\; never sent to a peer
-X-TEST;PARAMETER1=nonquoted;PARAMETER2="quoted because of spaces":Content with\nMultiple\nText lines\nand national chars: äöü
+X-TEST;PARAMETER1=nonquoted:Content with\nMultiple\nText lines\nand national chars: äöü
X-EVOLUTION-ANNIVERSARY:2006-01-09
X-EVOLUTION-SPOUSE:Joan Doe
NOTE:This is a test case which uses almost all Evolution fields.

View File

@ -3,7 +3,7 @@
NICKNAME:user17
NOTE:triggers parser bug in Funambol 3.0: trailing = is mistaken for soft line break=
-FN:parserbug=
N:parserbug=
N:parserbug=;;;;
X-EVOLUTION-FILE-AS:parserbug=
END:VCARD
@@ -11,7 +10,6 @@
@ -11,7 +11,7 @@
NICKNAME:user16
NOTE:test case with empty email
-FN:incomplete
N:incomplete
N:incomplete;;;;
EMAIL:
X-EVOLUTION-FILE-AS:incomplete
@@ -24,7 +22,6 @@
@ -113,7 +113,7 @@
END:VCARD
@@ -164,51 +153,35 @@
BDAY:2006-01-08
X-FOOBAR-EXTENSION;X-FOOBAR-PARAMETER=foobar:has to be stored internally by engine and preserved in testExtensions test; never sent to a peer
X-FOOBAR-EXTENSION;X-FOOBAR-PARAMETER=foobar:has to be stored internally by engine and preserved in testExtensions test\; never sent to a peer
X-TEST;PARAMETER1=nonquoted;PARAMETER2="quoted because of spaces":Content with\nMultiple\nText lines\nand national chars: äöü
-X-EVOLUTION-ANNIVERSARY:2006-01-09
-X-EVOLUTION-SPOUSE:Joan Doe

View File

@ -1,14 +1,14 @@
@@ -4,6 +4,7 @@
NOTE:triggers parser bug in Funambol 3.0: trailing = is mistaken for soft line break=
FN:parserbug=
N:parserbug=
N:parserbug=;;;;
+URL:http://john.doe.com
X-EVOLUTION-FILE-AS:parserbug=
END:VCARD
@@ -14,6 +15,7 @@
FN:incomplete
N:incomplete
N:incomplete;;;;
EMAIL:
+URL:http://john.doe.com
X-EVOLUTION-FILE-AS:incomplete
@ -82,7 +82,7 @@
NICKNAME:user1
-BDAY:2006-01-08
+BDAY:2007-03-01
X-FOOBAR-EXTENSION;X-FOOBAR-PARAMETER=foobar:has to be stored internally by engine and preserved in testExtensions test; never sent to a peer
X-FOOBAR-EXTENSION;X-FOOBAR-PARAMETER=foobar:has to be stored internally by engine and preserved in testExtensions test\; never sent to a peer
X-TEST;PARAMETER1=nonquoted;PARAMETER2="quoted because of spaces":Content with\nMultiple\nText lines\nand national chars: äöü
X-EVOLUTION-ANNIVERSARY:2006-01-09
@@ -184,7 +193,6 @@

View File

@ -9,7 +9,7 @@
X-EVOLUTION-ASSISTANT:John Doe Junior
NICKNAME:user1
BDAY:2006-01-08
X-FOOBAR-EXTENSION;X-FOOBAR-PARAMETER=foobar:has to be stored internally by engine and preserved in testExtensions test; never sent to a peer
X-FOOBAR-EXTENSION;X-FOOBAR-PARAMETER=foobar:has to be stored internally by engine and preserved in testExtensions test\; never sent to a peer
-X-TEST;PARAMETER1=nonquoted;PARAMETER2="quoted because of spaces":Content with\nMultiple\nText lines\nand national chars: äöü
+X-TEST;PARAMETER1=nonquoted;PARAMETER2="quoted because of spaces":Text with\nMultiple\nText lines\nand national chars: äöü
X-EVOLUTION-ANNIVERSARY:2006-01-09

View File

@ -150,7 +150,7 @@
X-AIM;X-EVOLUTION-UI-SLOT=1:AIM JOHN
X-YAHOO;X-EVOLUTION-UI-SLOT=2:YAHOO JDOE
X-ICQ;X-EVOLUTION-UI-SLOT=3:ICQ JD
@@ -211,226 +120,3 @@
@@ -210,226 +119,3 @@
X-SKYPE:SKYPE DOE
X-SIP:SIP DOE
END:VCARD

View File

@ -263,6 +263,101 @@ DESCRIPTION:second instance modified
END:VEVENT
END:VCALENDAR
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
BEGIN:VEVENT
UID:20080407T193125Z-19554-727-1-50@gollum
DTSTAMP:20080407T193125Z
DTSTART:20080413T100000Z
DTEND:20080413T103000Z
TRANSP:OPAQUE
SEQUENCE:7
SUMMARY:Recurring: Modified II
CLASS:PUBLIC
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193647
RECURRENCE-ID:20080420T090000Z
DESCRIPTION:third instance modified\, different time
END:VEVENT
END:VCALENDAR
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
BEGIN:VEVENT
UID:20080407T193125Z-19554-727-1-50-XX@gollum
DTSTAMP:20080407T193125Z
DTSTART:20080406T090000Z
DTEND:20080406T093000Z
TRANSP:OPAQUE
SEQUENCE:2
SUMMARY:Recurring 2
DESCRIPTION:recurs each Sunday\, 10 times
CLASS:PUBLIC
RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;UNTIL=20080608T090000Z
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193241
END:VEVENT
END:VCALENDAR
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
BEGIN:VEVENT
UID:20080407T193125Z-19554-727-1-50-XX@gollum
DTSTAMP:20080407T193125Z
DTSTART:20080413T090000Z
DTEND:20080413T093000Z
TRANSP:OPAQUE
SEQUENCE:7
SUMMARY:Recurring 2: Modified
CLASS:PUBLIC
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193647
RECURRENCE-ID:20080413T090000Z
DESCRIPTION:second instance modified\, single detached recurrence
END:VEVENT
END:VCALENDAR
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
BEGIN:VEVENT
UID:20080407T193125Z-19554-727-1-50-YY@gollum
DTSTAMP:20080407T193125Z
DTSTART:20080413T090000Z
DTEND:20080413T093000Z
TRANSP:OPAQUE
SEQUENCE:7
SUMMARY:Recurring 3: Modified
CLASS:PUBLIC
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193647
RECURRENCE-ID:20080413T090000Z
DESCRIPTION:second instance modified
END:VEVENT
END:VCALENDAR
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
BEGIN:VEVENT
UID:20080407T193125Z-19554-727-1-50-YY@gollum
DTSTAMP:20080407T193125Z
DTSTART:20080413T100000Z
DTEND:20080413T103000Z
TRANSP:OPAQUE
SEQUENCE:7
SUMMARY:Recurring 3: Modified II
CLASS:PUBLIC
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193647
RECURRENCE-ID:20080420T090000Z
DESCRIPTION:third instance modified\, different time
END:VEVENT
END:VCALENDAR
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0

View File

@ -134,8 +134,8 @@
CREATED:20060416T205003Z
LAST-MODIFIED:20060416T205003Z
END:VEVENT
@@ -262,37 +147,3 @@
DESCRIPTION:second instance modified
@@ -357,37 +242,3 @@
DESCRIPTION:third instance modified\, different time
END:VEVENT
END:VCALENDAR
-

View File

@ -125,8 +125,8 @@
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193241
END:VEVENT
@@ -262,37 +237,3 @@
DESCRIPTION:second instance modified
@@ -357,37 +332,3 @@
DESCRIPTION:third instance modified\, different time
END:VEVENT
END:VCALENDAR
-

View File

@ -198,7 +198,7 @@
TRANSP:TRANSPARENT
SEQUENCE:4
SUMMARY:all fields
@@ -204,34 +313,29 @@
@@ -205,34 +313,29 @@
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
@ -253,7 +253,7 @@
TRANSP:OPAQUE
SEQUENCE:2
SUMMARY:Recurring
@@ -246,52 +350,36 @@
@@ -247,18 +350,36 @@
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
@ -293,6 +293,157 @@
DESCRIPTION:second instance modified
END:VEVENT
END:VCALENDAR
@@ -266,18 +387,36 @@
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:/softwarestudio.org/Olson_20011030_5/Europe/Berlin
+X-LIC-LOCATION:Europe/Berlin
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+TZNAME:CEST
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+TZNAME:CET
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
+END:STANDARD
+END:VTIMEZONE
BEGIN:VEVENT
UID:20080407T193125Z-19554-727-1-50@gollum
DTSTAMP:20080407T193125Z
-DTSTART:20080413T100000Z
-DTEND:20080413T103000Z
+DTSTART;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:20080413T120000
+DTEND;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:20080413T123000
TRANSP:OPAQUE
SEQUENCE:7
SUMMARY:Recurring: Modified II
CLASS:PUBLIC
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193647
-RECURRENCE-ID:20080420T090000Z
+RECURRENCE-ID;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:20080420T110000
DESCRIPTION:third instance modified\, different time
END:VEVENT
END:VCALENDAR
@@ -285,11 +424,29 @@
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:/softwarestudio.org/Olson_20011030_5/Europe/Berlin
+X-LIC-LOCATION:Europe/Berlin
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+TZNAME:CEST
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+TZNAME:CET
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
+END:STANDARD
+END:VTIMEZONE
BEGIN:VEVENT
UID:20080407T193125Z-19554-727-1-50-XX@gollum
DTSTAMP:20080407T193125Z
-DTSTART:20080406T090000Z
-DTEND:20080406T093000Z
+DTSTART;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:20080406T110000
+DTEND;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:20080406T113000
TRANSP:OPAQUE
SEQUENCE:2
SUMMARY:Recurring 2
@@ -304,90 +461,36 @@
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
+BEGIN:VTIMEZONE
+TZID:/softwarestudio.org/Olson_20011030_5/Europe/Berlin
+X-LIC-LOCATION:Europe/Berlin
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+TZNAME:CEST
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+TZNAME:CET
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
+END:STANDARD
+END:VTIMEZONE
BEGIN:VEVENT
UID:20080407T193125Z-19554-727-1-50-XX@gollum
DTSTAMP:20080407T193125Z
-DTSTART:20080413T090000Z
-DTEND:20080413T093000Z
+DTSTART;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:20080413T110000
+DTEND;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:20080413T113000
TRANSP:OPAQUE
SEQUENCE:7
-SUMMARY:Recurring 2: Modified
+SUMMARY:Recurring: Modified
CLASS:PUBLIC
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193647
-RECURRENCE-ID:20080413T090000Z
+RECURRENCE-ID;TZID=/softwarestudio.org/Olson_20011030_5/Europe/Berlin:20080413T110000
DESCRIPTION:second instance modified\, single detached recurrence
END:VEVENT
END:VCALENDAR
-
-BEGIN:VCALENDAR
-PRODID:-//Ximian//NONSGML Evolution Calendar//EN
-VERSION:2.0
-BEGIN:VEVENT
-UID:20080407T193125Z-19554-727-1-50-YY@gollum
-DTSTAMP:20080407T193125Z
-DTSTART:20080413T090000Z
-DTEND:20080413T093000Z
-TRANSP:OPAQUE
-SEQUENCE:7
-SUMMARY:Recurring 3: Modified
-CLASS:PUBLIC
-CREATED:20080407T193241Z
-LAST-MODIFIED:20080407T193647
-RECURRENCE-ID:20080413T090000Z
-DESCRIPTION:second instance modified
-END:VEVENT
-END:VCALENDAR
-
-BEGIN:VCALENDAR
-PRODID:-//Ximian//NONSGML Evolution Calendar//EN
-VERSION:2.0
-BEGIN:VEVENT
-UID:20080407T193125Z-19554-727-1-50-YY@gollum
-DTSTAMP:20080407T193125Z
-DTSTART:20080413T100000Z
-DTEND:20080413T103000Z
-TRANSP:OPAQUE
-SEQUENCE:7
-SUMMARY:Recurring 3: Modified II
-CLASS:PUBLIC
-CREATED:20080407T193241Z
-LAST-MODIFIED:20080407T193647
-RECURRENCE-ID:20080420T090000Z
-DESCRIPTION:third instance modified\, different time
-END:VEVENT
-END:VCALENDAR
-
-BEGIN:VCALENDAR
-PRODID:-//Ximian//NONSGML Evolution Calendar//EN

View File

@ -138,7 +138,7 @@
SUMMARY:meeting invitation
CLASS:PUBLIC
ORGANIZER;CN=Patrick Ohly:MAILTO:Patrick.Ohly@gmx.de
@@ -231,14 +211,14 @@
@@ -231,14 +211,16 @@
BEGIN:VEVENT
UID:20080407T193125Z-19554-727-1-50@gollum
DTSTAMP:20080407T193125Z
@ -153,27 +153,49 @@
CLASS:PUBLIC
-RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;UNTIL=20080608T090000Z
+RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;UNTIL=20080608T110000
+EXDATE;VALUE=DATE:20080413
+EXDATE;VALUE=DATE:20080420
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193241
END:VEVENT
@@ -248,7 +228,7 @@
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
BEGIN:VEVENT
-UID:20080407T193125Z-19554-727-1-50@gollum
+UID:20080407T193125Z-19554-727-1-50@gollum-mod
DTSTAMP:20080407T193125Z
DTSTART:20080413T090000Z
DTEND:20080413T093000Z
@@ -258,7 +238,6 @@
@@ -258,7 +240,7 @@
CLASS:PUBLIC
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193647
-RECURRENCE-ID:20080413T090000Z
+RECURRENCE-ID:20080413T110000
DESCRIPTION:second instance modified
END:VEVENT
END:VCALENDAR
@@ -266,28 +245,11 @@
@@ -277,7 +259,7 @@
CLASS:PUBLIC
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193647
-RECURRENCE-ID:20080420T090000Z
+RECURRENCE-ID:20080420T110000
DESCRIPTION:third instance modified\, different time
END:VEVENT
END:VCALENDAR
@@ -288,14 +270,15 @@
BEGIN:VEVENT
UID:20080407T193125Z-19554-727-1-50-XX@gollum
DTSTAMP:20080407T193125Z
-DTSTART:20080406T090000Z
-DTEND:20080406T093000Z
+DTSTART:20080406T110000
+DTEND:20080406T113000
TRANSP:OPAQUE
SEQUENCE:2
SUMMARY:Recurring 2
DESCRIPTION:recurs each Sunday\, 10 times
CLASS:PUBLIC
-RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;UNTIL=20080608T090000Z
+RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU;UNTIL=20080608T110000
+EXDATE;VALUE=DATE:20080413
CREATED:20080407T193241Z
LAST-MODIFIED:20080407T193241
END:VEVENT
@@ -361,28 +344,11 @@
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0

View File

@ -116,7 +116,7 @@
END:VEVENT
END:VCALENDAR
@@ -265,28 +186,11 @@
@@ -361,28 +281,11 @@
BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0

View File

@ -11,6 +11,12 @@
-DTSTART:19700329T020000
-RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
-END:DAYLIGHT
-BEGIN:STANDARD
-TZOFFSETFROM:+0200
-TZOFFSETTO:+0100
-TZNAME:CET
-DTSTART:19701025T030000
-RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
+TZID:Asia/Shanghai
+BEGIN:STANDARD
+DTSTART:19670101T000000
@ -21,12 +27,7 @@
+BEGIN:VTIMEZONE
+TZID:/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai
+X-LIC-LOCATION:Asia/Shanghai
BEGIN:STANDARD
-TZOFFSETFROM:+0200
-TZOFFSETTO:+0100
-TZNAME:CET
-DTSTART:19701025T030000
-RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
+BEGIN:STANDARD
+TZNAME:CST
+DTSTART:19700914T230000
+TZOFFSETFROM:+0800