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 }