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 | |
36 | using namespace KCalCore; |
37 | using namespace KCalUtils; |
38 | using namespace Akonadi; |
39 | |
40 | //// ItemSerializerPlugin interface |
41 | |
42 | bool 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 | |
99 | void 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 | |
124 | static 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 | |
132 | static QString toString( const Attendee::Ptr &attendee ) |
133 | { |
134 | return attendee->name() + QLatin1Char( '<' ) + attendee->email() + QLatin1Char( '>' ); |
135 | } |
136 | |
137 | static QString toString( const Alarm::Ptr & ) |
138 | { |
139 | return QString(); |
140 | } |
141 | |
142 | /* |
143 | static QString toString( const Incidence::Ptr & ) |
144 | { |
145 | return QString(); |
146 | } |
147 | */ |
148 | |
149 | static QString toString( const Attachment::Ptr & ) |
150 | { |
151 | return QString(); |
152 | } |
153 | |
154 | static QString toString( const QDate &date ) |
155 | { |
156 | return date.toString(); |
157 | } |
158 | |
159 | static QString toString( const KDateTime &dateTime ) |
160 | { |
161 | return dateTime.dateTime().toString(); |
162 | } |
163 | |
164 | static QString toString( const QString &str ) |
165 | { |
166 | return str; |
167 | } |
168 | |
169 | static QString toString( bool value ) |
170 | { |
171 | if ( value ) |
172 | return i18n( "Yes" ); |
173 | else |
174 | return i18n( "No" ); |
175 | } |
176 | |
177 | template <class C> |
178 | static 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 | |
194 | static 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 | |
221 | static 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 | |
266 | static 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 | |
286 | static 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 | |
315 | void 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 | |
352 | QString SerializerPluginKCalCore::( const Item &item ) const |
353 | { |
354 | if ( !item.hasPayload<Incidence::Ptr>() ) { |
355 | return QString(); |
356 | } |
357 | return item.payload<Incidence::Ptr>()->instanceIdentifier(); |
358 | } |
359 | |
360 | Q_EXPORT_PLUGIN2( akonadi_serializer_kcalcore, SerializerPluginKCalCore ) |
361 | |
362 | |