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"