File indexing completed on 2024-11-24 04:44:12

0001 /*
0002  * SPDX-FileCopyrightText: 2011 Christian Mollekopf <mollekopf@kolabsys.com>
0003  *
0004  * SPDX-License-Identifier: LGPL-3.0-or-later
0005  */
0006 
0007 #include "kcalconversion.h"
0008 
0009 #include <KCalendarCore/Recurrence>
0010 #include <QDate>
0011 #include <QList>
0012 #include <QUrl>
0013 #include <vector>
0014 
0015 #include "commonconversion.h"
0016 #include "pimkolab_debug.h"
0017 namespace Kolab
0018 {
0019 namespace Conversion
0020 {
0021 // The uid of a contact which refers to the uuid of a contact in the addressbook
0022 #define CUSTOM_KOLAB_CONTACT_UUID "X_KOLAB_CONTACT_UUID"
0023 #define CUSTOM_KOLAB_CONTACT_CUTYPE "X_KOLAB_CONTACT_CUTYPE"
0024 #define CUSTOM_KOLAB_URL "X-KOLAB-URL"
0025 
0026 KCalendarCore::Duration toDuration(const Kolab::Duration &d)
0027 {
0028     int value = 0;
0029     if (d.hours() || d.minutes() || d.seconds()) {
0030         value = ((((d.weeks() * 7 + d.days()) * 24 + d.hours()) * 60 + d.minutes()) * 60 + d.seconds());
0031         if (d.isNegative()) {
0032             value = -value;
0033         }
0034         return {value};
0035     }
0036     value = d.weeks() * 7 + d.days();
0037     if (d.isNegative()) {
0038         value = -value;
0039     }
0040     return {value, KCalendarCore::Duration::Days};
0041 }
0042 
0043 Kolab::Duration fromDuration(const KCalendarCore::Duration &d)
0044 {
0045     int value = d.value();
0046     bool isNegative = false;
0047     if (value < 0) {
0048         isNegative = true;
0049         value = -value;
0050     }
0051     // We don't know how the seconds/days were distributed before, so no point in distributing them (probably)
0052     if (d.isDaily()) {
0053         int days = value;
0054         return {days, 0, 0, 0, isNegative};
0055     }
0056     int seconds = value;
0057     //         int minutes = seconds / 60;
0058     //         seconds = seconds % 60;
0059     //         int hours = minutes / 60;
0060     //         minutes = minutes % 60;
0061     return {0, 0, 0, seconds, isNegative};
0062 }
0063 
0064 KCalendarCore::Incidence::Secrecy toSecrecy(Kolab::Classification c)
0065 {
0066     switch (c) {
0067     case Kolab::ClassPublic:
0068         return KCalendarCore::Incidence::SecrecyPublic;
0069     case Kolab::ClassPrivate:
0070         return KCalendarCore::Incidence::SecrecyPrivate;
0071     case Kolab::ClassConfidential:
0072         return KCalendarCore::Incidence::SecrecyConfidential;
0073     default:
0074         qCCritical(PIMKOLAB_LOG) << "unhandled";
0075         Q_ASSERT(0);
0076     }
0077     return KCalendarCore::Incidence::SecrecyPublic;
0078 }
0079 
0080 Kolab::Classification fromSecrecy(KCalendarCore::Incidence::Secrecy c)
0081 {
0082     switch (c) {
0083     case KCalendarCore::Incidence::SecrecyPublic:
0084         return Kolab::ClassPublic;
0085     case KCalendarCore::Incidence::SecrecyPrivate:
0086         return Kolab::ClassPrivate;
0087     case KCalendarCore::Incidence::SecrecyConfidential:
0088         return Kolab::ClassConfidential;
0089     default:
0090         qCCritical(PIMKOLAB_LOG) << "unhandled";
0091         Q_ASSERT(0);
0092     }
0093     return Kolab::ClassPublic;
0094 }
0095 
0096 int toPriority(int priority)
0097 {
0098     // Same mapping
0099     return priority;
0100 }
0101 
0102 int fromPriority(int priority)
0103 {
0104     // Same mapping
0105     return priority;
0106 }
0107 
0108 KCalendarCore::Incidence::Status toStatus(Kolab::Status s)
0109 {
0110     switch (s) {
0111     case Kolab::StatusUndefined:
0112         return KCalendarCore::Incidence::StatusNone;
0113     case Kolab::StatusNeedsAction:
0114         return KCalendarCore::Incidence::StatusNeedsAction;
0115     case Kolab::StatusCompleted:
0116         return KCalendarCore::Incidence::StatusCompleted;
0117     case Kolab::StatusInProcess:
0118         return KCalendarCore::Incidence::StatusInProcess;
0119     case Kolab::StatusCancelled:
0120         return KCalendarCore::Incidence::StatusCanceled;
0121     case Kolab::StatusTentative:
0122         return KCalendarCore::Incidence::StatusTentative;
0123     case Kolab::StatusConfirmed:
0124         return KCalendarCore::Incidence::StatusConfirmed;
0125     case Kolab::StatusDraft:
0126         return KCalendarCore::Incidence::StatusDraft;
0127     case Kolab::StatusFinal:
0128         return KCalendarCore::Incidence::StatusFinal;
0129     default:
0130         qCCritical(PIMKOLAB_LOG) << "unhandled";
0131         Q_ASSERT(0);
0132     }
0133     return KCalendarCore::Incidence::StatusNone;
0134 }
0135 
0136 Kolab::Status fromStatus(KCalendarCore::Incidence::Status s)
0137 {
0138     switch (s) {
0139     case KCalendarCore::Incidence::StatusNone:
0140         return Kolab::StatusUndefined;
0141     case KCalendarCore::Incidence::StatusNeedsAction:
0142         return Kolab::StatusNeedsAction;
0143     case KCalendarCore::Incidence::StatusCompleted:
0144         return Kolab::StatusCompleted;
0145     case KCalendarCore::Incidence::StatusInProcess:
0146         return Kolab::StatusInProcess;
0147     case KCalendarCore::Incidence::StatusCanceled:
0148         return Kolab::StatusCancelled;
0149     case KCalendarCore::Incidence::StatusTentative:
0150         return Kolab::StatusTentative;
0151     case KCalendarCore::Incidence::StatusConfirmed:
0152         return Kolab::StatusConfirmed;
0153     case KCalendarCore::Incidence::StatusDraft:
0154         return Kolab::StatusDraft;
0155     case KCalendarCore::Incidence::StatusFinal:
0156         return Kolab::StatusFinal;
0157     default:
0158         qCCritical(PIMKOLAB_LOG) << "unhandled";
0159         Q_ASSERT(0);
0160     }
0161     return Kolab::StatusUndefined;
0162 }
0163 
0164 KCalendarCore::Attendee::PartStat toPartStat(Kolab::PartStatus p)
0165 {
0166     switch (p) {
0167     case Kolab::PartNeedsAction:
0168         return KCalendarCore::Attendee::NeedsAction;
0169     case Kolab::PartAccepted:
0170         return KCalendarCore::Attendee::Accepted;
0171     case Kolab::PartDeclined:
0172         return KCalendarCore::Attendee::Declined;
0173     case Kolab::PartTentative:
0174         return KCalendarCore::Attendee::Tentative;
0175     case Kolab::PartDelegated:
0176         return KCalendarCore::Attendee::Delegated;
0177     default:
0178         qCCritical(PIMKOLAB_LOG) << "unhandled";
0179         Q_ASSERT(0);
0180     }
0181     return KCalendarCore::Attendee::NeedsAction;
0182 }
0183 
0184 Kolab::PartStatus fromPartStat(KCalendarCore::Attendee::PartStat p)
0185 {
0186     switch (p) {
0187     case KCalendarCore::Attendee::NeedsAction:
0188         return Kolab::PartNeedsAction;
0189     case KCalendarCore::Attendee::Accepted:
0190         return Kolab::PartAccepted;
0191     case KCalendarCore::Attendee::Declined:
0192         return Kolab::PartDeclined;
0193     case KCalendarCore::Attendee::Tentative:
0194         return Kolab::PartTentative;
0195     case KCalendarCore::Attendee::Delegated:
0196         return Kolab::PartDelegated;
0197     default:
0198         qCCritical(PIMKOLAB_LOG) << "unhandled";
0199         Q_ASSERT(0);
0200     }
0201     return Kolab::PartNeedsAction;
0202 }
0203 
0204 KCalendarCore::Attendee::Role toRole(Kolab::Role r)
0205 {
0206     switch (r) {
0207     case Kolab::Required:
0208         return KCalendarCore::Attendee::ReqParticipant;
0209     case Kolab::Chair:
0210         return KCalendarCore::Attendee::Chair;
0211     case Kolab::Optional:
0212         return KCalendarCore::Attendee::OptParticipant;
0213     case Kolab::NonParticipant:
0214         return KCalendarCore::Attendee::NonParticipant;
0215     default:
0216         qCCritical(PIMKOLAB_LOG) << "unhandled";
0217         Q_ASSERT(0);
0218     }
0219     return KCalendarCore::Attendee::ReqParticipant;
0220 }
0221 
0222 Kolab::Role fromRole(KCalendarCore::Attendee::Role r)
0223 {
0224     switch (r) {
0225     case KCalendarCore::Attendee::ReqParticipant:
0226         return Kolab::Required;
0227     case KCalendarCore::Attendee::Chair:
0228         return Kolab::Chair;
0229     case KCalendarCore::Attendee::OptParticipant:
0230         return Kolab::Optional;
0231     case KCalendarCore::Attendee::NonParticipant:
0232         return Kolab::NonParticipant;
0233     default:
0234         qCCritical(PIMKOLAB_LOG) << "unhandled";
0235         Q_ASSERT(0);
0236     }
0237     return Kolab::Required;
0238 }
0239 
0240 template<typename T>
0241 QString getCustomProperty(const QString &id, const T &e)
0242 {
0243     const std::vector<Kolab::CustomProperty> &props = e.customProperties();
0244     for (const Kolab::CustomProperty &p : props) {
0245         if (fromStdString(p.identifier) == id) {
0246             return fromStdString(p.value);
0247         }
0248     }
0249 }
0250 
0251 template<typename T>
0252 void setIncidence(KCalendarCore::Incidence &i, const T &e)
0253 {
0254     if (!e.uid().empty()) {
0255         i.setUid(fromStdString(e.uid()));
0256     }
0257 
0258     i.setCreated(toDate(e.created()));
0259     i.setLastModified(toDate(e.lastModified()));
0260     i.setRevision(e.sequence());
0261     i.setSecrecy(toSecrecy(e.classification()));
0262     i.setCategories(toStringList(e.categories()));
0263 
0264     if (e.start().isValid()) {
0265         i.setDtStart(toDate(e.start()));
0266         i.setAllDay(e.start().isDateOnly());
0267     }
0268 
0269     i.setSummary(fromStdString(e.summary())); // TODO detect richtext
0270     i.setDescription(fromStdString(e.description())); // TODO detect richtext
0271     i.setStatus(toStatus(e.status()));
0272     const auto attendees{e.attendees()};
0273     for (const Kolab::Attendee &a : attendees) {
0274         /*
0275          * KCalendarCore always sets a UID if empty, but that's just a pointer, and not the uid of a real contact.
0276          * Since that means the semantics of the two are different, we have to store the kolab uid as a custom property.
0277          */
0278         KCalendarCore::Attendee attendee(fromStdString(a.contact().name()),
0279                                          fromStdString(a.contact().email()),
0280                                          a.rsvp(),
0281                                          toPartStat(a.partStat()),
0282                                          toRole(a.role()));
0283         if (!a.contact().uid().empty()) { // TODO Identify contact from addressbook based on uid
0284             attendee.customProperties().setNonKDECustomProperty(CUSTOM_KOLAB_CONTACT_UUID, fromStdString(a.contact().uid()));
0285         }
0286         if (!a.delegatedTo().empty()) {
0287             if (a.delegatedTo().size() > 1) {
0288                 qCWarning(PIMKOLAB_LOG) << "multiple delegatees are not supported";
0289             }
0290             attendee.setDelegate(toMailto(a.delegatedTo().front().email(), a.delegatedTo().front().name()).toString());
0291         }
0292         if (!a.delegatedFrom().empty()) {
0293             if (a.delegatedFrom().size() > 1) {
0294                 qCWarning(PIMKOLAB_LOG) << "multiple delegators are not supported";
0295             }
0296             attendee.setDelegator(toMailto(a.delegatedFrom().front().email(), a.delegatedFrom().front().name()).toString());
0297         }
0298         if (a.cutype() != Kolab::CutypeIndividual) {
0299             attendee.customProperties().setNonKDECustomProperty(CUSTOM_KOLAB_CONTACT_CUTYPE, QString::number(a.cutype()));
0300         }
0301         i.addAttendee(attendee);
0302     }
0303     const auto attachments{e.attachments()};
0304     for (const Kolab::Attachment &a : attachments) {
0305         KCalendarCore::Attachment att;
0306         if (!a.uri().empty()) {
0307             att = KCalendarCore::Attachment(fromStdString(a.uri()), fromStdString(a.mimetype()));
0308         } else {
0309             att = KCalendarCore::Attachment(QByteArray::fromRawData(a.data().c_str(), a.data().size()).toBase64(), fromStdString(a.mimetype()));
0310         }
0311         if (!a.label().empty()) {
0312             att.setLabel(fromStdString(a.label()));
0313         }
0314         i.addAttachment(att);
0315     }
0316 
0317     QMap<QByteArray, QString> props;
0318     const auto customProperties{e.customProperties()};
0319     for (const Kolab::CustomProperty &prop : customProperties) {
0320         QString key;
0321         if (prop.identifier.compare(0, 5, "X-KDE")) {
0322             key.append(QLatin1StringView("X-KOLAB-"));
0323         }
0324         key.append(fromStdString(prop.identifier));
0325         props.insert(key.toLatin1(), fromStdString(prop.value));
0326         //         i.setCustomProperty("KOLAB", fromStdString(prop.identifier).toLatin1(), fromStdString(prop.value));
0327     }
0328     i.setCustomProperties(props);
0329 }
0330 
0331 template<typename T, typename I>
0332 void getIncidence(T &i, const I &e)
0333 {
0334     i.setUid(toStdString(e.uid()));
0335     i.setCreated(fromDate(e.created(), false));
0336     i.setLastModified(fromDate(e.lastModified(), false));
0337     i.setSequence(e.revision());
0338     i.setClassification(fromSecrecy(e.secrecy()));
0339     i.setCategories(fromStringList(e.categories()));
0340 
0341     i.setStart(fromDate(e.dtStart(), e.allDay()));
0342     i.setSummary(toStdString(e.summary()));
0343     i.setDescription(toStdString(e.description()));
0344     i.setStatus(fromStatus(e.status()));
0345     std::vector<Kolab::Attendee> attendees;
0346     const auto eAttendees{e.attendees()};
0347     for (const KCalendarCore::Attendee &ptr : eAttendees) {
0348         const QString &uid = ptr.customProperties().nonKDECustomProperty(CUSTOM_KOLAB_CONTACT_UUID);
0349         Kolab::Attendee a(Kolab::ContactReference(toStdString(ptr.email()), toStdString(ptr.name()), toStdString(uid)));
0350         a.setRSVP(ptr.RSVP());
0351         a.setPartStat(fromPartStat(ptr.status()));
0352         a.setRole(fromRole(ptr.role()));
0353         if (!ptr.delegate().isEmpty()) {
0354             std::string name;
0355             const std::string &email = fromMailto(QUrl(ptr.delegate()), name);
0356             a.setDelegatedTo(std::vector<Kolab::ContactReference>() << Kolab::ContactReference(email, name));
0357         }
0358         if (!ptr.delegator().isEmpty()) {
0359             std::string name;
0360             const std::string &email = fromMailto(QUrl(ptr.delegator()), name);
0361             a.setDelegatedFrom(std::vector<Kolab::ContactReference>() << Kolab::ContactReference(email, name));
0362         }
0363         const QString &cutype = ptr.customProperties().nonKDECustomProperty(CUSTOM_KOLAB_CONTACT_CUTYPE);
0364         if (!cutype.isEmpty()) {
0365             a.setCutype(static_cast<Kolab::Cutype>(cutype.toInt()));
0366         }
0367 
0368         attendees.push_back(a);
0369     }
0370     i.setAttendees(attendees);
0371     std::vector<Kolab::Attachment> attachments;
0372     const auto eAttachments{e.attachments()};
0373     for (const KCalendarCore::Attachment &att : eAttachments) {
0374         Kolab::Attachment a;
0375         if (att.isUri()) {
0376             a.setUri(toStdString(att.uri()), toStdString(att.mimeType()));
0377         } else {
0378             a.setData(std::string(att.decodedData().data(), att.decodedData().size()), toStdString(att.mimeType()));
0379         }
0380         a.setLabel(toStdString(att.label()));
0381         attachments.push_back(a);
0382     }
0383     i.setAttachments(attachments);
0384 
0385     std::vector<Kolab::CustomProperty> customProperties;
0386     const QMap<QByteArray, QString> &props = e.customProperties();
0387     for (QMap<QByteArray, QString>::const_iterator it = props.cbegin(), end(props.cend()); it != end; ++it) {
0388         QString key(QString::fromUtf8(it.key()));
0389         if (key == QLatin1StringView(CUSTOM_KOLAB_URL)) {
0390             continue;
0391         }
0392         customProperties.push_back(Kolab::CustomProperty(toStdString(key.remove(QStringLiteral("X-KOLAB-"))), toStdString(it.value())));
0393     }
0394     i.setCustomProperties(customProperties);
0395 }
0396 
0397 int toWeekDay(Kolab::Weekday wday)
0398 {
0399     switch (wday) {
0400     case Kolab::Monday:
0401         return 1;
0402     case Kolab::Tuesday:
0403         return 2;
0404     case Kolab::Wednesday:
0405         return 3;
0406     case Kolab::Thursday:
0407         return 4;
0408     case Kolab::Friday:
0409         return 5;
0410     case Kolab::Saturday:
0411         return 6;
0412     case Kolab::Sunday:
0413         return 7;
0414     default:
0415         qCCritical(PIMKOLAB_LOG) << "unhandled";
0416         Q_ASSERT(0);
0417     }
0418     return 1;
0419 }
0420 
0421 Kolab::Weekday fromWeekDay(int wday)
0422 {
0423     switch (wday) {
0424     case 1:
0425         return Kolab::Monday;
0426     case 2:
0427         return Kolab::Tuesday;
0428     case 3:
0429         return Kolab::Wednesday;
0430     case 4:
0431         return Kolab::Thursday;
0432     case 5:
0433         return Kolab::Friday;
0434     case 6:
0435         return Kolab::Saturday;
0436     case 7:
0437         return Kolab::Sunday;
0438     default:
0439         qCCritical(PIMKOLAB_LOG) << "unhandled";
0440         Q_ASSERT(0);
0441     }
0442     return Kolab::Monday;
0443 }
0444 
0445 KCalendarCore::RecurrenceRule::PeriodType toRecurrenceType(Kolab::RecurrenceRule::Frequency freq)
0446 {
0447     switch (freq) {
0448     case Kolab::RecurrenceRule::FreqNone:
0449         qCWarning(PIMKOLAB_LOG) << "no recurrence?";
0450         break;
0451     case Kolab::RecurrenceRule::Yearly:
0452         return KCalendarCore::RecurrenceRule::rYearly;
0453     case Kolab::RecurrenceRule::Monthly:
0454         return KCalendarCore::RecurrenceRule::rMonthly;
0455     case Kolab::RecurrenceRule::Weekly:
0456         return KCalendarCore::RecurrenceRule::rWeekly;
0457     case Kolab::RecurrenceRule::Daily:
0458         return KCalendarCore::RecurrenceRule::rDaily;
0459     case Kolab::RecurrenceRule::Hourly:
0460         return KCalendarCore::RecurrenceRule::rHourly;
0461     case Kolab::RecurrenceRule::Minutely:
0462         return KCalendarCore::RecurrenceRule::rMinutely;
0463     case Kolab::RecurrenceRule::Secondly:
0464         return KCalendarCore::RecurrenceRule::rSecondly;
0465     default:
0466         qCCritical(PIMKOLAB_LOG) << "unhandled";
0467         Q_ASSERT(0);
0468     }
0469     return KCalendarCore::RecurrenceRule::rNone;
0470 }
0471 
0472 Kolab::RecurrenceRule::Frequency fromRecurrenceType(KCalendarCore::RecurrenceRule::PeriodType freq)
0473 {
0474     switch (freq) {
0475     case KCalendarCore::RecurrenceRule::rNone:
0476         qCWarning(PIMKOLAB_LOG) << "no recurrence?";
0477         break;
0478     case KCalendarCore::RecurrenceRule::rYearly:
0479         return Kolab::RecurrenceRule::Yearly;
0480     case KCalendarCore::RecurrenceRule::rMonthly:
0481         return Kolab::RecurrenceRule::Monthly;
0482     case KCalendarCore::RecurrenceRule::rWeekly:
0483         return Kolab::RecurrenceRule::Weekly;
0484     case KCalendarCore::RecurrenceRule::rDaily:
0485         return Kolab::RecurrenceRule::Daily;
0486     case KCalendarCore::RecurrenceRule::rHourly:
0487         return Kolab::RecurrenceRule::Hourly;
0488     case KCalendarCore::RecurrenceRule::rMinutely:
0489         return Kolab::RecurrenceRule::Minutely;
0490     case KCalendarCore::RecurrenceRule::rSecondly:
0491         return Kolab::RecurrenceRule::Secondly;
0492     default:
0493         qCCritical(PIMKOLAB_LOG) << "unhandled";
0494         Q_ASSERT(0);
0495     }
0496     return Kolab::RecurrenceRule::FreqNone;
0497 }
0498 
0499 KCalendarCore::RecurrenceRule::WDayPos toWeekDayPos(Kolab::DayPos dp)
0500 {
0501     return KCalendarCore::RecurrenceRule::WDayPos(dp.occurence(), toWeekDay(dp.weekday()));
0502 }
0503 
0504 Kolab::DayPos fromWeekDayPos(KCalendarCore::RecurrenceRule::WDayPos dp)
0505 {
0506     return {dp.pos(), fromWeekDay(dp.day())};
0507 }
0508 
0509 template<typename T>
0510 void setRecurrence(KCalendarCore::Incidence &e, const T &event)
0511 {
0512     const Kolab::RecurrenceRule &rrule = event.recurrenceRule();
0513     if (rrule.isValid()) {
0514         KCalendarCore::Recurrence *rec = e.recurrence();
0515 
0516         KCalendarCore::RecurrenceRule *defaultRR = rec->defaultRRule(true);
0517         Q_ASSERT(defaultRR);
0518 
0519         defaultRR->setWeekStart(toWeekDay(rrule.weekStart()));
0520         defaultRR->setRecurrenceType(toRecurrenceType(rrule.frequency()));
0521         defaultRR->setFrequency(rrule.interval());
0522 
0523         if (rrule.end().isValid()) {
0524             rec->setEndDateTime(toDate(rrule.end())); // TODO date/datetime setEndDate(). With date-only the start date has to be taken into account.
0525         } else {
0526             rec->setDuration(rrule.count());
0527         }
0528 
0529         if (!rrule.bysecond().empty()) {
0530             const std::vector<int> bySecond = rrule.bysecond();
0531             const QList<int> stdVector = QList<int>(bySecond.begin(), bySecond.end());
0532             defaultRR->setBySeconds(stdVector.toList());
0533         }
0534         if (!rrule.byminute().empty()) {
0535             const std::vector<int> byMinutes = rrule.byminute();
0536             const QList<int> stdVector = QList<int>(byMinutes.begin(), byMinutes.end());
0537             defaultRR->setByMinutes(stdVector.toList());
0538         }
0539         if (!rrule.byhour().empty()) {
0540             const std::vector<int> byHours = rrule.byhour();
0541             const QList<int> stdVector = QList<int>(byHours.begin(), byHours.end());
0542             defaultRR->setByHours(stdVector.toList());
0543         }
0544         if (!rrule.byday().empty()) {
0545             QList<KCalendarCore::RecurrenceRule::WDayPos> daypos;
0546             const auto bydays{rrule.byday()};
0547             for (const Kolab::DayPos &dp : bydays) {
0548                 daypos.append(toWeekDayPos(dp));
0549             }
0550             defaultRR->setByDays(daypos);
0551         }
0552         if (!rrule.bymonthday().empty()) {
0553             const std::vector<int> byMonthDays = rrule.bymonthday();
0554             const QList<int> stdVector = QList<int>(byMonthDays.begin(), byMonthDays.end());
0555             defaultRR->setByMonthDays(stdVector.toList());
0556         }
0557         if (!rrule.byyearday().empty()) {
0558             const std::vector<int> byYearDays = rrule.byyearday();
0559             const QList<int> stdVector = QList<int>(byYearDays.begin(), byYearDays.end());
0560             defaultRR->setByYearDays(stdVector.toList());
0561         }
0562         if (!rrule.byweekno().empty()) {
0563             const std::vector<int> byWeekNumbers = rrule.byweekno();
0564             const QList<int> stdVector = QList<int>(byWeekNumbers.begin(), byWeekNumbers.end());
0565             defaultRR->setByWeekNumbers(stdVector.toList());
0566         }
0567         if (!rrule.bymonth().empty()) {
0568             const std::vector<int> byMonths = rrule.bymonth();
0569             const QList<int> stdVector = QList<int>(byMonths.begin(), byMonths.end());
0570             defaultRR->setByMonths(stdVector.toList());
0571         }
0572     }
0573     const auto recurrenceDates{event.recurrenceDates()};
0574     for (const Kolab::cDateTime &dt : recurrenceDates) {
0575         const QDateTime &date = toDate(dt);
0576         if (dt.isDateOnly()) {
0577             e.recurrence()->addRDate(date.date());
0578         } else {
0579             e.recurrence()->addRDateTime(date);
0580         }
0581     }
0582     const auto exceptionDates{event.exceptionDates()};
0583     for (const Kolab::cDateTime &dt : exceptionDates) {
0584         const QDateTime &date = toDate(dt);
0585         if (dt.isDateOnly()) {
0586             e.recurrence()->addExDate(date.date());
0587         } else {
0588             e.recurrence()->addExDateTime(date);
0589         }
0590     }
0591 }
0592 
0593 template<typename T, typename I>
0594 void getRecurrence(T &i, const I &e)
0595 {
0596     if (!e.recurs()) {
0597         return;
0598     }
0599     KCalendarCore::Recurrence *rec = e.recurrence();
0600     KCalendarCore::RecurrenceRule *defaultRR = rec->defaultRRule(false);
0601     if (!defaultRR) {
0602         qCWarning(PIMKOLAB_LOG) << "no recurrence";
0603         return;
0604     }
0605     Q_ASSERT(defaultRR);
0606 
0607     Kolab::RecurrenceRule rrule;
0608     rrule.setWeekStart(fromWeekDay(defaultRR->weekStart()));
0609     rrule.setFrequency(fromRecurrenceType(defaultRR->recurrenceType()));
0610     rrule.setInterval(defaultRR->frequency());
0611 
0612     if (defaultRR->duration() != 0) { // Inidcates if end date is set or not
0613         if (defaultRR->duration() > 0) {
0614             rrule.setCount(defaultRR->duration());
0615         }
0616     } else {
0617         rrule.setEnd(fromDate(defaultRR->endDt(), e.allDay()));
0618     }
0619 
0620     const QList<int> bySecondsVector = defaultRR->bySeconds().toVector();
0621     const auto stdVectorBySeconds = std::vector<int>(bySecondsVector.begin(), bySecondsVector.end());
0622     rrule.setBysecond(stdVectorBySeconds);
0623 
0624     const QList<int> byMinutesVector = defaultRR->byMinutes().toVector();
0625     const auto stdVectorByMinutes = std::vector<int>(byMinutesVector.begin(), byMinutesVector.end());
0626     rrule.setByminute(stdVectorByMinutes);
0627 
0628     const QList<int> byHoursVector = defaultRR->byHours().toVector();
0629     const auto stdVectorByHours = std::vector<int>(byHoursVector.begin(), byHoursVector.end());
0630     rrule.setByhour(stdVectorByHours);
0631 
0632     std::vector<Kolab::DayPos> daypos;
0633     const auto defaultRRByDays{defaultRR->byDays()};
0634     daypos.reserve(defaultRRByDays.count());
0635 
0636     for (const KCalendarCore::RecurrenceRule::WDayPos &dp : defaultRRByDays) {
0637         daypos.push_back(fromWeekDayPos(dp));
0638     }
0639     rrule.setByday(daypos);
0640 
0641     const QList<int> bymonthdayVector = defaultRR->byMonthDays().toVector();
0642     const auto stdByMonthDayVector = std::vector<int>(bymonthdayVector.begin(), bymonthdayVector.end());
0643     rrule.setBymonthday(stdByMonthDayVector);
0644 
0645     const QList<int> byYearDaysVector = defaultRR->byYearDays().toVector();
0646     const auto stdByYearDayVector = std::vector<int>(byYearDaysVector.begin(), byYearDaysVector.end());
0647     rrule.setByyearday(stdByYearDayVector);
0648 
0649     const QList<int> byWeekNumberVector = defaultRR->byWeekNumbers().toVector();
0650     const auto stdWeekNumberVector = std::vector<int>(byWeekNumberVector.begin(), byWeekNumberVector.end());
0651     rrule.setByweekno(stdWeekNumberVector);
0652 
0653     const QList<int> byMonthVector = defaultRR->byMonths().toVector();
0654     const auto stdByMonthVector = std::vector<int>(byMonthVector.begin(), byMonthVector.end());
0655     rrule.setBymonth(stdByMonthVector);
0656 
0657     i.setRecurrenceRule(rrule);
0658 
0659     std::vector<Kolab::cDateTime> rdates;
0660     const auto rDateTimes{rec->rDateTimes()};
0661     for (const QDateTime &dt : rDateTimes) {
0662         rdates.push_back(fromDate(dt, e.allDay()));
0663     }
0664     const auto recRDates{rec->rDates()};
0665     for (const QDate &dt : recRDates) {
0666         rdates.push_back(fromDate(QDateTime(dt, {}), true));
0667     }
0668     i.setRecurrenceDates(rdates);
0669 
0670     std::vector<Kolab::cDateTime> exdates;
0671     const auto recExDateTimes{rec->exDateTimes()};
0672     for (const QDateTime &dt : recExDateTimes) {
0673         exdates.push_back(fromDate(dt, e.allDay()));
0674     }
0675     const auto exDates = rec->exDates();
0676     for (const QDate &dt : exDates) {
0677         exdates.push_back(fromDate(QDateTime(dt, {}), true));
0678     }
0679     i.setExceptionDates(exdates);
0680 
0681     if (!rec->exRules().empty()) {
0682         qCWarning(PIMKOLAB_LOG) << "exrules are not supported";
0683     }
0684 }
0685 
0686 template<typename T>
0687 void setTodoEvent(KCalendarCore::Incidence &i, const T &e)
0688 {
0689     i.setPriority(toPriority(e.priority()));
0690     if (!e.location().empty()) {
0691         i.setLocation(fromStdString(e.location())); // TODO detect richtext
0692     }
0693     if (e.organizer().isValid()) {
0694         i.setOrganizer(KCalendarCore::Person(fromStdString(e.organizer().name()), fromStdString(e.organizer().email()))); // TODO handle uid too
0695     }
0696     if (!e.url().empty()) {
0697         i.setNonKDECustomProperty(CUSTOM_KOLAB_URL, fromStdString(e.url()));
0698     }
0699     if (e.recurrenceID().isValid()) {
0700         i.setRecurrenceId(toDate(e.recurrenceID())); // TODO THISANDFUTURE
0701     }
0702     setRecurrence(i, e);
0703     const auto alarms{e.alarms()};
0704     for (const Kolab::Alarm &a : alarms) {
0705         KCalendarCore::Alarm::Ptr alarm = KCalendarCore::Alarm::Ptr(new KCalendarCore::Alarm(&i));
0706         switch (a.type()) {
0707         case Kolab::Alarm::EMailAlarm: {
0708             KCalendarCore::Person::List receipents;
0709             const auto aAttendees{a.attendees()};
0710             for (Kolab::ContactReference c : aAttendees) {
0711                 KCalendarCore::Person person(fromStdString(c.name()), fromStdString(c.email()));
0712                 receipents.append(person);
0713             }
0714             alarm->setEmailAlarm(fromStdString(a.summary()), fromStdString(a.description()), receipents);
0715             break;
0716         }
0717         case Kolab::Alarm::DisplayAlarm:
0718             alarm->setDisplayAlarm(fromStdString(a.text()));
0719             break;
0720         case Kolab::Alarm::AudioAlarm:
0721             alarm->setAudioAlarm(fromStdString(a.audioFile().uri()));
0722             break;
0723         default:
0724             qCCritical(PIMKOLAB_LOG) << "invalid alarm";
0725         }
0726 
0727         if (a.start().isValid()) {
0728             alarm->setTime(toDate(a.start()));
0729         } else if (a.relativeStart().isValid()) {
0730             if (a.relativeTo() == Kolab::End) {
0731                 alarm->setEndOffset(toDuration(a.relativeStart()));
0732             } else {
0733                 alarm->setStartOffset(toDuration(a.relativeStart()));
0734             }
0735         }
0736 
0737         alarm->setSnoozeTime(toDuration(a.duration()));
0738         alarm->setRepeatCount(a.numrepeat());
0739         alarm->setEnabled(true);
0740         i.addAlarm(alarm);
0741     }
0742 }
0743 
0744 template<typename T, typename I>
0745 void getTodoEvent(T &i, const I &e)
0746 {
0747     i.setPriority(fromPriority(e.priority()));
0748     i.setLocation(toStdString(e.location()));
0749     if (!e.organizer().email().isEmpty()) {
0750         i.setOrganizer(Kolab::ContactReference(Kolab::ContactReference::EmailReference,
0751                                                toStdString(e.organizer().email()),
0752                                                toStdString(e.organizer().name()))); // TODO handle uid too
0753     }
0754     i.setUrl(toStdString(e.nonKDECustomProperty(CUSTOM_KOLAB_URL)));
0755     i.setRecurrenceID(fromDate(e.recurrenceId(), e.allDay()), false); // TODO THISANDFUTURE
0756     getRecurrence(i, e);
0757     std::vector<Kolab::Alarm> alarms;
0758     const auto eAlarms{e.alarms()};
0759     for (const KCalendarCore::Alarm::Ptr &a : eAlarms) {
0760         Kolab::Alarm alarm;
0761         // TODO KCalendarCore disables alarms using KCalendarCore::Alarm::enabled() (X-KDE-KCALCORE-ENABLED) We should either delete the alarm, or store the
0762         // attribute . Ideally we would store the alarm somewhere and temporarily delete it, so we can restore it when parsing. For now we just remove disabled
0763         // alarms.
0764         if (!a->enabled()) {
0765             qCWarning(PIMKOLAB_LOG) << "skipping disabled alarm";
0766             continue;
0767         }
0768         switch (a->type()) {
0769         case KCalendarCore::Alarm::Display:
0770             alarm = Kolab::Alarm(toStdString(a->text()));
0771             break;
0772         case KCalendarCore::Alarm::Email: {
0773             std::vector<Kolab::ContactReference> receipents;
0774             const auto mailAddresses = a->mailAddresses();
0775             for (const KCalendarCore::Person &p : mailAddresses) {
0776                 receipents.emplace_back(toStdString(p.email()), toStdString(p.name()));
0777             }
0778             alarm = Kolab::Alarm(toStdString(a->mailSubject()), toStdString(a->mailText()), receipents);
0779             break;
0780         }
0781         case KCalendarCore::Alarm::Audio: {
0782             Kolab::Attachment audioFile;
0783             audioFile.setUri(toStdString(a->audioFile()), std::string());
0784             alarm = Kolab::Alarm(audioFile);
0785             break;
0786         }
0787         default:
0788             qCCritical(PIMKOLAB_LOG) << "unhandled alarm";
0789         }
0790 
0791         if (a->hasTime()) {
0792             alarm.setStart(fromDate(a->time(), false));
0793         } else if (a->hasStartOffset()) {
0794             alarm.setRelativeStart(fromDuration(a->startOffset()), Kolab::Start);
0795         } else if (a->hasEndOffset()) {
0796             alarm.setRelativeStart(fromDuration(a->endOffset()), Kolab::End);
0797         } else {
0798             qCCritical(PIMKOLAB_LOG) << "alarm trigger is missing";
0799             continue;
0800         }
0801 
0802         alarm.setDuration(fromDuration(a->snoozeTime()), a->repeatCount());
0803 
0804         alarms.push_back(alarm);
0805     }
0806     i.setAlarms(alarms);
0807 }
0808 
0809 KCalendarCore::Event::Ptr toKCalendarCore(const Kolab::Event &event)
0810 {
0811     KCalendarCore::Event::Ptr e(new KCalendarCore::Event);
0812     setIncidence(*e, event);
0813     setTodoEvent(*e, event);
0814     if (event.end().isValid()) {
0815         e->setDtEnd(toDate(event.end()));
0816     }
0817     if (event.duration().isValid()) {
0818         e->setDuration(toDuration(event.duration()));
0819     }
0820     if (event.transparency()) {
0821         e->setTransparency(KCalendarCore::Event::Transparent);
0822     } else {
0823         e->setTransparency(KCalendarCore::Event::Opaque);
0824     }
0825     return e;
0826 }
0827 
0828 Kolab::Event fromKCalendarCore(const KCalendarCore::Event &event)
0829 {
0830     Kolab::Event e;
0831     getIncidence(e, event);
0832     getTodoEvent(e, event);
0833     if (event.hasEndDate()) {
0834         e.setEnd(fromDate(event.dtEnd(), event.allDay()));
0835     } else if (event.hasDuration()) {
0836         e.setDuration(fromDuration(event.duration()));
0837     }
0838     if (event.transparency() == KCalendarCore::Event::Transparent) {
0839         e.setTransparency(true);
0840     } else {
0841         e.setTransparency(false);
0842     }
0843     return e;
0844 }
0845 
0846 KCalendarCore::Todo::Ptr toKCalendarCore(const Kolab::Todo &todo)
0847 {
0848     KCalendarCore::Todo::Ptr e(new KCalendarCore::Todo);
0849     setIncidence(*e, todo);
0850     setTodoEvent(*e, todo);
0851     if (todo.due().isValid()) {
0852         e->setDtDue(toDate(todo.due()));
0853     }
0854     if (!todo.relatedTo().empty()) {
0855         e->setRelatedTo(Kolab::Conversion::fromStdString(todo.relatedTo().front()), KCalendarCore::Incidence::RelTypeParent);
0856         if (todo.relatedTo().size() > 1) {
0857             qCCritical(PIMKOLAB_LOG) << "only one relation support but got multiple";
0858         }
0859     }
0860     e->setPercentComplete(todo.percentComplete());
0861     return e;
0862 }
0863 
0864 Kolab::Todo fromKCalendarCore(const KCalendarCore::Todo &todo)
0865 {
0866     Kolab::Todo t;
0867     getIncidence(t, todo);
0868     getTodoEvent(t, todo);
0869     t.setDue(fromDate(todo.dtDue(true), todo.allDay()));
0870     t.setPercentComplete(todo.percentComplete());
0871     const QString relatedTo = todo.relatedTo(KCalendarCore::Incidence::RelTypeParent);
0872     if (!relatedTo.isEmpty()) {
0873         std::vector<std::string> relateds;
0874         relateds.push_back(Kolab::Conversion::toStdString(relatedTo));
0875         t.setRelatedTo(relateds);
0876     }
0877     return t;
0878 }
0879 
0880 KCalendarCore::Journal::Ptr toKCalendarCore(const Kolab::Journal &journal)
0881 {
0882     KCalendarCore::Journal::Ptr e(new KCalendarCore::Journal);
0883     setIncidence(*e, journal);
0884     // TODO contacts
0885     return e;
0886 }
0887 
0888 Kolab::Journal fromKCalendarCore(const KCalendarCore::Journal &journal)
0889 {
0890     Kolab::Journal j;
0891     getIncidence(j, journal);
0892     // TODO contacts
0893     return j;
0894 }
0895 }
0896 }