File indexing completed on 2024-12-01 04:47:51
0001 /* 0002 This file is part of the kolab resource - the implementation of the 0003 Kolab storage format. See www.kolab.org for documentation on this. 0004 0005 SPDX-FileCopyrightText: 2004 Bo Thorsen <bo@sonofthor.dk> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "incidence.h" 0011 #include "libkolab-version.h" 0012 #include "pimkolab_debug.h" 0013 0014 #include <QList> 0015 0016 #include <QBitArray> 0017 0018 using namespace KolabV2; 0019 0020 Incidence::Incidence(const QString &tz, const KCalendarCore::Incidence::Ptr &incidence) 0021 : KolabBase(tz) 0022 { 0023 Q_UNUSED(incidence) 0024 } 0025 0026 Incidence::~Incidence() = default; 0027 0028 void Incidence::setPriority(int priority) 0029 { 0030 mPriority = priority; 0031 } 0032 0033 int Incidence::priority() const 0034 { 0035 return mPriority; 0036 } 0037 0038 void Incidence::setSummary(const QString &summary) 0039 { 0040 mSummary = summary; 0041 } 0042 0043 QString Incidence::summary() const 0044 { 0045 return mSummary; 0046 } 0047 0048 void Incidence::setLocation(const QString &location) 0049 { 0050 mLocation = location; 0051 } 0052 0053 QString Incidence::location() const 0054 { 0055 return mLocation; 0056 } 0057 0058 void Incidence::setOrganizer(const Email &organizer) 0059 { 0060 mOrganizer = organizer; 0061 } 0062 0063 KolabBase::Email Incidence::organizer() const 0064 { 0065 return mOrganizer; 0066 } 0067 0068 void Incidence::setStartDate(const QDateTime &startDate) 0069 { 0070 mStartDate = startDate; 0071 if (mFloatingStatus == AllDay) { 0072 qCDebug(PIMKOLAB_LOG) << "ERROR: Time on start date but no time on the event"; 0073 } 0074 mFloatingStatus = HasTime; 0075 } 0076 0077 void Incidence::setStartDate(const QDate &startDate) 0078 { 0079 mStartDate = QDateTime(startDate, QTime()); 0080 if (mFloatingStatus == HasTime) { 0081 qCDebug(PIMKOLAB_LOG) << "ERROR: No time on start date but time on the event"; 0082 } 0083 mFloatingStatus = AllDay; 0084 } 0085 0086 void Incidence::setStartDate(const QString &startDate) 0087 { 0088 if (startDate.length() > 10) { 0089 // This is a date + time 0090 setStartDate(stringToDateTime(startDate)); 0091 } else { 0092 // This is only a date 0093 setStartDate(stringToDate(startDate)); 0094 } 0095 } 0096 0097 QDateTime Incidence::startDate() const 0098 { 0099 return mStartDate; 0100 } 0101 0102 void Incidence::setAlarm(float alarm) 0103 { 0104 mAlarm = alarm; 0105 mHasAlarm = true; 0106 } 0107 0108 float Incidence::alarm() const 0109 { 0110 return mAlarm; 0111 } 0112 0113 Incidence::Recurrence Incidence::recurrence() const 0114 { 0115 return mRecurrence; 0116 } 0117 0118 void Incidence::addAttendee(const Attendee &attendee) 0119 { 0120 mAttendees.append(attendee); 0121 } 0122 0123 QList<Incidence::Attendee> &Incidence::attendees() 0124 { 0125 return mAttendees; 0126 } 0127 0128 const QList<Incidence::Attendee> &Incidence::attendees() const 0129 { 0130 return mAttendees; 0131 } 0132 0133 void Incidence::setInternalUID(const QString &iuid) 0134 { 0135 mInternalUID = iuid; 0136 } 0137 0138 QString Incidence::internalUID() const 0139 { 0140 return mInternalUID; 0141 } 0142 0143 bool Incidence::loadAttendeeAttribute(QDomElement &element, Attendee &attendee) 0144 { 0145 for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { 0146 if (n.isComment()) { 0147 continue; 0148 } 0149 if (n.isElement()) { 0150 QDomElement e = n.toElement(); 0151 const QString tagName = e.tagName(); 0152 0153 if (tagName == QLatin1StringView("display-name")) { 0154 attendee.displayName = e.text(); 0155 } else if (tagName == QLatin1StringView("smtp-address")) { 0156 attendee.smtpAddress = e.text(); 0157 } else if (tagName == QLatin1StringView("status")) { 0158 attendee.status = e.text(); 0159 } else if (tagName == QLatin1StringView("request-response")) { 0160 // This sets reqResp to false, if the text is "false". Otherwise it 0161 // sets it to true. This means the default setting is true. 0162 attendee.requestResponse = (e.text().toLower() != QLatin1StringView("false")); 0163 } else if (tagName == QLatin1StringView("invitation-sent")) { 0164 // Like above, only this defaults to false 0165 attendee.invitationSent = (e.text().toLower() != QLatin1StringView("true")); 0166 } else if (tagName == QLatin1StringView("role")) { 0167 attendee.role = e.text(); 0168 } else if (tagName == QLatin1StringView("delegated-to")) { 0169 attendee.delegate = e.text(); 0170 } else if (tagName == QLatin1StringView("delegated-from")) { 0171 attendee.delegator = e.text(); 0172 } else { 0173 // TODO: Unhandled tag - save for later storage 0174 qCDebug(PIMKOLAB_LOG) << "Warning: Unhandled tag" << e.tagName(); 0175 } 0176 } else { 0177 qCDebug(PIMKOLAB_LOG) << "Node is not a comment or an element???"; 0178 } 0179 } 0180 0181 return true; 0182 } 0183 0184 void Incidence::saveAttendeeAttribute(QDomElement &element, const Attendee &attendee) const 0185 { 0186 QDomElement e = element.ownerDocument().createElement(QStringLiteral("attendee")); 0187 element.appendChild(e); 0188 writeString(e, QStringLiteral("display-name"), attendee.displayName); 0189 writeString(e, QStringLiteral("smtp-address"), attendee.smtpAddress); 0190 writeString(e, QStringLiteral("status"), attendee.status); 0191 writeString(e, QStringLiteral("request-response"), (attendee.requestResponse ? QStringLiteral("true") : QStringLiteral("false"))); 0192 writeString(e, QStringLiteral("invitation-sent"), (attendee.invitationSent ? QStringLiteral("true") : QStringLiteral("false"))); 0193 writeString(e, QStringLiteral("role"), attendee.role); 0194 writeString(e, QStringLiteral("delegated-to"), attendee.delegate); 0195 writeString(e, QStringLiteral("delegated-from"), attendee.delegator); 0196 } 0197 0198 void Incidence::saveAttendees(QDomElement &element) const 0199 { 0200 for (const Attendee &attendee : std::as_const(mAttendees)) { 0201 saveAttendeeAttribute(element, attendee); 0202 } 0203 } 0204 0205 void Incidence::saveAttachments(QDomElement &element) const 0206 { 0207 for (const KCalendarCore::Attachment &a : std::as_const(mAttachments)) { 0208 if (a.isUri()) { 0209 writeString(element, QStringLiteral("link-attachment"), a.uri()); 0210 } else if (a.isBinary()) { 0211 writeString(element, QStringLiteral("inline-attachment"), a.label()); 0212 } 0213 } 0214 } 0215 0216 void Incidence::saveAlarms(QDomElement &element) const 0217 { 0218 if (mAlarms.isEmpty()) { 0219 return; 0220 } 0221 0222 QDomElement list = element.ownerDocument().createElement(QStringLiteral("advanced-alarms")); 0223 element.appendChild(list); 0224 for (const KCalendarCore::Alarm::Ptr &a : std::as_const(mAlarms)) { 0225 QDomElement e = list.ownerDocument().createElement(QStringLiteral("alarm")); 0226 list.appendChild(e); 0227 0228 writeString(e, QStringLiteral("enabled"), a->enabled() ? QStringLiteral("1") : QStringLiteral("0")); 0229 if (a->hasStartOffset()) { 0230 writeString(e, QStringLiteral("start-offset"), QString::number(a->startOffset().asSeconds() / 60)); 0231 } 0232 if (a->hasEndOffset()) { 0233 writeString(e, QStringLiteral("end-offset"), QString::number(a->endOffset().asSeconds() / 60)); 0234 } 0235 if (a->repeatCount()) { 0236 writeString(e, QStringLiteral("repeat-count"), QString::number(a->repeatCount())); 0237 writeString(e, QStringLiteral("repeat-interval"), QString::number(a->snoozeTime().asSeconds())); 0238 } 0239 0240 switch (a->type()) { 0241 case KCalendarCore::Alarm::Invalid: 0242 break; 0243 case KCalendarCore::Alarm::Display: 0244 e.setAttribute(QStringLiteral("type"), QStringLiteral("display")); 0245 writeString(e, QStringLiteral("text"), a->text()); 0246 break; 0247 case KCalendarCore::Alarm::Procedure: 0248 e.setAttribute(QStringLiteral("type"), QStringLiteral("procedure")); 0249 writeString(e, QStringLiteral("program"), a->programFile()); 0250 writeString(e, QStringLiteral("arguments"), a->programArguments()); 0251 break; 0252 case KCalendarCore::Alarm::Email: { 0253 e.setAttribute(QStringLiteral("type"), QStringLiteral("email")); 0254 QDomElement addresses = e.ownerDocument().createElement(QStringLiteral("addresses")); 0255 e.appendChild(addresses); 0256 const auto mailAddresses{a->mailAddresses()}; 0257 for (const KCalendarCore::Person &person : mailAddresses) { 0258 writeString(addresses, QStringLiteral("address"), person.fullName()); 0259 } 0260 writeString(e, QStringLiteral("subject"), a->mailSubject()); 0261 writeString(e, QStringLiteral("mail-text"), a->mailText()); 0262 QDomElement attachments = e.ownerDocument().createElement(QStringLiteral("attachments")); 0263 e.appendChild(attachments); 0264 const auto mailAttachments{a->mailAttachments()}; 0265 for (const QString &attachment : mailAttachments) { 0266 writeString(attachments, QStringLiteral("attachment"), attachment); 0267 } 0268 break; 0269 } 0270 case KCalendarCore::Alarm::Audio: 0271 e.setAttribute(QStringLiteral("type"), QStringLiteral("audio")); 0272 writeString(e, QStringLiteral("file"), a->audioFile()); 0273 break; 0274 default: 0275 qCWarning(PIMKOLAB_LOG) << "Unhandled alarm type:" << a->type(); 0276 break; 0277 } 0278 } 0279 } 0280 0281 void Incidence::saveRecurrence(QDomElement &element) const 0282 { 0283 QDomElement e = element.ownerDocument().createElement(QStringLiteral("recurrence")); 0284 element.appendChild(e); 0285 e.setAttribute(QStringLiteral("cycle"), mRecurrence.cycle); 0286 if (!mRecurrence.type.isEmpty()) { 0287 e.setAttribute(QStringLiteral("type"), mRecurrence.type); 0288 } 0289 writeString(e, QStringLiteral("interval"), QString::number(mRecurrence.interval)); 0290 const auto days{mRecurrence.days}; 0291 for (const QString &recurrence : days) { 0292 writeString(e, QStringLiteral("day"), recurrence); 0293 } 0294 if (!mRecurrence.dayNumber.isEmpty()) { 0295 writeString(e, QStringLiteral("daynumber"), mRecurrence.dayNumber); 0296 } 0297 if (!mRecurrence.month.isEmpty()) { 0298 writeString(e, QStringLiteral("month"), mRecurrence.month); 0299 } 0300 if (!mRecurrence.rangeType.isEmpty()) { 0301 QDomElement range = element.ownerDocument().createElement(QStringLiteral("range")); 0302 e.appendChild(range); 0303 range.setAttribute(QStringLiteral("type"), mRecurrence.rangeType); 0304 QDomText t = element.ownerDocument().createTextNode(mRecurrence.range); 0305 range.appendChild(t); 0306 } 0307 const auto exclusions{mRecurrence.exclusions}; 0308 for (const QDate &date : exclusions) { 0309 writeString(e, QStringLiteral("exclusion"), dateToString(date)); 0310 } 0311 } 0312 0313 void Incidence::loadRecurrence(const QDomElement &element) 0314 { 0315 mRecurrence.interval = 0; 0316 mRecurrence.cycle = element.attribute(QStringLiteral("cycle")); 0317 mRecurrence.type = element.attribute(QStringLiteral("type")); 0318 for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { 0319 if (n.isComment()) { 0320 continue; 0321 } 0322 if (n.isElement()) { 0323 QDomElement e = n.toElement(); 0324 QString tagName = e.tagName(); 0325 if (tagName == QLatin1StringView("interval")) { 0326 // kolab/issue4229, sometimes the interval value can be empty 0327 if (e.text().isEmpty() || e.text().toInt() <= 0) { 0328 mRecurrence.interval = 1; 0329 } else { 0330 mRecurrence.interval = e.text().toInt(); 0331 } 0332 } else if (tagName == QLatin1StringView("day")) { // can be present multiple times 0333 mRecurrence.days.append(e.text()); 0334 } else if (tagName == QLatin1StringView("daynumber")) { 0335 mRecurrence.dayNumber = e.text(); 0336 } else if (tagName == QLatin1StringView("month")) { 0337 mRecurrence.month = e.text(); 0338 } else if (tagName == QLatin1StringView("range")) { 0339 mRecurrence.rangeType = e.attribute(QStringLiteral("type")); 0340 mRecurrence.range = e.text(); 0341 } else if (tagName == QLatin1StringView("exclusion")) { 0342 mRecurrence.exclusions.append(stringToDate(e.text())); 0343 } else { 0344 // TODO: Unhandled tag - save for later storage 0345 qCDebug(PIMKOLAB_LOG) << "Warning: Unhandled tag" << e.tagName(); 0346 } 0347 } 0348 } 0349 } 0350 0351 static void loadAddressesHelper(const QDomElement &element, const KCalendarCore::Alarm::Ptr &a) 0352 { 0353 for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { 0354 if (n.isComment()) { 0355 continue; 0356 } 0357 if (n.isElement()) { 0358 QDomElement e = n.toElement(); 0359 QString tagName = e.tagName(); 0360 0361 if (tagName == QLatin1StringView("address")) { 0362 a->addMailAddress(KCalendarCore::Person::fromFullName(e.text())); 0363 } else { 0364 qCWarning(PIMKOLAB_LOG) << "Unhandled tag" << tagName; 0365 } 0366 } 0367 } 0368 } 0369 0370 static void loadAttachmentsHelper(const QDomElement &element, const KCalendarCore::Alarm::Ptr &a) 0371 { 0372 for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { 0373 if (n.isComment()) { 0374 continue; 0375 } 0376 if (n.isElement()) { 0377 QDomElement e = n.toElement(); 0378 QString tagName = e.tagName(); 0379 0380 if (tagName == QLatin1StringView("attachment")) { 0381 a->addMailAttachment(e.text()); 0382 } else { 0383 qCWarning(PIMKOLAB_LOG) << "Unhandled tag" << tagName; 0384 } 0385 } 0386 } 0387 } 0388 0389 static void loadAlarmHelper(const QDomElement &element, const KCalendarCore::Alarm::Ptr &a) 0390 { 0391 for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { 0392 if (n.isComment()) { 0393 continue; 0394 } 0395 if (n.isElement()) { 0396 QDomElement e = n.toElement(); 0397 QString tagName = e.tagName(); 0398 0399 if (tagName == QLatin1StringView("start-offset")) { 0400 a->setStartOffset(e.text().toInt() * 60); 0401 } else if (tagName == QLatin1StringView("end-offset")) { 0402 a->setEndOffset(e.text().toInt() * 60); 0403 } else if (tagName == QLatin1StringView("repeat-count")) { 0404 a->setRepeatCount(e.text().toInt()); 0405 } else if (tagName == QLatin1StringView("repeat-interval")) { 0406 a->setSnoozeTime(e.text().toInt()); 0407 } else if (tagName == QLatin1StringView("text")) { 0408 a->setText(e.text()); 0409 } else if (tagName == QLatin1StringView("program")) { 0410 a->setProgramFile(e.text()); 0411 } else if (tagName == QLatin1StringView("arguments")) { 0412 a->setProgramArguments(e.text()); 0413 } else if (tagName == QLatin1StringView("addresses")) { 0414 loadAddressesHelper(e, a); 0415 } else if (tagName == QLatin1StringView("subject")) { 0416 a->setMailSubject(e.text()); 0417 } else if (tagName == QLatin1StringView("mail-text")) { 0418 a->setMailText(e.text()); 0419 } else if (tagName == QLatin1StringView("attachments")) { 0420 loadAttachmentsHelper(e, a); 0421 } else if (tagName == QLatin1StringView("file")) { 0422 a->setAudioFile(e.text()); 0423 } else if (tagName == QLatin1StringView("enabled")) { 0424 a->setEnabled(e.text().toInt() != 0); 0425 } else { 0426 qCWarning(PIMKOLAB_LOG) << "Unhandled tag" << tagName; 0427 } 0428 } 0429 } 0430 } 0431 0432 void Incidence::loadAlarms(const QDomElement &element) 0433 { 0434 for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { 0435 if (n.isComment()) { 0436 continue; 0437 } 0438 if (n.isElement()) { 0439 QDomElement e = n.toElement(); 0440 QString tagName = e.tagName(); 0441 0442 if (tagName == QLatin1StringView("alarm")) { 0443 KCalendarCore::Alarm::Ptr a = KCalendarCore::Alarm::Ptr(new KCalendarCore::Alarm(nullptr)); 0444 a->setEnabled(true); // default to enabled, unless some XML attribute says otherwise. 0445 QString type = e.attribute(QStringLiteral("type")); 0446 if (type == QLatin1StringView("display")) { 0447 a->setType(KCalendarCore::Alarm::Display); 0448 } else if (type == QLatin1StringView("procedure")) { 0449 a->setType(KCalendarCore::Alarm::Procedure); 0450 } else if (type == QLatin1StringView("email")) { 0451 a->setType(KCalendarCore::Alarm::Email); 0452 } else if (type == QLatin1StringView("audio")) { 0453 a->setType(KCalendarCore::Alarm::Audio); 0454 } else { 0455 qCWarning(PIMKOLAB_LOG) << "Unhandled alarm type:" << type; 0456 } 0457 0458 loadAlarmHelper(e, a); 0459 mAlarms << a; 0460 } else { 0461 qCWarning(PIMKOLAB_LOG) << "Unhandled tag" << tagName; 0462 } 0463 } 0464 } 0465 } 0466 0467 bool Incidence::loadAttribute(QDomElement &element) 0468 { 0469 QString tagName = element.tagName(); 0470 0471 if (tagName == QLatin1StringView("priority")) { 0472 bool ok; 0473 int p = element.text().toInt(&ok); 0474 if (!ok || p < 1 || p > 9) { 0475 qCWarning(PIMKOLAB_LOG) << "Invalid \"priority\" value:" << element.text(); 0476 } else { 0477 setPriority(p); 0478 } 0479 } else if (tagName == QLatin1StringView("x-kcal-priority")) { // for backwards compat 0480 bool ok; 0481 int p = element.text().toInt(&ok); 0482 if (!ok || p < 0 || p > 9) { 0483 qCWarning(PIMKOLAB_LOG) << "Invalid \"x-kcal-priority\" value:" << element.text(); 0484 } else { 0485 if (priority() == 0) { 0486 setPriority(p); 0487 } 0488 } 0489 } else if (tagName == QLatin1StringView("summary")) { 0490 setSummary(element.text()); 0491 } else if (tagName == QLatin1StringView("location")) { 0492 setLocation(element.text()); 0493 } else if (tagName == QLatin1StringView("organizer")) { 0494 Email email; 0495 if (loadEmailAttribute(element, email)) { 0496 setOrganizer(email); 0497 return true; 0498 } else { 0499 return false; 0500 } 0501 } else if (tagName == QLatin1StringView("start-date")) { 0502 setStartDate(element.text()); 0503 } else if (tagName == QLatin1StringView("recurrence")) { 0504 loadRecurrence(element); 0505 } else if (tagName == QLatin1StringView("attendee")) { 0506 Attendee attendee; 0507 if (loadAttendeeAttribute(element, attendee)) { 0508 addAttendee(attendee); 0509 return true; 0510 } else { 0511 return false; 0512 } 0513 } else if (tagName == QLatin1StringView("link-attachment")) { 0514 mAttachments.push_back(KCalendarCore::Attachment(element.text())); 0515 } else if (tagName == QLatin1StringView("alarm")) { 0516 // Alarms should be minutes before. Libkcal uses event time + alarm time 0517 setAlarm(-element.text().toInt()); 0518 } else if (tagName == QLatin1StringView("advanced-alarms")) { 0519 loadAlarms(element); 0520 } else if (tagName == QLatin1StringView("x-kde-internaluid")) { 0521 setInternalUID(element.text()); 0522 } else if (tagName == QLatin1StringView("x-custom")) { 0523 loadCustomAttributes(element); 0524 } else if (tagName == QLatin1StringView("inline-attachment")) { 0525 // we handle that separately later on, so no need to create a KolabUnhandled entry for it 0526 } else { 0527 bool ok = KolabBase::loadAttribute(element); 0528 if (!ok) { 0529 // Unhandled tag - save for later storage 0530 qCDebug(PIMKOLAB_LOG) << "Saving unhandled tag" << element.tagName(); 0531 Custom c; 0532 c.key = QByteArray("X-KDE-KolabUnhandled-") + element.tagName().toLatin1(); 0533 c.value = element.text(); 0534 mCustomList.append(c); 0535 } 0536 } 0537 // We handled this 0538 return true; 0539 } 0540 0541 bool Incidence::saveAttributes(QDomElement &element) const 0542 { 0543 // Save the base class elements 0544 KolabBase::saveAttributes(element); 0545 0546 if (priority() != 0) { 0547 writeString(element, QStringLiteral("priority"), QString::number(priority())); 0548 } 0549 0550 if (mFloatingStatus == HasTime) { 0551 writeString(element, QStringLiteral("start-date"), dateTimeToString(startDate())); 0552 } else { 0553 writeString(element, QStringLiteral("start-date"), dateToString(startDate().date())); 0554 } 0555 writeString(element, QStringLiteral("summary"), summary()); 0556 writeString(element, QStringLiteral("location"), location()); 0557 saveEmailAttribute(element, organizer(), QStringLiteral("organizer")); 0558 if (!mRecurrence.cycle.isEmpty()) { 0559 saveRecurrence(element); 0560 } 0561 saveAttendees(element); 0562 saveAttachments(element); 0563 if (mHasAlarm) { 0564 // Alarms should be minutes before. Libkcal uses event time + alarm time 0565 int alarmTime = qRound(-alarm()); 0566 writeString(element, QStringLiteral("alarm"), QString::number(alarmTime)); 0567 } 0568 saveAlarms(element); 0569 writeString(element, QStringLiteral("x-kde-internaluid"), internalUID()); 0570 saveCustomAttributes(element); 0571 return true; 0572 } 0573 0574 void Incidence::saveCustomAttributes(QDomElement &element) const 0575 { 0576 for (const Custom &custom : std::as_const(mCustomList)) { 0577 QString key(QString::fromUtf8(custom.key)); 0578 Q_ASSERT(!key.isEmpty()); 0579 if (key.startsWith(QLatin1StringView("X-KDE-KolabUnhandled-"))) { 0580 key = key.mid(strlen("X-KDE-KolabUnhandled-")); 0581 writeString(element, key, custom.value); 0582 } else { 0583 // Let's use attributes so that other tag-preserving-code doesn't need sub-elements 0584 QDomElement e = element.ownerDocument().createElement(QStringLiteral("x-custom")); 0585 element.appendChild(e); 0586 e.setAttribute(QStringLiteral("key"), key); 0587 e.setAttribute(QStringLiteral("value"), custom.value); 0588 } 0589 } 0590 } 0591 0592 void Incidence::loadCustomAttributes(QDomElement &element) 0593 { 0594 Custom custom; 0595 custom.key = element.attribute(QStringLiteral("key")).toLatin1(); 0596 custom.value = element.attribute(QStringLiteral("value")); 0597 mCustomList.append(custom); 0598 } 0599 0600 static KCalendarCore::Attendee::PartStat attendeeStringToStatus(const QString &s) 0601 { 0602 if (s == QLatin1StringView("none")) { 0603 return KCalendarCore::Attendee::NeedsAction; 0604 } 0605 if (s == QLatin1StringView("tentative")) { 0606 return KCalendarCore::Attendee::Tentative; 0607 } 0608 if (s == QLatin1StringView("declined")) { 0609 return KCalendarCore::Attendee::Declined; 0610 } 0611 if (s == QLatin1StringView("delegated")) { 0612 return KCalendarCore::Attendee::Delegated; 0613 } 0614 0615 // Default: 0616 return KCalendarCore::Attendee::Accepted; 0617 } 0618 0619 static QString attendeeStatusToString(KCalendarCore::Attendee::PartStat status) 0620 { 0621 switch (status) { 0622 case KCalendarCore::Attendee::NeedsAction: 0623 return QStringLiteral("none"); 0624 case KCalendarCore::Attendee::Accepted: 0625 return QStringLiteral("accepted"); 0626 case KCalendarCore::Attendee::Declined: 0627 return QStringLiteral("declined"); 0628 case KCalendarCore::Attendee::Tentative: 0629 return QStringLiteral("tentative"); 0630 case KCalendarCore::Attendee::Delegated: 0631 return QStringLiteral("delegated"); 0632 case KCalendarCore::Attendee::Completed: 0633 case KCalendarCore::Attendee::InProcess: 0634 // These don't have any meaning in the Kolab format, so just use: 0635 return QStringLiteral("accepted"); 0636 default: 0637 // Default for the case that there are more added later: 0638 return QStringLiteral("accepted"); 0639 } 0640 } 0641 0642 static KCalendarCore::Attendee::Role attendeeStringToRole(const QString &s) 0643 { 0644 if (s == QLatin1StringView("optional")) { 0645 return KCalendarCore::Attendee::OptParticipant; 0646 } 0647 if (s == QLatin1StringView("resource")) { 0648 return KCalendarCore::Attendee::NonParticipant; 0649 } 0650 return KCalendarCore::Attendee::ReqParticipant; 0651 } 0652 0653 static QString attendeeRoleToString(KCalendarCore::Attendee::Role role) 0654 { 0655 switch (role) { 0656 case KCalendarCore::Attendee::ReqParticipant: 0657 return QStringLiteral("required"); 0658 case KCalendarCore::Attendee::OptParticipant: 0659 return QStringLiteral("optional"); 0660 case KCalendarCore::Attendee::Chair: 0661 // We don't have the notion of chair, so use 0662 return QStringLiteral("required"); 0663 case KCalendarCore::Attendee::NonParticipant: 0664 // In Kolab, a non-participant is a resource 0665 return QStringLiteral("resource"); 0666 } 0667 0668 // Default for the case that there are more added later: 0669 return QStringLiteral("required"); 0670 } 0671 0672 static const char *s_weekDayName[] = { 0673 "monday", 0674 "tuesday", 0675 "wednesday", 0676 "thursday", 0677 "friday", 0678 "saturday", 0679 "sunday", 0680 }; 0681 0682 static const char *s_monthName[] = { 0683 "january", 0684 "february", 0685 "march", 0686 "april", 0687 "may", 0688 "june", 0689 "july", 0690 "august", 0691 "september", 0692 "october", 0693 "november", 0694 "december", 0695 }; 0696 0697 void Incidence::setRecurrence(KCalendarCore::Recurrence *recur) 0698 { 0699 mRecurrence.interval = recur->frequency(); 0700 switch (recur->recurrenceType()) { 0701 case KCalendarCore::Recurrence::rMinutely: // Not handled by the kolab XML 0702 mRecurrence.cycle = QStringLiteral("minutely"); 0703 break; 0704 case KCalendarCore::Recurrence::rHourly: // Not handled by the kolab XML 0705 mRecurrence.cycle = QStringLiteral("hourly"); 0706 break; 0707 case KCalendarCore::Recurrence::rDaily: 0708 mRecurrence.cycle = QStringLiteral("daily"); 0709 break; 0710 case KCalendarCore::Recurrence::rWeekly: // every X weeks 0711 mRecurrence.cycle = QStringLiteral("weekly"); 0712 { 0713 QBitArray arr = recur->days(); 0714 for (int idx = 0; idx < 7; ++idx) { 0715 if (arr.testBit(idx)) { 0716 mRecurrence.days.append(QString::fromUtf8(s_weekDayName[idx])); 0717 } 0718 } 0719 } 0720 break; 0721 case KCalendarCore::Recurrence::rMonthlyPos: { 0722 mRecurrence.cycle = QStringLiteral("monthly"); 0723 mRecurrence.type = QStringLiteral("weekday"); 0724 QList<KCalendarCore::RecurrenceRule::WDayPos> monthPositions = recur->monthPositions(); 0725 if (!monthPositions.isEmpty()) { 0726 KCalendarCore::RecurrenceRule::WDayPos monthPos = monthPositions.first(); 0727 // TODO: Handle multiple days in the same week 0728 mRecurrence.dayNumber = QString::number(monthPos.pos()); 0729 mRecurrence.days.append(QString::fromUtf8(s_weekDayName[monthPos.day() - 1])); 0730 // Not (properly) handled(?): monthPos.negative (nth days before end of month) 0731 } 0732 break; 0733 } 0734 case KCalendarCore::Recurrence::rMonthlyDay: { 0735 mRecurrence.cycle = QStringLiteral("monthly"); 0736 mRecurrence.type = QStringLiteral("daynumber"); 0737 QList<int> monthDays = recur->monthDays(); 0738 // ####### Kolab XML limitation: only the first month day is used 0739 if (!monthDays.isEmpty()) { 0740 mRecurrence.dayNumber = QString::number(monthDays.first()); 0741 } 0742 break; 0743 } 0744 case KCalendarCore::Recurrence::rYearlyMonth: // (day n of Month Y) 0745 { 0746 mRecurrence.cycle = QStringLiteral("yearly"); 0747 mRecurrence.type = QStringLiteral("monthday"); 0748 QList<int> rmd = recur->yearDates(); 0749 int day = !rmd.isEmpty() ? rmd.first() : recur->startDate().day(); 0750 mRecurrence.dayNumber = QString::number(day); 0751 QList<int> months = recur->yearMonths(); 0752 if (!months.isEmpty()) { 0753 mRecurrence.month = QString::fromUtf8(s_monthName[months.first() - 1]); // #### Kolab XML limitation: only one month specified 0754 } 0755 break; 0756 } 0757 case KCalendarCore::Recurrence::rYearlyDay: // YearlyDay (day N of the year). Not supported by Outlook 0758 mRecurrence.cycle = QStringLiteral("yearly"); 0759 mRecurrence.type = QStringLiteral("yearday"); 0760 mRecurrence.dayNumber = QString::number(recur->yearDays().constFirst()); 0761 break; 0762 case KCalendarCore::Recurrence::rYearlyPos: // (weekday X of week N of month Y) 0763 mRecurrence.cycle = QStringLiteral("yearly"); 0764 mRecurrence.type = QStringLiteral("weekday"); 0765 QList<int> months = recur->yearMonths(); 0766 if (!months.isEmpty()) { 0767 mRecurrence.month = QString::fromUtf8(s_monthName[months.first() - 1]); // #### Kolab XML limitation: only one month specified 0768 } 0769 QList<KCalendarCore::RecurrenceRule::WDayPos> monthPositions = recur->yearPositions(); 0770 if (!monthPositions.isEmpty()) { 0771 KCalendarCore::RecurrenceRule::WDayPos monthPos = monthPositions.first(); 0772 // TODO: Handle multiple days in the same week 0773 mRecurrence.dayNumber = QString::number(monthPos.pos()); 0774 mRecurrence.days.append(QString::fromUtf8(s_weekDayName[monthPos.day() - 1])); 0775 0776 // mRecurrence.dayNumber = QString::number( *recur->yearNums().getFirst() ); 0777 // Not handled: monthPos.negative (nth days before end of month) 0778 } 0779 break; 0780 } 0781 int howMany = recur->duration(); 0782 if (howMany > 0) { 0783 mRecurrence.rangeType = QStringLiteral("number"); 0784 mRecurrence.range = QString::number(howMany); 0785 } else if (howMany == 0) { 0786 mRecurrence.rangeType = QStringLiteral("date"); 0787 mRecurrence.range = dateToString(recur->endDate()); 0788 } else { 0789 mRecurrence.rangeType = QStringLiteral("none"); 0790 } 0791 } 0792 0793 void Incidence::setFields(const KCalendarCore::Incidence::Ptr &incidence) 0794 { 0795 KolabBase::setFields(incidence); 0796 0797 setPriority(incidence->priority()); 0798 if (incidence->allDay()) { 0799 // This is a all-day event. Don't timezone move this one 0800 mFloatingStatus = AllDay; 0801 setStartDate(incidence->dtStart().date()); 0802 } else { 0803 mFloatingStatus = HasTime; 0804 setStartDate(localToUTC(incidence->dtStart())); 0805 } 0806 0807 setSummary(incidence->summary()); 0808 setLocation(incidence->location()); 0809 0810 // Alarm 0811 mHasAlarm = false; // Will be set to true, if we actually have one 0812 if (incidence->hasEnabledAlarms()) { 0813 const KCalendarCore::Alarm::List &alarms = incidence->alarms(); 0814 if (!alarms.isEmpty()) { 0815 const KCalendarCore::Alarm::Ptr alarm = alarms.first(); 0816 if (alarm->hasStartOffset()) { 0817 int dur = alarm->startOffset().asSeconds(); 0818 setAlarm((float)dur / 60.0); 0819 } 0820 } 0821 } 0822 0823 if (!incidence->organizer().isEmpty()) { 0824 Email org(incidence->organizer().name(), incidence->organizer().email()); 0825 setOrganizer(org); 0826 } 0827 0828 // Attendees: 0829 const KCalendarCore::Attendee::List attendees = incidence->attendees(); 0830 for (const KCalendarCore::Attendee &kcalAttendee : attendees) { 0831 Attendee attendee; 0832 0833 attendee.displayName = kcalAttendee.name(); 0834 attendee.smtpAddress = kcalAttendee.email(); 0835 attendee.status = attendeeStatusToString(kcalAttendee.status()); 0836 attendee.requestResponse = kcalAttendee.RSVP(); 0837 // TODO: KCalendarCore::Attendee::mFlag is not accessible 0838 // attendee.invitationSent = kcalAttendee->mFlag; 0839 // DF: Hmm? mFlag is set to true and never used at all.... Did you mean another field? 0840 attendee.role = attendeeRoleToString(kcalAttendee.role()); 0841 attendee.delegate = kcalAttendee.delegate(); 0842 attendee.delegator = kcalAttendee.delegator(); 0843 0844 addAttendee(attendee); 0845 } 0846 0847 mAttachments.clear(); 0848 0849 // Attachments 0850 const KCalendarCore::Attachment::List attachments = incidence->attachments(); 0851 mAttachments.reserve(attachments.size()); 0852 for (const KCalendarCore::Attachment &a : attachments) { 0853 mAttachments.push_back(a); 0854 } 0855 0856 mAlarms.clear(); 0857 0858 // Alarms 0859 const KCalendarCore::Alarm::List alarms = incidence->alarms(); 0860 mAlarms.reserve(alarms.count()); 0861 for (const KCalendarCore::Alarm::Ptr &a : alarms) { 0862 mAlarms.push_back(a); 0863 } 0864 0865 if (incidence->recurs()) { 0866 setRecurrence(incidence->recurrence()); 0867 mRecurrence.exclusions = incidence->recurrence()->exDates(); 0868 } 0869 0870 // Handle the scheduling ID 0871 if (incidence->schedulingID() == incidence->uid()) { 0872 // There is no scheduling ID 0873 setInternalUID(QString()); // krazy:exclude=nullstrassign for old broken gcc 0874 } else { 0875 // We've internally been using a different uid, so save that as the 0876 // temporary (internal) uid and restore the original uid, the one that 0877 // is used in the folder and the outside world 0878 setUid(incidence->schedulingID()); 0879 setInternalUID(incidence->uid()); 0880 } 0881 0882 // Unhandled tags and other custom properties (see libkcal/customproperties.h) 0883 const QMap<QByteArray, QString> map = incidence->customProperties(); 0884 QMap<QByteArray, QString>::ConstIterator cit = map.cbegin(); 0885 QMap<QByteArray, QString>::ConstIterator cend = map.cend(); 0886 for (; cit != cend; ++cit) { 0887 Custom c; 0888 c.key = cit.key(); 0889 c.value = cit.value(); 0890 mCustomList.append(c); 0891 } 0892 } 0893 0894 static QBitArray daysListToBitArray(const QStringList &days) 0895 { 0896 QBitArray arr(7); 0897 arr.fill(false); 0898 for (const QString &day : days) { 0899 for (int i = 0; i < 7; ++i) { 0900 if (day == QLatin1StringView(s_weekDayName[i])) { 0901 arr.setBit(i, true); 0902 } 0903 } 0904 } 0905 return arr; 0906 } 0907 0908 void Incidence::saveTo(const KCalendarCore::Incidence::Ptr &incidence) 0909 { 0910 KolabBase::saveTo(incidence); 0911 0912 incidence->setPriority(priority()); 0913 if (mFloatingStatus == AllDay) { 0914 // This is an all-day event. Don't timezone move this one 0915 incidence->setDtStart(startDate()); 0916 incidence->setAllDay(true); 0917 } else { 0918 incidence->setDtStart(utcToLocal(startDate())); 0919 incidence->setAllDay(false); 0920 } 0921 0922 incidence->setSummary(summary()); 0923 incidence->setLocation(location()); 0924 0925 if (mHasAlarm && mAlarms.isEmpty()) { 0926 KCalendarCore::Alarm::Ptr alarm = incidence->newAlarm(); 0927 alarm->setStartOffset(qRound(mAlarm * 60.0)); 0928 alarm->setEnabled(true); 0929 alarm->setType(KCalendarCore::Alarm::Display); 0930 } else if (!mAlarms.isEmpty()) { 0931 for (const KCalendarCore::Alarm::Ptr &a : std::as_const(mAlarms)) { 0932 a->setParent(incidence.data()); 0933 incidence->addAlarm(a); 0934 } 0935 } 0936 0937 if (organizer().displayName.isEmpty()) { 0938 incidence->setOrganizer(organizer().smtpAddress); 0939 } else { 0940 incidence->setOrganizer(organizer().displayName + QLatin1Char('<') + organizer().smtpAddress + QLatin1Char('>')); 0941 } 0942 0943 incidence->clearAttendees(); 0944 for (const Attendee &attendee : std::as_const(mAttendees)) { 0945 KCalendarCore::Attendee::PartStat status = attendeeStringToStatus(attendee.status); 0946 KCalendarCore::Attendee::Role role = attendeeStringToRole(attendee.role); 0947 KCalendarCore::Attendee a(attendee.displayName, attendee.smtpAddress, attendee.requestResponse, status, role); 0948 a.setDelegate(attendee.delegate); 0949 a.setDelegator(attendee.delegator); 0950 incidence->addAttendee(a); 0951 } 0952 0953 incidence->clearAttachments(); 0954 for (const KCalendarCore::Attachment &a : std::as_const(mAttachments)) { 0955 incidence->addAttachment(a); 0956 } 0957 0958 if (!mRecurrence.cycle.isEmpty()) { 0959 KCalendarCore::Recurrence *recur = incidence->recurrence(); // yeah, this creates it 0960 // done below recur->setFrequency( mRecurrence.interval ); 0961 if (mRecurrence.cycle == QLatin1StringView("minutely")) { 0962 recur->setMinutely(mRecurrence.interval); 0963 } else if (mRecurrence.cycle == QLatin1StringView("hourly")) { 0964 recur->setHourly(mRecurrence.interval); 0965 } else if (mRecurrence.cycle == QLatin1StringView("daily")) { 0966 recur->setDaily(mRecurrence.interval); 0967 } else if (mRecurrence.cycle == QLatin1StringView("weekly")) { 0968 QBitArray rDays = daysListToBitArray(mRecurrence.days); 0969 recur->setWeekly(mRecurrence.interval, rDays); 0970 } else if (mRecurrence.cycle == QLatin1StringView("monthly")) { 0971 recur->setMonthly(mRecurrence.interval); 0972 if (mRecurrence.type == QLatin1StringView("weekday")) { 0973 recur->addMonthlyPos(mRecurrence.dayNumber.toInt(), daysListToBitArray(mRecurrence.days)); 0974 } else if (mRecurrence.type == QLatin1StringView("daynumber")) { 0975 recur->addMonthlyDate(mRecurrence.dayNumber.toInt()); 0976 } else { 0977 qCWarning(PIMKOLAB_LOG) << "Unhandled monthly recurrence type" << mRecurrence.type; 0978 } 0979 } else if (mRecurrence.cycle == QLatin1StringView("yearly")) { 0980 recur->setYearly(mRecurrence.interval); 0981 if (mRecurrence.type == QLatin1StringView("monthday")) { 0982 recur->addYearlyDate(mRecurrence.dayNumber.toInt()); 0983 for (int i = 0; i < 12; ++i) { 0984 if (QLatin1StringView(s_monthName[i]) == mRecurrence.month) { 0985 recur->addYearlyMonth(i + 1); 0986 } 0987 } 0988 } else if (mRecurrence.type == QLatin1StringView("yearday")) { 0989 recur->addYearlyDay(mRecurrence.dayNumber.toInt()); 0990 } else if (mRecurrence.type == QLatin1StringView("weekday")) { 0991 for (int i = 0; i < 12; ++i) { 0992 if (QLatin1StringView(s_monthName[i]) == mRecurrence.month) { 0993 recur->addYearlyMonth(i + 1); 0994 } 0995 } 0996 recur->addYearlyPos(mRecurrence.dayNumber.toInt(), daysListToBitArray(mRecurrence.days)); 0997 } else { 0998 qCWarning(PIMKOLAB_LOG) << "Unhandled yearly recurrence type" << mRecurrence.type; 0999 } 1000 } else { 1001 qCWarning(PIMKOLAB_LOG) << "Unhandled recurrence cycle" << mRecurrence.cycle; 1002 } 1003 1004 if (mRecurrence.rangeType == QLatin1StringView("number")) { 1005 recur->setDuration(mRecurrence.range.toInt()); 1006 } else if (mRecurrence.rangeType == QLatin1StringView("date")) { 1007 recur->setEndDate(stringToDate(mRecurrence.range)); 1008 } // "none" is default since tje set*ly methods set infinite recurrence 1009 1010 incidence->recurrence()->setExDates(mRecurrence.exclusions); 1011 } 1012 /* If we've stored a uid to be used internally instead of the real one 1013 * (to deal with duplicates of events in different folders) before, then 1014 * restore it, so it does not change. Keep the original uid around for 1015 * scheduling purposes. */ 1016 if (!internalUID().isEmpty()) { 1017 incidence->setUid(internalUID()); 1018 incidence->setSchedulingID(uid()); 1019 } 1020 1021 for (const Custom &custom : std::as_const(mCustomList)) { 1022 incidence->setNonKDECustomProperty(custom.key, custom.value); 1023 } 1024 } 1025 1026 QString Incidence::productID() const 1027 { 1028 return QStringLiteral("%1, Kolab resource").arg(QStringLiteral(LIBKOLAB_LIB_VERSION_STRING)); 1029 } 1030 1031 // Unhandled KCalendarCore::Incidence fields: 1032 // revision, status (unused), attendee.uid, 1033 // mComments, mReadOnly