/* SPDX-FileCopyrightText: 2010 Tobias Koenig SPDX-License-Identifier: GPL-2.0-or-later */ #include "utils.h" #include "davprotocolattribute.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "davresource_debug.h" using IncidencePtr = QSharedPointer; static QString createUniqueId() { const qint64 time = QDateTime::currentMSecsSinceEpoch() / 1000; const int r = QRandomGenerator::global()->bounded(1000); const QString id = QLatin1Char('R') + QString::number(r); const QString uid = QString::number(time) + QLatin1Char('.') + id; return uid; } QString Utils::translatedProtocolName(KDAV::Protocol protocol) { QString protocolName; switch (protocol) { case KDAV::CalDav: protocolName = i18n("CalDav"); break; case KDAV::CardDav: protocolName = i18n("CardDav"); break; case KDAV::GroupDav: protocolName = i18n("GroupDav"); break; } return protocolName; } KDAV::Protocol Utils::protocolByTranslatedName(const QString &name) { KDAV::Protocol protocol = KDAV::CalDav; if (name == i18n("CalDav")) { protocol = KDAV::CalDav; } else if (name == i18n("CardDav")) { protocol = KDAV::CardDav; } else if (name == i18n("GroupDav")) { protocol = KDAV::GroupDav; } return protocol; } KDAV::DavItem Utils::createDavItem(const Akonadi::Item &item, const Akonadi::Collection &collection, const Akonadi::Item::List &dependentItems) { QByteArray rawData; QString mimeType; QUrl url; KDAV::DavItem davItem; const QString basePath = collection.remoteId(); if (item.hasPayload()) { const auto contact = item.payload(); const QString fileName = createUniqueId(); url = QUrl::fromUserInput(basePath + fileName + QLatin1String(".vcf")); const auto protoAttr = collection.attribute(); if (protoAttr) { mimeType = KDAV::ProtocolInfo::contactsMimeType(KDAV::Protocol(protoAttr->davProtocol())); } else { mimeType = KContacts::Addressee::mimeType(); } KContacts::VCardConverter converter; // rawData is already UTF-8 rawData = converter.exportVCard(contact, KContacts::VCardConverter::v3_0); } else if (item.hasPayload()) { const KCalendarCore::MemoryCalendar::Ptr calendar(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone())); calendar->addIncidence(item.payload()); for (const Akonadi::Item &dependentItem : std::as_const(dependentItems)) { calendar->addIncidence(dependentItem.payload()); } const QString fileName = createUniqueId(); url = QUrl::fromUserInput(basePath + fileName + QLatin1String(".ics")); mimeType = QStringLiteral("text/calendar"); KCalendarCore::ICalFormat formatter; rawData = formatter.toString(calendar, QString()).toUtf8(); } davItem.setContentType(mimeType); davItem.setData(rawData); davItem.setUrl(KDAV::DavUrl(url, KDAV::CalDav)); davItem.setEtag(item.remoteRevision()); return davItem; } bool Utils::parseDavData(const KDAV::DavItem &source, Akonadi::Item &target, Akonadi::Item::List &extraItems) { const QString data = QString::fromUtf8(source.data()); if (target.mimeType() == KContacts::Addressee::mimeType()) { KContacts::VCardConverter converter; const KContacts::Addressee contact = converter.parseVCard(source.data()); if (contact.isEmpty()) { return false; } target.setPayloadFromData(source.data()); } else { KCalendarCore::ICalFormat formatter; const KCalendarCore::MemoryCalendar::Ptr calendar(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone())); formatter.fromString(calendar, data); KCalendarCore::Incidence::List incidences = calendar->incidences(); if (incidences.isEmpty()) { return false; } // All items must have the same uid in a single object. // Find the main VEVENT (if that's indeed what we have, // could be a VTODO or a VJOURNAL but that doesn't matter) // and then apply the recurrence exceptions IncidencePtr mainIncidence; KCalendarCore::Incidence::List exceptions; for (const IncidencePtr &incidence : std::as_const(incidences)) { if (incidence->hasRecurrenceId()) { qCDebug(DAVRESOURCE_LOG) << "Exception found with ID" << incidence->instanceIdentifier(); exceptions << incidence; } else { mainIncidence = incidence; } } if (!mainIncidence) { // Some broken events have only one incidence, with a recurrence ID - like a detached exception. // Rather than skipping those, make them appear: pick first incidence as the main one mainIncidence = incidences.at(0); exceptions.removeFirst(); } for (const IncidencePtr &exception : std::as_const(exceptions)) { if (exception->status() == KCalendarCore::Incidence::StatusCanceled) { const QDateTime exDateTime(exception->recurrenceId()); mainIncidence->recurrence()->addExDateTime(exDateTime); } else { // The exception remote id will contain a fragment pointing to // its instance identifier to distinguish it from the main // event. const QString rid = target.remoteId() + QLatin1String("#") + exception->instanceIdentifier(); qCDebug(DAVRESOURCE_LOG) << "Extra incidence at" << rid; Akonadi::Item extraItem = target; extraItem.setRemoteId(rid); extraItem.setRemoteRevision(source.etag()); extraItem.setMimeType(exception->mimeType()); extraItem.setPayload(exception); extraItems << extraItem; } } target.setPayload(mainIncidence); // fix mime type for CalDAV collections target.setMimeType(mainIncidence->mimeType()); /* foreach ( const IncidencePtr &incidence, incidences ) { QString rid = item.remoteId() + QLatin1String( "#" ) + incidence->instanceIdentifier(); Akonadi::Item extraItem = item; extraItem.setRemoteId( rid ); extraItem.setRemoteRevision( davItem.etag() ); extraItem.setMimeType( incidence->mimeType() ); extraItem.setPayload( incidence ); items << extraItem; } */ } return true; }