File indexing completed on 2024-12-22 04:56:57

0001 /*
0002     SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "utils.h"
0008 #include "davprotocolattribute.h"
0009 
0010 #include <KDAV/DavItem>
0011 #include <KDAV/DavUrl>
0012 #include <KDAV/ProtocolInfo>
0013 
0014 #include <Akonadi/Collection>
0015 #include <KCalendarCore/ICalFormat>
0016 #include <KCalendarCore/Incidence>
0017 #include <KCalendarCore/MemoryCalendar>
0018 #include <KContacts/Addressee>
0019 #include <KContacts/VCardConverter>
0020 
0021 #include <KLocalizedString>
0022 
0023 #include <QByteArray>
0024 #include <QRandomGenerator>
0025 #include <QString>
0026 #include <QTimeZone>
0027 
0028 #include "davresource_debug.h"
0029 
0030 using IncidencePtr = QSharedPointer<KCalendarCore::Incidence>;
0031 
0032 static QString createUniqueId()
0033 {
0034     const qint64 time = QDateTime::currentMSecsSinceEpoch() / 1000;
0035     const int r = QRandomGenerator::global()->bounded(1000);
0036     const QString id = QLatin1Char('R') + QString::number(r);
0037     const QString uid = QString::number(time) + QLatin1Char('.') + id;
0038     return uid;
0039 }
0040 
0041 QString Utils::translatedProtocolName(KDAV::Protocol protocol)
0042 {
0043     QString protocolName;
0044 
0045     switch (protocol) {
0046     case KDAV::CalDav:
0047         protocolName = i18n("CalDav");
0048         break;
0049     case KDAV::CardDav:
0050         protocolName = i18n("CardDav");
0051         break;
0052     case KDAV::GroupDav:
0053         protocolName = i18n("GroupDav");
0054         break;
0055     }
0056 
0057     return protocolName;
0058 }
0059 
0060 KDAV::Protocol Utils::protocolByTranslatedName(const QString &name)
0061 {
0062     KDAV::Protocol protocol = KDAV::CalDav;
0063 
0064     if (name == i18n("CalDav")) {
0065         protocol = KDAV::CalDav;
0066     } else if (name == i18n("CardDav")) {
0067         protocol = KDAV::CardDav;
0068     } else if (name == i18n("GroupDav")) {
0069         protocol = KDAV::GroupDav;
0070     }
0071 
0072     return protocol;
0073 }
0074 
0075 KDAV::DavItem Utils::createDavItem(const Akonadi::Item &item, const Akonadi::Collection &collection, const Akonadi::Item::List &dependentItems)
0076 {
0077     QByteArray rawData;
0078     QString mimeType;
0079     QUrl url;
0080     KDAV::DavItem davItem;
0081     const QString basePath = collection.remoteId();
0082 
0083     if (item.hasPayload<KContacts::Addressee>()) {
0084         const auto contact = item.payload<KContacts::Addressee>();
0085         const QString fileName = createUniqueId();
0086 
0087         url = QUrl::fromUserInput(basePath + fileName + QLatin1StringView(".vcf"));
0088 
0089         const auto protoAttr = collection.attribute<DavProtocolAttribute>();
0090         if (protoAttr) {
0091             mimeType = KDAV::ProtocolInfo::contactsMimeType(KDAV::Protocol(protoAttr->davProtocol()));
0092         } else {
0093             mimeType = KContacts::Addressee::mimeType();
0094         }
0095 
0096         KContacts::VCardConverter converter;
0097         // rawData is already UTF-8
0098         rawData = converter.exportVCard(contact, KContacts::VCardConverter::v3_0);
0099     } else if (item.hasPayload<IncidencePtr>()) {
0100         const KCalendarCore::MemoryCalendar::Ptr calendar(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone()));
0101         calendar->addIncidence(item.payload<IncidencePtr>());
0102         for (const Akonadi::Item &dependentItem : std::as_const(dependentItems)) {
0103             calendar->addIncidence(dependentItem.payload<IncidencePtr>());
0104         }
0105 
0106         const QString fileName = createUniqueId();
0107 
0108         url = QUrl::fromUserInput(basePath + fileName + QLatin1StringView(".ics"));
0109         mimeType = QStringLiteral("text/calendar");
0110 
0111         KCalendarCore::ICalFormat formatter;
0112         rawData = formatter.toString(calendar).toUtf8();
0113     }
0114 
0115     davItem.setContentType(mimeType);
0116     davItem.setData(rawData);
0117     davItem.setUrl(KDAV::DavUrl(url, KDAV::CalDav));
0118     davItem.setEtag(item.remoteRevision());
0119 
0120     return davItem;
0121 }
0122 
0123 bool Utils::parseDavData(const KDAV::DavItem &source, Akonadi::Item &target, Akonadi::Item::List &extraItems)
0124 {
0125     const QString data = QString::fromUtf8(source.data());
0126 
0127     if (target.mimeType() == KContacts::Addressee::mimeType()) {
0128         KContacts::VCardConverter converter;
0129         const KContacts::Addressee contact = converter.parseVCard(source.data());
0130 
0131         if (contact.isEmpty()) {
0132             return false;
0133         }
0134 
0135         target.setPayloadFromData(source.data());
0136     } else {
0137         KCalendarCore::ICalFormat formatter;
0138         const KCalendarCore::MemoryCalendar::Ptr calendar(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone()));
0139         formatter.fromString(calendar, data);
0140         KCalendarCore::Incidence::List incidences = calendar->incidences();
0141 
0142         if (incidences.isEmpty()) {
0143             return false;
0144         }
0145 
0146         // All items must have the same uid in a single object.
0147         // Find the main VEVENT (if that's indeed what we have,
0148         // could be a VTODO or a VJOURNAL but that doesn't matter)
0149         // and then apply the recurrence exceptions
0150         IncidencePtr mainIncidence;
0151         KCalendarCore::Incidence::List exceptions;
0152 
0153         for (const IncidencePtr &incidence : std::as_const(incidences)) {
0154             if (incidence->hasRecurrenceId()) {
0155                 qCDebug(DAVRESOURCE_LOG) << "Exception found with ID" << incidence->instanceIdentifier();
0156                 exceptions << incidence;
0157             } else {
0158                 mainIncidence = incidence;
0159             }
0160         }
0161 
0162         if (!mainIncidence) {
0163             // Some broken events have only one incidence, with a recurrence ID - like a detached exception.
0164             // Rather than skipping those, make them appear: pick first incidence as the main one
0165             mainIncidence = incidences.at(0);
0166             exceptions.removeFirst();
0167         }
0168 
0169         for (const IncidencePtr &exception : std::as_const(exceptions)) {
0170             if (exception->status() == KCalendarCore::Incidence::StatusCanceled) {
0171                 const QDateTime exDateTime(exception->recurrenceId());
0172                 mainIncidence->recurrence()->addExDateTime(exDateTime);
0173             } else {
0174                 // The exception remote id will contain a fragment pointing to
0175                 // its instance identifier to distinguish it from the main
0176                 // event.
0177                 const QString rid = target.remoteId() + QLatin1StringView("#") + exception->instanceIdentifier();
0178                 qCDebug(DAVRESOURCE_LOG) << "Extra incidence at" << rid;
0179                 Akonadi::Item extraItem = target;
0180                 extraItem.setRemoteId(rid);
0181                 extraItem.setRemoteRevision(source.etag());
0182                 extraItem.setMimeType(exception->mimeType());
0183                 extraItem.setPayload<IncidencePtr>(exception);
0184                 extraItems << extraItem;
0185             }
0186         }
0187 
0188         target.setPayload<IncidencePtr>(mainIncidence);
0189         // fix mime type for CalDAV collections
0190         target.setMimeType(mainIncidence->mimeType());
0191 
0192         /*
0193         for ( const IncidencePtr &incidence : incidences ) {
0194           QString rid = item.remoteId() + QLatin1StringView( "#" ) + incidence->instanceIdentifier();
0195           Akonadi::Item extraItem = item;
0196           extraItem.setRemoteId( rid );
0197           extraItem.setRemoteRevision( davItem.etag() );
0198           extraItem.setMimeType( incidence->mimeType() );
0199           extraItem.setPayload<IncidencePtr>( incidence );
0200           items << extraItem;
0201         }
0202         */
0203     }
0204 
0205     return true;
0206 }