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 "notificationmessage_p.h"
21#include "imapparser_p.h"
22
23#include <QtCore/QDebug>
24#include <QtCore/QHash>
25#include <QtDBus/QDBusMetaType>
26
27using namespace Akonadi;
28
29class NotificationMessage::Private : public QSharedData
30{
31public:
32 Private()
33 : QSharedData()
34 , type(NotificationMessage::InvalidType)
35 , operation(NotificationMessage::InvalidOp)
36 , uid(-1)
37 , parentCollection(-1)
38 , parentDestCollection(-1)
39 {
40 }
41
42 Private(const Private &other)
43 : QSharedData(other)
44 {
45 sessionId = other.sessionId;
46 type = other.type;
47 operation = other.operation;
48 uid = other.uid;
49 remoteId = other.remoteId;
50 resource = other.resource;
51 destResource = other.destResource;
52 parentCollection = other.parentCollection;
53 parentDestCollection = other.parentDestCollection;
54 mimeType = other.mimeType;
55 parts = other.parts;
56 }
57
58 bool compareWithoutOpAndParts(const Private &other) const
59 {
60 return uid == other.uid
61 && type == other.type
62 && sessionId == other.sessionId
63 && remoteId == other.remoteId
64 && resource == other.resource
65 && destResource == other.destResource
66 && parentCollection == other.parentCollection
67 && parentDestCollection == other.parentDestCollection
68 && mimeType == other.mimeType;
69 }
70
71 bool operator==(const Private &other) const
72 {
73 return operation == other.operation && parts == other.parts && compareWithoutOpAndParts(other);
74 }
75
76 QByteArray sessionId;
77 NotificationMessage::Type type;
78 NotificationMessage::Operation operation;
79 Id uid;
80 QString remoteId;
81 QByteArray resource;
82 QByteArray destResource;
83 Id parentCollection;
84 Id parentDestCollection;
85 QString mimeType;
86 QSet<QByteArray> parts;
87};
88
89NotificationMessage::NotificationMessage()
90 : d(new Private)
91{
92}
93
94NotificationMessage::NotificationMessage(const NotificationMessage &other)
95 : d(other.d)
96{
97}
98
99NotificationMessage::~NotificationMessage()
100{
101}
102
103NotificationMessage &NotificationMessage::operator=(const NotificationMessage &other)
104{
105 if (this != &other) {
106 d = other.d;
107 }
108
109 return *this;
110}
111
112bool NotificationMessage::operator==(const NotificationMessage &other) const
113{
114 return d == other.d;
115}
116
117void NotificationMessage::registerDBusTypes()
118{
119 qDBusRegisterMetaType<Akonadi::NotificationMessage>();
120 qDBusRegisterMetaType<Akonadi::NotificationMessage::List>();
121}
122
123QByteArray NotificationMessage::sessionId() const
124{
125 return d->sessionId;
126}
127
128void NotificationMessage::setSessionId(const QByteArray &sessionId)
129{
130 d->sessionId = sessionId;
131}
132
133NotificationMessage::Type NotificationMessage::type() const
134{
135 return d->type;
136}
137
138void NotificationMessage::setType(Type type)
139{
140 d->type = type;
141}
142
143NotificationMessage::Operation NotificationMessage::operation() const
144{
145 return d->operation;
146}
147
148void NotificationMessage::setOperation(Operation operation)
149{
150 d->operation = operation;
151}
152
153NotificationMessage::Id NotificationMessage::uid() const
154{
155 return d->uid;
156}
157
158void NotificationMessage::setUid(Id uid)
159{
160 d->uid = uid;
161}
162
163QString NotificationMessage::remoteId() const
164{
165 return d->remoteId;
166}
167
168void NotificationMessage::setRemoteId(const QString &remoteId)
169{
170 d->remoteId = remoteId;
171}
172
173QByteArray NotificationMessage::resource() const
174{
175 return d->resource;
176}
177
178void NotificationMessage::setResource(const QByteArray &resource)
179{
180 d->resource = resource;
181}
182
183NotificationMessage::Id NotificationMessage::parentCollection() const
184{
185 return d->parentCollection;
186}
187
188NotificationMessage::Id NotificationMessage::parentDestCollection() const
189{
190 return d->parentDestCollection;
191}
192
193void NotificationMessage::setParentCollection(Id parent)
194{
195 d->parentCollection = parent;
196}
197
198void NotificationMessage::setParentDestCollection(Id parent)
199{
200 d->parentDestCollection = parent;
201}
202
203void NotificationMessage::setDestinationResource(const QByteArray &destResource)
204{
205 d->destResource = destResource;
206}
207
208QByteArray NotificationMessage::destinationResource() const
209{
210 return d->destResource;
211}
212
213QString NotificationMessage::mimeType() const
214{
215 return d->mimeType;
216}
217
218void NotificationMessage::setMimeType(const QString &mimeType)
219{
220 d->mimeType = mimeType;
221}
222
223QSet<QByteArray> NotificationMessage::itemParts() const
224{
225 return d->parts;
226}
227
228void NotificationMessage::setItemParts(const QSet<QByteArray> &parts)
229{
230 d->parts = parts;
231}
232
233QString NotificationMessage::toString() const
234{
235 QString rv;
236 // some tests before making the string
237 if (type() == InvalidType) {
238 return QLatin1String("Error: Type is not set");
239 }
240 if (uid() == -1) {
241 return QLatin1String("Error: uid is not set");
242 }
243 if (remoteId().isEmpty()) {
244 return QLatin1String("Error: remoteId is empty");
245 }
246 if (operation() == InvalidOp) {
247 return QLatin1String("Error: operation is not set");
248 }
249
250 switch (type()) {
251 case Item:
252 rv += QLatin1String("Item ");
253 break;
254 case Collection:
255 rv += QLatin1String("Collection ");
256 break;
257 case InvalidType:
258 // already done above
259 break;
260 }
261
262 rv += QString::fromLatin1("(%1, %2) ").arg(uid()).arg(remoteId());
263
264 if (parentCollection() >= 0) {
265 if (parentDestCollection() >= 0) {
266 rv += QString::fromLatin1("from ");
267 } else {
268 rv += QString::fromLatin1("in ");
269 }
270 rv += QString::fromLatin1("collection %1 ").arg(parentCollection());
271 } else {
272 rv += QLatin1String("unspecified parent collection ");
273 }
274
275 rv += QString::fromLatin1("mimetype %1 ").arg(mimeType().isEmpty() ? QLatin1String("unknown") : mimeType());
276
277 switch (operation()) {
278 case Add:
279 rv += QLatin1String("added");
280 break;
281 case Modify:
282 rv += QLatin1String("modified parts (");
283 rv += QString::fromLatin1(ImapParser::join(itemParts().toList(), ", "));
284 rv += QLatin1String(")");
285 break;
286 case Move:
287 rv += QLatin1String("moved");
288 break;
289 case Remove:
290 rv += QLatin1String("removed");
291 break;
292 case Link:
293 rv += QLatin1String("linked");
294 break;
295 case Unlink:
296 rv += QLatin1String("unlinked");
297 break;
298 case Subscribe:
299 rv += QLatin1String("subscribed");
300 break;
301 case Unsubscribe:
302 rv += QLatin1String("unsubscribed");
303 break;
304 case InvalidOp:
305 // already done above
306 break;
307 }
308
309 if (parentDestCollection() >= 0) {
310 rv += QString::fromLatin1(" to collection %1").arg(parentDestCollection());
311 }
312
313 return rv;
314}
315
316void NotificationMessage::appendAndCompress(NotificationMessage::List &list, const NotificationMessage &msg)
317{
318 bool appended;
319 appendAndCompress(list, msg, &appended);
320}
321
322void NotificationMessage::appendAndCompress(NotificationMessage::List &list, const NotificationMessage &msg, bool *appended)
323{
324 // fast-path for stuff that is not considered during O(n) compression below
325 if (msg.operation() != Add && msg.operation() != Link && msg.operation() != Unlink && msg.operation() != Subscribe && msg.operation() != Unsubscribe && msg.operation() != Move) {
326 NotificationMessage::List::Iterator end = list.end();
327 for (NotificationMessage::List::Iterator it = list.begin(); it != end;) {
328 if (msg.d.constData()->compareWithoutOpAndParts(*((*it).d.constData()))) {
329 // same operation: merge changed parts and drop the new one
330 if (msg.operation() == (*it).operation()) {
331 (*it).setItemParts((*it).itemParts() + msg.itemParts());
332 *appended = false;
333 return;
334 } else if (msg.operation() == Modify) {
335 // new one is a modification, the existing one not, so drop the new one
336 *appended = false;
337 return;
338 } else if (msg.operation() == Remove && (*it).operation() == Modify) {
339 // new on is a deletion, erase the existing modification ones (and keep going, in case there are more)
340 it = list.erase(it);
341 end = list.end();
342 } else {
343 // keep looking
344 ++it;
345 }
346 } else {
347 ++it;
348 }
349 }
350 }
351 *appended = true;
352 list.append(msg);
353}
354
355QDBusArgument &operator<<(QDBusArgument &arg, const NotificationMessage &msg)
356{
357 arg.beginStructure();
358 arg << msg.sessionId();
359 arg << msg.type();
360 arg << msg.operation();
361 arg << msg.uid();
362 arg << msg.remoteId();
363 arg << msg.resource();
364 arg << msg.parentCollection();
365 arg << msg.parentDestCollection();
366 arg << msg.mimeType();
367
368 QStringList itemParts;
369 if (msg.operation() == NotificationMessage::Move) {
370 // encode destination resource in parts, as a backward compat hack
371 itemParts.push_back(QString::fromLatin1(msg.destinationResource()));
372 } else {
373 Q_FOREACH (const QByteArray &itemPart, msg.itemParts()) {
374 itemParts.append(QString::fromLatin1(itemPart));
375 }
376 }
377
378 arg << itemParts;
379 arg.endStructure();
380 return arg;
381}
382
383const QDBusArgument &operator>>(const QDBusArgument &arg, NotificationMessage &msg)
384{
385 arg.beginStructure();
386 QByteArray b;
387 arg >> b;
388 msg.setSessionId(b);
389 int i;
390 arg >> i;
391 msg.setType(static_cast<NotificationMessage::Type>(i));
392 arg >> i;
393 msg.setOperation(static_cast<NotificationMessage::Operation>(i));
394 NotificationMessage::Id id;
395 arg >> id;
396 msg.setUid(id);
397 QString s;
398 arg >> s;
399 msg.setRemoteId(s);
400 arg >> b;
401 msg.setResource(b);
402 arg >> id;
403 msg.setParentCollection(id);
404 arg >> id;
405 msg.setParentDestCollection(id);
406 arg >> s;
407 msg.setMimeType(s);
408 QStringList l;
409 arg >> l;
410
411 QSet<QByteArray> itemParts;
412 if (msg.operation() == NotificationMessage::Move && l.size() >= 1) {
413 // decode destination resource, which is stored in parts as a backward compat hack
414 msg.setDestinationResource(l.first().toLatin1());
415 } else {
416 Q_FOREACH (const QString &itemPart, l) {
417 itemParts.insert(itemPart.toLatin1());
418 }
419 }
420
421 msg.setItemParts(itemParts);
422 arg.endStructure();
423 return arg;
424}
425
426uint qHash(const Akonadi::NotificationMessage &msg)
427{
428 return qHash(msg.uid() + (msg.type() << 31) + (msg.operation() << 28));
429}
430