File indexing completed on 2024-05-12 05:10:41
0001 /* 0002 SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "akonadi_serializer_kcalcore.h" 0008 0009 #include <Akonadi/AbstractDifferencesReporter> 0010 #include <Akonadi/Collection> 0011 #include <Akonadi/Item> 0012 0013 #include <KCalendarCore/Event> 0014 #include <KCalendarCore/Todo> 0015 0016 #include <KCalUtils/Stringify> 0017 0018 #include <KLocalizedString> 0019 0020 #include "serializer_debug.h" 0021 #include <QDate> 0022 #include <QIODevice> 0023 0024 using namespace KCalendarCore; 0025 using namespace KCalUtils; 0026 using namespace Akonadi; 0027 0028 SerializerPluginKCalCore::SerializerPluginKCalCore() = default; 0029 0030 //// ItemSerializerPlugin interface 0031 0032 namespace 0033 { 0034 bool isSerializedBinary(QIODevice &data) 0035 { 0036 const QByteArray buffer = data.peek(4); 0037 // Use QDataStream because of endianness 0038 quint32 magic; 0039 QDataStream input(buffer); 0040 input >> magic; 0041 return magic == IncidenceBase::magicSerializationIdentifier(); 0042 } 0043 0044 struct Header { 0045 quint32 magic; 0046 quint32 incidenceVersion; 0047 qint32 type; 0048 }; 0049 0050 } // namespace 0051 0052 bool SerializerPluginKCalCore::deserialize(Item &item, const QByteArray &label, QIODevice &data, int version) 0053 { 0054 Q_UNUSED(version) 0055 0056 if (label != Item::FullPayload) { 0057 return false; 0058 } 0059 0060 Incidence::Ptr incidence; 0061 0062 if (isSerializedBinary(data)) { 0063 const auto buffer = data.peek(sizeof(Header)); 0064 QDataStream input(buffer); 0065 Header header; 0066 input >> header.magic; 0067 input >> header.incidenceVersion; 0068 input >> header.type; 0069 0070 IncidenceBase::Ptr base; 0071 switch (static_cast<KCalendarCore::Incidence::IncidenceType>(header.type)) { 0072 case KCalendarCore::Incidence::TypeEvent: 0073 base = Event::Ptr(new Event()); 0074 break; 0075 case KCalendarCore::Incidence::TypeTodo: 0076 base = Todo::Ptr(new Todo()); 0077 break; 0078 case KCalendarCore::Incidence::TypeJournal: 0079 base = Journal::Ptr(new Journal()); 0080 break; 0081 case KCalendarCore::Incidence::TypeFreeBusy: 0082 base = FreeBusy::Ptr(new FreeBusy()); 0083 break; 0084 case KCalendarCore::Incidence::TypeUnknown: 0085 return false; 0086 } 0087 QDataStream stream(&data); 0088 stream >> base; 0089 incidence = base.staticCast<KCalendarCore::Incidence>(); 0090 } else { 0091 // Use the old format 0092 incidence = mFormat.readIncidence(data.readAll()); 0093 } 0094 0095 if (!incidence) { 0096 qCWarning(AKONADI_SERIALIZER_CALENDAR_LOG) << "Failed to parse incidence! Item id = " << item.id() << "Storage collection id " 0097 << item.storageCollectionId() << "parentCollectionId = " << item.parentCollection().id(); 0098 if (!data.isSequential()) { 0099 data.seek(0); 0100 qCWarning(AKONADI_SERIALIZER_CALENDAR_LOG) << QString::fromUtf8(data.readAll()); 0101 } 0102 return false; 0103 } 0104 0105 item.setPayload(incidence); 0106 return true; 0107 } 0108 0109 void SerializerPluginKCalCore::serialize(const Item &item, const QByteArray &label, QIODevice &data, int &version) 0110 { 0111 Q_UNUSED(version) 0112 0113 if (label != Item::FullPayload || !item.hasPayload<Incidence::Ptr>()) { 0114 return; 0115 } 0116 auto i = item.payload<Incidence::Ptr>(); 0117 0118 // Using an env variable for now while testing 0119 if (qgetenv("KCALCORE_BINARY_SERIALIZER") == QByteArray("1")) { 0120 QDataStream output(&data); 0121 IncidenceBase::Ptr base = i; 0122 output << base; 0123 } else { 0124 // ### I guess this can be done without hardcoding stuff 0125 data.write("BEGIN:VCALENDAR\nPRODID:-//K Desktop Environment//NONSGML libkcal 4.3//EN\nVERSION:2.0\nX-KDE-ICAL-IMPLEMENTATION-VERSION:1.0\n"); 0126 data.write(mFormat.toRawString(i)); 0127 data.write("\nEND:VCALENDAR"); 0128 } 0129 } 0130 0131 //// DifferencesAlgorithmInterface 0132 0133 static bool compareString(const QString &left, const QString &right) 0134 { 0135 if (left.isEmpty() && right.isEmpty()) { 0136 return true; 0137 } else { 0138 return left == right; 0139 } 0140 } 0141 0142 static QString toString(const Attendee &attendee) 0143 { 0144 return attendee.name() + QLatin1Char('<') + attendee.email() + QLatin1Char('>'); 0145 } 0146 0147 static QString toString(const Alarm::Ptr &) 0148 { 0149 return {}; 0150 } 0151 0152 static QString toString(const Attachment &) 0153 { 0154 return {}; 0155 } 0156 0157 static QString toString(QDate date) 0158 { 0159 return date.toString(); 0160 } 0161 0162 static QString toString(const QDateTime &dateTime) 0163 { 0164 return dateTime.toString(); 0165 } 0166 0167 static QString toString(const QString &str) 0168 { 0169 return str; 0170 } 0171 0172 static QString toString(bool value) 0173 { 0174 if (value) { 0175 return i18n("Yes"); 0176 } else { 0177 return i18n("No"); 0178 } 0179 } 0180 0181 template<class C> 0182 static void compareList(AbstractDifferencesReporter *reporter, const QString &id, const C &left, const C &right) 0183 { 0184 for (typename C::const_iterator it = left.begin(), end = left.end(); it != end; ++it) { 0185 if (!right.contains(*it)) { 0186 reporter->addProperty(AbstractDifferencesReporter::AdditionalLeftMode, id, toString(*it), QString()); 0187 } 0188 } 0189 0190 for (typename C::const_iterator it = right.begin(), end = right.end(); it != end; ++it) { 0191 if (!left.contains(*it)) { 0192 reporter->addProperty(AbstractDifferencesReporter::AdditionalRightMode, id, QString(), toString(*it)); 0193 } 0194 } 0195 } 0196 0197 static void compareIncidenceBase(AbstractDifferencesReporter *reporter, const IncidenceBase::Ptr &left, const IncidenceBase::Ptr &right) 0198 { 0199 compareList(reporter, i18n("Attendees"), left->attendees(), right->attendees()); 0200 0201 if (!compareString(left->organizer().fullName(), right->organizer().fullName())) { 0202 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Organizer"), left->organizer().fullName(), right->organizer().fullName()); 0203 } 0204 0205 if (!compareString(left->uid(), right->uid())) { 0206 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("UID"), left->uid(), right->uid()); 0207 } 0208 0209 if (left->allDay() != right->allDay()) { 0210 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Is all-day"), toString(left->allDay()), toString(right->allDay())); 0211 } 0212 0213 if (left->hasDuration() != right->hasDuration()) { 0214 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Has duration"), toString(left->hasDuration()), toString(right->hasDuration())); 0215 } 0216 0217 if (left->duration() != right->duration()) { 0218 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, 0219 i18n("Duration"), 0220 QString::number(left->duration().asSeconds()), 0221 QString::number(right->duration().asSeconds())); 0222 } 0223 } 0224 0225 static void compareIncidence(AbstractDifferencesReporter *reporter, const Incidence::Ptr &left, const Incidence::Ptr &right) 0226 { 0227 if (!compareString(left->description(), right->description())) { 0228 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Description"), left->description(), right->description()); 0229 } 0230 0231 if (!compareString(left->summary(), right->summary())) { 0232 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Summary"), left->summary(), right->summary()); 0233 } 0234 0235 if (left->status() != right->status()) { 0236 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Status"), Stringify::incidenceStatus(left), Stringify::incidenceStatus(right)); 0237 } 0238 0239 if (left->secrecy() != right->secrecy()) { 0240 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Secrecy"), toString(left->secrecy()), toString(right->secrecy())); 0241 } 0242 0243 if (left->priority() != right->priority()) { 0244 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Priority"), toString(left->priority()), toString(right->priority())); 0245 } 0246 0247 if (!compareString(left->location(), right->location())) { 0248 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Location"), left->location(), right->location()); 0249 } 0250 0251 compareList(reporter, i18n("Categories"), left->categories(), right->categories()); 0252 compareList(reporter, i18n("Alarms"), left->alarms(), right->alarms()); 0253 compareList(reporter, i18n("Resources"), left->resources(), right->resources()); 0254 compareList(reporter, i18n("Attachments"), left->attachments(), right->attachments()); 0255 compareList(reporter, i18n("Exception Dates"), left->recurrence()->exDates(), right->recurrence()->exDates()); 0256 compareList(reporter, i18n("Exception Times"), left->recurrence()->exDateTimes(), right->recurrence()->exDateTimes()); 0257 // TODO: recurrence dates and date/times, exrules, rrules 0258 0259 if (left->created() != right->created()) { 0260 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Created"), left->created().toString(), right->created().toString()); 0261 } 0262 0263 if (!compareString(left->relatedTo(), right->relatedTo())) { 0264 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Related Uid"), left->relatedTo(), right->relatedTo()); 0265 } 0266 } 0267 0268 static void compareEvent(AbstractDifferencesReporter *reporter, const Event::Ptr &left, const Event::Ptr &right) 0269 { 0270 if (left->dtStart() != right->dtStart()) { 0271 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Start time"), left->dtStart().toString(), right->dtStart().toString()); 0272 } 0273 0274 if (left->hasEndDate() != right->hasEndDate()) { 0275 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Has End Date"), toString(left->hasEndDate()), toString(right->hasEndDate())); 0276 } 0277 0278 if (left->dtEnd() != right->dtEnd()) { 0279 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("End Date"), left->dtEnd().toString(), right->dtEnd().toString()); 0280 } 0281 0282 // TODO: check transparency 0283 } 0284 0285 static void compareTodo(AbstractDifferencesReporter *reporter, const Todo::Ptr &left, const Todo::Ptr &right) 0286 { 0287 if (left->hasStartDate() != right->hasStartDate()) { 0288 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, 0289 i18n("Has Start Date"), 0290 toString(left->hasStartDate()), 0291 toString(right->hasStartDate())); 0292 } 0293 0294 if (left->hasDueDate() != right->hasDueDate()) { 0295 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Has Due Date"), toString(left->hasDueDate()), toString(right->hasDueDate())); 0296 } 0297 0298 if (left->dtDue() != right->dtDue()) { 0299 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Due Date"), left->dtDue().toString(), right->dtDue().toString()); 0300 } 0301 0302 if (left->hasCompletedDate() != right->hasCompletedDate()) { 0303 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, 0304 i18n("Has Complete Date"), 0305 toString(left->hasCompletedDate()), 0306 toString(right->hasCompletedDate())); 0307 } 0308 0309 if (left->percentComplete() != right->percentComplete()) { 0310 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, 0311 i18n("Complete"), 0312 QString::number(left->percentComplete()), 0313 QString::number(right->percentComplete())); 0314 } 0315 0316 if (left->completed() != right->completed()) { 0317 reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Completed"), toString(left->completed()), toString(right->completed())); 0318 } 0319 } 0320 0321 void SerializerPluginKCalCore::compare(Akonadi::AbstractDifferencesReporter *reporter, const Akonadi::Item &leftItem, const Akonadi::Item &rightItem) 0322 { 0323 Q_ASSERT(reporter); 0324 Q_ASSERT(leftItem.hasPayload<Incidence::Ptr>()); 0325 Q_ASSERT(rightItem.hasPayload<Incidence::Ptr>()); 0326 0327 const auto leftIncidencePtr = leftItem.payload<Incidence::Ptr>(); 0328 const auto rightIncidencePtr = rightItem.payload<Incidence::Ptr>(); 0329 0330 if (leftIncidencePtr->type() == Incidence::TypeEvent) { 0331 reporter->setLeftPropertyValueTitle(i18n("Changed Event")); 0332 reporter->setRightPropertyValueTitle(i18n("Conflicting Event")); 0333 } else if (leftIncidencePtr->type() == Incidence::TypeTodo) { 0334 reporter->setLeftPropertyValueTitle(i18n("Changed Todo")); 0335 reporter->setRightPropertyValueTitle(i18n("Conflicting Todo")); 0336 } 0337 0338 compareIncidenceBase(reporter, leftIncidencePtr, rightIncidencePtr); 0339 compareIncidence(reporter, leftIncidencePtr, rightIncidencePtr); 0340 0341 const Event::Ptr leftEvent = leftIncidencePtr.dynamicCast<Event>(); 0342 const Event::Ptr rightEvent = rightIncidencePtr.dynamicCast<Event>(); 0343 if (leftEvent && rightEvent) { 0344 compareEvent(reporter, leftEvent, rightEvent); 0345 } else { 0346 const Todo::Ptr leftTodo = leftIncidencePtr.dynamicCast<Todo>(); 0347 const Todo::Ptr rightTodo = rightIncidencePtr.dynamicCast<Todo>(); 0348 if (leftTodo && rightTodo) { 0349 compareTodo(reporter, leftTodo, rightTodo); 0350 } 0351 } 0352 } 0353 0354 //// GidExtractorInterface 0355 0356 QString SerializerPluginKCalCore::extractGid(const Item &item) const 0357 { 0358 if (!item.hasPayload<Incidence::Ptr>()) { 0359 return {}; 0360 } 0361 return item.payload<Incidence::Ptr>()->instanceIdentifier(); 0362 } 0363 0364 #include "moc_akonadi_serializer_kcalcore.cpp"