1 /*
2 * SPDX-FileCopyrightText: 2012 Christian Mollekopf <mollekopf@kolabsys.com>
3 *
4 * SPDX-License-Identifier: LGPL-3.0-or-later
5 */
6
7 #include "kolabobject.h"
8 #include "libkolab-version.h"
9 #include "pimkolab_debug.h"
10 #include "v2helpers.h"
11
12 #include "kolabbase.h"
13
14 #include <Akonadi/Notes/NoteUtils>
15
16 #include <conversion/commonconversion.h>
17 #include <conversion/kabcconversion.h>
18 #include <conversion/kcalconversion.h>
19 #include <conversion/kolabconversion.h>
20 #include <kolabformat.h>
21 #include <kolabformat/mimeobject.h>
22 #include <kolabformatV2/contact.h>
23 #include <kolabformatV2/distributionlist.h>
24 #include <kolabformatV2/event.h>
25 #include <kolabformatV2/journal.h>
26 #include <kolabformatV2/note.h>
27 #include <kolabformatV2/task.h>
28 #include <mime/mimeutils.h>
29
30 #include <QUrlQuery>
31
32 namespace Kolab
33 {
eventKolabType()34 static inline QString eventKolabType()
35 {
36 return QStringLiteral(KOLAB_TYPE_EVENT);
37 }
38
todoKolabType()39 static inline QString todoKolabType()
40 {
41 return QStringLiteral(KOLAB_TYPE_TASK);
42 }
43
journalKolabType()44 static inline QString journalKolabType()
45 {
46 return QStringLiteral(KOLAB_TYPE_JOURNAL);
47 }
48
contactKolabType()49 static inline QString contactKolabType()
50 {
51 return QStringLiteral(KOLAB_TYPE_CONTACT);
52 }
53
distlistKolabType()54 static inline QString distlistKolabType()
55 {
56 return QStringLiteral(KOLAB_TYPE_DISTLIST);
57 }
58
distlistKolabTypeCompat()59 static inline QString distlistKolabTypeCompat()
60 {
61 return QStringLiteral(KOLAB_TYPE_DISTLIST_V2);
62 }
63
noteKolabType()64 static inline QString noteKolabType()
65 {
66 return QStringLiteral(KOLAB_TYPE_NOTE);
67 }
68
configurationKolabType()69 static inline QString configurationKolabType()
70 {
71 return QStringLiteral(KOLAB_TYPE_CONFIGURATION);
72 }
73
dictKolabType()74 static inline QString dictKolabType()
75 {
76 return QStringLiteral(KOLAB_TYPE_DICT);
77 }
78
freebusyKolabType()79 static inline QString freebusyKolabType()
80 {
81 return QStringLiteral(KOLAB_TYPE_FREEBUSY);
82 }
83
relationKolabType()84 static inline QString relationKolabType()
85 {
86 return QStringLiteral(KOLAB_TYPE_RELATION);
87 }
88
xCalMimeType()89 static inline QString xCalMimeType()
90 {
91 return QStringLiteral(MIME_TYPE_XCAL);
92 }
93
xCardMimeType()94 static inline QString xCardMimeType()
95 {
96 return QStringLiteral(MIME_TYPE_XCARD);
97 }
98
kolabMimeType()99 static inline QString kolabMimeType()
100 {
101 return QStringLiteral(MIME_TYPE_KOLAB);
102 }
103
readV2EventXML(const QByteArray & xmlData,QStringList & attachments)104 KCalendarCore::Event::Ptr readV2EventXML(const QByteArray &xmlData, QStringList &attachments)
105 {
106 return fromXML<KCalendarCore::Event::Ptr, KolabV2::Event>(xmlData, attachments);
107 }
108
ownUrlDecode(QByteArray encodedParam)109 QString ownUrlDecode(QByteArray encodedParam)
110 {
111 encodedParam.replace('+', ' ');
112 return QUrl::fromPercentEncoding(encodedParam);
113 }
114
parseMemberUrl(const QString & string)115 RelationMember parseMemberUrl(const QString &string)
116 {
117 if (string.startsWith(QLatin1String("urn:uuid:"))) {
118 RelationMember member;
119 member.gid = string.mid(9);
120 return member;
121 }
122 QUrl url(QUrl::fromEncoded(string.toLatin1()));
123 QList<QByteArray> path;
124 const QList<QByteArray> fragments = url.path(QUrl::FullyEncoded).toLatin1().split('/');
125 path.reserve(fragments.count());
126 for (const QByteArray &fragment : fragments) {
127 path.append(ownUrlDecode(fragment).toUtf8());
128 }
129 // qCDebug(PIMKOLAB_LOG) << path;
130 bool isShared = false;
131 int start = path.indexOf("user");
132 if (start < 0) {
133 start = path.indexOf("shared");
134 isShared = true;
135 }
136 if (start < 0) {
137 qCWarning(PIMKOLAB_LOG) << R"(Couldn't find "user" or "shared" in path: )" << path;
138 return RelationMember();
139 }
140 path = path.mid(start + 1);
141 if (path.size() < 2) {
142 qCWarning(PIMKOLAB_LOG) << "Incomplete path: " << path;
143 return RelationMember();
144 }
145 RelationMember member;
146 if (!isShared) {
147 member.user = QString::fromUtf8(path.takeFirst());
148 }
149 member.uid = path.takeLast().toLong();
150 member.mailbox = path;
151 QUrlQuery query(url);
152 member.messageId = ownUrlDecode(query.queryItemValue(QStringLiteral("message-id"), QUrl::FullyEncoded).toUtf8());
153 member.subject = ownUrlDecode(query.queryItemValue(QStringLiteral("subject"), QUrl::FullyEncoded).toUtf8());
154 member.date = ownUrlDecode(query.queryItemValue(QStringLiteral("date"), QUrl::FullyEncoded).toUtf8());
155 // qCDebug(PIMKOLAB_LOG) << member.uid << member.mailbox;
156 return member;
157 }
158
join(const QList<QByteArray> & list,const QByteArray & c)159 static QByteArray join(const QList<QByteArray> &list, const QByteArray &c)
160 {
161 QByteArray result;
162 for (const QByteArray &a : list) {
163 result += a + c;
164 }
165 result.chop(c.size());
166 return result;
167 }
168
generateMemberUrl(const RelationMember & member)169 KOLAB_EXPORT QString generateMemberUrl(const RelationMember &member)
170 {
171 if (!member.gid.isEmpty()) {
172 return QStringLiteral("urn:uuid:%1").arg(member.gid);
173 }
174 QUrl url;
175 url.setScheme(QStringLiteral("imap"));
176 QList<QByteArray> path;
177 path << "/";
178 if (!member.user.isEmpty()) {
179 path << "user";
180 path << QUrl::toPercentEncoding(member.user);
181 } else {
182 path << "shared";
183 }
184 const auto memberMailbox{member.mailbox};
185 for (const QByteArray &mb : memberMailbox) {
186 path << QUrl::toPercentEncoding(QString::fromUtf8(mb));
187 }
188 path << QByteArray::number(member.uid);
189 url.setPath(QString::fromUtf8('/' + join(path, "/")), QUrl::TolerantMode);
190
191 QUrlQuery query;
192 query.addQueryItem(QStringLiteral("message-id"), member.messageId);
193 query.addQueryItem(QStringLiteral("subject"), member.subject);
194 query.addQueryItem(QStringLiteral("date"), member.date);
195 url.setQuery(query);
196
197 return QString::fromLatin1(url.toEncoded());
198 }
199
200 //@cond PRIVATE
201 class KolabObjectReaderPrivate
202 {
203 public:
KolabObjectReaderPrivate()204 KolabObjectReaderPrivate()
205 : mAddressee(KContacts::Addressee())
206 , mObjectType(InvalidObject)
207 , mVersion(KolabV3)
208 , mOverrideObjectType(InvalidObject)
209 {
210 }
211
212 KCalendarCore::Incidence::Ptr mIncidence;
213 KContacts::Addressee mAddressee;
214 KContacts::ContactGroup mContactGroup;
215 KMime::Message::Ptr mNote;
216 QStringList mDictionary;
217 QString mDictionaryLanguage;
218 ObjectType mObjectType;
219 Version mVersion;
220 Kolab::Freebusy mFreebusy;
221 ObjectType mOverrideObjectType;
222 Version mOverrideVersion;
223 bool mDoOverrideVersion = false;
224 Akonadi::Relation mRelation;
225 Akonadi::Tag mTag;
226 QStringList mTagMembers;
227 };
228 //@endcond
229
KolabObjectReader()230 KolabObjectReader::KolabObjectReader()
231 : d(new KolabObjectReaderPrivate)
232 {
233 }
234
KolabObjectReader(const KMime::Message::Ptr & msg)235 KolabObjectReader::KolabObjectReader(const KMime::Message::Ptr &msg)
236 : d(new KolabObjectReaderPrivate)
237 {
238 parseMimeMessage(msg);
239 }
240
241 KolabObjectReader::~KolabObjectReader() = default;
242
setObjectType(ObjectType type)243 void KolabObjectReader::setObjectType(ObjectType type)
244 {
245 d->mOverrideObjectType = type;
246 }
247
setVersion(Version version)248 void KolabObjectReader::setVersion(Version version)
249 {
250 d->mOverrideVersion = version;
251 d->mDoOverrideVersion = true;
252 }
253
printMessageDebugInfo(const KMime::Message::Ptr & msg)254 void printMessageDebugInfo(const KMime::Message::Ptr &msg)
255 {
256 // TODO replace by Debug stream for Mimemessage
257 qCDebug(PIMKOLAB_LOG) << "MessageId: " << msg->messageID()->asUnicodeString();
258 qCDebug(PIMKOLAB_LOG) << "Subject: " << msg->subject()->asUnicodeString();
259 // Debug() << msg->encodedContent();
260 }
261
parseMimeMessage(const KMime::Message::Ptr & msg)262 ObjectType KolabObjectReader::parseMimeMessage(const KMime::Message::Ptr &msg)
263 {
264 ErrorHandler::clearErrors();
265 d->mObjectType = InvalidObject;
266 if (msg->contents().isEmpty()) {
267 qCCritical(PIMKOLAB_LOG) << "message has no contents (we likely failed to parse it correctly)";
268 printMessageDebugInfo(msg);
269 return InvalidObject;
270 }
271 const std::string message = msg->encodedContent().toStdString();
272 Kolab::MIMEObject mimeObject;
273 mimeObject.setObjectType(d->mOverrideObjectType);
274 if (d->mDoOverrideVersion) {
275 mimeObject.setVersion(d->mOverrideVersion);
276 }
277 d->mObjectType = mimeObject.parseMessage(message);
278 d->mVersion = mimeObject.getVersion();
279 switch (mimeObject.getType()) {
280 case EventObject: {
281 const Kolab::Event &event = mimeObject.getEvent();
282 d->mIncidence = Kolab::Conversion::toKCalendarCore(event);
283 break;
284 }
285 case TodoObject: {
286 const Kolab::Todo &event = mimeObject.getTodo();
287 d->mIncidence = Kolab::Conversion::toKCalendarCore(event);
288 break;
289 }
290 case JournalObject: {
291 const Kolab::Journal &event = mimeObject.getJournal();
292 d->mIncidence = Kolab::Conversion::toKCalendarCore(event);
293 break;
294 }
295 case ContactObject: {
296 const Kolab::Contact &contact = mimeObject.getContact();
297 d->mAddressee = Kolab::Conversion::toKABC(contact); // TODO extract attachments
298 break;
299 }
300 case DistlistObject: {
301 const Kolab::DistList &distlist = mimeObject.getDistlist();
302 d->mContactGroup = Kolab::Conversion::toKABC(distlist);
303 break;
304 }
305 case NoteObject: {
306 const Kolab::Note ¬e = mimeObject.getNote();
307 d->mNote = Kolab::Conversion::toNote(note);
308 break;
309 }
310 case DictionaryConfigurationObject: {
311 const Kolab::Configuration &configuration = mimeObject.getConfiguration();
312 const Kolab::Dictionary &dictionary = configuration.dictionary();
313 d->mDictionary.clear();
314 const std::vector<std::string> entries = dictionary.entries();
315 d->mDictionary.reserve(entries.size());
316 for (const std::string &entry : entries) {
317 d->mDictionary.append(Conversion::fromStdString(entry));
318 }
319 d->mDictionaryLanguage = Conversion::fromStdString(dictionary.language());
320 break;
321 }
322 case FreebusyObject: {
323 const Kolab::Freebusy &fb = mimeObject.getFreebusy();
324 d->mFreebusy = fb;
325 break;
326 }
327 case RelationConfigurationObject: {
328 const Kolab::Configuration &configuration = mimeObject.getConfiguration();
329 const Kolab::Relation &relation = configuration.relation();
330
331 if (relation.type() == "tag") {
332 d->mTag = Akonadi::Tag();
333 d->mTag.setName(Conversion::fromStdString(relation.name()));
334 d->mTag.setGid(Conversion::fromStdString(configuration.uid()).toLatin1());
335 d->mTag.setType(Akonadi::Tag::GENERIC);
336
337 d->mTagMembers.reserve(relation.members().size());
338 const auto members{relation.members()};
339 for (const std::string &member : members) {
340 d->mTagMembers << Conversion::fromStdString(member);
341 }
342 } else if (relation.type() == "generic") {
343 if (relation.members().size() == 2) {
344 d->mRelation = Akonadi::Relation();
345 d->mRelation.setRemoteId(Conversion::fromStdString(configuration.uid()).toLatin1());
346 d->mRelation.setType(Akonadi::Relation::GENERIC);
347
348 d->mTagMembers.reserve(relation.members().size());
349 const auto members{relation.members()};
350 for (const std::string &member : members) {
351 d->mTagMembers << Conversion::fromStdString(member);
352 }
353 } else {
354 qCCritical(PIMKOLAB_LOG) << "generic relation had wrong number of members:" << relation.members().size();
355 printMessageDebugInfo(msg);
356 }
357 } else {
358 qCCritical(PIMKOLAB_LOG) << "unknown configuration object type" << relation.type();
359 printMessageDebugInfo(msg);
360 }
361 break;
362 }
363 default:
364 qCCritical(PIMKOLAB_LOG) << "no kolab object found ";
365 printMessageDebugInfo(msg);
366 break;
367 }
368 return d->mObjectType;
369 }
370
getVersion() const371 Version KolabObjectReader::getVersion() const
372 {
373 return d->mVersion;
374 }
375
getType() const376 ObjectType KolabObjectReader::getType() const
377 {
378 return d->mObjectType;
379 }
380
getEvent() const381 KCalendarCore::Event::Ptr KolabObjectReader::getEvent() const
382 {
383 return d->mIncidence.dynamicCast<KCalendarCore::Event>();
384 }
385
getTodo() const386 KCalendarCore::Todo::Ptr KolabObjectReader::getTodo() const
387 {
388 return d->mIncidence.dynamicCast<KCalendarCore::Todo>();
389 }
390
getJournal() const391 KCalendarCore::Journal::Ptr KolabObjectReader::getJournal() const
392 {
393 return d->mIncidence.dynamicCast<KCalendarCore::Journal>();
394 }
395
getIncidence() const396 KCalendarCore::Incidence::Ptr KolabObjectReader::getIncidence() const
397 {
398 return d->mIncidence;
399 }
400
getContact() const401 KContacts::Addressee KolabObjectReader::getContact() const
402 {
403 return d->mAddressee;
404 }
405
getDistlist() const406 KContacts::ContactGroup KolabObjectReader::getDistlist() const
407 {
408 return d->mContactGroup;
409 }
410
getNote() const411 KMime::Message::Ptr KolabObjectReader::getNote() const
412 {
413 return d->mNote;
414 }
415
getDictionary(QString & lang) const416 QStringList KolabObjectReader::getDictionary(QString &lang) const
417 {
418 lang = d->mDictionaryLanguage;
419 return d->mDictionary;
420 }
421
getFreebusy() const422 Freebusy KolabObjectReader::getFreebusy() const
423 {
424 return d->mFreebusy;
425 }
426
isTag() const427 bool KolabObjectReader::isTag() const
428 {
429 return !d->mTag.gid().isEmpty();
430 }
431
getTag() const432 Akonadi::Tag KolabObjectReader::getTag() const
433 {
434 return d->mTag;
435 }
436
getTagMembers() const437 QStringList KolabObjectReader::getTagMembers() const
438 {
439 return d->mTagMembers;
440 }
441
isRelation() const442 bool KolabObjectReader::isRelation() const
443 {
444 return d->mRelation.isValid();
445 }
446
getRelation() const447 Akonadi::Relation KolabObjectReader::getRelation() const
448 {
449 return d->mRelation;
450 }
451
createMimeMessage(const std::string & mimeMessage)452 static KMime::Message::Ptr createMimeMessage(const std::string &mimeMessage)
453 {
454 KMime::Message::Ptr msg(new KMime::Message);
455 msg->setContent(QByteArray(mimeMessage.c_str()));
456 msg->parse();
457 return msg;
458 }
459
writeEvent(const KCalendarCore::Event::Ptr & i,Version v,const QString & productId,const QString &)460 KMime::Message::Ptr KolabObjectWriter::writeEvent(const KCalendarCore::Event::Ptr &i, Version v, const QString &productId, const QString &)
461 {
462 ErrorHandler::clearErrors();
463 if (!i) {
464 qCCritical(PIMKOLAB_LOG) << "passed a null pointer";
465 return KMime::Message::Ptr();
466 }
467 const Kolab::Event &event = Kolab::Conversion::fromKCalendarCore(*i);
468 Kolab::MIMEObject mimeObject;
469 const std::string mimeMessage = mimeObject.writeEvent(event, v, productId.toStdString());
470 return createMimeMessage(mimeMessage);
471 }
472
writeTodo(const KCalendarCore::Todo::Ptr & i,Version v,const QString & productId,const QString &)473 KMime::Message::Ptr KolabObjectWriter::writeTodo(const KCalendarCore::Todo::Ptr &i, Version v, const QString &productId, const QString &)
474 {
475 ErrorHandler::clearErrors();
476 if (!i) {
477 qCCritical(PIMKOLAB_LOG) << "passed a null pointer";
478 return KMime::Message::Ptr();
479 }
480 const Kolab::Todo &todo = Kolab::Conversion::fromKCalendarCore(*i);
481 Kolab::MIMEObject mimeObject;
482 const std::string mimeMessage = mimeObject.writeTodo(todo, v, productId.toStdString());
483 return createMimeMessage(mimeMessage);
484 }
485
writeJournal(const KCalendarCore::Journal::Ptr & i,Version v,const QString & productId,const QString &)486 KMime::Message::Ptr KolabObjectWriter::writeJournal(const KCalendarCore::Journal::Ptr &i, Version v, const QString &productId, const QString &)
487 {
488 ErrorHandler::clearErrors();
489 if (!i) {
490 qCCritical(PIMKOLAB_LOG) << "passed a null pointer";
491 return KMime::Message::Ptr();
492 }
493 const Kolab::Journal &journal = Kolab::Conversion::fromKCalendarCore(*i);
494 Kolab::MIMEObject mimeObject;
495 const std::string mimeMessage = mimeObject.writeJournal(journal, v, productId.toStdString());
496 return createMimeMessage(mimeMessage);
497 }
498
writeIncidence(const KCalendarCore::Incidence::Ptr & i,Version v,const QString & productId,const QString & tz)499 KMime::Message::Ptr KolabObjectWriter::writeIncidence(const KCalendarCore::Incidence::Ptr &i, Version v, const QString &productId, const QString &tz)
500 {
501 if (!i) {
502 qCCritical(PIMKOLAB_LOG) << "passed a null pointer";
503 return KMime::Message::Ptr();
504 }
505 switch (i->type()) {
506 case KCalendarCore::IncidenceBase::TypeEvent:
507 return writeEvent(i.dynamicCast<KCalendarCore::Event>(), v, productId, tz);
508 case KCalendarCore::IncidenceBase::TypeTodo:
509 return writeTodo(i.dynamicCast<KCalendarCore::Todo>(), v, productId, tz);
510 case KCalendarCore::IncidenceBase::TypeJournal:
511 return writeJournal(i.dynamicCast<KCalendarCore::Journal>(), v, productId, tz);
512 default:
513 qCCritical(PIMKOLAB_LOG) << "unknown incidence type";
514 }
515 return KMime::Message::Ptr();
516 }
517
writeContact(const KContacts::Addressee & addressee,Version v,const QString & productId)518 KMime::Message::Ptr KolabObjectWriter::writeContact(const KContacts::Addressee &addressee, Version v, const QString &productId)
519 {
520 ErrorHandler::clearErrors();
521 const Kolab::Contact &contact = Kolab::Conversion::fromKABC(addressee);
522 Kolab::MIMEObject mimeObject;
523 const std::string mimeMessage = mimeObject.writeContact(contact, v, productId.toStdString());
524 return createMimeMessage(mimeMessage);
525 }
526
writeDistlist(const KContacts::ContactGroup & kDistList,Version v,const QString & productId)527 KMime::Message::Ptr KolabObjectWriter::writeDistlist(const KContacts::ContactGroup &kDistList, Version v, const QString &productId)
528 {
529 ErrorHandler::clearErrors();
530 const Kolab::DistList &distlist = Kolab::Conversion::fromKABC(kDistList);
531 Kolab::MIMEObject mimeObject;
532 const std::string mimeMessage = mimeObject.writeDistlist(distlist, v, productId.toStdString());
533 return createMimeMessage(mimeMessage);
534 }
535
writeNote(const KMime::Message::Ptr & n,Version v,const QString & productId)536 KMime::Message::Ptr KolabObjectWriter::writeNote(const KMime::Message::Ptr &n, Version v, const QString &productId)
537 {
538 ErrorHandler::clearErrors();
539 if (!n) {
540 qCCritical(PIMKOLAB_LOG) << "passed a null pointer";
541 return KMime::Message::Ptr();
542 }
543 const Kolab::Note ¬e = Kolab::Conversion::fromNote(n);
544 Kolab::MIMEObject mimeObject;
545 const std::string mimeMessage = mimeObject.writeNote(note, v, productId.toStdString());
546 return createMimeMessage(mimeMessage);
547 }
548
writeDictionary(const QStringList & entries,const QString & lang,Version v,const QString & productId)549 KMime::Message::Ptr KolabObjectWriter::writeDictionary(const QStringList &entries, const QString &lang, Version v, const QString &productId)
550 {
551 ErrorHandler::clearErrors();
552
553 Kolab::Dictionary dictionary(Conversion::toStdString(lang));
554 std::vector<std::string> ent;
555 ent.reserve(entries.count());
556 for (const QString &e : entries) {
557 ent.push_back(Conversion::toStdString(e));
558 }
559 dictionary.setEntries(ent);
560 Kolab::Configuration configuration(dictionary); // TODO preserve creation/lastModified date
561 Kolab::MIMEObject mimeObject;
562 const std::string mimeMessage = mimeObject.writeConfiguration(configuration, v, productId.toStdString());
563 return createMimeMessage(mimeMessage);
564 }
565
writeFreebusy(const Freebusy & freebusy,Version v,const QString & productId)566 KMime::Message::Ptr KolabObjectWriter::writeFreebusy(const Freebusy &freebusy, Version v, const QString &productId)
567 {
568 ErrorHandler::clearErrors();
569 Kolab::MIMEObject mimeObject;
570 const std::string mimeMessage = mimeObject.writeFreebusy(freebusy, v, productId.toStdString());
571 return createMimeMessage(mimeMessage);
572 }
573
writeRelationHelper(const Kolab::Relation & relation,const QByteArray & uid,const QString & productId)574 KMime::Message::Ptr writeRelationHelper(const Kolab::Relation &relation, const QByteArray &uid, const QString &productId)
575 {
576 ErrorHandler::clearErrors();
577 Kolab::MIMEObject mimeObject;
578
579 Kolab::Configuration configuration(relation); // TODO preserve creation/lastModified date
580 configuration.setUid(uid.constData());
581 const std::string mimeMessage = mimeObject.writeConfiguration(configuration, Kolab::KolabV3, Conversion::toStdString(productId));
582 return createMimeMessage(mimeMessage);
583 }
584
writeTag(const Akonadi::Tag & tag,const QStringList & members,Version v,const QString & productId)585 KMime::Message::Ptr KolabObjectWriter::writeTag(const Akonadi::Tag &tag, const QStringList &members, Version v, const QString &productId)
586 {
587 ErrorHandler::clearErrors();
588 if (v != KolabV3) {
589 qCCritical(PIMKOLAB_LOG) << "only v3 implementation available";
590 }
591
592 Kolab::Relation relation(Conversion::toStdString(tag.name()), "tag");
593 std::vector<std::string> m;
594 m.reserve(members.count());
595 for (const QString &member : members) {
596 m.push_back(Conversion::toStdString(member));
597 }
598 relation.setMembers(m);
599
600 return writeRelationHelper(relation, tag.gid(), productId);
601 }
602
writeRelation(const Akonadi::Relation & relation,const QStringList & items,Version v,const QString & productId)603 KMime::Message::Ptr KolabObjectWriter::writeRelation(const Akonadi::Relation &relation, const QStringList &items, Version v, const QString &productId)
604 {
605 ErrorHandler::clearErrors();
606 if (v != KolabV3) {
607 qCCritical(PIMKOLAB_LOG) << "only v3 implementation available";
608 }
609
610 if (items.size() != 2) {
611 qCCritical(PIMKOLAB_LOG) << "Wrong number of members for generic relation.";
612 return KMime::Message::Ptr();
613 }
614
615 Kolab::Relation kolabRelation(std::string(), "generic");
616 std::vector<std::string> m;
617 m.reserve(2);
618 m.push_back(Conversion::toStdString(items.at(0)));
619 m.push_back(Conversion::toStdString(items.at(1)));
620 kolabRelation.setMembers(m);
621
622 return writeRelationHelper(kolabRelation, relation.remoteId(), productId);
623 }
624 } // Namespace
625