File indexing completed on 2024-04-28 11:34:06

0001 /*
0002   This file is part of the kcalcore library.
0003 
0004   SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
0005 
0006   SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 /**
0009   @file
0010   This file is part of the API for handling calendar data and
0011   defines the ICalFormat class.
0012 
0013   @brief
0014   iCalendar format implementation: a layer of abstraction for libical.
0015 
0016   @author Cornelius Schumacher \<schumacher@kde.org\>
0017 */
0018 #include "icalformat.h"
0019 #include "calendar_p.h"
0020 #include "calformat_p.h"
0021 #include "icalformat_p.h"
0022 #include "icaltimezones_p.h"
0023 #include "kcalendarcore_debug.h"
0024 #include "memorycalendar.h"
0025 
0026 #include <QFile>
0027 #include <QSaveFile>
0028 #include <QTimeZone>
0029 
0030 extern "C" {
0031 #include <libical/ical.h>
0032 #include <libical/icalmemory.h>
0033 #include <libical/icalparser.h>
0034 #include <libical/icalrestriction.h>
0035 #include <libical/icalss.h>
0036 }
0037 
0038 using namespace KCalendarCore;
0039 
0040 //@cond PRIVATE
0041 class KCalendarCore::ICalFormatPrivate : public KCalendarCore::CalFormatPrivate
0042 {
0043 public:
0044     ICalFormatPrivate(ICalFormat *parent)
0045         : mImpl(parent)
0046         , mTimeZone(QTimeZone::utc())
0047     {
0048     }
0049     ICalFormatImpl mImpl;
0050     QTimeZone mTimeZone;
0051 };
0052 //@endcond
0053 
0054 ICalFormat::ICalFormat()
0055     : CalFormat(new ICalFormatPrivate(this))
0056 {
0057 }
0058 
0059 ICalFormat::~ICalFormat()
0060 {
0061     icalmemory_free_ring();
0062 }
0063 
0064 bool ICalFormat::load(const Calendar::Ptr &calendar, const QString &fileName)
0065 {
0066     qCDebug(KCALCORE_LOG) << fileName;
0067 
0068     clearException();
0069 
0070     QFile file(fileName);
0071     if (!file.open(QIODevice::ReadOnly)) {
0072         qCritical() << "load error: unable to open " << fileName;
0073         setException(new Exception(Exception::LoadError));
0074         return false;
0075     }
0076     const QByteArray text = file.readAll().trimmed();
0077     file.close();
0078 
0079     if (!text.isEmpty()) {
0080         if (!calendar->hasValidNotebook(fileName) && !calendar->addNotebook(fileName, true)) {
0081             qCWarning(KCALCORE_LOG) << "Unable to add" << fileName << "as a notebook in calendar";
0082         }
0083         if (!fromRawString(calendar, text, false, fileName)) {
0084             qCWarning(KCALCORE_LOG) << fileName << " is not a valid iCalendar file";
0085             setException(new Exception(Exception::ParseErrorIcal));
0086             return false;
0087         }
0088     }
0089 
0090     // Note: we consider empty files to be valid
0091 
0092     return true;
0093 }
0094 
0095 bool ICalFormat::save(const Calendar::Ptr &calendar, const QString &fileName)
0096 {
0097     qCDebug(KCALCORE_LOG) << fileName;
0098 
0099     clearException();
0100 
0101     QString text = toString(calendar);
0102     if (text.isEmpty()) {
0103         return false;
0104     }
0105 
0106     // Write backup file
0107     const QString backupFile = fileName + QLatin1Char('~');
0108     QFile::remove(backupFile);
0109     QFile::copy(fileName, backupFile);
0110 
0111     QSaveFile file(fileName);
0112     if (!file.open(QIODevice::WriteOnly)) {
0113         qCritical() << "file open error: " << file.errorString() << ";filename=" << fileName;
0114         setException(new Exception(Exception::SaveErrorOpenFile, QStringList(fileName)));
0115 
0116         return false;
0117     }
0118 
0119     // Convert to UTF8 and save
0120     QByteArray textUtf8 = text.toUtf8();
0121     file.write(textUtf8.data(), textUtf8.size());
0122     // QSaveFile doesn't report a write error when the device is full (see Qt
0123     // bug 75077), so check that the data can actually be written.
0124     if (!file.flush()) {
0125         qCDebug(KCALCORE_LOG) << "file write error (flush failed)";
0126         setException(new Exception(Exception::SaveErrorSaveFile, QStringList(fileName)));
0127         return false;
0128     }
0129 
0130     if (!file.commit()) {
0131         qCDebug(KCALCORE_LOG) << "file finalize error:" << file.errorString();
0132         setException(new Exception(Exception::SaveErrorSaveFile, QStringList(fileName)));
0133 
0134         return false;
0135     }
0136 
0137     return true;
0138 }
0139 
0140 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 97)
0141 bool ICalFormat::fromString(const Calendar::Ptr &cal, const QString &string, bool deleted, const QString &notebook)
0142 {
0143     return fromRawString(cal, string.toUtf8(), deleted, notebook);
0144 }
0145 #endif
0146 
0147 Incidence::Ptr ICalFormat::readIncidence(const QByteArray &string)
0148 {
0149     Q_D(ICalFormat);
0150 
0151     // Let's defend const correctness until the very gates of hell^Wlibical
0152     icalcomponent *calendar = icalcomponent_new_from_string(const_cast<char *>(string.constData()));
0153     if (!calendar) {
0154         qCritical() << "parse error from icalcomponent_new_from_string. string=" << QString::fromLatin1(string);
0155         setException(new Exception(Exception::ParseErrorIcal));
0156         return Incidence::Ptr();
0157     }
0158 
0159     ICalTimeZoneCache tzCache;
0160     ICalTimeZoneParser parser(&tzCache);
0161     parser.parse(calendar);
0162 
0163     Incidence::Ptr incidence;
0164     if (icalcomponent_isa(calendar) == ICAL_VCALENDAR_COMPONENT) {
0165         incidence = d->mImpl.readOneIncidence(calendar, &tzCache);
0166     } else if (icalcomponent_isa(calendar) == ICAL_XROOT_COMPONENT) {
0167         icalcomponent *comp = icalcomponent_get_first_component(calendar, ICAL_VCALENDAR_COMPONENT);
0168         if (comp) {
0169             incidence = d->mImpl.readOneIncidence(comp, &tzCache);
0170         }
0171     }
0172 
0173     if (!incidence) {
0174         qCDebug(KCALCORE_LOG) << "No VCALENDAR component found";
0175         setException(new Exception(Exception::NoCalendar));
0176     }
0177 
0178     icalcomponent_free(calendar);
0179     icalmemory_free_ring();
0180 
0181     return incidence;
0182 }
0183 
0184 bool ICalFormat::fromRawString(const Calendar::Ptr &cal, const QByteArray &string, bool deleted, const QString &notebook)
0185 {
0186     Q_D(ICalFormat);
0187 
0188     // Get first VCALENDAR component.
0189     // TODO: Handle more than one VCALENDAR or non-VCALENDAR top components
0190     icalcomponent *calendar;
0191 
0192     // Let's defend const correctness until the very gates of hell^Wlibical
0193     calendar = icalcomponent_new_from_string(const_cast<char *>(string.constData()));
0194     if (!calendar) {
0195         qCritical() << "parse error from icalcomponent_new_from_string. string=" << QString::fromLatin1(string);
0196         setException(new Exception(Exception::ParseErrorIcal));
0197         return false;
0198     }
0199 
0200     bool success = true;
0201 
0202     if (icalcomponent_isa(calendar) == ICAL_XROOT_COMPONENT) {
0203         icalcomponent *comp;
0204         for (comp = icalcomponent_get_first_component(calendar, ICAL_VCALENDAR_COMPONENT); comp;
0205              comp = icalcomponent_get_next_component(calendar, ICAL_VCALENDAR_COMPONENT)) {
0206             // put all objects into their proper places
0207             if (!d->mImpl.populate(cal, comp, deleted)) {
0208                 qCritical() << "Could not populate calendar";
0209                 if (!exception()) {
0210                     setException(new Exception(Exception::ParseErrorKcal));
0211                 }
0212                 success = false;
0213             } else {
0214                 setLoadedProductId(d->mImpl.loadedProductId());
0215             }
0216         }
0217     } else if (icalcomponent_isa(calendar) != ICAL_VCALENDAR_COMPONENT) {
0218         qCDebug(KCALCORE_LOG) << "No VCALENDAR component found";
0219         setException(new Exception(Exception::NoCalendar));
0220         success = false;
0221     } else {
0222         // put all objects into their proper places
0223         if (!d->mImpl.populate(cal, calendar, deleted, notebook)) {
0224             qCDebug(KCALCORE_LOG) << "Could not populate calendar";
0225             if (!exception()) {
0226                 setException(new Exception(Exception::ParseErrorKcal));
0227             }
0228             success = false;
0229         } else {
0230             setLoadedProductId(d->mImpl.loadedProductId());
0231         }
0232     }
0233 
0234     icalcomponent_free(calendar);
0235     icalmemory_free_ring();
0236 
0237     return success;
0238 }
0239 
0240 Incidence::Ptr ICalFormat::fromString(const QString &string)
0241 {
0242     Q_D(ICalFormat);
0243 
0244     MemoryCalendar::Ptr cal(new MemoryCalendar(d->mTimeZone));
0245     fromString(cal, string);
0246 
0247     const Incidence::List list = cal->incidences();
0248     return !list.isEmpty() ? list.first() : Incidence::Ptr();
0249 }
0250 
0251 QString ICalFormat::toString(const Calendar::Ptr &cal, const QString &notebook, bool deleted)
0252 {
0253     Q_D(ICalFormat);
0254 
0255     icalcomponent *calendar = d->mImpl.createCalendarComponent(cal);
0256     icalcomponent *component;
0257 
0258     QVector<QTimeZone> tzUsedList;
0259     TimeZoneEarliestDate earliestTz;
0260 
0261     // todos
0262     Todo::List todoList = deleted ? cal->deletedTodos() : cal->rawTodos();
0263     for (auto it = todoList.cbegin(), end = todoList.cend(); it != end; ++it) {
0264         if (!deleted || !cal->todo((*it)->uid(), (*it)->recurrenceId())) {
0265             // use existing ones, or really deleted ones
0266             if (notebook.isEmpty() || (!cal->notebook(*it).isEmpty() && notebook.endsWith(cal->notebook(*it)))) {
0267                 component = d->mImpl.writeTodo(*it, &tzUsedList);
0268                 icalcomponent_add_component(calendar, component);
0269                 ICalTimeZoneParser::updateTzEarliestDate((*it), &earliestTz);
0270             }
0271         }
0272     }
0273     // events
0274     Event::List events = deleted ? cal->deletedEvents() : cal->rawEvents();
0275     for (auto it = events.cbegin(), end = events.cend(); it != end; ++it) {
0276         if (!deleted || !cal->event((*it)->uid(), (*it)->recurrenceId())) {
0277             // use existing ones, or really deleted ones
0278             if (notebook.isEmpty() || (!cal->notebook(*it).isEmpty() && notebook.endsWith(cal->notebook(*it)))) {
0279                 component = d->mImpl.writeEvent(*it, &tzUsedList);
0280                 icalcomponent_add_component(calendar, component);
0281                 ICalTimeZoneParser::updateTzEarliestDate((*it), &earliestTz);
0282             }
0283         }
0284     }
0285 
0286     // journals
0287     Journal::List journals = deleted ? cal->deletedJournals() : cal->rawJournals();
0288     for (auto it = journals.cbegin(), end = journals.cend(); it != end; ++it) {
0289         if (!deleted || !cal->journal((*it)->uid(), (*it)->recurrenceId())) {
0290             // use existing ones, or really deleted ones
0291             if (notebook.isEmpty() || (!cal->notebook(*it).isEmpty() && notebook.endsWith(cal->notebook(*it)))) {
0292                 component = d->mImpl.writeJournal(*it, &tzUsedList);
0293                 icalcomponent_add_component(calendar, component);
0294                 ICalTimeZoneParser::updateTzEarliestDate((*it), &earliestTz);
0295             }
0296         }
0297     }
0298 
0299     // time zones
0300     if (todoList.isEmpty() && events.isEmpty() && journals.isEmpty()) {
0301         // no incidences means no used timezones, use all timezones
0302         // this will export a calendar having only timezone definitions
0303         tzUsedList = cal->d->mTimeZones;
0304     }
0305     for (const auto &qtz : std::as_const(tzUsedList)) {
0306         if (qtz != QTimeZone::utc()) {
0307             icaltimezone *tz = ICalTimeZoneParser::icaltimezoneFromQTimeZone(qtz, earliestTz[qtz]);
0308             if (!tz) {
0309                 qCritical() << "bad time zone";
0310             } else {
0311                 component = icalcomponent_new_clone(icaltimezone_get_component(tz));
0312                 icalcomponent_add_component(calendar, component);
0313                 icaltimezone_free(tz, 1);
0314             }
0315         }
0316     }
0317 
0318     char *const componentString = icalcomponent_as_ical_string_r(calendar);
0319     const QString &text = QString::fromUtf8(componentString);
0320     free(componentString);
0321 
0322     icalcomponent_free(calendar);
0323     icalmemory_free_ring();
0324 
0325     if (text.isEmpty()) {
0326         setException(new Exception(Exception::LibICalError));
0327     }
0328 
0329     return text;
0330 }
0331 
0332 QString ICalFormat::toICalString(const Incidence::Ptr &incidence)
0333 {
0334     Q_D(ICalFormat);
0335 
0336     MemoryCalendar::Ptr cal(new MemoryCalendar(d->mTimeZone));
0337     cal->addIncidence(Incidence::Ptr(incidence->clone()));
0338     return toString(cal.staticCast<Calendar>());
0339 }
0340 
0341 QString ICalFormat::toString(const Incidence::Ptr &incidence)
0342 {
0343     return QString::fromUtf8(toRawString(incidence));
0344 }
0345 
0346 QByteArray ICalFormat::toRawString(const Incidence::Ptr &incidence)
0347 {
0348     Q_D(ICalFormat);
0349     TimeZoneList tzUsedList;
0350 
0351     icalcomponent *component = d->mImpl.writeIncidence(incidence, iTIPRequest, &tzUsedList);
0352 
0353     QByteArray text = icalcomponent_as_ical_string(component);
0354 
0355     TimeZoneEarliestDate earliestTzDt;
0356     ICalTimeZoneParser::updateTzEarliestDate(incidence, &earliestTzDt);
0357 
0358     // time zones
0359     for (const auto &qtz : std::as_const(tzUsedList)) {
0360         if (qtz != QTimeZone::utc()) {
0361             icaltimezone *tz = ICalTimeZoneParser::icaltimezoneFromQTimeZone(qtz, earliestTzDt[qtz]);
0362             if (!tz) {
0363                 qCritical() << "bad time zone";
0364             } else {
0365                 icalcomponent *tzcomponent = icaltimezone_get_component(tz);
0366                 icalcomponent_add_component(component, component);
0367                 text.append(icalcomponent_as_ical_string(tzcomponent));
0368                 icaltimezone_free(tz, 1);
0369             }
0370         }
0371     }
0372 
0373     icalcomponent_free(component);
0374 
0375     return text;
0376 }
0377 
0378 QString ICalFormat::toString(RecurrenceRule *recurrence)
0379 {
0380     Q_D(ICalFormat);
0381     icalproperty *property = icalproperty_new_rrule(d->mImpl.writeRecurrenceRule(recurrence));
0382     QString text = QString::fromUtf8(icalproperty_as_ical_string(property));
0383     icalproperty_free(property);
0384     return text;
0385 }
0386 
0387 QString KCalendarCore::ICalFormat::toString(const KCalendarCore::Duration &duration) const
0388 {
0389     Q_D(const ICalFormat);
0390     const auto icalDuration = d->mImpl.writeICalDuration(duration);
0391     // contrary to the libical API docs, the returned string is actually freed by icalmemory_free_ring,
0392     // freeing it here explicitly causes a double deletion failure
0393     return QString::fromUtf8(icaldurationtype_as_ical_string(icalDuration));
0394 }
0395 
0396 bool ICalFormat::fromString(RecurrenceRule *recurrence, const QString &rrule)
0397 {
0398     Q_D(ICalFormat);
0399     if (!recurrence) {
0400         return false;
0401     }
0402     bool success = true;
0403     icalerror_clear_errno();
0404     struct icalrecurrencetype recur = icalrecurrencetype_from_string(rrule.toLatin1().constData());
0405     if (icalerrno != ICAL_NO_ERROR) {
0406         qCDebug(KCALCORE_LOG) << "Recurrence parsing error:" << icalerror_strerror(icalerrno);
0407         success = false;
0408     }
0409 
0410     if (success) {
0411         d->mImpl.readRecurrence(recur, recurrence);
0412     }
0413 
0414     return success;
0415 }
0416 
0417 Duration ICalFormat::durationFromString(const QString &duration) const
0418 {
0419     Q_D(const ICalFormat);
0420     icalerror_clear_errno();
0421     const auto icalDuration = icaldurationtype_from_string(duration.toUtf8().constData());
0422     if (icalerrno != ICAL_NO_ERROR) {
0423         qCDebug(KCALCORE_LOG) << "Duration parsing error:" << icalerror_strerror(icalerrno);
0424         return {};
0425     }
0426     return d->mImpl.readICalDuration(icalDuration);
0427 }
0428 
0429 QString ICalFormat::createScheduleMessage(const IncidenceBase::Ptr &incidence, iTIPMethod method)
0430 {
0431     Q_D(ICalFormat);
0432     icalcomponent *message = nullptr;
0433 
0434     if (incidence->type() == Incidence::TypeEvent || incidence->type() == Incidence::TypeTodo) {
0435         Incidence::Ptr i = incidence.staticCast<Incidence>();
0436 
0437         // Recurring events need timezone information to allow proper calculations
0438         // across timezones with different DST.
0439         const bool useUtcTimes = !i->recurs() && !i->allDay();
0440 
0441         const bool hasSchedulingId = (i->schedulingID() != i->uid());
0442 
0443         const bool incidenceNeedChanges = (useUtcTimes || hasSchedulingId);
0444 
0445         if (incidenceNeedChanges) {
0446             // The incidence need changes, so clone it before we continue
0447             i = Incidence::Ptr(i->clone());
0448 
0449             // Handle conversion to UTC times
0450             if (useUtcTimes) {
0451                 i->shiftTimes(QTimeZone::utc(), QTimeZone::utc());
0452             }
0453 
0454             // Handle scheduling ID being present
0455             if (hasSchedulingId) {
0456                 // We have a separation of scheduling ID and UID
0457                 i->setSchedulingID(QString(), i->schedulingID());
0458             }
0459 
0460             // Build the message with the cloned incidence
0461             message = d->mImpl.createScheduleComponent(i, method);
0462         }
0463     }
0464 
0465     if (message == nullptr) {
0466         message = d->mImpl.createScheduleComponent(incidence, method);
0467     }
0468 
0469     QString messageText = QString::fromUtf8(icalcomponent_as_ical_string(message));
0470 
0471     icalcomponent_free(message);
0472     return messageText;
0473 }
0474 
0475 FreeBusy::Ptr ICalFormat::parseFreeBusy(const QString &str)
0476 {
0477     Q_D(ICalFormat);
0478     clearException();
0479 
0480     icalcomponent *message = icalparser_parse_string(str.toUtf8().constData());
0481 
0482     if (!message) {
0483         return FreeBusy::Ptr();
0484     }
0485 
0486     FreeBusy::Ptr freeBusy;
0487 
0488     icalcomponent *c = nullptr;
0489     for (c = icalcomponent_get_first_component(message, ICAL_VFREEBUSY_COMPONENT); c != nullptr;
0490          c = icalcomponent_get_next_component(message, ICAL_VFREEBUSY_COMPONENT)) {
0491         FreeBusy::Ptr fb = d->mImpl.readFreeBusy(c);
0492 
0493         if (freeBusy) {
0494             freeBusy->merge(fb);
0495         } else {
0496             freeBusy = fb;
0497         }
0498     }
0499 
0500     if (!freeBusy) {
0501         qCDebug(KCALCORE_LOG) << "object is not a freebusy.";
0502     }
0503 
0504     icalcomponent_free(message);
0505 
0506     return freeBusy;
0507 }
0508 
0509 ScheduleMessage::Ptr ICalFormat::parseScheduleMessage(const Calendar::Ptr &cal, const QString &messageText)
0510 {
0511     Q_D(ICalFormat);
0512     setTimeZone(cal->timeZone());
0513     clearException();
0514 
0515     if (messageText.isEmpty()) {
0516         setException(new Exception(Exception::ParseErrorEmptyMessage));
0517         return ScheduleMessage::Ptr();
0518     }
0519 
0520     icalcomponent *message = icalparser_parse_string(messageText.toUtf8().constData());
0521 
0522     if (!message) {
0523         setException(new Exception(Exception::ParseErrorUnableToParse));
0524 
0525         return ScheduleMessage::Ptr();
0526     }
0527 
0528     icalproperty *m = icalcomponent_get_first_property(message, ICAL_METHOD_PROPERTY);
0529     if (!m) {
0530         setException(new Exception(Exception::ParseErrorMethodProperty));
0531 
0532         return ScheduleMessage::Ptr();
0533     }
0534 
0535     // Populate the message's time zone collection with all VTIMEZONE components
0536     ICalTimeZoneCache tzlist;
0537     ICalTimeZoneParser parser(&tzlist);
0538     parser.parse(message);
0539 
0540     IncidenceBase::Ptr incidence;
0541     icalcomponent *c = icalcomponent_get_first_component(message, ICAL_VEVENT_COMPONENT);
0542     if (c) {
0543         incidence = d->mImpl.readEvent(c, &tzlist).staticCast<IncidenceBase>();
0544     }
0545 
0546     if (!incidence) {
0547         c = icalcomponent_get_first_component(message, ICAL_VTODO_COMPONENT);
0548         if (c) {
0549             incidence = d->mImpl.readTodo(c, &tzlist).staticCast<IncidenceBase>();
0550         }
0551     }
0552 
0553     if (!incidence) {
0554         c = icalcomponent_get_first_component(message, ICAL_VJOURNAL_COMPONENT);
0555         if (c) {
0556             incidence = d->mImpl.readJournal(c, &tzlist).staticCast<IncidenceBase>();
0557         }
0558     }
0559 
0560     if (!incidence) {
0561         c = icalcomponent_get_first_component(message, ICAL_VFREEBUSY_COMPONENT);
0562         if (c) {
0563             incidence = d->mImpl.readFreeBusy(c).staticCast<IncidenceBase>();
0564         }
0565     }
0566 
0567     if (!incidence) {
0568         qCDebug(KCALCORE_LOG) << "object is not a freebusy, event, todo or journal";
0569         setException(new Exception(Exception::ParseErrorNotIncidence));
0570 
0571         return ScheduleMessage::Ptr();
0572     }
0573 
0574     icalproperty_method icalmethod = icalproperty_get_method(m);
0575     iTIPMethod method;
0576 
0577     switch (icalmethod) {
0578     case ICAL_METHOD_PUBLISH:
0579         method = iTIPPublish;
0580         break;
0581     case ICAL_METHOD_REQUEST:
0582         method = iTIPRequest;
0583         break;
0584     case ICAL_METHOD_REFRESH:
0585         method = iTIPRefresh;
0586         break;
0587     case ICAL_METHOD_CANCEL:
0588         method = iTIPCancel;
0589         break;
0590     case ICAL_METHOD_ADD:
0591         method = iTIPAdd;
0592         break;
0593     case ICAL_METHOD_REPLY:
0594         method = iTIPReply;
0595         break;
0596     case ICAL_METHOD_COUNTER:
0597         method = iTIPCounter;
0598         break;
0599     case ICAL_METHOD_DECLINECOUNTER:
0600         method = iTIPDeclineCounter;
0601         break;
0602     default:
0603         method = iTIPNoMethod;
0604         qCDebug(KCALCORE_LOG) << "Unknown method";
0605         break;
0606     }
0607 
0608     if (!icalrestriction_check(message)) {
0609         qCWarning(KCALCORE_LOG) << "\nkcalcore library reported a problem while parsing:";
0610         qCWarning(KCALCORE_LOG) << ScheduleMessage::methodName(method) << ":" << d->mImpl.extractErrorProperty(c);
0611     }
0612 
0613     Incidence::Ptr existingIncidence = cal->incidence(incidence->uid());
0614 
0615     icalcomponent *calendarComponent = nullptr;
0616     if (existingIncidence) {
0617         calendarComponent = d->mImpl.createCalendarComponent(cal);
0618 
0619         // TODO: check, if cast is required, or if it can be done by virtual funcs.
0620         // TODO: Use a visitor for this!
0621         if (existingIncidence->type() == Incidence::TypeTodo) {
0622             Todo::Ptr todo = existingIncidence.staticCast<Todo>();
0623             icalcomponent_add_component(calendarComponent, d->mImpl.writeTodo(todo));
0624         }
0625         if (existingIncidence->type() == Incidence::TypeEvent) {
0626             Event::Ptr event = existingIncidence.staticCast<Event>();
0627             icalcomponent_add_component(calendarComponent, d->mImpl.writeEvent(event));
0628         }
0629     } else {
0630         icalcomponent_free(message);
0631         return ScheduleMessage::Ptr(new ScheduleMessage(incidence, method, ScheduleMessage::Unknown));
0632     }
0633 
0634     icalproperty_xlicclass result = icalclassify(message, calendarComponent, static_cast<const char *>(""));
0635 
0636     ScheduleMessage::Status status;
0637 
0638     switch (result) {
0639     case ICAL_XLICCLASS_PUBLISHNEW:
0640         status = ScheduleMessage::PublishNew;
0641         break;
0642     case ICAL_XLICCLASS_PUBLISHUPDATE:
0643         status = ScheduleMessage::PublishUpdate;
0644         break;
0645     case ICAL_XLICCLASS_OBSOLETE:
0646         status = ScheduleMessage::Obsolete;
0647         break;
0648     case ICAL_XLICCLASS_REQUESTNEW:
0649         status = ScheduleMessage::RequestNew;
0650         break;
0651     case ICAL_XLICCLASS_REQUESTUPDATE:
0652         status = ScheduleMessage::RequestUpdate;
0653         break;
0654     case ICAL_XLICCLASS_UNKNOWN:
0655     default:
0656         status = ScheduleMessage::Unknown;
0657         break;
0658     }
0659 
0660     icalcomponent_free(message);
0661     icalcomponent_free(calendarComponent);
0662 
0663     return ScheduleMessage::Ptr(new ScheduleMessage(incidence, method, status));
0664 }
0665 
0666 void ICalFormat::setTimeZone(const QTimeZone &timeZone)
0667 {
0668     Q_D(ICalFormat);
0669     d->mTimeZone = timeZone;
0670 }
0671 
0672 QTimeZone ICalFormat::timeZone() const
0673 {
0674     Q_D(const ICalFormat);
0675     return d->mTimeZone;
0676 }
0677 
0678 QByteArray ICalFormat::timeZoneId() const
0679 {
0680     Q_D(const ICalFormat);
0681     return d->mTimeZone.id();
0682 }
0683 
0684 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
0685 void ICalFormat::virtual_hook(int id, void *data)
0686 {
0687     Q_UNUSED(id);
0688     Q_UNUSED(data);
0689     Q_ASSERT(false);
0690 }
0691 #endif