1/*
2 This file is part of the kcalcore library.
3
4 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6 Copyright (c) 2006 David Jarvie <software@astrojar.org.uk>
7 Copyright (C) 2012 Christian Mollekopf <mollekopf@kolabsys.com>
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public
11 License as published by the Free Software Foundation; either
12 version 2 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
18
19 You should have received a copy of the GNU Library General Public License
20 along with this library; see the file COPYING.LIB. If not, write to
21 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 Boston, MA 02110-1301, USA.
23*/
24/**
25 @file
26 This file is part of the API for handling calendar data and
27 defines the internal ICalFormat classes.
28
29 @brief
30 This class provides the libical dependent functions for ICalFormat.
31
32 @author Cornelius Schumacher \<schumacher@kde.org\>
33 @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
34 @author David Jarvie \<software@astrojar.org.uk\>
35*/
36
37#include <config-kcalcore.h>
38#include "icalformat_p.h"
39#include "compat.h"
40#include "event.h"
41#include "freebusy.h"
42#include "icalformat.h"
43#include "icaltimezones.h"
44#include "incidencebase.h"
45#include "journal.h"
46#include "memorycalendar.h"
47#include "todo.h"
48#include "visitor.h"
49
50#include <KCodecs>
51#include <KDebug>
52
53#include <QtCore/QFile>
54
55using namespace KCalCore;
56
57static const char APP_NAME_FOR_XPROPERTIES[] = "KCALCORE";
58static const char ENABLED_ALARM_XPROPERTY[] = "ENABLED";
59static const char IMPLEMENTATION_VERSION_XPROPERTY[] = "X-KDE-ICAL-IMPLEMENTATION-VERSION";
60
61/* Static helpers */
62/*
63static void _dumpIcaltime( const icaltimetype& t)
64{
65 kDebug() << "--- Y:" << t.year << "M:" << t.month << "D:" << t.day;
66 kDebug() << "--- H:" << t.hour << "M:" << t.minute << "S:" << t.second;
67 kDebug() << "--- isUtc:" << icaltime_is_utc( t );
68 kDebug() << "--- zoneId:" << icaltimezone_get_tzid( const_cast<icaltimezone*>( t.zone ) );
69}
70*/
71
72//@cond PRIVATE
73template <typename K>
74void removeAllICal(QVector< QSharedPointer<K> > &c, const QSharedPointer<K> &x)
75{
76 if (c.count() < 1) {
77 return;
78 }
79
80 int cnt = c.count(x);
81 if (cnt != 1) {
82 qCritical() << "There number of relatedTos for this incidence is "
83 << cnt << " (there must be 1 relatedTo only)";
84 Q_ASSERT_X(false, "removeAllICal", "Count is not 1.");
85 return;
86 }
87
88 c.remove(c.indexOf(x));
89}
90
91static QString quoteForParam(const QString &text)
92{
93 QString tmp = text;
94 tmp.remove(QLatin1Char('"'));
95 if (tmp.contains(QLatin1Char(';')) || tmp.contains(QLatin1Char(':')) || tmp.contains(QLatin1Char(','))) {
96 return tmp; // libical quotes in this case already, see icalparameter_as_ical_string()
97 }
98 return QString::fromLatin1("\"") + tmp + QString::fromLatin1("\"");
99}
100
101const int gSecondsPerMinute = 60;
102const int gSecondsPerHour = gSecondsPerMinute * 60;
103const int gSecondsPerDay = gSecondsPerHour * 24;
104const int gSecondsPerWeek = gSecondsPerDay * 7;
105
106class ToComponentVisitor : public Visitor
107{
108public:
109 ToComponentVisitor(ICalFormatImpl *impl, iTIPMethod m, ICalTimeZones *tzList = 0,
110 ICalTimeZones *tzUsedList = 0)
111 : mImpl(impl), mComponent(0), mMethod(m), mTzList(tzList), mTzUsedList(tzUsedList)
112 {
113 }
114
115 bool visit(Event::Ptr e)
116 {
117 mComponent = mImpl->writeEvent(e, mTzList, mTzUsedList);
118 return true;
119 }
120 bool visit(Todo::Ptr t)
121 {
122 mComponent = mImpl->writeTodo(t, mTzList, mTzUsedList);
123 return true;
124 }
125 bool visit(Journal::Ptr j)
126 {
127 mComponent = mImpl->writeJournal(j, mTzList, mTzUsedList);
128 return true;
129 }
130 bool visit(FreeBusy::Ptr fb)
131 {
132 mComponent = mImpl->writeFreeBusy(fb, mMethod);
133 return true;
134 }
135
136 icalcomponent *component()
137 {
138 return mComponent;
139 }
140
141private:
142 ICalFormatImpl *mImpl;
143 icalcomponent *mComponent;
144 iTIPMethod mMethod;
145 ICalTimeZones *mTzList;
146 ICalTimeZones *mTzUsedList;
147};
148
149class ICalFormatImpl::Private
150{
151public:
152 Private(ICalFormatImpl *impl, ICalFormat *parent)
153 : mImpl(impl), mParent(parent), mCompat(new Compat) {}
154 ~Private() {
155 delete mCompat;
156 }
157 void writeIncidenceBase(icalcomponent *parent, IncidenceBase::Ptr);
158 void readIncidenceBase(icalcomponent *parent, IncidenceBase::Ptr);
159 void writeCustomProperties(icalcomponent *parent, CustomProperties *);
160 void readCustomProperties(icalcomponent *parent, CustomProperties *);
161
162 ICalFormatImpl *mImpl;
163 ICalFormat *mParent;
164 QString mLoadedProductId; // PRODID string loaded from calendar file
165 Event::List mEventsRelate; // events with relations
166 Todo::List mTodosRelate; // todos with relations
167 Compat *mCompat;
168};
169//@endcond
170
171inline icaltimetype ICalFormatImpl::writeICalUtcDateTime(const KDateTime &dt)
172{
173 return writeICalDateTime(dt.toUtc());
174}
175
176ICalFormatImpl::ICalFormatImpl(ICalFormat *parent)
177 : d(new Private(this, parent))
178{
179}
180
181ICalFormatImpl::~ICalFormatImpl()
182{
183 delete d;
184}
185
186QString ICalFormatImpl::loadedProductId() const
187{
188 return d->mLoadedProductId;
189}
190
191icalcomponent *ICalFormatImpl::writeIncidence(const IncidenceBase::Ptr &incidence,
192 iTIPMethod method,
193 ICalTimeZones *tzList,
194 ICalTimeZones *tzUsedList)
195{
196 ToComponentVisitor v(this, method, tzList, tzUsedList);
197 if (incidence->accept(v, incidence)) {
198 return v.component();
199 } else {
200 return 0;
201 }
202}
203
204icalcomponent *ICalFormatImpl::writeTodo(const Todo::Ptr &todo, ICalTimeZones *tzlist,
205 ICalTimeZones *tzUsedList)
206{
207 QString tmpStr;
208 QStringList tmpStrList;
209
210 icalcomponent *vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
211
212 writeIncidence(vtodo, todo.staticCast<Incidence>(), tzlist, tzUsedList);
213
214 // due date
215 icalproperty *prop;
216 if (todo->hasDueDate()) {
217 icaltimetype due;
218 if (todo->allDay()) {
219 due = writeICalDate(todo->dtDue(true).date());
220 prop = icalproperty_new_due(due);
221 } else {
222 prop = writeICalDateTimeProperty(
223 ICAL_DUE_PROPERTY, todo->dtDue(true), tzlist, tzUsedList);
224 }
225 icalcomponent_add_property(vtodo, prop);
226 }
227
228 // start time
229 if (todo->hasStartDate()) {
230 icaltimetype start;
231 if (todo->allDay()) {
232 start = writeICalDate(todo->dtStart(true).date());
233 prop = icalproperty_new_dtstart(start);
234 } else {
235 prop = writeICalDateTimeProperty(
236 ICAL_DTSTART_PROPERTY, todo->dtStart(true), tzlist, tzUsedList);
237 }
238 icalcomponent_add_property(vtodo, prop);
239 }
240
241 // completion date (UTC)
242 if (todo->isCompleted()) {
243 if (!todo->hasCompletedDate()) {
244 // If the todo was created by KOrganizer<2.2 it does not have
245 // a correct completion date. Set one now.
246 todo->setCompleted(KDateTime::currentUtcDateTime());
247 }
248 icaltimetype completed = writeICalUtcDateTime(todo->completed());
249 icalcomponent_add_property(
250 vtodo, icalproperty_new_completed(completed));
251 }
252
253 icalcomponent_add_property(
254 vtodo, icalproperty_new_percentcomplete(todo->percentComplete()));
255
256 if (todo->isCompleted()) {
257 if (icalcomponent_count_properties(vtodo, ICAL_STATUS_PROPERTY)) {
258 icalproperty *p = icalcomponent_get_first_property(vtodo, ICAL_STATUS_PROPERTY);
259 icalcomponent_remove_property(vtodo, p);
260 icalproperty_free(p);
261 }
262 icalcomponent_add_property(vtodo, icalproperty_new_status(ICAL_STATUS_COMPLETED));
263 }
264
265 if (todo->recurs() && todo->dtDue().isValid()) {
266 // dtDue( first = true ) returns the dtRecurrence()
267 prop = writeICalDateTimeProperty(ICAL_X_PROPERTY, todo->dtDue(), tzlist, tzUsedList);
268 icalproperty_set_x_name(prop, "X-KDE-LIBKCAL-DTRECURRENCE");
269 icalcomponent_add_property(vtodo, prop);
270 }
271
272 return vtodo;
273}
274
275icalcomponent *ICalFormatImpl::writeEvent(const Event::Ptr &event,
276 ICalTimeZones *tzlist,
277 ICalTimeZones *tzUsedList)
278{
279 icalcomponent *vevent = icalcomponent_new(ICAL_VEVENT_COMPONENT);
280
281 writeIncidence(vevent, event.staticCast<Incidence>(), tzlist, tzUsedList);
282
283 // start time
284 icalproperty *prop;
285 icaltimetype start;
286
287 KDateTime dt = event->dtStart();
288 if (dt.isValid()) {
289 if (event->allDay()) {
290 start = writeICalDate(event->dtStart().date());
291 prop = icalproperty_new_dtstart(start);
292 } else {
293 prop = writeICalDateTimeProperty(
294 ICAL_DTSTART_PROPERTY, event->dtStart(), tzlist, tzUsedList);
295 }
296 icalcomponent_add_property(vevent, prop);
297 }
298
299 if (event->hasEndDate()) {
300 // End time.
301 // RFC2445 says that if DTEND is present, it has to be greater than DTSTART.
302 icaltimetype end;
303 KDateTime dt = event->dtEnd();
304 if (event->allDay()) {
305#if !defined(KCALCORE_FOR_MEEGO)
306 // +1 day because end date is non-inclusive.
307 end = writeICalDate(dt.date().addDays(1));
308#else
309 end = writeICalDate(dt.date());
310#endif
311 icalcomponent_add_property(vevent, icalproperty_new_dtend(end));
312 } else {
313 if (dt != event->dtStart()) {
314 icalcomponent_add_property(
315 vevent, writeICalDateTimeProperty(
316 ICAL_DTEND_PROPERTY, dt, tzlist, tzUsedList));
317 }
318 }
319 }
320
321// TODO: resources
322#if 0
323 // resources
324 QStringList tmpStrList = anEvent->resources();
325 QString tmpStr = tmpStrList.join(";");
326 if (!tmpStr.isEmpty()) {
327 addPropValue(vevent, VCResourcesProp, tmpStr.toUtf8());
328 }
329
330#endif
331
332 // Transparency
333 switch (event->transparency()) {
334 case Event::Transparent:
335 icalcomponent_add_property(
336 vevent,
337 icalproperty_new_transp(ICAL_TRANSP_TRANSPARENT));
338 break;
339 case Event::Opaque:
340 icalcomponent_add_property(
341 vevent,
342 icalproperty_new_transp(ICAL_TRANSP_OPAQUE));
343 break;
344 }
345
346 return vevent;
347}
348
349icalcomponent *ICalFormatImpl::writeFreeBusy(const FreeBusy::Ptr &freebusy,
350 iTIPMethod method)
351{
352 icalcomponent *vfreebusy = icalcomponent_new(ICAL_VFREEBUSY_COMPONENT);
353
354 d->writeIncidenceBase(vfreebusy, freebusy.staticCast<IncidenceBase>());
355
356 icalcomponent_add_property(
357 vfreebusy, icalproperty_new_dtstart(writeICalUtcDateTime(freebusy->dtStart())));
358
359 icalcomponent_add_property(
360 vfreebusy, icalproperty_new_dtend(writeICalUtcDateTime(freebusy->dtEnd())));
361
362#ifdef USE_ICAL_1_0
363 Q_UNUSED(method);
364 icalcomponent_add_property(
365 vfreebusy, icalproperty_new_uid(freebusy->uid().toUtf8()));
366#else
367 if (method == iTIPRequest) {
368 icalcomponent_add_property(
369 vfreebusy, icalproperty_new_uid(freebusy->uid().toUtf8()));
370 }
371#endif
372
373 //Loops through all the periods in the freebusy object
374 Period::List list = freebusy->busyPeriods();
375 icalperiodtype period = icalperiodtype_null_period();
376 for (int i = 0, count = list.count(); i < count; ++i) {
377 period.start = writeICalUtcDateTime(list[i].start());
378 if (list[i].hasDuration()) {
379 period.duration = writeICalDuration(list[i].duration());
380 } else {
381 period.end = writeICalUtcDateTime(list[i].end());
382 }
383 icalcomponent_add_property(
384 vfreebusy, icalproperty_new_freebusy(period));
385 }
386
387 return vfreebusy;
388}
389
390icalcomponent *ICalFormatImpl::writeJournal(const Journal::Ptr &journal,
391 ICalTimeZones *tzlist,
392 ICalTimeZones *tzUsedList)
393{
394 icalcomponent *vjournal = icalcomponent_new(ICAL_VJOURNAL_COMPONENT);
395
396 writeIncidence(vjournal, journal.staticCast<Incidence>(), tzlist, tzUsedList);
397
398 // start time
399 icalproperty *prop;
400 KDateTime dt = journal->dtStart();
401 if (dt.isValid()) {
402 icaltimetype start;
403 if (journal->allDay()) {
404 start = writeICalDate(dt.date());
405 prop = icalproperty_new_dtstart(start);
406 } else {
407 prop = writeICalDateTimeProperty(
408 ICAL_DTSTART_PROPERTY, dt, tzlist, tzUsedList);
409 }
410 icalcomponent_add_property(vjournal, prop);
411 }
412
413 return vjournal;
414}
415
416void ICalFormatImpl::writeIncidence(icalcomponent *parent,
417 const Incidence::Ptr &incidence,
418 ICalTimeZones *tzlist,
419 ICalTimeZones *tzUsedList)
420{
421 if (incidence->schedulingID() != incidence->uid()) {
422 // We need to store the UID in here. The rawSchedulingID will
423 // go into the iCal UID component
424 incidence->setCustomProperty("LIBKCAL", "ID", incidence->uid());
425 } else {
426 incidence->removeCustomProperty("LIBKCAL", "ID");
427 }
428
429 d->writeIncidenceBase(parent, incidence.staticCast<IncidenceBase>());
430
431 // creation date in storage
432 icalcomponent_add_property(
433 parent, writeICalDateTimeProperty(
434 ICAL_CREATED_PROPERTY, incidence->created()));
435
436 // unique id
437 // If the scheduling ID is different from the real UID, the real
438 // one is stored on X-REALID above
439 if (!incidence->schedulingID().isEmpty()) {
440 icalcomponent_add_property(
441 parent, icalproperty_new_uid(incidence->schedulingID().toUtf8()));
442 }
443
444 // revision
445 if (incidence->revision() > 0) { // 0 is default, so don't write that out
446 icalcomponent_add_property(
447 parent, icalproperty_new_sequence(incidence->revision()));
448 }
449
450 // last modification date
451 if (incidence->lastModified().isValid()) {
452 icalcomponent_add_property(
453 parent, writeICalDateTimeProperty(
454 ICAL_LASTMODIFIED_PROPERTY, incidence->lastModified()));
455 }
456
457 // description
458 if (!incidence->description().isEmpty()) {
459 icalcomponent_add_property(
460 parent, writeDescription(
461 incidence->description(), incidence->descriptionIsRich()));
462 }
463
464 // summary
465 if (!incidence->summary().isEmpty()) {
466 icalcomponent_add_property(
467 parent, writeSummary(
468 incidence->summary(), incidence->summaryIsRich()));
469 }
470
471 // location
472 if (!incidence->location().isEmpty()) {
473 icalcomponent_add_property(
474 parent, writeLocation(
475 incidence->location(), incidence->locationIsRich()));
476 }
477
478 // status
479 icalproperty_status status = ICAL_STATUS_NONE;
480 switch (incidence->status()) {
481 case Incidence::StatusTentative:
482 status = ICAL_STATUS_TENTATIVE;
483 break;
484 case Incidence::StatusConfirmed:
485 status = ICAL_STATUS_CONFIRMED;
486 break;
487 case Incidence::StatusCompleted:
488 status = ICAL_STATUS_COMPLETED;
489 break;
490 case Incidence::StatusNeedsAction:
491 status = ICAL_STATUS_NEEDSACTION;
492 break;
493 case Incidence::StatusCanceled:
494 status = ICAL_STATUS_CANCELLED;
495 break;
496 case Incidence::StatusInProcess:
497 status = ICAL_STATUS_INPROCESS;
498 break;
499 case Incidence::StatusDraft:
500 status = ICAL_STATUS_DRAFT;
501 break;
502 case Incidence::StatusFinal:
503 status = ICAL_STATUS_FINAL;
504 break;
505 case Incidence::StatusX:
506 {
507 icalproperty *p = icalproperty_new_status(ICAL_STATUS_X);
508 icalvalue_set_x(icalproperty_get_value(p), incidence->customStatus().toUtf8());
509 icalcomponent_add_property(parent, p);
510 break;
511 }
512 case Incidence::StatusNone:
513 default:
514 break;
515 }
516 if (status != ICAL_STATUS_NONE) {
517 icalcomponent_add_property(parent, icalproperty_new_status(status));
518 }
519
520 // secrecy
521 icalproperty_class secClass;
522 switch (incidence->secrecy()) {
523 case Incidence::SecrecyPublic:
524 secClass = ICAL_CLASS_PUBLIC;
525 break;
526 case Incidence::SecrecyConfidential:
527 secClass = ICAL_CLASS_CONFIDENTIAL;
528 break;
529 case Incidence::SecrecyPrivate:
530 default:
531 secClass = ICAL_CLASS_PRIVATE;
532 break;
533 }
534 if (secClass != ICAL_CLASS_PUBLIC) {
535 icalcomponent_add_property(parent, icalproperty_new_class(secClass));
536 }
537
538 // geo
539 if (incidence->hasGeo()) {
540 icalgeotype geo;
541 geo.lat = incidence->geoLatitude();
542 geo.lon = incidence->geoLongitude();
543 icalcomponent_add_property(parent, icalproperty_new_geo(geo));
544 }
545
546 // priority
547 if (incidence->priority() > 0) { // 0 is undefined priority
548 icalcomponent_add_property(
549 parent, icalproperty_new_priority(incidence->priority()));
550 }
551
552 // categories
553 QString categories = incidence->categories().join(QLatin1String(","));
554 if (!categories.isEmpty()) {
555 icalcomponent_add_property(
556 parent, icalproperty_new_categories(categories.toUtf8()));
557 }
558
559 // related event
560 if (!incidence->relatedTo().isEmpty()) {
561 icalcomponent_add_property(
562 parent, icalproperty_new_relatedto(incidence->relatedTo().toUtf8()));
563 }
564
565 // recurrenceid
566 if (incidence->hasRecurrenceId()) {
567 icalproperty *p = writeICalDateTimeProperty(
568 ICAL_RECURRENCEID_PROPERTY, incidence->recurrenceId(), tzlist, tzUsedList);
569 if (incidence->thisAndFuture()) {
570 icalproperty_add_parameter(
571 p, icalparameter_new_range(ICAL_RANGE_THISANDFUTURE));
572 }
573 icalcomponent_add_property(parent, p);
574 }
575
576 RecurrenceRule::List rrules(incidence->recurrence()->rRules());
577 RecurrenceRule::List::ConstIterator rit;
578 for (rit = rrules.constBegin(); rit != rrules.constEnd(); ++rit) {
579 icalcomponent_add_property(
580 parent, icalproperty_new_rrule(writeRecurrenceRule((*rit))));
581 }
582
583 RecurrenceRule::List exrules(incidence->recurrence()->exRules());
584 RecurrenceRule::List::ConstIterator exit;
585 for (exit = exrules.constBegin(); exit != exrules.constEnd(); ++exit) {
586 icalcomponent_add_property(
587 parent, icalproperty_new_exrule(writeRecurrenceRule((*exit))));
588 }
589
590 DateList dateList = incidence->recurrence()->exDates();
591 DateList::ConstIterator exIt;
592 for (exIt = dateList.constBegin(); exIt != dateList.constEnd(); ++exIt) {
593 icalcomponent_add_property(
594 parent, icalproperty_new_exdate(writeICalDate(*exIt)));
595 }
596
597 DateTimeList dateTimeList = incidence->recurrence()->exDateTimes();
598 DateTimeList::ConstIterator extIt;
599 for (extIt = dateTimeList.constBegin(); extIt != dateTimeList.constEnd(); ++extIt) {
600 icalcomponent_add_property(
601 parent, writeICalDateTimeProperty(ICAL_EXDATE_PROPERTY, *extIt, tzlist, tzUsedList));
602 }
603
604 dateList = incidence->recurrence()->rDates();
605 DateList::ConstIterator rdIt;
606 for (rdIt = dateList.constBegin(); rdIt != dateList.constEnd(); ++rdIt) {
607 icalcomponent_add_property(
608 parent, icalproperty_new_rdate(writeICalDatePeriod(*rdIt)));
609 }
610 dateTimeList = incidence->recurrence()->rDateTimes();
611 DateTimeList::ConstIterator rdtIt;
612 for (rdtIt = dateTimeList.constBegin(); rdtIt != dateTimeList.constEnd(); ++rdtIt) {
613 icalcomponent_add_property(
614 parent, writeICalDateTimeProperty(ICAL_RDATE_PROPERTY, *rdtIt, tzlist, tzUsedList));
615 }
616
617 // attachments
618 Attachment::List attachments = incidence->attachments();
619 Attachment::List::ConstIterator atIt;
620 for (atIt = attachments.constBegin(); atIt != attachments.constEnd(); ++atIt) {
621 icalcomponent_add_property(parent, writeAttachment(*atIt));
622 }
623
624 // alarms
625 Alarm::List::ConstIterator alarmIt;
626 for (alarmIt = incidence->alarms().constBegin();
627 alarmIt != incidence->alarms().constEnd(); ++alarmIt) {
628 icalcomponent_add_component(parent, writeAlarm(*alarmIt));
629 }
630
631 // duration
632 if (incidence->hasDuration()) {
633 icaldurationtype duration;
634 duration = writeICalDuration(incidence->duration());
635 icalcomponent_add_property(parent, icalproperty_new_duration(duration));
636 }
637}
638
639//@cond PRIVATE
640void ICalFormatImpl::Private::writeIncidenceBase(icalcomponent *parent,
641 IncidenceBase::Ptr incidenceBase)
642{
643 // organizer stuff
644 if (!incidenceBase->organizer()->isEmpty()) {
645 icalproperty *p = mImpl->writeOrganizer(incidenceBase->organizer());
646 if (p) {
647 icalcomponent_add_property(parent, p);
648 }
649 }
650
651 icalcomponent_add_property(
652 parent, icalproperty_new_dtstamp(writeICalUtcDateTime(incidenceBase->lastModified())));
653
654 // attendees
655 if (incidenceBase->attendeeCount() > 0) {
656 Attendee::List::ConstIterator it;
657 for (it = incidenceBase->attendees().constBegin();
658 it != incidenceBase->attendees().constEnd(); ++it) {
659 icalproperty *p = mImpl->writeAttendee(*it);
660 if (p) {
661 icalcomponent_add_property(parent, p);
662 }
663 }
664 }
665
666 //contacts
667 QStringList contacts = incidenceBase->contacts();
668 for (QStringList::const_iterator it = contacts.constBegin(); it != contacts.constEnd(); ++it) {
669 icalcomponent_add_property(parent, icalproperty_new_contact((*it).toUtf8()));
670 }
671
672 // comments
673 QStringList comments = incidenceBase->comments();
674 for (QStringList::const_iterator it = comments.constBegin(); it != comments.constEnd(); ++it) {
675 icalcomponent_add_property(parent, icalproperty_new_comment((*it).toUtf8()));
676 }
677
678 // url
679 const QUrl url = incidenceBase->url();
680 if (url.isValid()) {
681 icalcomponent_add_property(parent, icalproperty_new_url(url.toString().toUtf8()));
682 }
683
684 // custom properties
685 writeCustomProperties(parent, incidenceBase.data());
686}
687
688void ICalFormatImpl::Private::writeCustomProperties(icalcomponent *parent,
689 CustomProperties *properties)
690{
691 const QMap<QByteArray, QString> custom = properties->customProperties();
692 for (QMap<QByteArray, QString>::ConstIterator c = custom.begin(); c != custom.end(); ++c) {
693 if (c.key().startsWith("X-KDE-VOLATILE")) { //krazy:exclude=strings
694 // We don't write these properties to disk to disk
695 continue;
696 }
697 icalproperty *p = icalproperty_new_x(c.value().toUtf8());
698 QString parameters = properties->nonKDECustomPropertyParameters(c.key());
699
700 // Minimalist parameter handler: extract icalparameter's out of
701 // the given input text (not really parsing as such)
702 if (!parameters.isEmpty()) {
703 QStringList sl = parameters.split(QLatin1Char(';'));
704 foreach(const QString &parameter, sl) {
705 icalparameter *param = icalparameter_new_from_string(parameter.toUtf8());
706 if (param) {
707 icalproperty_add_parameter(p, param);
708 }
709 }
710 }
711
712 icalproperty_set_x_name(p, c.key());
713 icalcomponent_add_property(parent, p);
714 }
715}
716//@endcond
717
718icalproperty *ICalFormatImpl::writeOrganizer(const Person::Ptr &organizer)
719{
720 if (organizer->email().isEmpty()) {
721 return 0;
722 }
723
724 icalproperty *p = icalproperty_new_organizer(QByteArray(QByteArray("MAILTO:") + organizer->email().toUtf8()));
725
726 if (!organizer->name().isEmpty()) {
727 icalproperty_add_parameter(
728 p, icalparameter_new_cn(quoteForParam(organizer->name()).toUtf8()));
729 }
730 // TODO: Write dir, sent-by and language
731
732 return p;
733}
734
735icalproperty *ICalFormatImpl::writeDescription(const QString &description, bool isRich)
736{
737 icalproperty *p = icalproperty_new_description(description.toUtf8());
738 if (isRich) {
739 icalproperty_add_parameter(p, icalparameter_new_from_string("X-KDE-TEXTFORMAT=HTML"));
740 }
741 return p;
742}
743
744icalproperty *ICalFormatImpl::writeSummary(const QString &summary, bool isRich)
745{
746 icalproperty *p = icalproperty_new_summary(summary.toUtf8());
747 if (isRich) {
748 icalproperty_add_parameter(p, icalparameter_new_from_string("X-KDE-TEXTFORMAT=HTML"));
749 }
750 return p;
751}
752
753icalproperty *ICalFormatImpl::writeLocation(const QString &location, bool isRich)
754{
755 icalproperty *p = icalproperty_new_location(location.toUtf8());
756 if (isRich) {
757 icalproperty_add_parameter(p, icalparameter_new_from_string("X-KDE-TEXTFORMAT=HTML"));
758 }
759 return p;
760}
761
762icalproperty *ICalFormatImpl::writeAttendee(const Attendee::Ptr &attendee)
763{
764 if (attendee->email().isEmpty()) {
765 return 0;
766 }
767
768 icalproperty *p =
769 icalproperty_new_attendee(QByteArray(QByteArray("mailto:") + attendee->email().toUtf8()));
770
771 if (!attendee->name().isEmpty()) {
772 icalproperty_add_parameter(
773 p, icalparameter_new_cn(quoteForParam(attendee->name()).toUtf8()));
774 }
775
776 icalproperty_add_parameter(
777 p, icalparameter_new_rsvp(attendee->RSVP() ? ICAL_RSVP_TRUE : ICAL_RSVP_FALSE));
778
779 icalparameter_partstat status = ICAL_PARTSTAT_NEEDSACTION;
780 switch (attendee->status()) {
781 default:
782 case Attendee::NeedsAction:
783 status = ICAL_PARTSTAT_NEEDSACTION;
784 break;
785 case Attendee::Accepted:
786 status = ICAL_PARTSTAT_ACCEPTED;
787 break;
788 case Attendee::Declined:
789 status = ICAL_PARTSTAT_DECLINED;
790 break;
791 case Attendee::Tentative:
792 status = ICAL_PARTSTAT_TENTATIVE;
793 break;
794 case Attendee::Delegated:
795 status = ICAL_PARTSTAT_DELEGATED;
796 break;
797 case Attendee::Completed:
798 status = ICAL_PARTSTAT_COMPLETED;
799 break;
800 case Attendee::InProcess:
801 status = ICAL_PARTSTAT_INPROCESS;
802 break;
803 }
804 icalproperty_add_parameter(p, icalparameter_new_partstat(status));
805
806 icalparameter_role role = ICAL_ROLE_REQPARTICIPANT;
807 switch (attendee->role()) {
808 case Attendee::Chair:
809 role = ICAL_ROLE_CHAIR;
810 break;
811 default:
812 case Attendee::ReqParticipant:
813 role = ICAL_ROLE_REQPARTICIPANT;
814 break;
815 case Attendee::OptParticipant:
816 role = ICAL_ROLE_OPTPARTICIPANT;
817 break;
818 case Attendee::NonParticipant:
819 role = ICAL_ROLE_NONPARTICIPANT;
820 break;
821 }
822 icalproperty_add_parameter(p, icalparameter_new_role(role));
823
824 icalparameter_cutype cutype = ICAL_CUTYPE_INDIVIDUAL;
825 switch (attendee->cuType()) {
826 case Attendee::Unknown:
827 cutype = ICAL_CUTYPE_UNKNOWN;
828 break;
829 default:
830 case Attendee::Individual:
831 cutype = ICAL_CUTYPE_INDIVIDUAL;
832 break;
833 case Attendee::Group:
834 cutype = ICAL_CUTYPE_GROUP;
835 break;
836 case Attendee::Resource:
837 cutype = ICAL_CUTYPE_RESOURCE;
838 break;
839 case Attendee::Room:
840 cutype = ICAL_CUTYPE_ROOM;
841 break;
842 }
843 icalproperty_add_parameter(p, icalparameter_new_cutype(cutype));
844
845 if (!attendee->uid().isEmpty()) {
846 icalparameter *icalparameter_uid = icalparameter_new_x(attendee->uid().toUtf8());
847
848 icalparameter_set_xname(icalparameter_uid, "X-UID");
849 icalproperty_add_parameter(p, icalparameter_uid);
850 }
851
852 if (!attendee->delegate().isEmpty()) {
853 icalparameter *icalparameter_delegate =
854 icalparameter_new_delegatedto(attendee->delegate().toUtf8());
855 icalproperty_add_parameter(p, icalparameter_delegate);
856 }
857
858 if (!attendee->delegator().isEmpty()) {
859 icalparameter *icalparameter_delegator =
860 icalparameter_new_delegatedfrom(attendee->delegator().toUtf8());
861 icalproperty_add_parameter(p, icalparameter_delegator);
862 }
863
864 return p;
865}
866
867icalproperty *ICalFormatImpl::writeAttachment(const Attachment::Ptr &att)
868{
869 icalattach *attach;
870 if (att->isUri()) {
871 attach = icalattach_new_from_url(att->uri().toUtf8().data());
872 } else {
873#ifdef USE_ICAL_0_46
874 attach = icalattach_new_from_data((const char *)att->data().data(), 0, 0);
875#else
876 attach = icalattach_new_from_data((unsigned char *)att->data().data(), 0, 0);
877#endif
878 }
879 icalproperty *p = icalproperty_new_attach(attach);
880
881 icalattach_unref(attach);
882
883 if (!att->mimeType().isEmpty()) {
884 icalproperty_add_parameter(
885 p, icalparameter_new_fmttype(att->mimeType().toUtf8().data()));
886 }
887
888 if (att->isBinary()) {
889 icalproperty_add_parameter(p, icalparameter_new_value(ICAL_VALUE_BINARY));
890 icalproperty_add_parameter(p, icalparameter_new_encoding(ICAL_ENCODING_BASE64));
891 }
892
893 if (att->showInline()) {
894 icalparameter *icalparameter_inline = icalparameter_new_x("inline");
895 icalparameter_set_xname(icalparameter_inline, "X-CONTENT-DISPOSITION");
896 icalproperty_add_parameter(p, icalparameter_inline);
897 }
898
899 if (!att->label().isEmpty()) {
900 icalparameter *icalparameter_label = icalparameter_new_x(att->label().toUtf8());
901 icalparameter_set_xname(icalparameter_label, "X-LABEL");
902 icalproperty_add_parameter(p, icalparameter_label);
903 }
904
905 if (att->isLocal()) {
906 icalparameter *icalparameter_local = icalparameter_new_x("local");
907 icalparameter_set_xname(icalparameter_local, "X-KONTACT-TYPE");
908 icalproperty_add_parameter(p, icalparameter_local);
909 }
910
911 return p;
912}
913
914icalrecurrencetype ICalFormatImpl::writeRecurrenceRule(RecurrenceRule *recur)
915{
916 icalrecurrencetype r;
917 icalrecurrencetype_clear(&r);
918
919 switch (recur->recurrenceType()) {
920 case RecurrenceRule::rSecondly:
921 r.freq = ICAL_SECONDLY_RECURRENCE;
922 break;
923 case RecurrenceRule::rMinutely:
924 r.freq = ICAL_MINUTELY_RECURRENCE;
925 break;
926 case RecurrenceRule::rHourly:
927 r.freq = ICAL_HOURLY_RECURRENCE;
928 break;
929 case RecurrenceRule::rDaily:
930 r.freq = ICAL_DAILY_RECURRENCE;
931 break;
932 case RecurrenceRule::rWeekly:
933 r.freq = ICAL_WEEKLY_RECURRENCE;
934 break;
935 case RecurrenceRule::rMonthly:
936 r.freq = ICAL_MONTHLY_RECURRENCE;
937 break;
938 case RecurrenceRule::rYearly:
939 r.freq = ICAL_YEARLY_RECURRENCE;
940 break;
941 default:
942 r.freq = ICAL_NO_RECURRENCE;
943 kDebug() << "no recurrence";
944 break;
945 }
946
947 int index = 0;
948 QList<int> bys;
949 QList<int>::ConstIterator it;
950
951 // Now write out the BY* parts:
952 bys = recur->bySeconds();
953 index = 0;
954 for (it = bys.constBegin(); it != bys.constEnd(); ++it) {
955 r.by_second[index++] = *it;
956 r.by_second[index++] = static_cast<short>(*it);
957 }
958
959 bys = recur->byMinutes();
960 index = 0;
961 for (it = bys.constBegin(); it != bys.constEnd(); ++it) {
962 r.by_minute[index++] = *it;
963 r.by_minute[index++] = static_cast<short>(*it);
964 }
965
966 bys = recur->byHours();
967 index = 0;
968 for (it = bys.constBegin(); it != bys.constEnd(); ++it) {
969 r.by_hour[index++] = *it;
970 r.by_hour[index++] = static_cast<short>(*it);
971 }
972
973 bys = recur->byMonthDays();
974 index = 0;
975 for (it = bys.constBegin(); it != bys.constEnd(); ++it) {
976 short dShort = static_cast<short>((*it) * 8);
977 r.by_month_day[index++] = static_cast<short>(icalrecurrencetype_day_position(dShort));
978 }
979
980 bys = recur->byYearDays();
981 index = 0;
982 for (it = bys.constBegin(); it != bys.constEnd(); ++it) {
983 r.by_year_day[index++] = static_cast<short>(*it);
984 }
985
986 bys = recur->byWeekNumbers();
987 index = 0;
988 for (it = bys.constBegin(); it != bys.constEnd(); ++it) {
989 r.by_week_no[index++] = static_cast<short>(*it);
990 }
991
992 bys = recur->byMonths();
993 index = 0;
994 for (it = bys.constBegin(); it != bys.constEnd(); ++it) {
995 r.by_month[index++] = static_cast<short>(*it);
996 }
997
998 bys = recur->bySetPos();
999 index = 0;
1000 for (it = bys.constBegin(); it != bys.constEnd(); ++it) {
1001 r.by_set_pos[index++] = static_cast<short>(*it);
1002 }
1003
1004 QList<RecurrenceRule::WDayPos> byd = recur->byDays();
1005 int day;
1006 index = 0;
1007 for (QList<RecurrenceRule::WDayPos>::ConstIterator dit = byd.constBegin();
1008 dit != byd.constEnd(); ++dit) {
1009 day = (*dit).day() % 7 + 1; // convert from Monday=1 to Sunday=1
1010 if ((*dit).pos() < 0) {
1011 day += (-(*dit).pos()) * 8;
1012 day = -day;
1013 } else {
1014 day += (*dit).pos() * 8;
1015 }
1016 r.by_day[index++] = static_cast<short>(day);
1017 }
1018
1019 r.week_start =
1020 static_cast<icalrecurrencetype_weekday>(recur->weekStart() % 7 + 1);
1021
1022 if (recur->frequency() > 1) {
1023 // Dont' write out INTERVAL=1, because that's the default anyway
1024 r.interval = static_cast<short>(recur->frequency());
1025 }
1026
1027 if (recur->duration() > 0) {
1028 r.count = recur->duration();
1029 } else if (recur->duration() == -1) {
1030 r.count = 0;
1031 } else {
1032 if (recur->allDay()) {
1033 r.until = writeICalDate(recur->endDt().date());
1034 } else {
1035 r.until = writeICalUtcDateTime(recur->endDt());
1036 }
1037 }
1038
1039 return r;
1040}
1041
1042icalcomponent *ICalFormatImpl::writeAlarm(const Alarm::Ptr &alarm)
1043{
1044 if (alarm->enabled()) {
1045 alarm->setCustomProperty(APP_NAME_FOR_XPROPERTIES, ENABLED_ALARM_XPROPERTY, QLatin1String("TRUE"));
1046 } else {
1047 alarm->setCustomProperty(APP_NAME_FOR_XPROPERTIES, ENABLED_ALARM_XPROPERTY, QLatin1String("FALSE"));
1048 }
1049
1050 icalcomponent *a = icalcomponent_new(ICAL_VALARM_COMPONENT);
1051
1052 icalproperty_action action;
1053 icalattach *attach = 0;
1054
1055 switch (alarm->type()) {
1056 case Alarm::Procedure:
1057 action = ICAL_ACTION_PROCEDURE;
1058 attach = icalattach_new_from_url(
1059 QFile::encodeName(alarm->programFile()).data());
1060 icalcomponent_add_property(a, icalproperty_new_attach(attach));
1061 if (!alarm->programArguments().isEmpty()) {
1062 icalcomponent_add_property(
1063 a, icalproperty_new_description(alarm->programArguments().toUtf8()));
1064 }
1065 break;
1066 case Alarm::Audio:
1067 action = ICAL_ACTION_AUDIO;
1068 if (!alarm->audioFile().isEmpty()) {
1069 attach = icalattach_new_from_url(
1070 QFile::encodeName(alarm->audioFile()).data());
1071 icalcomponent_add_property(a, icalproperty_new_attach(attach));
1072 }
1073 break;
1074 case Alarm::Email:
1075 {
1076 action = ICAL_ACTION_EMAIL;
1077 const Person::List addresses = alarm->mailAddresses();
1078 for (Person::List::ConstIterator ad = addresses.constBegin();
1079 ad != addresses.constEnd(); ++ad) {
1080 if (!(*ad)->email().isEmpty()) {
1081 icalproperty *p = icalproperty_new_attendee(QByteArray(QByteArray("MAILTO:") + (*ad)->email().toUtf8()));
1082 if (!(*ad)->name().isEmpty()) {
1083 icalproperty_add_parameter(
1084 p, icalparameter_new_cn(quoteForParam((*ad)->name()).toUtf8()));
1085 }
1086 icalcomponent_add_property(a, p);
1087 }
1088 }
1089 icalcomponent_add_property(
1090 a, icalproperty_new_summary(alarm->mailSubject().toUtf8()));
1091 icalcomponent_add_property(
1092 a, icalproperty_new_description(alarm->mailText().toUtf8()));
1093 QStringList attachments = alarm->mailAttachments();
1094 if (attachments.count() > 0) {
1095 for (QStringList::const_iterator at = attachments.constBegin();
1096 at != attachments.constEnd(); ++at) {
1097 attach = icalattach_new_from_url(QFile::encodeName(*at).data());
1098 icalcomponent_add_property(a, icalproperty_new_attach(attach));
1099 }
1100 }
1101 break;
1102 }
1103 case Alarm::Display:
1104 action = ICAL_ACTION_DISPLAY;
1105 icalcomponent_add_property(
1106 a, icalproperty_new_description(alarm->text().toUtf8()));
1107 break;
1108 case Alarm::Invalid:
1109 default:
1110 kDebug() << "Unknown type of alarm";
1111 action = ICAL_ACTION_NONE;
1112 break;
1113 }
1114 icalcomponent_add_property(a, icalproperty_new_action(action));
1115
1116 // Trigger time
1117 icaltriggertype trigger;
1118 if (alarm->hasTime()) {
1119 trigger.time = writeICalUtcDateTime(alarm->time());
1120 trigger.duration = icaldurationtype_null_duration();
1121 } else {
1122 trigger.time = icaltime_null_time();
1123 Duration offset;
1124 if (alarm->hasStartOffset()) {
1125 offset = alarm->startOffset();
1126 } else {
1127 offset = alarm->endOffset();
1128 }
1129 trigger.duration = writeICalDuration(offset);
1130 }
1131 icalproperty *p = icalproperty_new_trigger(trigger);
1132 if (alarm->hasEndOffset()) {
1133 icalproperty_add_parameter(p, icalparameter_new_related(ICAL_RELATED_END));
1134 }
1135 icalcomponent_add_property(a, p);
1136
1137 // Repeat count and duration
1138 if (alarm->repeatCount()) {
1139 icalcomponent_add_property(
1140 a, icalproperty_new_repeat(alarm->repeatCount()));
1141 icalcomponent_add_property(
1142 a, icalproperty_new_duration(writeICalDuration(alarm->snoozeTime())));
1143 }
1144
1145 // Custom properties
1146 const QMap<QByteArray, QString> custom = alarm->customProperties();
1147 for (QMap<QByteArray, QString>::ConstIterator c = custom.begin(); c != custom.end(); ++c) {
1148 icalproperty *p = icalproperty_new_x(c.value().toUtf8());
1149 icalproperty_set_x_name(p, c.key());
1150 icalcomponent_add_property(a, p);
1151 }
1152
1153 icalattach_unref(attach);
1154
1155 return a;
1156}
1157
1158Todo::Ptr ICalFormatImpl::readTodo(icalcomponent *vtodo, ICalTimeZones *tzlist)
1159{
1160 Todo::Ptr todo(new Todo);
1161
1162 readIncidence(vtodo, todo, tzlist);
1163
1164 icalproperty *p = icalcomponent_get_first_property(vtodo, ICAL_ANY_PROPERTY);
1165
1166 while (p) {
1167 icalproperty_kind kind = icalproperty_isa(p);
1168 switch (kind) {
1169 case ICAL_DUE_PROPERTY:
1170 { // due date/time
1171 KDateTime kdt = readICalDateTimeProperty(p, tzlist);
1172 todo->setDtDue(kdt, true);
1173 todo->setAllDay(kdt.isDateOnly());
1174 break;
1175 }
1176 case ICAL_COMPLETED_PROPERTY: // completion date/time
1177 todo->setCompleted(readICalDateTimeProperty(p, tzlist));
1178 break;
1179
1180 case ICAL_PERCENTCOMPLETE_PROPERTY: // Percent completed
1181 todo->setPercentComplete(icalproperty_get_percentcomplete(p));
1182 break;
1183
1184 case ICAL_RELATEDTO_PROPERTY: // related todo (parent)
1185 todo->setRelatedTo(QString::fromUtf8(icalproperty_get_relatedto(p)));
1186 d->mTodosRelate.append(todo);
1187 break;
1188
1189 case ICAL_DTSTART_PROPERTY:
1190 // Flag that todo has start date. Value is read in by readIncidence().
1191 if (todo->comments().filter(QLatin1String("NoStartDate")).count()) {
1192 todo->setDtStart(KDateTime());
1193 } else {
1194 todo->setHasStartDate(true);
1195 }
1196 break;
1197 case ICAL_X_PROPERTY:
1198 {
1199 const KDateTime dateTime = readICalDateTimeProperty(p, tzlist);
1200 if (dateTime.isValid()) {
1201 todo->setDtRecurrence(dateTime);
1202 } else {
1203 kDebug() << "Invalid dateTime";
1204 }
1205 }
1206 break;
1207 default:
1208 // TODO: do something about unknown properties?
1209 break;
1210 }
1211
1212 p = icalcomponent_get_next_property(vtodo, ICAL_ANY_PROPERTY);
1213 }
1214
1215 if (d->mCompat) {
1216 d->mCompat->fixEmptySummary(todo);
1217 }
1218
1219 todo->resetDirtyFields();
1220 return todo;
1221}
1222
1223Event::Ptr ICalFormatImpl::readEvent(icalcomponent *vevent, ICalTimeZones *tzlist)
1224{
1225 Event::Ptr event(new Event);
1226
1227 readIncidence(vevent, event, tzlist);
1228
1229 icalproperty *p = icalcomponent_get_first_property(vevent, ICAL_ANY_PROPERTY);
1230
1231 bool dtEndProcessed = false;
1232
1233 while (p) {
1234 icalproperty_kind kind = icalproperty_isa(p);
1235 switch (kind) {
1236 case ICAL_DTEND_PROPERTY:
1237 { // end date and time
1238 KDateTime kdt = readICalDateTimeProperty(p, tzlist);
1239 if (kdt.isDateOnly()) {
1240 // End date is non-inclusive
1241 QDate endDate = kdt.date().addDays(-1);
1242 if (d->mCompat) {
1243 d->mCompat->fixFloatingEnd(endDate);
1244 }
1245 if (endDate < event->dtStart().date()) {
1246 endDate = event->dtStart().date();
1247 }
1248 event->setDtEnd(KDateTime(endDate, event->dtStart().timeSpec()));
1249 } else {
1250 event->setDtEnd(kdt);
1251 event->setAllDay(false);
1252 }
1253 dtEndProcessed = true;
1254 break;
1255 }
1256 case ICAL_RELATEDTO_PROPERTY: // related event (parent)
1257 event->setRelatedTo(QString::fromUtf8(icalproperty_get_relatedto(p)));
1258 d->mEventsRelate.append(event);
1259 break;
1260
1261 case ICAL_TRANSP_PROPERTY: // Transparency
1262 {
1263 icalproperty_transp transparency = icalproperty_get_transp(p);
1264 if (transparency == ICAL_TRANSP_TRANSPARENT) {
1265 event->setTransparency(Event::Transparent);
1266 } else {
1267 event->setTransparency(Event::Opaque);
1268 }
1269 break;
1270 }
1271
1272 default:
1273 // TODO: do something about unknown properties?
1274 break;
1275 }
1276
1277 p = icalcomponent_get_next_property(vevent, ICAL_ANY_PROPERTY);
1278 }
1279
1280 // according to rfc2445 the dtend shouldn't be written when it equals
1281 // start date. so assign one equal to start date.
1282 if (!dtEndProcessed && !event->hasDuration()) {
1283 event->setDtEnd(event->dtStart());
1284 event->setHasEndDate(false);
1285 }
1286
1287 QString msade = event->nonKDECustomProperty("X-MICROSOFT-CDO-ALLDAYEVENT");
1288 if (!msade.isEmpty()) {
1289 bool allDay = (msade == QLatin1String("TRUE"));
1290 event->setAllDay(allDay);
1291 }
1292
1293 if (d->mCompat) {
1294 d->mCompat->fixEmptySummary(event);
1295 }
1296
1297 event->resetDirtyFields();
1298 return event;
1299}
1300
1301FreeBusy::Ptr ICalFormatImpl::readFreeBusy(icalcomponent *vfreebusy)
1302{
1303 FreeBusy::Ptr freebusy(new FreeBusy);
1304
1305 d->readIncidenceBase(vfreebusy, freebusy);
1306
1307 icalproperty *p = icalcomponent_get_first_property(vfreebusy, ICAL_ANY_PROPERTY);
1308
1309 FreeBusyPeriod::List periods;
1310
1311 while (p) {
1312 icalproperty_kind kind = icalproperty_isa(p);
1313 switch (kind) {
1314 case ICAL_DTSTART_PROPERTY: // start date and time (UTC)
1315 freebusy->setDtStart(readICalUtcDateTimeProperty(p));
1316 break;
1317
1318 case ICAL_DTEND_PROPERTY: // end Date and Time (UTC)
1319 freebusy->setDtEnd(readICalUtcDateTimeProperty(p));
1320 break;
1321
1322 case ICAL_FREEBUSY_PROPERTY: //Any FreeBusy Times (UTC)
1323 {
1324 icalperiodtype icalperiod = icalproperty_get_freebusy(p);
1325 KDateTime period_start = readICalUtcDateTime(p, icalperiod.start);
1326 FreeBusyPeriod period;
1327 if (!icaltime_is_null_time(icalperiod.end)) {
1328 KDateTime period_end = readICalUtcDateTime(p, icalperiod.end);
1329 period = FreeBusyPeriod(period_start, period_end);
1330 } else {
1331 Duration duration(readICalDuration(icalperiod.duration));
1332 period = FreeBusyPeriod(period_start, duration);
1333 }
1334
1335 icalparameter *param = icalproperty_get_first_parameter(p, ICAL_X_PARAMETER);
1336 while (param) {
1337 if (strncmp(icalparameter_get_xname(param), "X-SUMMARY", 9) == 0) {
1338 period.setSummary(QString::fromUtf8(
1339 KCodecs::base64Decode(icalparameter_get_xvalue(param))));
1340 }
1341 if (strncmp(icalparameter_get_xname(param), "X-LOCATION", 10) == 0) {
1342 period.setLocation(QString::fromUtf8(
1343 KCodecs::base64Decode(icalparameter_get_xvalue(param))));
1344 }
1345 param = icalproperty_get_next_parameter(p, ICAL_X_PARAMETER);
1346 }
1347
1348 periods.append(period);
1349 break;
1350 }
1351
1352 default:
1353 // TODO: do something about unknown properties?
1354 break;
1355 }
1356 p = icalcomponent_get_next_property(vfreebusy, ICAL_ANY_PROPERTY);
1357 }
1358 freebusy->addPeriods(periods);
1359
1360 freebusy->resetDirtyFields();
1361 return freebusy;
1362}
1363
1364Journal::Ptr ICalFormatImpl::readJournal(icalcomponent *vjournal,
1365 ICalTimeZones *tzlist)
1366{
1367 Journal::Ptr journal(new Journal);
1368 readIncidence(vjournal, journal, tzlist);
1369
1370 journal->resetDirtyFields();
1371 return journal;
1372}
1373
1374Attendee::Ptr ICalFormatImpl::readAttendee(icalproperty *attendee)
1375{
1376 // the following is a hack to support broken calendars (like WebCalendar 1.0.x)
1377 // that include non-RFC-compliant attendees. Otherwise libical 0.42 asserts.
1378 if (!icalproperty_get_value(attendee)) {
1379 return Attendee::Ptr();
1380 }
1381
1382 icalparameter *p = 0;
1383
1384 QString email = QString::fromUtf8(icalproperty_get_attendee(attendee));
1385 if (email.startsWith(QLatin1String("mailto:"), Qt::CaseInsensitive)) {
1386 email = email.mid(7);
1387 }
1388
1389 // libical may return everything after ATTENDEE tag if the rest is
1390 // not meaningful. Verify the address to filter out these cases.
1391 if (!Person::isValidEmail(email)) {
1392 return Attendee::Ptr();
1393 }
1394
1395 QString name;
1396 QString uid;
1397 p = icalproperty_get_first_parameter(attendee, ICAL_CN_PARAMETER);
1398 if (p) {
1399 name = QString::fromUtf8(icalparameter_get_cn(p));
1400 } else {
1401 }
1402
1403 bool rsvp = false;
1404 p = icalproperty_get_first_parameter(attendee, ICAL_RSVP_PARAMETER);
1405 if (p) {
1406 icalparameter_rsvp rsvpParameter = icalparameter_get_rsvp(p);
1407 if (rsvpParameter == ICAL_RSVP_TRUE) {
1408 rsvp = true;
1409 }
1410 }
1411
1412 Attendee::PartStat status = Attendee::NeedsAction;
1413 p = icalproperty_get_first_parameter(attendee, ICAL_PARTSTAT_PARAMETER);
1414 if (p) {
1415 icalparameter_partstat partStatParameter = icalparameter_get_partstat(p);
1416 switch (partStatParameter) {
1417 default:
1418 case ICAL_PARTSTAT_NEEDSACTION:
1419 status = Attendee::NeedsAction;
1420 break;
1421 case ICAL_PARTSTAT_ACCEPTED:
1422 status = Attendee::Accepted;
1423 break;
1424 case ICAL_PARTSTAT_DECLINED:
1425 status = Attendee::Declined;
1426 break;
1427 case ICAL_PARTSTAT_TENTATIVE:
1428 status = Attendee::Tentative;
1429 break;
1430 case ICAL_PARTSTAT_DELEGATED:
1431 status = Attendee::Delegated;
1432 break;
1433 case ICAL_PARTSTAT_COMPLETED:
1434 status = Attendee::Completed;
1435 break;
1436 case ICAL_PARTSTAT_INPROCESS:
1437 status = Attendee::InProcess;
1438 break;
1439 }
1440 }
1441
1442 Attendee::Role role = Attendee::ReqParticipant;
1443 p = icalproperty_get_first_parameter(attendee, ICAL_ROLE_PARAMETER);
1444 if (p) {
1445 icalparameter_role roleParameter = icalparameter_get_role(p);
1446 switch (roleParameter) {
1447 case ICAL_ROLE_CHAIR:
1448 role = Attendee::Chair;
1449 break;
1450 default:
1451 case ICAL_ROLE_REQPARTICIPANT:
1452 role = Attendee::ReqParticipant;
1453 break;
1454 case ICAL_ROLE_OPTPARTICIPANT:
1455 role = Attendee::OptParticipant;
1456 break;
1457 case ICAL_ROLE_NONPARTICIPANT:
1458 role = Attendee::NonParticipant;
1459 break;
1460 }
1461 }
1462
1463 Attendee::CuType cuType = Attendee::Individual;
1464 p = icalproperty_get_first_parameter( attendee, ICAL_CUTYPE_PARAMETER );
1465 if (p) {
1466 icalparameter_cutype cutypeParameter = icalparameter_get_cutype(p);
1467 switch (cutypeParameter) {
1468 case ICAL_CUTYPE_X:
1469 case ICAL_CUTYPE_UNKNOWN:
1470 cuType = Attendee::Unknown;
1471 break;
1472 default:
1473 case ICAL_CUTYPE_NONE:
1474 case ICAL_CUTYPE_INDIVIDUAL:
1475 cuType = Attendee::Individual;
1476 break;
1477 case ICAL_CUTYPE_GROUP:
1478 cuType = Attendee::Group;
1479 break;
1480 case ICAL_CUTYPE_RESOURCE:
1481 cuType = Attendee::Resource;
1482 break;
1483 case ICAL_CUTYPE_ROOM:
1484 cuType = Attendee::Room;
1485 break;
1486 }
1487 }
1488
1489 p = icalproperty_get_first_parameter(attendee, ICAL_X_PARAMETER);
1490 QMap<QByteArray, QString> custom;
1491 while (p) {
1492 QString xname = QString::fromLatin1(icalparameter_get_xname(p)).toUpper();
1493 QString xvalue = QString::fromUtf8(icalparameter_get_xvalue(p));
1494 if (xname == QLatin1String("X-UID")) {
1495 uid = xvalue;
1496 } else {
1497 custom[xname.toUtf8()] = xvalue;
1498 }
1499 p = icalproperty_get_next_parameter(attendee, ICAL_X_PARAMETER);
1500 }
1501
1502 Attendee::Ptr a(new Attendee(name, email, rsvp, status, role, uid));
1503 a->setCuType(cuType);
1504 a->customProperties().setCustomProperties(custom);
1505
1506 p = icalproperty_get_first_parameter(attendee, ICAL_DELEGATEDTO_PARAMETER);
1507 if (p) {
1508 a->setDelegate(QLatin1String(icalparameter_get_delegatedto(p)));
1509 }
1510
1511 p = icalproperty_get_first_parameter(attendee, ICAL_DELEGATEDFROM_PARAMETER);
1512 if (p) {
1513 a->setDelegator(QLatin1String(icalparameter_get_delegatedfrom(p)));
1514 }
1515
1516 return a;
1517}
1518
1519Person::Ptr ICalFormatImpl::readOrganizer(icalproperty *organizer)
1520{
1521 QString email = QString::fromUtf8(icalproperty_get_organizer(organizer));
1522 if (email.startsWith(QLatin1String("mailto:"), Qt::CaseInsensitive)) {
1523 email = email.mid(7);
1524 }
1525 QString cn;
1526
1527 icalparameter *p = icalproperty_get_first_parameter(organizer, ICAL_CN_PARAMETER);
1528
1529 if (p) {
1530 cn = QString::fromUtf8(icalparameter_get_cn(p));
1531 }
1532 Person::Ptr org(new Person(cn, email));
1533 // TODO: Treat sent-by, dir and language here, too
1534 return org;
1535}
1536
1537Attachment::Ptr ICalFormatImpl::readAttachment(icalproperty *attach)
1538{
1539 Attachment::Ptr attachment;
1540
1541 QByteArray p;
1542 icalvalue *value = icalproperty_get_value(attach);
1543
1544 switch (icalvalue_isa(value)) {
1545 case ICAL_ATTACH_VALUE:
1546 {
1547 icalattach *a = icalproperty_get_attach(attach);
1548 if (!icalattach_get_is_url(a)) {
1549 p = QByteArray(reinterpret_cast<const char *>(icalattach_get_data(a)));
1550 if (!p.isEmpty()) {
1551 attachment = Attachment::Ptr(new Attachment(p));
1552 }
1553 } else {
1554 p = icalattach_get_url(a);
1555 if (!p.isEmpty()) {
1556 attachment = Attachment::Ptr(new Attachment(QString::fromUtf8(p)));
1557 }
1558 }
1559 break;
1560 }
1561 case ICAL_BINARY_VALUE:
1562 {
1563 icalattach *a = icalproperty_get_attach(attach);
1564 p = QByteArray(reinterpret_cast<const char *>(icalattach_get_data(a)));
1565 if (!p.isEmpty()) {
1566 attachment = Attachment::Ptr(new Attachment(p));
1567 }
1568 break;
1569 }
1570 case ICAL_URI_VALUE:
1571 p = icalvalue_get_uri(value);
1572 attachment = Attachment::Ptr(new Attachment(QString::fromUtf8(p)));
1573 break;
1574 default:
1575 break;
1576 }
1577
1578 if (attachment) {
1579 icalparameter *p =
1580 icalproperty_get_first_parameter(attach, ICAL_FMTTYPE_PARAMETER);
1581 if (p) {
1582 attachment->setMimeType(QLatin1String(icalparameter_get_fmttype(p)));
1583 }
1584
1585 p = icalproperty_get_first_parameter(attach, ICAL_X_PARAMETER);
1586 while (p) {
1587 QString xname = QString::fromLatin1(icalparameter_get_xname(p)).toUpper();
1588 QString xvalue = QString::fromUtf8(icalparameter_get_xvalue(p));
1589 if (xname == QLatin1String("X-CONTENT-DISPOSITION")) {
1590 attachment->setShowInline(xvalue.toLower() == QLatin1String("inline"));
1591 }
1592 if (xname == QLatin1String("X-LABEL")) {
1593 attachment->setLabel(xvalue);
1594 }
1595 if (xname == QLatin1String("X-KONTACT-TYPE")) {
1596 attachment->setLocal(xvalue.toLower() == QLatin1String("local"));
1597 }
1598 p = icalproperty_get_next_parameter(attach, ICAL_X_PARAMETER);
1599 }
1600
1601 p = icalproperty_get_first_parameter(attach, ICAL_X_PARAMETER);
1602 while (p) {
1603 if (strncmp(icalparameter_get_xname(p), "X-LABEL", 7) == 0) {
1604 attachment->setLabel(QString::fromUtf8(icalparameter_get_xvalue(p)));
1605 }
1606 p = icalproperty_get_next_parameter(attach, ICAL_X_PARAMETER);
1607 }
1608 }
1609
1610 return attachment;
1611}
1612
1613void ICalFormatImpl::readIncidence(icalcomponent *parent,
1614 Incidence::Ptr incidence,
1615 ICalTimeZones *tzlist)
1616{
1617 d->readIncidenceBase(parent, incidence);
1618
1619 icalproperty *p = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY);
1620
1621 const char *text;
1622 int intvalue, inttext;
1623 icaldurationtype icalduration;
1624 KDateTime kdt;
1625 KDateTime dtstamp;
1626
1627 QStringList categories;
1628
1629 while (p) {
1630 icalproperty_kind kind = icalproperty_isa(p);
1631 switch (kind) {
1632 case ICAL_CREATED_PROPERTY:
1633 incidence->setCreated(readICalDateTimeProperty(p, tzlist));
1634 break;
1635
1636 case ICAL_DTSTAMP_PROPERTY:
1637 dtstamp = readICalDateTimeProperty(p, tzlist);
1638 break;
1639
1640 case ICAL_SEQUENCE_PROPERTY: // sequence
1641 intvalue = icalproperty_get_sequence(p);
1642 incidence->setRevision(intvalue);
1643 break;
1644
1645 case ICAL_LASTMODIFIED_PROPERTY: // last modification UTC date/time
1646 incidence->setLastModified(readICalDateTimeProperty(p, tzlist));
1647 break;
1648
1649 case ICAL_DTSTART_PROPERTY: // start date and time
1650 kdt = readICalDateTimeProperty(p, tzlist);
1651 incidence->setDtStart(kdt);
1652 incidence->setAllDay(kdt.isDateOnly());
1653 break;
1654
1655 case ICAL_DURATION_PROPERTY: // start date and time
1656 icalduration = icalproperty_get_duration(p);
1657 incidence->setDuration(readICalDuration(icalduration));
1658 break;
1659
1660 case ICAL_DESCRIPTION_PROPERTY: // description
1661 {
1662 QString textStr = QString::fromUtf8(icalproperty_get_description(p));
1663 if (!textStr.isEmpty()) {
1664 QString valStr = QString::fromUtf8(
1665 icalproperty_get_parameter_as_string(p, "X-KDE-TEXTFORMAT"));
1666 if (!valStr.compare(QLatin1String("HTML"), Qt::CaseInsensitive)) {
1667 incidence->setDescription(textStr, true);
1668 } else {
1669 incidence->setDescription(textStr, false);
1670 }
1671 }
1672 }
1673 break;
1674
1675 case ICAL_SUMMARY_PROPERTY: // summary
1676 {
1677 QString textStr = QString::fromUtf8(icalproperty_get_summary(p));
1678 if (!textStr.isEmpty()) {
1679 QString valStr = QString::fromUtf8(
1680 icalproperty_get_parameter_as_string(p, "X-KDE-TEXTFORMAT"));
1681 if (!valStr.compare(QLatin1String("HTML"), Qt::CaseInsensitive)) {
1682 incidence->setSummary(textStr, true);
1683 } else {
1684 incidence->setSummary(textStr, false);
1685 }
1686 }
1687 }
1688 break;
1689
1690 case ICAL_LOCATION_PROPERTY: // location
1691 {
1692 if (!icalproperty_get_value(p)) {
1693 //Fix for #191472. This is a pre-crash guard in case libical was
1694 //compiled in superstrict mode (--enable-icalerrors-are-fatal)
1695 //TODO: pre-crash guard other property getters too.
1696 break;
1697 }
1698 QString textStr = QString::fromUtf8(icalproperty_get_location(p));
1699 if (!textStr.isEmpty()) {
1700 QString valStr = QString::fromUtf8(
1701 icalproperty_get_parameter_as_string(p, "X-KDE-TEXTFORMAT"));
1702 if (!valStr.compare(QLatin1String("HTML"), Qt::CaseInsensitive)) {
1703 incidence->setLocation(textStr, true);
1704 } else {
1705 incidence->setLocation(textStr, false);
1706 }
1707 }
1708 }
1709 break;
1710
1711 case ICAL_STATUS_PROPERTY: // status
1712 {
1713 Incidence::Status stat;
1714 switch (icalproperty_get_status(p)) {
1715 case ICAL_STATUS_TENTATIVE:
1716 stat = Incidence::StatusTentative;
1717 break;
1718 case ICAL_STATUS_CONFIRMED:
1719 stat = Incidence::StatusConfirmed;
1720 break;
1721 case ICAL_STATUS_COMPLETED:
1722 stat = Incidence::StatusCompleted;
1723 break;
1724 case ICAL_STATUS_NEEDSACTION:
1725 stat = Incidence::StatusNeedsAction;
1726 break;
1727 case ICAL_STATUS_CANCELLED:
1728 stat = Incidence::StatusCanceled;
1729 break;
1730 case ICAL_STATUS_INPROCESS:
1731 stat = Incidence::StatusInProcess;
1732 break;
1733 case ICAL_STATUS_DRAFT:
1734 stat = Incidence::StatusDraft;
1735 break;
1736 case ICAL_STATUS_FINAL:
1737 stat = Incidence::StatusFinal;
1738 break;
1739 case ICAL_STATUS_X:
1740 incidence->setCustomStatus(
1741 QString::fromUtf8(icalvalue_get_x(icalproperty_get_value(p))));
1742 stat = Incidence::StatusX;
1743 break;
1744 case ICAL_STATUS_NONE:
1745 default:
1746 stat = Incidence::StatusNone;
1747 break;
1748 }
1749 if (stat != Incidence::StatusX) {
1750 incidence->setStatus(stat);
1751 }
1752 break;
1753 }
1754
1755 case ICAL_GEO_PROPERTY: // geo
1756 {
1757 icalgeotype geo = icalproperty_get_geo(p);
1758 incidence->setGeoLatitude(geo.lat);
1759 incidence->setGeoLongitude(geo.lon);
1760 incidence->setHasGeo(true);
1761 break;
1762 }
1763
1764 case ICAL_PRIORITY_PROPERTY: // priority
1765 intvalue = icalproperty_get_priority(p);
1766 if (d->mCompat) {
1767 intvalue = d->mCompat->fixPriority(intvalue);
1768 }
1769 incidence->setPriority(intvalue);
1770 break;
1771
1772 case ICAL_CATEGORIES_PROPERTY: // categories
1773 {
1774 // We have always supported multiple CATEGORIES properties per component
1775 // even though the RFC seems to indicate only 1 is permitted.
1776 // We can't change that -- in order to retain backwards compatibility.
1777 text = icalproperty_get_categories(p);
1778 const QString val = QString::fromUtf8(text);
1779 foreach(const QString &cat, val.split(QLatin1Char(','), QString::SkipEmptyParts)) {
1780 // ensure no duplicates
1781 if (!categories.contains(cat)) {
1782 categories.append(cat);
1783 }
1784 }
1785 break;
1786 }
1787
1788 case ICAL_RECURRENCEID_PROPERTY: // recurrenceId
1789 kdt = readICalDateTimeProperty(p, tzlist);
1790 if (kdt.isValid()) {
1791 incidence->setRecurrenceId(kdt);
1792 const icalparameter *param =
1793 icalproperty_get_first_parameter(p, ICAL_RANGE_PARAMETER);
1794 if (param && icalparameter_get_range(param) == ICAL_RANGE_THISANDFUTURE) {
1795 incidence->setThisAndFuture(true);
1796 }
1797 }
1798 break;
1799
1800 case ICAL_RRULE_PROPERTY:
1801 readRecurrenceRule(p, incidence);
1802 break;
1803
1804 case ICAL_RDATE_PROPERTY:
1805 kdt = readICalDateTimeProperty(p, tzlist);
1806 if (kdt.isValid()) {
1807 if (kdt.isDateOnly()) {
1808 incidence->recurrence()->addRDate(kdt.date());
1809 } else {
1810 incidence->recurrence()->addRDateTime(kdt);
1811 }
1812 } else {
1813 // TODO: RDates as period are not yet implemented!
1814 }
1815 break;
1816
1817 case ICAL_EXRULE_PROPERTY:
1818 readExceptionRule(p, incidence);
1819 break;
1820
1821 case ICAL_EXDATE_PROPERTY:
1822 kdt = readICalDateTimeProperty(p, tzlist);
1823 if (kdt.isDateOnly()) {
1824 incidence->recurrence()->addExDate(kdt.date());
1825 } else {
1826 incidence->recurrence()->addExDateTime(kdt);
1827 }
1828 break;
1829
1830 case ICAL_CLASS_PROPERTY:
1831 inttext = icalproperty_get_class(p);
1832 if (inttext == ICAL_CLASS_PUBLIC) {
1833 incidence->setSecrecy(Incidence::SecrecyPublic);
1834 } else if (inttext == ICAL_CLASS_CONFIDENTIAL) {
1835 incidence->setSecrecy(Incidence::SecrecyConfidential);
1836 } else {
1837 incidence->setSecrecy(Incidence::SecrecyPrivate);
1838 }
1839 break;
1840
1841 case ICAL_ATTACH_PROPERTY: // attachments
1842 incidence->addAttachment(readAttachment(p));
1843 break;
1844
1845 default:
1846 // TODO: do something about unknown properties?
1847 break;
1848 }
1849
1850 p = icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY);
1851 }
1852
1853 // Set the scheduling ID
1854 const QString uid = incidence->customProperty("LIBKCAL", "ID");
1855 if (!uid.isNull()) {
1856 // The UID stored in incidencebase is actually the scheduling ID
1857 // It has to be stored in the iCal UID component for compatibility
1858 // with other iCal applications
1859 incidence->setSchedulingID(incidence->uid(), uid);
1860 }
1861
1862 // Now that recurrence and exception stuff is completely set up,
1863 // do any backwards compatibility adjustments.
1864 if (incidence->recurs() && d->mCompat) {
1865 d->mCompat->fixRecurrence(incidence);
1866 }
1867
1868 // add categories
1869 incidence->setCategories(categories);
1870
1871 // iterate through all alarms
1872 for (icalcomponent *alarm = icalcomponent_get_first_component(parent, ICAL_VALARM_COMPONENT);
1873 alarm;
1874 alarm = icalcomponent_get_next_component(parent, ICAL_VALARM_COMPONENT)) {
1875 readAlarm(alarm, incidence, tzlist);
1876 }
1877
1878 if (d->mCompat) {
1879 // Fix incorrect alarm settings by other applications (like outloook 9)
1880 d->mCompat->fixAlarms(incidence);
1881 d->mCompat->setCreatedToDtStamp(incidence, dtstamp);
1882 }
1883}
1884
1885//@cond PRIVATE
1886void ICalFormatImpl::Private::readIncidenceBase(icalcomponent *parent,
1887 IncidenceBase::Ptr incidenceBase)
1888{
1889 icalproperty *p = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY);
1890 bool uidProcessed = false;
1891 while (p) {
1892 icalproperty_kind kind = icalproperty_isa(p);
1893 switch (kind) {
1894 case ICAL_UID_PROPERTY: // unique id
1895 uidProcessed = true;
1896 incidenceBase->setUid(QString::fromUtf8(icalproperty_get_uid(p)));
1897 break;
1898
1899 case ICAL_ORGANIZER_PROPERTY: // organizer
1900 incidenceBase->setOrganizer(mImpl->readOrganizer(p));
1901 break;
1902
1903 case ICAL_ATTENDEE_PROPERTY: // attendee
1904 incidenceBase->addAttendee(mImpl->readAttendee(p));
1905 break;
1906
1907 case ICAL_COMMENT_PROPERTY:
1908 incidenceBase->addComment(
1909 QString::fromUtf8(icalproperty_get_comment(p)));
1910 break;
1911
1912 case ICAL_CONTACT_PROPERTY:
1913 incidenceBase->addContact(
1914 QString::fromUtf8(icalproperty_get_contact(p)));
1915 break;
1916
1917 case ICAL_URL_PROPERTY:
1918 incidenceBase->setUrl(
1919 QUrl(QString::fromUtf8(icalproperty_get_url(p))));
1920 break;
1921
1922 default:
1923 break;
1924 }
1925
1926 p = icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY);
1927 }
1928
1929 if (!uidProcessed) {
1930 kWarning() << "The incidence didn't have any UID! Report a bug "
1931 << "to the application that generated this file."
1932 << endl;
1933
1934 // Our in-memory incidence has a random uid generated in Event's ctor.
1935 // Make it empty so it matches what's in the file:
1936 incidenceBase->setUid(QString());
1937
1938 // Otherwise, next time we read the file, this function will return
1939 // an event with another random uid and we will have two events in the calendar.
1940 }
1941
1942 // custom properties
1943 readCustomProperties(parent, incidenceBase.data());
1944}
1945
1946void ICalFormatImpl::Private::readCustomProperties(icalcomponent *parent,
1947 CustomProperties *properties)
1948{
1949 QByteArray property;
1950 QString value, parameters;
1951 icalproperty *p = icalcomponent_get_first_property(parent, ICAL_X_PROPERTY);
1952 icalparameter *param;
1953
1954 while (p) {
1955 QString nvalue = QString::fromUtf8(icalproperty_get_x(p));
1956 if (nvalue.isEmpty()) {
1957 icalvalue *value = icalproperty_get_value(p);
1958 if (icalvalue_isa(value) == ICAL_TEXT_VALUE) {
1959 // Calling icalvalue_get_text( value ) on a datetime value crashes.
1960 nvalue = QString::fromUtf8(icalvalue_get_text(value));
1961 } else {
1962 p = icalcomponent_get_next_property(parent, ICAL_X_PROPERTY);
1963 continue;
1964 }
1965 }
1966 const char *name = icalproperty_get_x_name(p);
1967 QByteArray nproperty(name);
1968 if (property != nproperty) {
1969 // New property
1970 if (!property.isEmpty()) {
1971 properties->setNonKDECustomProperty(property, value, parameters);
1972 }
1973 property = name;
1974 value = nvalue;
1975 QStringList parametervalues;
1976 for (param = icalproperty_get_first_parameter(p, ICAL_ANY_PARAMETER);
1977 param;
1978 param = icalproperty_get_next_parameter(p, ICAL_ANY_PARAMETER)) {
1979 // 'c' is owned by ical library => all we need to do is just use it
1980 const char *c = icalparameter_as_ical_string(param);
1981 parametervalues.push_back(QLatin1String(c));
1982 }
1983 parameters = parametervalues.join(QLatin1String(";"));
1984 } else {
1985 value = value.append(QLatin1String(",")).append(nvalue);
1986 }
1987 p = icalcomponent_get_next_property(parent, ICAL_X_PROPERTY);
1988 }
1989 if (!property.isEmpty()) {
1990 properties->setNonKDECustomProperty(property, value, parameters);
1991 }
1992}
1993//@endcond
1994
1995void ICalFormatImpl::readRecurrenceRule(icalproperty *rrule, Incidence::Ptr incidence)
1996{
1997 Recurrence *recur = incidence->recurrence();
1998
1999 struct icalrecurrencetype r = icalproperty_get_rrule(rrule);
2000 // dumpIcalRecurrence(r);
2001
2002 RecurrenceRule *recurrule = new RecurrenceRule(/*incidence*/);
2003 recurrule->setStartDt(incidence->dtStart());
2004 readRecurrence(r, recurrule);
2005 recur->addRRule(recurrule);
2006}
2007
2008void ICalFormatImpl::readExceptionRule(icalproperty *rrule, Incidence::Ptr incidence)
2009{
2010 struct icalrecurrencetype r = icalproperty_get_exrule(rrule);
2011 // dumpIcalRecurrence(r);
2012
2013 RecurrenceRule *recurrule = new RecurrenceRule(/*incidence*/);
2014 recurrule->setStartDt(incidence->dtStart());
2015 readRecurrence(r, recurrule);
2016
2017 Recurrence *recur = incidence->recurrence();
2018 recur->addExRule(recurrule);
2019}
2020
2021void ICalFormatImpl::readRecurrence(const struct icalrecurrencetype &r, RecurrenceRule *recur)
2022{
2023 // Generate the RRULE string
2024 recur->setRRule(
2025 QLatin1String(icalrecurrencetype_as_string(const_cast<struct icalrecurrencetype*>(&r))));
2026 // Period
2027 switch (r.freq) {
2028 case ICAL_SECONDLY_RECURRENCE:
2029 recur->setRecurrenceType(RecurrenceRule::rSecondly);
2030 break;
2031 case ICAL_MINUTELY_RECURRENCE:
2032 recur->setRecurrenceType(RecurrenceRule::rMinutely);
2033 break;
2034 case ICAL_HOURLY_RECURRENCE:
2035 recur->setRecurrenceType(RecurrenceRule::rHourly);
2036 break;
2037 case ICAL_DAILY_RECURRENCE:
2038 recur->setRecurrenceType(RecurrenceRule::rDaily);
2039 break;
2040 case ICAL_WEEKLY_RECURRENCE:
2041 recur->setRecurrenceType(RecurrenceRule::rWeekly);
2042 break;
2043 case ICAL_MONTHLY_RECURRENCE:
2044 recur->setRecurrenceType(RecurrenceRule::rMonthly);
2045 break;
2046 case ICAL_YEARLY_RECURRENCE:
2047 recur->setRecurrenceType(RecurrenceRule::rYearly);
2048 break;
2049 case ICAL_NO_RECURRENCE:
2050 default:
2051 recur->setRecurrenceType(RecurrenceRule::rNone);
2052 }
2053 // Frequency
2054 recur->setFrequency(r.interval);
2055
2056 // Duration & End Date
2057 if (!icaltime_is_null_time(r.until)) {
2058 icaltimetype t = r.until;
2059 recur->setEndDt(readICalUtcDateTime(0, t));
2060 } else {
2061 if (r.count == 0) {
2062 recur->setDuration(-1);
2063 } else {
2064 recur->setDuration(r.count);
2065 }
2066 }
2067
2068 // Week start setting
2069 short wkst = static_cast<short>((r.week_start + 5) % 7 + 1);
2070 recur->setWeekStart(wkst);
2071
2072 // And now all BY*
2073 QList<int> lst;
2074 int i;
2075 int index = 0;
2076
2077//@cond PRIVATE
2078#define readSetByList( rrulecomp, setfunc ) \
2079 index = 0; \
2080 lst.clear(); \
2081 while ( ( i = r.rrulecomp[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { \
2082 lst.append( i ); \
2083 } \
2084 if ( !lst.isEmpty() ) { \
2085 recur->setfunc( lst ); \
2086 }
2087//@endcond
2088
2089 // BYSECOND, MINUTE and HOUR, MONTHDAY, YEARDAY, WEEKNUMBER, MONTH
2090 // and SETPOS are standard int lists, so we can treat them with the
2091 // same macro
2092 readSetByList(by_second, setBySeconds);
2093 readSetByList(by_minute, setByMinutes);
2094 readSetByList(by_hour, setByHours);
2095 readSetByList(by_month_day, setByMonthDays);
2096 readSetByList(by_year_day, setByYearDays);
2097 readSetByList(by_week_no, setByWeekNumbers);
2098 readSetByList(by_month, setByMonths);
2099 readSetByList(by_set_pos, setBySetPos);
2100#undef readSetByList
2101
2102 // BYDAY is a special case, since it's not an int list
2103 QList<RecurrenceRule::WDayPos> wdlst;
2104 short day;
2105 index=0;
2106 while ((day = r.by_day[index++]) != ICAL_RECURRENCE_ARRAY_MAX) {
2107 RecurrenceRule::WDayPos pos;
2108 pos.setDay(static_cast<short>((icalrecurrencetype_day_day_of_week(day) + 5) % 7 + 1));
2109 pos.setPos(icalrecurrencetype_day_position(day));
2110 wdlst.append(pos);
2111 }
2112 if (!wdlst.isEmpty()) {
2113 recur->setByDays(wdlst);
2114 }
2115
2116 // TODO: Store all X- fields of the RRULE inside the recurrence (so they are
2117 // preserved
2118}
2119
2120void ICalFormatImpl::readAlarm(icalcomponent *alarm,
2121 Incidence::Ptr incidence,
2122 ICalTimeZones *tzlist)
2123{
2124 Alarm::Ptr ialarm = incidence->newAlarm();
2125 ialarm->setRepeatCount(0);
2126 ialarm->setEnabled(true);
2127
2128 // Determine the alarm's action type
2129 icalproperty *p = icalcomponent_get_first_property(alarm, ICAL_ACTION_PROPERTY);
2130 Alarm::Type type = Alarm::Display;
2131 icalproperty_action action = ICAL_ACTION_DISPLAY;
2132 if (!p) {
2133 kDebug() << "Unknown type of alarm, using default";
2134 // TODO: do something about unknown alarm type?
2135 } else {
2136
2137 action = icalproperty_get_action(p);
2138 switch (action) {
2139 case ICAL_ACTION_DISPLAY:
2140 type = Alarm::Display;
2141 break;
2142 case ICAL_ACTION_AUDIO:
2143 type = Alarm::Audio;
2144 break;
2145 case ICAL_ACTION_PROCEDURE:
2146 type = Alarm::Procedure;
2147 break;
2148 case ICAL_ACTION_EMAIL:
2149 type = Alarm::Email;
2150 break;
2151 default:
2152 break;
2153 // TODO: do something about invalid alarm type?
2154 }
2155 }
2156 ialarm->setType(type);
2157
2158 p = icalcomponent_get_first_property(alarm, ICAL_ANY_PROPERTY);
2159 while (p) {
2160 icalproperty_kind kind = icalproperty_isa(p);
2161
2162 switch (kind) {
2163 case ICAL_TRIGGER_PROPERTY:
2164 {
2165 icaltriggertype trigger = icalproperty_get_trigger(p);
2166 if (!icaltime_is_null_time(trigger.time)) {
2167 //set the trigger to a specific time (which is not in rfc2445, btw)
2168 ialarm->setTime(readICalUtcDateTime(p, trigger.time, tzlist));
2169 } else {
2170 //set the trigger to an offset from the incidence start or end time.
2171 if (!icaldurationtype_is_bad_duration(trigger.duration)) {
2172 Duration duration(readICalDuration(trigger.duration));
2173 icalparameter *param =
2174 icalproperty_get_first_parameter(p, ICAL_RELATED_PARAMETER);
2175 if (param && icalparameter_get_related(param) == ICAL_RELATED_END) {
2176 ialarm->setEndOffset(duration);
2177 } else {
2178 ialarm->setStartOffset(duration);
2179 }
2180 } else {
2181 // a bad duration was encountered, just set a 0 duration from start
2182 ialarm->setStartOffset(Duration(0));
2183 }
2184 }
2185 break;
2186 }
2187 case ICAL_DURATION_PROPERTY:
2188 {
2189 icaldurationtype duration = icalproperty_get_duration(p);
2190 ialarm->setSnoozeTime(readICalDuration(duration));
2191 break;
2192 }
2193 case ICAL_REPEAT_PROPERTY:
2194 ialarm->setRepeatCount(icalproperty_get_repeat(p));
2195 break;
2196
2197 case ICAL_DESCRIPTION_PROPERTY:
2198 { // Only in DISPLAY and EMAIL and PROCEDURE alarms
2199 QString description = QString::fromUtf8(icalproperty_get_description(p));
2200 switch (action) {
2201 case ICAL_ACTION_DISPLAY:
2202 ialarm->setText(description);
2203 break;
2204 case ICAL_ACTION_PROCEDURE:
2205 ialarm->setProgramArguments(description);
2206 break;
2207 case ICAL_ACTION_EMAIL:
2208 ialarm->setMailText(description);
2209 break;
2210 default:
2211 break;
2212 }
2213 break;
2214 }
2215 case ICAL_SUMMARY_PROPERTY:
2216 // Only in EMAIL alarm
2217 ialarm->setMailSubject(QString::fromUtf8(icalproperty_get_summary(p)));
2218 break;
2219
2220 case ICAL_ATTENDEE_PROPERTY:
2221 { // Only in EMAIL alarm
2222 QString email = QString::fromUtf8(icalproperty_get_attendee(p));
2223 if (email.startsWith(QLatin1String("mailto:"), Qt::CaseInsensitive)) {
2224 email = email.mid(7);
2225 }
2226 QString name;
2227 icalparameter *param = icalproperty_get_first_parameter(p, ICAL_CN_PARAMETER);
2228 if (param) {
2229 name = QString::fromUtf8(icalparameter_get_cn(param));
2230 }
2231 ialarm->addMailAddress(Person::Ptr(new Person(name, email)));
2232 break;
2233 }
2234
2235 case ICAL_ATTACH_PROPERTY:
2236 { // Only in AUDIO and EMAIL and PROCEDURE alarms
2237 Attachment::Ptr attach = readAttachment(p);
2238 if (attach && attach->isUri()) {
2239 switch (action) {
2240 case ICAL_ACTION_AUDIO:
2241 ialarm->setAudioFile(attach->uri());
2242 break;
2243 case ICAL_ACTION_PROCEDURE:
2244 ialarm->setProgramFile(attach->uri());
2245 break;
2246 case ICAL_ACTION_EMAIL:
2247 ialarm->addMailAttachment(attach->uri());
2248 break;
2249 default:
2250 break;
2251 }
2252 } else {
2253 kDebug() << "Alarm attachments currently only support URIs,"
2254 << "but no binary data";
2255 }
2256 break;
2257 }
2258 default:
2259 break;
2260 }
2261 p = icalcomponent_get_next_property(alarm, ICAL_ANY_PROPERTY);
2262 }
2263
2264 // custom properties
2265 d->readCustomProperties(alarm, ialarm.data());
2266
2267 QString locationRadius = ialarm->nonKDECustomProperty("X-LOCATION-RADIUS");
2268 if (!locationRadius.isEmpty()) {
2269 ialarm->setLocationRadius(locationRadius.toInt());
2270 ialarm->setHasLocationRadius(true);
2271 }
2272
2273 if (ialarm->customProperty(APP_NAME_FOR_XPROPERTIES,
2274 ENABLED_ALARM_XPROPERTY) == QLatin1String("FALSE")) {
2275 ialarm->setEnabled(false);
2276 }
2277 // TODO: check for consistency of alarm properties
2278}
2279
2280icaldatetimeperiodtype ICalFormatImpl::writeICalDatePeriod(const QDate &date)
2281{
2282 icaldatetimeperiodtype t;
2283 t.time = writeICalDate(date);
2284 t.period = icalperiodtype_null_period();
2285 return t;
2286}
2287
2288icaltimetype ICalFormatImpl::writeICalDate(const QDate &date)
2289{
2290 icaltimetype t = icaltime_null_time();
2291
2292 t.year = date.year();
2293 t.month = date.month();
2294 t.day = date.day();
2295
2296 t.hour = 0;
2297 t.minute = 0;
2298 t.second = 0;
2299
2300 t.is_date = 1;
2301 t.is_utc = 0;
2302 t.zone = 0;
2303
2304 return t;
2305}
2306
2307icaltimetype ICalFormatImpl::writeICalDateTime(const KDateTime &datetime)
2308{
2309 icaltimetype t = icaltime_null_time();
2310
2311 t.year = datetime.date().year();
2312 t.month = datetime.date().month();
2313 t.day = datetime.date().day();
2314
2315 t.is_date = datetime.isDateOnly() ? 1 : 0;
2316
2317 if (!t.is_date) {
2318 t.hour = datetime.time().hour();
2319 t.minute = datetime.time().minute();
2320 t.second = datetime.time().second();
2321 }
2322 t.zone = 0; // zone is NOT set
2323 t.is_utc = datetime.isUtc() ? 1 : 0;
2324
2325 // _dumpIcaltime( t );
2326
2327 return t;
2328}
2329
2330icalproperty *ICalFormatImpl::writeICalDateTimeProperty(const icalproperty_kind type,
2331 const KDateTime &dt,
2332 ICalTimeZones *tzlist,
2333 ICalTimeZones *tzUsedList)
2334{
2335 icaltimetype t;
2336
2337 switch (type) {
2338 case ICAL_DTSTAMP_PROPERTY:
2339 case ICAL_CREATED_PROPERTY:
2340 case ICAL_LASTMODIFIED_PROPERTY:
2341 t = writeICalDateTime(dt.toUtc());
2342 break;
2343 default:
2344 t = writeICalDateTime(dt);
2345 break;
2346 }
2347
2348 icalproperty *p;
2349 switch (type) {
2350 case ICAL_DTSTAMP_PROPERTY:
2351 p = icalproperty_new_dtstamp(t);
2352 break;
2353 case ICAL_CREATED_PROPERTY:
2354 p = icalproperty_new_created(t);
2355 break;
2356 case ICAL_LASTMODIFIED_PROPERTY:
2357 p = icalproperty_new_lastmodified(t);
2358 break;
2359 case ICAL_DTSTART_PROPERTY: // start date and time
2360 p = icalproperty_new_dtstart(t);
2361 break;
2362 case ICAL_DTEND_PROPERTY: // end date and time
2363 p = icalproperty_new_dtend(t);
2364 break;
2365 case ICAL_DUE_PROPERTY:
2366 p = icalproperty_new_due(t);
2367 break;
2368 case ICAL_RECURRENCEID_PROPERTY:
2369 p = icalproperty_new_recurrenceid(t);
2370 break;
2371 case ICAL_EXDATE_PROPERTY:
2372 p = icalproperty_new_exdate(t);
2373 break;
2374 case ICAL_X_PROPERTY:
2375 {
2376 p = icalproperty_new_x("");
2377 icaltimetype timeType = writeICalDateTime(dt);
2378 icalvalue *text = icalvalue_new_datetime(timeType);
2379 icalproperty_set_value(p, text);
2380 }
2381 break;
2382 default:
2383 {
2384 icaldatetimeperiodtype tp;
2385 tp.time = t;
2386 tp.period = icalperiodtype_null_period();
2387 switch (type) {
2388 case ICAL_RDATE_PROPERTY:
2389 p = icalproperty_new_rdate(tp);
2390 break;
2391 default:
2392 return 0;
2393 }
2394 }
2395 }
2396
2397 KTimeZone ktz;
2398 if (!t.is_utc) {
2399 ktz = dt.timeZone();
2400 }
2401
2402 if (ktz.isValid()) {
2403 if (tzlist) {
2404 ICalTimeZone tz = tzlist->zone(ktz.name());
2405 if (!tz.isValid()) {
2406 // The time zone isn't in the list of known zones for the calendar
2407 // - add it to the calendar's zone list
2408 ICalTimeZone tznew(ktz);
2409 tzlist->add(tznew);
2410 tz = tznew;
2411 }
2412 if (tzUsedList) {
2413 tzUsedList->add(tz);
2414 }
2415 }
2416 icalproperty_add_parameter(
2417 p, icalparameter_new_tzid(ktz.name().toUtf8()));
2418 }
2419 return p;
2420}
2421
2422KDateTime ICalFormatImpl::readICalDateTime(icalproperty *p,
2423 const icaltimetype &t,
2424 ICalTimeZones *tzlist,
2425 bool utc)
2426{
2427// kDebug();
2428// _dumpIcaltime( t );
2429
2430 KDateTime::Spec timeSpec;
2431 if (t.is_utc || t.zone == icaltimezone_get_utc_timezone()) {
2432 timeSpec = KDateTime::UTC; // the time zone is UTC
2433 utc = false; // no need to convert to UTC
2434 } else {
2435 if (!tzlist) {
2436 utc = true; // should be UTC, but it isn't
2437 }
2438 icalparameter *param =
2439 p ? icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) : 0;
2440 const char *tzid = param ? icalparameter_get_tzid(param) : 0;
2441 if (!tzid) {
2442 timeSpec = KDateTime::ClockTime;
2443 } else {
2444 QString tzidStr = QString::fromUtf8(tzid);
2445 ICalTimeZone tz;
2446 if (tzlist) {
2447 tz = tzlist->zone(tzidStr);
2448 }
2449 if (!tz.isValid()) {
2450 // The time zone is not in the existing list for the calendar.
2451 // Try to read it from the system or libical databases.
2452 ICalTimeZoneSource tzsource;
2453 ICalTimeZone newtz = tzsource.standardZone(tzidStr);
2454 if (newtz.isValid() && tzlist) {
2455 tzlist->add(newtz);
2456 }
2457 tz = newtz;
2458 }
2459 timeSpec = tz.isValid() ? KDateTime::Spec(tz) : KDateTime::LocalZone;
2460 }
2461 }
2462 KDateTime result;
2463 if (t.is_date) {
2464 result = KDateTime(QDate(t.year, t.month, t.day), timeSpec);
2465 } else {
2466 result = KDateTime(QDate(t.year, t.month, t.day),
2467 QTime(t.hour, t.minute, t.second), timeSpec);
2468 }
2469 return utc ? result.toUtc() : result;
2470}
2471
2472QDate ICalFormatImpl::readICalDate(icaltimetype t)
2473{
2474 return QDate(t.year, t.month, t.day);
2475}
2476
2477KDateTime ICalFormatImpl::readICalDateTimeProperty(icalproperty *p,
2478 ICalTimeZones *tzlist,
2479 bool utc)
2480{
2481 icaldatetimeperiodtype tp;
2482 icalproperty_kind kind = icalproperty_isa(p);
2483 switch (kind) {
2484 case ICAL_CREATED_PROPERTY: // UTC date/time
2485 tp.time = icalproperty_get_created(p);
2486 utc = true;
2487 break;
2488 case ICAL_DTSTAMP_PROPERTY: // UTC date/time
2489 tp.time = icalproperty_get_dtstamp(p);
2490 utc = true;
2491 break;
2492 case ICAL_LASTMODIFIED_PROPERTY: // last modification UTC date/time
2493 tp.time = icalproperty_get_lastmodified(p);
2494 utc = true;
2495 break;
2496 case ICAL_DTSTART_PROPERTY: // start date and time (UTC for freebusy)
2497 tp.time = icalproperty_get_dtstart(p);
2498 break;
2499 case ICAL_DTEND_PROPERTY: // end date and time (UTC for freebusy)
2500 tp.time = icalproperty_get_dtend(p);
2501 break;
2502 case ICAL_DUE_PROPERTY: // due date/time
2503 tp.time = icalproperty_get_due(p);
2504 break;
2505 case ICAL_COMPLETED_PROPERTY: // UTC completion date/time
2506 tp.time = icalproperty_get_completed(p);
2507 utc = true;
2508 break;
2509 case ICAL_RECURRENCEID_PROPERTY:
2510 tp.time = icalproperty_get_recurrenceid(p);
2511 break;
2512 case ICAL_EXDATE_PROPERTY:
2513 tp.time = icalproperty_get_exdate(p);
2514 break;
2515 case ICAL_X_PROPERTY:
2516 {
2517 const char *name = icalproperty_get_x_name(p);
2518 if (QLatin1String(name) == QLatin1String("X-KDE-LIBKCAL-DTRECURRENCE")) {
2519 const char *value = icalvalue_as_ical_string(icalproperty_get_value(p));
2520 icalvalue *v = icalvalue_new_from_string(ICAL_DATETIME_VALUE, value);
2521 tp.time = icalvalue_get_datetime(v);
2522 icalvalue_free(v);
2523 break;
2524 }
2525 }
2526 default:
2527 switch (kind) {
2528 case ICAL_RDATE_PROPERTY:
2529 tp = icalproperty_get_rdate(p);
2530 break;
2531 default:
2532 return KDateTime();
2533 }
2534 if (!icaltime_is_valid_time(tp.time)) {
2535 return KDateTime(); // a time period was found (not implemented yet)
2536 }
2537 break;
2538 }
2539 if (tp.time.is_date) {
2540 return KDateTime(readICalDate(tp.time), KDateTime::Spec::ClockTime());
2541 } else {
2542 return readICalDateTime(p, tp.time, tzlist, utc);
2543 }
2544}
2545
2546icaldurationtype ICalFormatImpl::writeICalDuration(const Duration &duration)
2547{
2548 // should be able to use icaldurationtype_from_int(), except we know
2549 // that some older tools do not properly support weeks. So we never
2550 // set a week duration, only days
2551
2552 icaldurationtype d;
2553
2554 int value = duration.value();
2555 d.is_neg = (value < 0) ? 1 : 0;
2556 if (value < 0) {
2557 value = -value;
2558 }
2559 // RFC2445 states that an ical duration value must be
2560 // EITHER weeks OR days/time, not both.
2561 if (duration.isDaily()) {
2562 if (!(value % 7)) {
2563 d.weeks = value / 7;
2564 d.days = 0;
2565 } else {
2566 d.weeks = 0;
2567 d.days = value;
2568 }
2569 d.hours = d.minutes = d.seconds = 0;
2570 } else {
2571 if (!(value % gSecondsPerWeek)) {
2572 d.weeks = value / gSecondsPerWeek;
2573 d.days = d.hours = d.minutes = d.seconds = 0;
2574 } else {
2575 d.weeks = 0;
2576 d.days = value / gSecondsPerDay;
2577 value %= gSecondsPerDay;
2578 d.hours = value / gSecondsPerHour;
2579 value %= gSecondsPerHour;
2580 d.minutes = value / gSecondsPerMinute;
2581 value %= gSecondsPerMinute;
2582 d.seconds = value;
2583 }
2584 }
2585
2586 return d;
2587}
2588
2589Duration ICalFormatImpl::readICalDuration(icaldurationtype d)
2590{
2591 int days = d.weeks * 7;
2592 days += d.days;
2593 int seconds = d.hours * gSecondsPerHour;
2594 seconds += d.minutes * gSecondsPerMinute;
2595 seconds += d.seconds;
2596 if (seconds) {
2597 seconds += days * gSecondsPerDay;
2598 if (d.is_neg) {
2599 seconds = -seconds;
2600 }
2601 return Duration(seconds, Duration::Seconds);
2602 } else {
2603 if (d.is_neg) {
2604 days = -days;
2605 }
2606 return Duration(days, Duration::Days);
2607 }
2608}
2609
2610icalcomponent *ICalFormatImpl::createCalendarComponent(const Calendar::Ptr &cal)
2611{
2612 icalcomponent *calendar;
2613
2614 // Root component
2615 calendar = icalcomponent_new(ICAL_VCALENDAR_COMPONENT);
2616
2617 icalproperty *p;
2618
2619 // Product Identifier
2620 p = icalproperty_new_prodid(CalFormat::productId().toUtf8());
2621 icalcomponent_add_property(calendar, p);
2622
2623 // iCalendar version (2.0)
2624 p = icalproperty_new_version(const_cast<char *>(_ICAL_VERSION));
2625 icalcomponent_add_property(calendar, p);
2626
2627 // Implementation Version
2628 p = icalproperty_new_x(_ICAL_IMPLEMENTATION_VERSION);
2629 icalproperty_set_x_name(p, IMPLEMENTATION_VERSION_XPROPERTY);
2630 icalcomponent_add_property(calendar, p);
2631
2632 // Add time zone
2633 // NOTE: Commented out since relevant timezones are added by the caller.
2634 // Previously we got some timezones listed twice in the ical file.
2635 /*
2636 if ( cal && cal->timeZones() ) {
2637 const ICalTimeZones::ZoneMap zmaps = cal->timeZones()->zones();
2638 for ( ICalTimeZones::ZoneMap::ConstIterator it=zmaps.constBegin();
2639 it != zmaps.constEnd(); ++it ) {
2640 icaltimezone *icaltz = (*it).icalTimezone();
2641 if ( !icaltz ) {
2642 kError() << "bad time zone";
2643 } else {
2644 icalcomponent *tz = icalcomponent_new_clone( icaltimezone_get_component( icaltz ) );
2645 icalcomponent_add_component( calendar, tz );
2646 icaltimezone_free( icaltz, 1 );
2647 }
2648 }
2649 }
2650 */
2651 // Custom properties
2652 if (cal != 0) {
2653 d->writeCustomProperties(calendar, cal.data());
2654 }
2655
2656 return calendar;
2657}
2658
2659// take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc.
2660// and break it down from its tree-like format into the dictionary format
2661// that is used internally in the ICalFormatImpl.
2662bool ICalFormatImpl::populate(const Calendar::Ptr &cal, icalcomponent *calendar,
2663 bool deleted, const QString &notebook)
2664{
2665 Q_UNUSED(notebook);
2666
2667 // kDebug()<<"Populate called";
2668
2669 // this function will populate the caldict dictionary and other event
2670 // lists. It turns vevents into Events and then inserts them.
2671
2672 if (!calendar) {
2673 kWarning() << "Populate called with empty calendar";
2674 return false;
2675 }
2676
2677// TODO: check for METHOD
2678
2679 icalproperty *p;
2680
2681 p = icalcomponent_get_first_property(calendar, ICAL_X_PROPERTY);
2682 QString implementationVersion;
2683
2684 while (p) {
2685 const char *name = icalproperty_get_x_name(p);
2686 QByteArray nproperty(name);
2687 if (nproperty == QByteArray(IMPLEMENTATION_VERSION_XPROPERTY)) {
2688 QString nvalue = QString::fromUtf8(icalproperty_get_x(p));
2689 if (nvalue.isEmpty()) {
2690 icalvalue *value = icalproperty_get_value(p);
2691 if (icalvalue_isa(value) == ICAL_TEXT_VALUE) {
2692 nvalue = QString::fromUtf8(icalvalue_get_text(value));
2693 }
2694 }
2695 implementationVersion = nvalue;
2696 icalcomponent_remove_property(calendar, p);
2697 icalproperty_free(p);
2698 }
2699 p = icalcomponent_get_next_property(calendar, ICAL_X_PROPERTY);
2700 }
2701
2702 p = icalcomponent_get_first_property(calendar, ICAL_PRODID_PROPERTY);
2703 if (!p) {
2704 kDebug() << "No PRODID property found";
2705 d->mLoadedProductId = QLatin1String("");
2706 } else {
2707 d->mLoadedProductId = QString::fromUtf8(icalproperty_get_prodid(p));
2708
2709 delete d->mCompat;
2710 d->mCompat = CompatFactory::createCompat(d->mLoadedProductId, implementationVersion);
2711 }
2712
2713 p = icalcomponent_get_first_property(calendar, ICAL_VERSION_PROPERTY);
2714 if (!p) {
2715 kDebug() << "No VERSION property found";
2716 d->mParent->setException(new Exception(Exception::CalVersionUnknown));
2717 return false;
2718 } else {
2719 const char *version = icalproperty_get_version(p);
2720 if (!version) {
2721 kDebug() << "No VERSION property found";
2722 d->mParent->setException(new Exception(Exception::VersionPropertyMissing));
2723
2724 return false;
2725 }
2726 if (strcmp(version, "1.0") == 0) {
2727 kDebug() << "Expected iCalendar, got vCalendar";
2728 d->mParent->setException(new Exception(Exception::CalVersion1));
2729 return false;
2730 } else if (strcmp(version, "2.0") != 0) {
2731 kDebug() << "Expected iCalendar, got unknown format";
2732 d->mParent->setException(new Exception(
2733 Exception::CalVersionUnknown));
2734 return false;
2735 }
2736 }
2737
2738 // Populate the calendar's time zone collection with all VTIMEZONE components
2739 ICalTimeZones *tzlist = cal->timeZones();
2740 ICalTimeZoneSource tzs;
2741 tzs.parse(calendar, *tzlist);
2742
2743 // custom properties
2744 d->readCustomProperties(calendar, cal.data());
2745
2746 // Store all events with a relatedTo property in a list for post-processing
2747 d->mEventsRelate.clear();
2748 d->mTodosRelate.clear();
2749 // TODO: make sure that only actually added events go to this lists.
2750
2751 icalcomponent *c;
2752
2753 c = icalcomponent_get_first_component(calendar, ICAL_VTODO_COMPONENT);
2754 while (c) {
2755 Todo::Ptr todo = readTodo(c, tzlist);
2756 if (todo) {
2757 // kDebug() << "todo is not zero and deleted is " << deleted;
2758 Todo::Ptr old = cal->todo(todo->uid(), todo->recurrenceId());
2759 if (old) {
2760 if (old->uid().isEmpty()) {
2761 kWarning() << "Skipping invalid VTODO";
2762 c = icalcomponent_get_next_component(calendar, ICAL_VTODO_COMPONENT);
2763 continue;
2764 }
2765 // kDebug() << "Found an old todo with uid " << old->uid();
2766 if (deleted) {
2767 // kDebug() << "Todo " << todo->uid() << " already deleted";
2768 cal->deleteTodo(old); // move old to deleted
2769 removeAllICal(d->mTodosRelate, old);
2770 } else if (todo->revision() > old->revision()) {
2771 // kDebug() << "Replacing old todo " << old.data() << " with this one " << todo.data();
2772 cal->deleteTodo(old); // move old to deleted
2773 removeAllICal(d->mTodosRelate, old);
2774 cal->addTodo(todo); // and replace it with this one
2775 }
2776 } else if (deleted) {
2777 // kDebug() << "Todo " << todo->uid() << " already deleted";
2778 old = cal->deletedTodo(todo->uid(), todo->recurrenceId());
2779 if (!old) {
2780 cal->addTodo(todo); // add this one
2781 cal->deleteTodo(todo); // and move it to deleted
2782 }
2783 } else {
2784 // kDebug() << "Adding todo " << todo.data() << todo->uid();
2785 cal->addTodo(todo); // just add this one
2786 }
2787 }
2788 c = icalcomponent_get_next_component(calendar, ICAL_VTODO_COMPONENT);
2789 }
2790
2791 // Iterate through all events
2792 c = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT);
2793 while (c) {
2794 Event::Ptr event = readEvent(c, tzlist);
2795 if (event) {
2796 // kDebug() << "event is not zero and deleted is " << deleted;
2797 Event::Ptr old = cal->event(event->uid(), event->recurrenceId());
2798 if (old) {
2799 if (old->uid().isEmpty()) {
2800 kWarning() << "Skipping invalid VEVENT";
2801 c = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT);
2802 continue;
2803 }
2804 // kDebug() << "Found an old event with uid " << old->uid();
2805 if (deleted) {
2806 // kDebug() << "Event " << event->uid() << " already deleted";
2807 cal->deleteEvent(old); // move old to deleted
2808 removeAllICal(d->mEventsRelate, old);
2809 } else if (event->revision() > old->revision()) {
2810 // kDebug() << "Replacing old event " << old.data() << " with this one " << event.data();
2811 cal->deleteEvent(old); // move old to deleted
2812 removeAllICal(d->mEventsRelate, old);
2813 cal->addEvent(event); // and replace it with this one
2814 }
2815 } else if (deleted) {
2816 // kDebug() << "Event " << event->uid() << " already deleted";
2817 old = cal->deletedEvent(event->uid(), event->recurrenceId());
2818 if (!old) {
2819 cal->addEvent(event); // add this one
2820 cal->deleteEvent(event); // and move it to deleted
2821 }
2822 } else {
2823 // kDebug() << "Adding event " << event.data() << event->uid();
2824 cal->addEvent(event); // just add this one
2825 }
2826 }
2827 c = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT);
2828 }
2829
2830 // Iterate through all journals
2831 c = icalcomponent_get_first_component(calendar, ICAL_VJOURNAL_COMPONENT);
2832 while (c) {
2833 Journal::Ptr journal = readJournal(c, tzlist);
2834 if (journal) {
2835 Journal::Ptr old = cal->journal(journal->uid(), journal->recurrenceId());
2836 if (old) {
2837 if (deleted) {
2838 cal->deleteJournal(old); // move old to deleted
2839 } else if (journal->revision() > old->revision()) {
2840 cal->deleteJournal(old); // move old to deleted
2841 cal->addJournal(journal); // and replace it with this one
2842 }
2843 } else if (deleted) {
2844 old = cal->deletedJournal(journal->uid(), journal->recurrenceId());
2845 if (!old) {
2846 cal->addJournal(journal); // add this one
2847 cal->deleteJournal(journal); // and move it to deleted
2848 }
2849 } else {
2850 cal->addJournal(journal); // just add this one
2851 }
2852 }
2853 c = icalcomponent_get_next_component(calendar, ICAL_VJOURNAL_COMPONENT);
2854 }
2855
2856 // TODO: Remove any previous time zones no longer referenced in the calendar
2857
2858 return true;
2859}
2860
2861QString ICalFormatImpl::extractErrorProperty(icalcomponent *c)
2862{
2863 QString errorMessage;
2864
2865 icalproperty *error;
2866 error = icalcomponent_get_first_property(c, ICAL_XLICERROR_PROPERTY);
2867 while (error) {
2868 errorMessage += QLatin1String(icalproperty_get_xlicerror(error));
2869 errorMessage += QLatin1Char('\n');
2870 error = icalcomponent_get_next_property(c, ICAL_XLICERROR_PROPERTY);
2871 }
2872
2873 return errorMessage;
2874}
2875
2876/*
2877void ICalFormatImpl::dumpIcalRecurrence( const icalrecurrencetype &r )
2878{
2879 int i;
2880
2881 kDebug() << " Freq:" << int( r.freq );
2882 kDebug() << " Until:" << icaltime_as_ical_string( r.until );
2883 kDebug() << " Count:" << r.count;
2884 if ( r.by_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2885 int index = 0;
2886 QString out = " By Day: ";
2887 while ( ( i = r.by_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2888 out.append( QString::number( i ) + ' ' );
2889 }
2890 kDebug() << out;
2891 }
2892 if ( r.by_month_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2893 int index = 0;
2894 QString out = " By Month Day: ";
2895 while ( ( i = r.by_month_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2896 out.append( QString::number( i ) + ' ' );
2897 }
2898 kDebug() << out;
2899 }
2900 if ( r.by_year_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2901 int index = 0;
2902 QString out = " By Year Day: ";
2903 while ( ( i = r.by_year_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2904 out.append( QString::number( i ) + ' ' );
2905 }
2906 kDebug() << out;
2907 }
2908 if ( r.by_month[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2909 int index = 0;
2910 QString out = " By Month: ";
2911 while ( ( i = r.by_month[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2912 out.append( QString::number( i ) + ' ' );
2913 }
2914 kDebug() << out;
2915 }
2916 if ( r.by_set_pos[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
2917 int index = 0;
2918 QString out = " By Set Pos: ";
2919 while ( ( i = r.by_set_pos[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) {
2920 kDebug() << "=========" << i;
2921 out.append( QString::number( i ) + ' ' );
2922 }
2923 kDebug() << out;
2924 }
2925}
2926*/
2927
2928icalcomponent *ICalFormatImpl::createScheduleComponent(const IncidenceBase::Ptr &incidence,
2929 iTIPMethod method)
2930{
2931 icalcomponent *message = createCalendarComponent();
2932
2933 // Create VTIMEZONE components for this incidence
2934 ICalTimeZones zones;
2935 if (incidence) {
2936 const KDateTime kd1 = incidence->dateTime(IncidenceBase::RoleStartTimeZone);
2937 const KDateTime kd2 = incidence->dateTime(IncidenceBase::RoleEndTimeZone);
2938
2939 if (kd1.isValid() && kd1.timeZone() != KTimeZone::utc()) {
2940 zones.add(ICalTimeZone(kd1.timeZone()));
2941 }
2942
2943 if (kd2.isValid() && kd2.timeZone() != KTimeZone::utc()) {
2944 zones.add(ICalTimeZone(kd2.timeZone()));
2945 }
2946
2947 const ICalTimeZones::ZoneMap zmaps = zones.zones();
2948 for (ICalTimeZones::ZoneMap::ConstIterator it=zmaps.constBegin();
2949 it != zmaps.constEnd(); ++it) {
2950 icaltimezone *icaltz = (*it).icalTimezone();
2951 if (!icaltz) {
2952 kError() << "bad time zone";
2953 } else {
2954 icalcomponent *tz = icalcomponent_new_clone(icaltimezone_get_component(icaltz));
2955 icalcomponent_add_component(message, tz);
2956 icaltimezone_free(icaltz, 1);
2957 }
2958 }
2959 } else {
2960 kDebug() << "No incidence";
2961 return message;
2962 }
2963
2964 icalproperty_method icalmethod = ICAL_METHOD_NONE;
2965
2966 switch (method) {
2967 case iTIPPublish:
2968 icalmethod = ICAL_METHOD_PUBLISH;
2969 break;
2970 case iTIPRequest:
2971 icalmethod = ICAL_METHOD_REQUEST;
2972 break;
2973 case iTIPRefresh:
2974 icalmethod = ICAL_METHOD_REFRESH;
2975 break;
2976 case iTIPCancel:
2977 icalmethod = ICAL_METHOD_CANCEL;
2978 break;
2979 case iTIPAdd:
2980 icalmethod = ICAL_METHOD_ADD;
2981 break;
2982 case iTIPReply:
2983 icalmethod = ICAL_METHOD_REPLY;
2984 break;
2985 case iTIPCounter:
2986 icalmethod = ICAL_METHOD_COUNTER;
2987 break;
2988 case iTIPDeclineCounter:
2989 icalmethod = ICAL_METHOD_DECLINECOUNTER;
2990 break;
2991 default:
2992 kDebug() << "Unknown method";
2993 return message;
2994 }
2995
2996 icalcomponent_add_property(message, icalproperty_new_method(icalmethod));
2997
2998 icalcomponent *inc = writeIncidence(incidence, method);
2999
3000 if (method != KCalCore::iTIPNoMethod) {
3001 //Not very nice, but since dtstamp changes semantics if used in scheduling, we have to adapt
3002 icalcomponent_set_dtstamp(
3003 inc, writeICalUtcDateTime(KDateTime::currentUtcDateTime()));
3004 }
3005
3006 /*
3007 * RFC 2446 states in section 3.4.3 ( REPLY to a VTODO ), that
3008 * a REQUEST-STATUS property has to be present. For the other two, event and
3009 * free busy, it can be there, but is optional. Until we do more
3010 * fine grained handling, assume all is well. Note that this is the
3011 * status of the _request_, not the attendee. Just to avoid confusion.
3012 * - till
3013 */
3014 if (icalmethod == ICAL_METHOD_REPLY) {
3015 struct icalreqstattype rst;
3016 rst.code = ICAL_2_0_SUCCESS_STATUS;
3017 rst.desc = 0;
3018 rst.debug = 0;
3019 icalcomponent_add_property(inc, icalproperty_new_requeststatus(rst));
3020 }
3021 icalcomponent_add_component(message, inc);
3022
3023 return message;
3024}
3025