1/*
2 Copyright (c) 2007 Volker Krause <vkrause@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "akonadi_serializer_kcalcore.h"
21
22#include <akonadi/abstractdifferencesreporter.h>
23#include <akonadi/item.h>
24#include <akonadi/collection.h>
25
26#include <KCalCore/Event>
27#include <KCalCore/Todo>
28
29#include <KCalUtils/Stringify>
30
31#include <kdebug.h>
32#include <klocale.h>
33
34#include <QtCore/qplugin.h>
35
36using namespace KCalCore;
37using namespace KCalUtils;
38using namespace Akonadi;
39
40//// ItemSerializerPlugin interface
41
42bool SerializerPluginKCalCore::deserialize( Item &item, const QByteArray &label,
43 QIODevice &data, int version )
44{
45 Q_UNUSED( version );
46
47 if ( label != Item::FullPayload ) {
48 return false;
49 }
50
51 qint32 type;
52 quint32 magic, incidenceVersion;
53 QDataStream input( &data );
54 input >> magic;
55 input >> incidenceVersion;
56 input >> type;
57 data.seek( 0 );
58
59 Incidence::Ptr incidence;
60
61 if (magic == IncidenceBase::magicSerializationIdentifier()) {
62 IncidenceBase::Ptr base;
63 switch ( static_cast<KCalCore::Incidence::IncidenceType>( type ) ) {
64 case KCalCore::Incidence::TypeEvent: {
65 base = Event::Ptr( new Event() );
66 break;
67 }
68 case KCalCore::Incidence::TypeTodo: {
69 base = Todo::Ptr( new Todo() );
70 break;
71 }
72 case KCalCore::Incidence::TypeJournal: {
73 base = Journal::Ptr( new Journal() );
74 break;
75 }
76 default:
77 break;
78 }
79 input >> base;
80 incidence = base.staticCast<KCalCore::Incidence>();
81 } else {
82 // Use the old format
83 incidence = mFormat.fromString( QString::fromUtf8( data.readAll() ) );
84 }
85
86 if ( !incidence ) {
87 kWarning( 5263 ) << "Failed to parse incidence! Item id = " << item.id()
88 << "Storage collection id " << item.storageCollectionId()
89 << "parentCollectionId = " << item.parentCollection().id();
90 data.seek( 0 );
91 kWarning( 5263 ) << QString::fromUtf8( data.readAll() );
92 return false;
93 }
94
95 item.setPayload( incidence );
96 return true;
97}
98
99void SerializerPluginKCalCore::serialize( const Item &item,
100 const QByteArray &label,
101 QIODevice &data, int &version )
102{
103 Q_UNUSED( version );
104
105 if ( label != Item::FullPayload || !item.hasPayload<Incidence::Ptr>() )
106 return;
107 Incidence::Ptr i = item.payload<Incidence::Ptr>();
108
109 // Using an env variable for now while testing
110 if (qgetenv("KCALCORE_BINARY_SERIALIZER") == QByteArray("1")) {
111 QDataStream output(&data);
112 IncidenceBase::Ptr base = i;
113 output << base;
114 } else {
115 // ### I guess this can be done without hardcoding stuff
116 data.write( "BEGIN:VCALENDAR\nPRODID:-//K Desktop Environment//NONSGML libkcal 4.3//EN\nVERSION:2.0\nX-KDE-ICAL-IMPLEMENTATION-VERSION:1.0\n" );
117 data.write( mFormat.toRawString( i ) );
118 data.write( "\nEND:VCALENDAR" );
119 }
120}
121
122//// DifferencesAlgorithmInterface
123
124static bool compareString( const QString &left, const QString &right )
125{
126 if ( left.isEmpty() && right.isEmpty() )
127 return true;
128 else
129 return left == right;
130}
131
132static QString toString( const Attendee::Ptr &attendee )
133{
134 return attendee->name() + QLatin1Char( '<' ) + attendee->email() + QLatin1Char( '>' );
135}
136
137static QString toString( const Alarm::Ptr & )
138{
139 return QString();
140}
141
142/*
143static QString toString( const Incidence::Ptr & )
144{
145 return QString();
146}
147*/
148
149static QString toString( const Attachment::Ptr & )
150{
151 return QString();
152}
153
154static QString toString( const QDate &date )
155{
156 return date.toString();
157}
158
159static QString toString( const KDateTime &dateTime )
160{
161 return dateTime.dateTime().toString();
162}
163
164static QString toString( const QString &str )
165{
166 return str;
167}
168
169static QString toString( bool value )
170{
171 if ( value )
172 return i18n( "Yes" );
173 else
174 return i18n( "No" );
175}
176
177template <class C>
178static void compareList( AbstractDifferencesReporter *reporter,
179 const QString &id,
180 const C &left,
181 const C &right )
182{
183 for ( typename C::const_iterator it = left.begin(), end = left.end() ; it != end ; ++it ) {
184 if ( !right.contains( *it ) )
185 reporter->addProperty( AbstractDifferencesReporter::AdditionalLeftMode, id, toString( *it ), QString() );
186 }
187
188 for ( typename C::const_iterator it = right.begin(), end = right.end() ; it != end ; ++it ) {
189 if ( !left.contains( *it ) )
190 reporter->addProperty( AbstractDifferencesReporter::AdditionalRightMode, id, QString(), toString( *it ) );
191 }
192}
193
194static void compareIncidenceBase( AbstractDifferencesReporter *reporter,
195 const IncidenceBase::Ptr &left,
196 const IncidenceBase::Ptr &right )
197{
198 compareList( reporter, i18n( "Attendees" ), left->attendees(), right->attendees() );
199
200 if ( !compareString( left->organizer()->fullName(), right->organizer()->fullName() ) )
201 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Organizer" ),
202 left->organizer()->fullName(), right->organizer()->fullName() );
203
204 if ( !compareString( left->uid(), right->uid() ) )
205 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "UID" ),
206 left->uid(), right->uid() );
207
208 if ( left->allDay() != right->allDay() )
209 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Is all-day" ),
210 toString( left->allDay() ), toString( right->allDay() ) );
211
212 if ( left->hasDuration() != right->hasDuration() )
213 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has duration" ),
214 toString( left->hasDuration() ), toString( right->hasDuration() ) );
215
216 if ( left->duration() != right->duration() )
217 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Duration" ),
218 QString::number( left->duration().asSeconds() ), QString::number( right->duration().asSeconds() ) );
219}
220
221static void compareIncidence( AbstractDifferencesReporter *reporter,
222 const Incidence::Ptr &left,
223 const Incidence::Ptr &right )
224{
225 if ( !compareString( left->description(), right->description() ) )
226 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Description" ),
227 left->description(), right->description() );
228
229 if ( !compareString( left->summary(), right->summary() ) )
230 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Summary" ),
231 left->summary(), right->summary() );
232
233 if ( left->status() != right->status() )
234 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Status" ),
235 Stringify::incidenceStatus( left ), Stringify::incidenceStatus( right ) );
236
237 if ( left->secrecy() != right->secrecy() )
238 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Secrecy" ),
239 toString( left->secrecy() ), toString( right->secrecy() ) );
240
241 if ( left->priority() != right->priority() )
242 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Priority" ),
243 toString( left->priority() ), toString( right->priority() ) );
244
245 if ( !compareString( left->location(), right->location() ) )
246 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Location" ),
247 left->location(), right->location() );
248
249 compareList( reporter, i18n( "Categories" ), left->categories(), right->categories() );
250 compareList( reporter, i18n( "Alarms" ), left->alarms(), right->alarms() );
251 compareList( reporter, i18n( "Resources" ), left->resources(), right->resources() );
252 compareList( reporter, i18n( "Attachments" ), left->attachments(), right->attachments() );
253 compareList( reporter, i18n( "Exception Dates" ), left->recurrence()->exDates(), right->recurrence()->exDates() );
254 compareList( reporter, i18n( "Exception Times" ), left->recurrence()->exDateTimes(), right->recurrence()->exDateTimes() );
255 // TODO: recurrence dates and date/times, exrules, rrules
256
257 if ( left->created() != right->created() )
258 reporter->addProperty( AbstractDifferencesReporter::ConflictMode,
259 i18n( "Created" ), left->created().toString(), right->created().toString() );
260
261 if ( !compareString( left->relatedTo(), right->relatedTo() ) )
262 reporter->addProperty( AbstractDifferencesReporter::ConflictMode,
263 i18n( "Related Uid" ), left->relatedTo(), right->relatedTo() );
264}
265
266static void compareEvent( AbstractDifferencesReporter *reporter,
267 const Event::Ptr &left,
268 const Event::Ptr &right )
269{
270
271 if ( left->dtStart() != right->dtStart() )
272 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Start time" ),
273 left->dtStart().toString(), right->dtStart().toString() );
274
275 if ( left->hasEndDate() != right->hasEndDate() )
276 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has End Date" ),
277 toString( left->hasEndDate() ), toString( right->hasEndDate() ) );
278
279 if ( left->dtEnd() != right->dtEnd() )
280 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "End Date" ),
281 left->dtEnd().toString(), right->dtEnd().toString() );
282
283 // TODO: check transparency
284}
285
286static void compareTodo( AbstractDifferencesReporter *reporter,
287 const Todo::Ptr &left,
288 const Todo::Ptr &right )
289{
290 if ( left->hasStartDate() != right->hasStartDate() )
291 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has Start Date" ),
292 toString( left->hasStartDate() ), toString( right->hasStartDate() ) );
293
294 if ( left->hasDueDate() != right->hasDueDate() )
295 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has Due Date" ),
296 toString( left->hasDueDate() ), toString( right->hasDueDate() ) );
297
298 if ( left->dtDue() != right->dtDue() )
299 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Due Date" ),
300 left->dtDue().toString(), right->dtDue().toString() );
301
302 if ( left->hasCompletedDate() != right->hasCompletedDate() )
303 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Has Complete Date" ),
304 toString( left->hasCompletedDate() ), toString( right->hasCompletedDate() ) );
305
306 if ( left->percentComplete() != right->percentComplete() )
307 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Complete" ),
308 QString::number( left->percentComplete() ), QString::number( right->percentComplete() ) );
309
310 if ( left->completed() != right->completed() )
311 reporter->addProperty( AbstractDifferencesReporter::ConflictMode, i18n( "Completed" ),
312 toString( left->completed() ), toString( right->completed() ) );
313}
314
315void SerializerPluginKCalCore::compare( Akonadi::AbstractDifferencesReporter *reporter,
316 const Akonadi::Item &leftItem,
317 const Akonadi::Item &rightItem )
318{
319 Q_ASSERT( reporter );
320 Q_ASSERT( leftItem.hasPayload<Incidence::Ptr>() );
321 Q_ASSERT( rightItem.hasPayload<Incidence::Ptr>() );
322
323 const Incidence::Ptr leftIncidencePtr = leftItem.payload<Incidence::Ptr>();
324 const Incidence::Ptr rightIncidencePtr = rightItem.payload<Incidence::Ptr>();
325
326 if ( leftIncidencePtr->type() == Incidence::TypeEvent ) {
327 reporter->setLeftPropertyValueTitle( i18n( "Changed Event" ) );
328 reporter->setRightPropertyValueTitle( i18n( "Conflicting Event" ) );
329 } else if ( leftIncidencePtr->type() == Incidence::TypeTodo ) {
330 reporter->setLeftPropertyValueTitle( i18n( "Changed Todo" ) );
331 reporter->setRightPropertyValueTitle( i18n( "Conflicting Todo" ) );
332 }
333
334 compareIncidenceBase( reporter, leftIncidencePtr, rightIncidencePtr );
335 compareIncidence( reporter, leftIncidencePtr, rightIncidencePtr );
336
337 const Event::Ptr leftEvent = leftIncidencePtr.dynamicCast<Event>() ;
338 const Event::Ptr rightEvent = rightIncidencePtr.dynamicCast<Event>() ;
339 if ( leftEvent && rightEvent ) {
340 compareEvent( reporter, leftEvent, rightEvent );
341 } else {
342 const Todo::Ptr leftTodo = leftIncidencePtr.dynamicCast<Todo>();
343 const Todo::Ptr rightTodo = rightIncidencePtr.dynamicCast<Todo>();
344 if ( leftTodo && rightTodo ) {
345 compareTodo( reporter, leftTodo, rightTodo );
346 }
347 }
348}
349
350//// GidExtractorInterface
351
352QString SerializerPluginKCalCore::extractGid( const Item &item ) const
353{
354 if ( !item.hasPayload<Incidence::Ptr>() ) {
355 return QString();
356 }
357 return item.payload<Incidence::Ptr>()->instanceIdentifier();
358}
359
360Q_EXPORT_PLUGIN2( akonadi_serializer_kcalcore, SerializerPluginKCalCore )
361
362