1/*
2 This file is part of the kcalcore library.
3
4 Copyright (c) 1998 Preston Brown <pbrown@kde.org>
5 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22/**
23 @file
24 This file is part of the API for handling calendar data and
25 defines the VCalFormat base class.
26
27 This class implements the vCalendar format. It provides methods for
28 loading/saving/converting vCalendar format data into the internal
29 representation as Calendar and Incidences.
30
31 @brief
32 vCalendar format implementation.
33
34 @author Preston Brown \<pbrown@kde.org\>
35 @author Cornelius Schumacher \<schumacher@kde.org\>
36*/
37#include "vcalformat.h"
38#include "calendar.h"
39#include "event.h"
40#include "exceptions.h"
41#include "icaltimezones.h"
42#include "todo.h"
43#include "versit/vcc.h"
44#include "versit/vobject.h"
45
46#include <KCodecs>
47#include <KDebug>
48
49#include <QtCore/QBitArray>
50#include <QtCore/QFile>
51#include <QTextDocument> // for Qt::escape() and Qt::mightBeRichText()
52
53using namespace KCalCore;
54
55/**
56 Private class that helps to provide binary compatibility between releases.
57 @internal
58*/
59//@cond PRIVATE
60template <typename K>
61void removeAllVCal(QVector< QSharedPointer<K> > &c, const QSharedPointer<K> &x)
62{
63 if (c.count() < 1) {
64 return;
65 }
66
67 int cnt = c.count(x);
68 if (cnt != 1) {
69 qCritical() << "There number of relatedTos for this incidence is "
70 << cnt << " (there must be 1 relatedTo only)";
71 Q_ASSERT_X(false, "removeAllVCal", "Count is not 1.");
72 return;
73 }
74
75 c.remove(c.indexOf(x));
76}
77
78class KCalCore::VCalFormat::Private
79{
80public:
81 Calendar::Ptr mCalendar;
82 Event::List mEventsRelate; // Events with relations
83 Todo::List mTodosRelate; // To-dos with relations
84 QSet<QByteArray> mManuallyWrittenExtensionFields; // X- fields that are manually dumped
85};
86//@endcond
87
88VCalFormat::VCalFormat() : d(new KCalCore::VCalFormat::Private)
89{
90#if defined(KCALCORE_FOR_SYMBIAN)
91 d->mManuallyWrittenExtensionFields << VCRecurrenceIdProp;
92 d->mManuallyWrittenExtensionFields << EPOCAgendaEntryTypeProp;
93#endif
94 d->mManuallyWrittenExtensionFields << KPilotIdProp;
95 d->mManuallyWrittenExtensionFields << KPilotStatusProp;
96}
97
98VCalFormat::~VCalFormat()
99{
100 delete d;
101}
102
103bool VCalFormat::load(const Calendar::Ptr &calendar, const QString &fileName)
104{
105 d->mCalendar = calendar;
106
107 clearException();
108
109 VObject *vcal = 0;
110
111 // this is not necessarily only 1 vcal. Could be many vcals, or include
112 // a vcard...
113 vcal = Parse_MIME_FromFileName(const_cast<char *>(QFile::encodeName(fileName).data()));
114
115 if (!vcal) {
116 setException(new Exception(Exception::CalVersionUnknown));
117 return false;
118 }
119
120 // any other top-level calendar stuff should be added/initialized here
121
122 // put all vobjects into their proper places
123 QString savedTimeZoneId = d->mCalendar->timeZoneId();
124 populate(vcal, false, fileName);
125 d->mCalendar->setTimeZoneId(savedTimeZoneId);
126
127 // clean up from vcal API stuff
128 cleanVObjects(vcal);
129 cleanStrTbl();
130
131 return true;
132}
133
134bool VCalFormat::save(const Calendar::Ptr &calendar, const QString &fileName)
135{
136 d->mCalendar = calendar;
137
138 ICalTimeZones *tzlist = d->mCalendar->timeZones();
139
140 QString tmpStr;
141 VObject *vcal, *vo;
142
143 vcal = newVObject(VCCalProp);
144
145 // addPropValue(vcal,VCLocationProp, "0.0");
146 addPropValue(vcal, VCProdIdProp, productId().toLatin1());
147 addPropValue(vcal, VCVersionProp, _VCAL_VERSION);
148
149 // TODO STUFF
150 Todo::List todoList = d->mCalendar->rawTodos();
151 Todo::List::ConstIterator it;
152 for (it = todoList.constBegin(); it != todoList.constEnd(); ++it) {
153 if ((*it)->dtStart().timeZone().name().mid(0, 4) == QLatin1String("VCAL")) {
154 ICalTimeZone zone = tzlist->zone((*it)->dtStart().timeZone().name());
155 if (zone.isValid()) {
156 QByteArray timezone = zone.vtimezone();
157 addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toLocal8Bit());
158 QString dst = parseDst(timezone);
159 while (!dst.isEmpty()) {
160 addPropValue(vcal, VCDayLightProp, dst.toLocal8Bit());
161 dst = parseDst(timezone);
162 }
163 }
164 }
165 vo = eventToVTodo(*it);
166 addVObjectProp(vcal, vo);
167 }
168 // EVENT STUFF
169 Event::List events = d->mCalendar->rawEvents();
170 Event::List::ConstIterator it2;
171 for (it2 = events.constBegin(); it2 != events.constEnd(); ++it2) {
172 if ((*it2)->dtStart().timeZone().name().mid(0, 4) == QLatin1String("VCAL")) {
173 ICalTimeZone zone = tzlist->zone((*it2)->dtStart().timeZone().name());
174 if (zone.isValid()) {
175 QByteArray timezone = zone.vtimezone();
176 addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toLocal8Bit());
177 QString dst = parseDst(timezone);
178 while (!dst.isEmpty()) {
179 addPropValue(vcal, VCDayLightProp, dst.toLocal8Bit());
180 dst = parseDst(timezone);
181 }
182 }
183 }
184 vo = eventToVEvent(*it2);
185 addVObjectProp(vcal, vo);
186 }
187 writeVObjectToFile(QFile::encodeName(fileName).data(), vcal);
188 cleanVObjects(vcal);
189 cleanStrTbl();
190
191 if (QFile::exists(fileName)) {
192 return true;
193 } else {
194 return false; // error
195 }
196
197 return false;
198}
199
200bool VCalFormat::fromString(const Calendar::Ptr &calendar, const QString &string,
201 bool deleted, const QString &notebook)
202{
203 return fromRawString(calendar, string.toUtf8(), deleted, notebook);
204}
205
206bool VCalFormat::fromRawString(const Calendar::Ptr &calendar, const QByteArray &string,
207 bool deleted, const QString &notebook)
208{
209 d->mCalendar = calendar;
210
211 if (!string.size()) {
212 return false;
213 }
214
215 VObject *vcal = Parse_MIME(string.data(), string.size());
216 if (!vcal) {
217 return false;
218 }
219
220 VObjectIterator i;
221 initPropIterator(&i, vcal);
222
223 // put all vobjects into their proper places
224 QString savedTimeZoneId = d->mCalendar->timeZoneId();
225 populate(vcal, deleted, notebook);
226 d->mCalendar->setTimeZoneId(savedTimeZoneId);
227
228 // clean up from vcal API stuff
229 cleanVObjects(vcal);
230 cleanStrTbl();
231
232 return true;
233}
234
235QString VCalFormat::toString(const Calendar::Ptr &calendar,
236 const QString &notebook, bool deleted)
237{
238 // TODO: Factor out VCalFormat::asString()
239 d->mCalendar = calendar;
240
241 ICalTimeZones *tzlist = d->mCalendar->timeZones();
242
243 VObject *vo;
244 VObject *vcal = newVObject(VCCalProp);
245
246 addPropValue(vcal, VCProdIdProp, CalFormat::productId().toLatin1());
247 addPropValue(vcal, VCVersionProp, _VCAL_VERSION);
248
249 // TODO STUFF
250 Todo::List todoList = deleted ? d->mCalendar->deletedTodos() : d->mCalendar->rawTodos();
251 Todo::List::ConstIterator it;
252 for (it = todoList.constBegin(); it != todoList.constEnd(); ++it) {
253 if (!deleted || !d->mCalendar->todo((*it)->uid(), (*it)->recurrenceId())) {
254 // use existing ones, or really deleted ones
255 if (notebook.isEmpty() ||
256 (!calendar->notebook(*it).isEmpty() &&
257 notebook.endsWith(calendar->notebook(*it)))) {
258 if ((*it)->dtStart().timeZone().name().mid(0, 4) == QLatin1String("VCAL")) {
259 ICalTimeZone zone = tzlist->zone((*it)->dtStart().timeZone().name());
260 if (zone.isValid()) {
261 QByteArray timezone = zone.vtimezone();
262 addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toUtf8());
263 QString dst = parseDst(timezone);
264 while (!dst.isEmpty()) {
265 addPropValue(vcal, VCDayLightProp, dst.toUtf8());
266 dst = parseDst(timezone);
267 }
268 }
269 }
270 vo = eventToVTodo(*it);
271 addVObjectProp(vcal, vo);
272 }
273 }
274 }
275
276 // EVENT STUFF
277 Event::List events = deleted ? d->mCalendar->deletedEvents() : d->mCalendar->rawEvents();
278 Event::List::ConstIterator it2;
279 for (it2 = events.constBegin(); it2 != events.constEnd(); ++it2) {
280 if (!deleted || !d->mCalendar->event((*it2)->uid(), (*it2)->recurrenceId())) {
281 // use existing ones, or really deleted ones
282 if (notebook.isEmpty() ||
283 (!calendar->notebook(*it2).isEmpty() &&
284 notebook.endsWith(calendar->notebook(*it2)))) {
285 if ((*it2)->dtStart().timeZone().name().mid(0, 4) == QLatin1String("VCAL")) {
286 ICalTimeZone zone = tzlist->zone((*it2)->dtStart().timeZone().name());
287 if (zone.isValid()) {
288 QByteArray timezone = zone.vtimezone();
289 addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toUtf8());
290 QString dst = parseDst(timezone);
291 while (!dst.isEmpty()) {
292 addPropValue(vcal, VCDayLightProp, dst.toUtf8());
293 dst = parseDst(timezone);
294 }
295 }
296 }
297 vo = eventToVEvent(*it2);
298 addVObjectProp(vcal, vo);
299 }
300 }
301 }
302
303 char *buf = writeMemVObject(0, 0, vcal);
304
305 QString result(QString::fromUtf8(buf));
306
307 deleteStr(buf);
308
309 cleanVObject(vcal);
310
311 return result;
312}
313
314VObject *VCalFormat::eventToVTodo(const Todo::Ptr &anEvent)
315{
316 VObject *vtodo;
317 QString tmpStr;
318
319 vtodo = newVObject(VCTodoProp);
320
321 // due date
322 if (anEvent->hasDueDate()) {
323 tmpStr = kDateTimeToISO(anEvent->dtDue(), !anEvent->allDay());
324 addPropValue(vtodo, VCDueProp, tmpStr.toUtf8());
325 }
326
327 // start date
328 if (anEvent->hasStartDate()) {
329 tmpStr = kDateTimeToISO(anEvent->dtStart(), !anEvent->allDay());
330 addPropValue(vtodo, VCDTstartProp, tmpStr.toUtf8());
331 }
332
333 // creation date
334 tmpStr = kDateTimeToISO(anEvent->created());
335 addPropValue(vtodo, VCDCreatedProp, tmpStr.toUtf8());
336
337 // unique id
338 addPropValue(vtodo, VCUniqueStringProp,
339 anEvent->uid().toUtf8());
340
341 // revision
342 tmpStr.sprintf("%i", anEvent->revision());
343 addPropValue(vtodo, VCSequenceProp, tmpStr.toUtf8());
344
345 // last modification date
346 tmpStr = kDateTimeToISO(anEvent->lastModified());
347 addPropValue(vtodo, VCLastModifiedProp, tmpStr.toUtf8());
348
349 // organizer stuff
350 // @TODO: How about the common name?
351 if (!anEvent->organizer()->email().isEmpty()) {
352 tmpStr = "MAILTO:" + anEvent->organizer()->email();
353 addPropValue(vtodo, ICOrganizerProp, tmpStr.toUtf8());
354 }
355
356 // attendees
357 if (anEvent->attendeeCount() > 0) {
358 Attendee::List::ConstIterator it;
359 Attendee::Ptr curAttendee;
360 for (it = anEvent->attendees().constBegin(); it != anEvent->attendees().constEnd();
361 ++it) {
362 curAttendee = *it;
363 if (!curAttendee->email().isEmpty() && !curAttendee->name().isEmpty()) {
364 tmpStr = "MAILTO:" + curAttendee->name() + " <" + curAttendee->email() + '>';
365 } else if (curAttendee->name().isEmpty() && curAttendee->email().isEmpty()) {
366 tmpStr = "MAILTO: ";
367 kDebug() << "warning! this Event has an attendee w/o name or email!";
368 } else if (curAttendee->name().isEmpty()) {
369 tmpStr = "MAILTO: " + curAttendee->email();
370 } else {
371 tmpStr = "MAILTO: " + curAttendee->name();
372 }
373 VObject *aProp = addPropValue(vtodo, VCAttendeeProp, tmpStr.toUtf8());
374 addPropValue(aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE");
375 addPropValue(aProp, VCStatusProp, writeStatus(curAttendee->status()));
376 }
377 }
378
379 // recurrence rule stuff
380 const Recurrence *recur = anEvent->recurrence();
381 if (recur->recurs()) {
382 bool validRecur = true;
383 QString tmpStr2;
384 switch (recur->recurrenceType()) {
385 case Recurrence::rDaily:
386 tmpStr.sprintf("D%i ", recur->frequency());
387 break;
388 case Recurrence::rWeekly:
389 tmpStr.sprintf("W%i ", recur->frequency());
390 for (int i = 0; i < 7; ++i) {
391 QBitArray days(recur->days());
392 if (days.testBit(i)) {
393 tmpStr += dayFromNum(i);
394 }
395 }
396 break;
397 case Recurrence::rMonthlyPos:
398 {
399 tmpStr.sprintf("MP%i ", recur->frequency());
400 // write out all rMonthPos's
401 QList<RecurrenceRule::WDayPos> tmpPositions = recur->monthPositions();
402 for (QList<RecurrenceRule::WDayPos>::ConstIterator posit = tmpPositions.constBegin();
403 posit != tmpPositions.constEnd(); ++posit) {
404 int pos = (*posit).pos();
405 tmpStr2.sprintf("%i", (pos > 0) ? pos : (-pos));
406 if (pos < 0) {
407 tmpStr2 += "- ";
408 } else {
409 tmpStr2 += "+ ";
410 }
411 tmpStr += tmpStr2;
412 tmpStr += dayFromNum((*posit).day() - 1);
413 }
414 break;
415 }
416 case Recurrence::rMonthlyDay:
417 {
418 tmpStr.sprintf("MD%i ", recur->frequency());
419 // write out all rMonthDays;
420 const QList<int> tmpDays = recur->monthDays();
421 for (QList<int>::ConstIterator tmpDay = tmpDays.constBegin();
422 tmpDay != tmpDays.constEnd(); ++tmpDay) {
423 tmpStr2.sprintf("%i ", *tmpDay);
424 tmpStr += tmpStr2;
425 }
426 break;
427 }
428 case Recurrence::rYearlyMonth:
429 {
430 tmpStr.sprintf("YM%i ", recur->frequency());
431 // write out all the months;'
432 // TODO: Any way to write out the day within the month???
433 const QList<int> months = recur->yearMonths();
434 for (QList<int>::ConstIterator mit = months.constBegin();
435 mit != months.constEnd(); ++mit) {
436 tmpStr2.sprintf("%i ", *mit);
437 tmpStr += tmpStr2;
438 }
439 break;
440 }
441 case Recurrence::rYearlyDay:
442 {
443 tmpStr.sprintf("YD%i ", recur->frequency());
444 // write out all the rYearNums;
445 const QList<int> tmpDays = recur->yearDays();
446 for (QList<int>::ConstIterator tmpDay = tmpDays.begin();
447 tmpDay != tmpDays.end(); ++tmpDay) {
448 tmpStr2.sprintf("%i ", *tmpDay);
449 tmpStr += tmpStr2;
450 }
451 break;
452 }
453 default:
454 // TODO: Write rYearlyPos and arbitrary rules!
455 kDebug() << "ERROR, it should never get here in eventToVTodo!";
456 validRecur = false;
457 break;
458 } // switch
459
460 if (recur->duration() > 0) {
461 tmpStr2.sprintf("#%i", recur->duration());
462 tmpStr += tmpStr2;
463 } else if (recur->duration() == -1) {
464 tmpStr += "#0"; // defined as repeat forever
465 } else {
466 tmpStr += kDateTimeToISO(recur->endDateTime(), false);
467 }
468 // Only write out the rrule if we have a valid recurrence (i.e. a known
469 // type in thee switch above)
470 if (validRecur) {
471 addPropValue(vtodo, VCRRuleProp, tmpStr.toUtf8());
472 }
473
474 } // event repeats
475
476 // exceptions dates to recurrence
477 DateList dateList = recur->exDates();
478 DateList::ConstIterator id;
479 QString tmpStr2;
480
481 for (id = dateList.constBegin(); id != dateList.constEnd(); ++id) {
482 tmpStr = qDateToISO(*id) + QLatin1Char(';');
483 tmpStr2 += tmpStr;
484 }
485 if (!tmpStr2.isEmpty()) {
486 tmpStr2.truncate(tmpStr2.length() - 1);
487 addPropValue(vtodo, VCExpDateProp, tmpStr2.toUtf8());
488 }
489 // exceptions datetimes to recurrence
490 DateTimeList dateTimeList = recur->exDateTimes();
491 DateTimeList::ConstIterator idt;
492 tmpStr2.clear();
493
494 for (idt = dateTimeList.constBegin(); idt != dateTimeList.constEnd(); ++idt) {
495 tmpStr = kDateTimeToISO(*idt) + QLatin1Char(';');
496 tmpStr2 += tmpStr;
497 }
498 if (!tmpStr2.isEmpty()) {
499 tmpStr2.truncate(tmpStr2.length() - 1);
500 addPropValue(vtodo, VCExpDateProp, tmpStr2.toUtf8());
501 }
502
503 // description BL:
504 if (!anEvent->description().isEmpty()) {
505 QByteArray in = anEvent->description().toUtf8();
506 QByteArray out;
507 KCodecs::quotedPrintableEncode(in, out, true);
508 if (out != in) {
509 VObject *d = addPropValue(vtodo, VCDescriptionProp, out);
510 addPropValue(d, VCEncodingProp, VCQuotedPrintableProp);
511 addPropValue(d, VCCharSetProp, VCUtf8Prop);
512 } else {
513 addPropValue(vtodo, VCDescriptionProp, in);
514 }
515 }
516
517 // summary
518 if (!anEvent->summary().isEmpty()) {
519 QByteArray in = anEvent->summary().toUtf8();
520 QByteArray out;
521 KCodecs::quotedPrintableEncode(in, out, true);
522 if (out != in) {
523 VObject *d = addPropValue(vtodo, VCSummaryProp, out);
524 addPropValue(d, VCEncodingProp, VCQuotedPrintableProp);
525 addPropValue(d, VCCharSetProp, VCUtf8Prop);
526 } else {
527 addPropValue(vtodo, VCSummaryProp, in);
528 }
529 }
530
531 // location
532 if (!anEvent->location().isEmpty()) {
533 QByteArray in = anEvent->location().toUtf8();
534 QByteArray out;
535 KCodecs::quotedPrintableEncode(in, out, true);
536 if (out != in) {
537 VObject *d = addPropValue(vtodo, VCLocationProp, out);
538 addPropValue(d, VCEncodingProp, VCQuotedPrintableProp);
539 addPropValue(d, VCCharSetProp, VCUtf8Prop);
540 } else {
541 addPropValue(vtodo, VCLocationProp, in);
542 }
543 }
544
545 // completed status
546 // backward compatibility, KOrganizer used to interpret only these two values
547 addPropValue(vtodo, VCStatusProp, anEvent->isCompleted() ? "COMPLETED" : "NEEDS ACTION");
548
549 // completion date
550 if (anEvent->hasCompletedDate()) {
551 tmpStr = kDateTimeToISO(anEvent->completed());
552 addPropValue(vtodo, VCCompletedProp, tmpStr.toUtf8());
553 }
554
555 // priority
556 tmpStr.sprintf("%i", anEvent->priority());
557 addPropValue(vtodo, VCPriorityProp, tmpStr.toUtf8());
558
559 // related event
560 if (!anEvent->relatedTo().isEmpty()) {
561 addPropValue(vtodo, VCRelatedToProp,
562 anEvent->relatedTo().toUtf8());
563 }
564
565 // secrecy
566 const char *text = 0;
567 switch (anEvent->secrecy()) {
568 case Incidence::SecrecyPublic:
569 text = "PUBLIC";
570 break;
571 case Incidence::SecrecyPrivate:
572 text = "PRIVATE";
573 break;
574 case Incidence::SecrecyConfidential:
575 text = "CONFIDENTIAL";
576 break;
577 }
578 if (text) {
579 addPropValue(vtodo, VCClassProp, text);
580 }
581
582 // categories
583 const QStringList tmpStrList = anEvent->categories();
584 tmpStr = "";
585 QString catStr;
586 QStringList::const_iterator its;
587 for (its = tmpStrList.constBegin(); its != tmpStrList.constEnd(); ++its) {
588 catStr = *its;
589 if (catStr[0] == QLatin1Char(' ')) {
590 tmpStr += catStr.mid(1);
591 } else {
592 tmpStr += catStr;
593 }
594 // this must be a ';' character as the vCalendar specification requires!
595 // vcc.y has been hacked to translate the ';' to a ',' when the vcal is
596 // read in.
597 tmpStr += QLatin1Char(';');
598 }
599 if (!tmpStr.isEmpty()) {
600 tmpStr.truncate(tmpStr.length() - 1);
601 addPropValue(vtodo, VCCategoriesProp, tmpStr.toUtf8());
602 }
603
604 // alarm stuff
605 Alarm::List::ConstIterator it;
606 for (it = anEvent->alarms().constBegin(); it != anEvent->alarms().constEnd(); ++it) {
607 Alarm::Ptr alarm = *it;
608 if (alarm->enabled()) {
609 VObject *a;
610 if (alarm->type() == Alarm::Display) {
611 a = addProp(vtodo, VCDAlarmProp);
612 tmpStr = kDateTimeToISO(alarm->time());
613 addPropValue(a, VCRunTimeProp, tmpStr.toUtf8());
614 addPropValue(a, VCRepeatCountProp, "1");
615 if (alarm->text().isNull()) {
616 addPropValue(a, VCDisplayStringProp, "beep!");
617 } else {
618 addPropValue(a, VCDisplayStringProp, alarm->text().toLatin1().data());
619 }
620 } else if (alarm->type() == Alarm::Audio) {
621 a = addProp(vtodo, VCAAlarmProp);
622 tmpStr = kDateTimeToISO(alarm->time());
623 addPropValue(a, VCRunTimeProp, tmpStr.toUtf8());
624 addPropValue(a, VCRepeatCountProp, "1");
625 addPropValue(a, VCAudioContentProp, QFile::encodeName(alarm->audioFile()));
626 } else if (alarm->type() == Alarm::Procedure) {
627 a = addProp(vtodo, VCPAlarmProp);
628 tmpStr = kDateTimeToISO(alarm->time());
629 addPropValue(a, VCRunTimeProp, tmpStr.toUtf8());
630 addPropValue(a, VCRepeatCountProp, "1");
631 addPropValue(a, VCProcedureNameProp, QFile::encodeName(alarm->programFile()));
632 }
633 }
634 }
635
636 QString pilotId = anEvent->nonKDECustomProperty(KPilotIdProp);
637 if (!pilotId.isEmpty()) {
638 // pilot sync stuff
639 addPropValue(vtodo, KPilotIdProp, pilotId.toUtf8());
640 addPropValue(vtodo, KPilotStatusProp,
641 anEvent->nonKDECustomProperty(KPilotStatusProp).toUtf8());
642 }
643#if defined(KCALCORE_FOR_SYMBIAN)
644 if (anEvent->nonKDECustomProperty(EPOCAgendaEntryTypeProp).isEmpty()) {
645 // Propagate braindeath by setting this property also so that
646 // S60 is happy
647 addPropValue(vtodo, EPOCAgendaEntryTypeProp, "TODO");
648 }
649
650 writeCustomProperties(vtodo, anEvent);
651#endif
652
653 return vtodo;
654}
655
656VObject *VCalFormat::eventToVEvent(const Event::Ptr &anEvent)
657{
658 VObject *vevent;
659 QString tmpStr;
660
661 vevent = newVObject(VCEventProp);
662
663 // start and end time
664 tmpStr = kDateTimeToISO(anEvent->dtStart(), !anEvent->allDay());
665 addPropValue(vevent, VCDTstartProp, tmpStr.toUtf8());
666
667#if !defined(KCALCORE_FOR_MEEGO)
668 // events that have time associated but take up no time should
669 // not have both DTSTART and DTEND.
670 if (anEvent->dtStart() != anEvent->dtEnd()) {
671 tmpStr = kDateTimeToISO(anEvent->dtEnd(), !anEvent->allDay());
672 addPropValue(vevent, VCDTendProp, tmpStr.toUtf8());
673 }
674#else
675 // N900 and s60-phones need enddate
676 tmpStr = kDateTimeToISO(anEvent->dtEnd(), !anEvent->allDay());
677 addPropValue(vevent, VCDTendProp, tmpStr.toUtf8());
678#endif
679
680 // creation date
681 tmpStr = kDateTimeToISO(anEvent->created());
682 addPropValue(vevent, VCDCreatedProp, tmpStr.toUtf8());
683
684 // unique id
685 addPropValue(vevent, VCUniqueStringProp,
686 anEvent->uid().toUtf8());
687
688 // revision
689 tmpStr.sprintf("%i", anEvent->revision());
690 addPropValue(vevent, VCSequenceProp, tmpStr.toUtf8());
691
692 // last modification date
693 tmpStr = kDateTimeToISO(anEvent->lastModified());
694 addPropValue(vevent, VCLastModifiedProp, tmpStr.toUtf8());
695
696 // attendee and organizer stuff
697 // TODO: What to do with the common name?
698 if (!anEvent->organizer()->email().isEmpty()) {
699 tmpStr = QLatin1String("MAILTO:") + anEvent->organizer()->email();
700 addPropValue(vevent, ICOrganizerProp, tmpStr.toUtf8());
701 }
702
703 // TODO: Put this functionality into Attendee class
704 if (anEvent->attendeeCount() > 0) {
705 Attendee::List::ConstIterator it;
706 for (it = anEvent->attendees().constBegin(); it != anEvent->attendees().constEnd();
707 ++it) {
708 Attendee::Ptr curAttendee = *it;
709 if (!curAttendee->email().isEmpty() && !curAttendee->name().isEmpty()) {
710 tmpStr = QLatin1String("MAILTO:") + curAttendee->name() + QLatin1String(" <") + curAttendee->email() + QLatin1Char('>');
711 } else if (curAttendee->name().isEmpty() && curAttendee->email().isEmpty()) {
712 tmpStr = QLatin1String("MAILTO: ");
713 kDebug() << "warning! this Event has an attendee w/o name or email!";
714 } else if (curAttendee->name().isEmpty()) {
715 tmpStr = QLatin1String("MAILTO: ") + curAttendee->email();
716 } else {
717 tmpStr = QLatin1String("MAILTO: ") + curAttendee->name();
718 }
719 VObject *aProp = addPropValue(vevent, VCAttendeeProp, tmpStr.toUtf8());
720 addPropValue(aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE");
721 addPropValue(aProp, VCStatusProp, writeStatus(curAttendee->status()));
722 }
723 }
724
725 // recurrence rule stuff
726 const Recurrence *recur = anEvent->recurrence();
727 if (recur->recurs()) {
728 bool validRecur = true;
729 QString tmpStr2;
730 switch (recur->recurrenceType()) {
731 case Recurrence::rDaily:
732 tmpStr.sprintf("D%i ", recur->frequency());
733 break;
734 case Recurrence::rWeekly:
735 tmpStr.sprintf("W%i ", recur->frequency());
736 for (int i = 0; i < 7; ++i) {
737 QBitArray days(recur->days());
738 if (days.testBit(i)) {
739 tmpStr += dayFromNum(i);
740 }
741 }
742 break;
743 case Recurrence::rMonthlyPos:
744 {
745 tmpStr.sprintf("MP%i ", recur->frequency());
746 // write out all rMonthPos's
747 QList<RecurrenceRule::WDayPos> tmpPositions = recur->monthPositions();
748 for (QList<RecurrenceRule::WDayPos>::ConstIterator posit = tmpPositions.constBegin();
749 posit != tmpPositions.constEnd(); ++posit) {
750 int pos = (*posit).pos();
751 tmpStr2.sprintf("%i", (pos > 0) ? pos : (-pos));
752 if (pos < 0) {
753 tmpStr2 += "- ";
754 } else {
755 tmpStr2 += "+ ";
756 }
757 tmpStr += tmpStr2;
758 tmpStr += dayFromNum((*posit).day() - 1);
759 }
760 break;
761 }
762 case Recurrence::rMonthlyDay:
763 {
764 tmpStr.sprintf("MD%i ", recur->frequency());
765 // write out all rMonthDays;
766 const QList<int> tmpDays = recur->monthDays();
767 for (QList<int>::ConstIterator tmpDay = tmpDays.constBegin();
768 tmpDay != tmpDays.constEnd(); ++tmpDay) {
769 tmpStr2.sprintf("%i ", *tmpDay);
770 tmpStr += tmpStr2;
771 }
772 break;
773 }
774 case Recurrence::rYearlyMonth:
775 {
776 tmpStr.sprintf("YM%i ", recur->frequency());
777 // write out all the months;'
778 // TODO: Any way to write out the day within the month???
779 const QList<int> months = recur->yearMonths();
780 for (QList<int>::ConstIterator mit = months.constBegin();
781 mit != months.constEnd(); ++mit) {
782 tmpStr2.sprintf("%i ", *mit);
783 tmpStr += tmpStr2;
784 }
785 break;
786 }
787 case Recurrence::rYearlyDay:
788 {
789 tmpStr.sprintf("YD%i ", recur->frequency());
790 // write out all the rYearNums;
791 const QList<int> tmpDays = recur->yearDays();
792 for (QList<int>::ConstIterator tmpDay = tmpDays.begin();
793 tmpDay != tmpDays.end(); ++tmpDay) {
794 tmpStr2.sprintf("%i ", *tmpDay);
795 tmpStr += tmpStr2;
796 }
797 break;
798 }
799 default:
800 // TODO: Write rYearlyPos and arbitrary rules!
801 kDebug() << "ERROR, it should never get here in eventToVEvent!";
802 validRecur = false;
803 break;
804 } // switch
805
806 if (recur->duration() > 0) {
807 tmpStr2.sprintf("#%i", recur->duration());
808 tmpStr += tmpStr2;
809 } else if (recur->duration() == -1) {
810 tmpStr += "#0"; // defined as repeat forever
811 } else {
812#if !defined(KCALCORE_FOR_MEEGO)
813 tmpStr += kDateTimeToISO(recur->endDateTime(), false);
814#else
815 tmpStr +=
816 kDateTimeToISO(recur->endDateTime().toTimeSpec(d->mCalendar->timeSpec()), false);
817#endif
818 }
819 // Only write out the rrule if we have a valid recurrence (i.e. a known
820 // type in thee switch above)
821 if (validRecur) {
822 addPropValue(vevent, VCRRuleProp, tmpStr.toUtf8());
823 }
824
825 } // event repeats
826
827 // exceptions dates to recurrence
828 DateList dateList = recur->exDates();
829 DateList::ConstIterator it;
830 QString tmpStr2;
831
832 for (it = dateList.constBegin(); it != dateList.constEnd(); ++it) {
833 tmpStr = qDateToISO(*it) + QLatin1Char(';');
834 tmpStr2 += tmpStr;
835 }
836 if (!tmpStr2.isEmpty()) {
837 tmpStr2.truncate(tmpStr2.length() - 1);
838 addPropValue(vevent, VCExpDateProp, tmpStr2.toUtf8());
839 }
840 // exceptions datetimes to recurrence
841 DateTimeList dateTimeList = recur->exDateTimes();
842 DateTimeList::ConstIterator idt;
843 tmpStr2.clear();
844
845 for (idt = dateTimeList.constBegin(); idt != dateTimeList.constEnd(); ++idt) {
846 tmpStr = kDateTimeToISO(*idt) + QLatin1Char(';');
847 tmpStr2 += tmpStr;
848 }
849 if (!tmpStr2.isEmpty()) {
850 tmpStr2.truncate(tmpStr2.length() - 1);
851 addPropValue(vevent, VCExpDateProp, tmpStr2.toUtf8());
852 }
853
854 // description
855 if (!anEvent->description().isEmpty()) {
856 QByteArray in = anEvent->description().toUtf8();
857 QByteArray out;
858 KCodecs::quotedPrintableEncode(in, out, true);
859 if (out != in) {
860 VObject *d = addPropValue(vevent, VCDescriptionProp, out);
861 addPropValue(d, VCEncodingProp, VCQuotedPrintableProp);
862 addPropValue(d, VCCharSetProp, VCUtf8Prop);
863 } else {
864 addPropValue(vevent, VCDescriptionProp, in);
865 }
866 }
867
868 // summary
869 if (!anEvent->summary().isEmpty()) {
870 QByteArray in = anEvent->summary().toUtf8();
871 QByteArray out;
872 KCodecs::quotedPrintableEncode(in, out, true);
873 if (out != in) {
874 VObject *d = addPropValue(vevent, VCSummaryProp, out);
875 addPropValue(d, VCEncodingProp, VCQuotedPrintableProp);
876 addPropValue(d, VCCharSetProp, VCUtf8Prop);
877 } else {
878 addPropValue(vevent, VCSummaryProp, in);
879 }
880 }
881
882 // location
883 if (!anEvent->location().isEmpty()) {
884 QByteArray in = anEvent->location().toUtf8();
885 QByteArray out;
886 KCodecs::quotedPrintableEncode(in, out, true);
887 if (out != in) {
888 VObject *d = addPropValue(vevent, VCLocationProp, out);
889 addPropValue(d, VCEncodingProp, VCQuotedPrintableProp);
890 addPropValue(d, VCCharSetProp, VCUtf8Prop);
891 } else {
892 addPropValue(vevent, VCLocationProp, in);
893 }
894 }
895
896 // status
897// TODO: define Event status
898// addPropValue( vevent, VCStatusProp, anEvent->statusStr().toUtf8() );
899
900 // secrecy
901 const char *text = 0;
902 switch (anEvent->secrecy()) {
903 case Incidence::SecrecyPublic:
904 text = "PUBLIC";
905 break;
906 case Incidence::SecrecyPrivate:
907 text = "PRIVATE";
908 break;
909 case Incidence::SecrecyConfidential:
910 text = "CONFIDENTIAL";
911 break;
912 }
913 if (text) {
914 addPropValue(vevent, VCClassProp, text);
915 }
916
917 // categories
918 QStringList tmpStrList = anEvent->categories();
919 tmpStr = "";
920 QString catStr;
921 for (QStringList::const_iterator it = tmpStrList.constBegin(); it != tmpStrList.constEnd();
922 ++it) {
923 catStr = *it;
924 if (catStr[0] == ' ') {
925 tmpStr += catStr.mid(1);
926 } else {
927 tmpStr += catStr;
928 }
929 // this must be a ';' character as the vCalendar specification requires!
930 // vcc.y has been hacked to translate the ';' to a ',' when the vcal is
931 // read in.
932 tmpStr += ';';
933 }
934 if (!tmpStr.isEmpty()) {
935 tmpStr.truncate(tmpStr.length() - 1);
936 addPropValue(vevent, VCCategoriesProp, tmpStr.toUtf8());
937 }
938
939 // attachments
940 // TODO: handle binary attachments!
941 Attachment::List attachments = anEvent->attachments();
942 Attachment::List::ConstIterator atIt;
943 for (atIt = attachments.constBegin(); atIt != attachments.constEnd(); ++atIt) {
944 addPropValue(vevent, VCAttachProp, (*atIt)->uri().toUtf8());
945 }
946
947 // resources
948 tmpStrList = anEvent->resources();
949 tmpStr = tmpStrList.join(";");
950 if (!tmpStr.isEmpty()) {
951 addPropValue(vevent, VCResourcesProp, tmpStr.toUtf8());
952 }
953
954 // alarm stuff
955 Alarm::List::ConstIterator it2;
956 for (it2 = anEvent->alarms().constBegin(); it2 != anEvent->alarms().constEnd(); ++it2) {
957 Alarm::Ptr alarm = *it2;
958 if (alarm->enabled()) {
959 VObject *a;
960 if (alarm->type() == Alarm::Display) {
961 a = addProp(vevent, VCDAlarmProp);
962 tmpStr = kDateTimeToISO(alarm->time());
963 addPropValue(a, VCRunTimeProp, tmpStr.toUtf8());
964 addPropValue(a, VCRepeatCountProp, "1");
965 if (alarm->text().isNull()) {
966 addPropValue(a, VCDisplayStringProp, "beep!");
967 } else {
968 addPropValue(a, VCDisplayStringProp, alarm->text().toLatin1().data());
969 }
970 } else if (alarm->type() == Alarm::Audio) {
971 a = addProp(vevent, VCAAlarmProp);
972 tmpStr = kDateTimeToISO(alarm->time());
973 addPropValue(a, VCRunTimeProp, tmpStr.toUtf8());
974 addPropValue(a, VCRepeatCountProp, "1");
975 addPropValue(a, VCAudioContentProp, QFile::encodeName(alarm->audioFile()));
976 }
977 if (alarm->type() == Alarm::Procedure) {
978 a = addProp(vevent, VCPAlarmProp);
979 tmpStr = kDateTimeToISO(alarm->time());
980 addPropValue(a, VCRunTimeProp, tmpStr.toUtf8());
981 addPropValue(a, VCRepeatCountProp, "1");
982 addPropValue(a, VCProcedureNameProp, QFile::encodeName(alarm->programFile()));
983 }
984 }
985 }
986
987 // priority
988 tmpStr.sprintf("%i", anEvent->priority());
989 addPropValue(vevent, VCPriorityProp, tmpStr.toUtf8());
990
991 // transparency
992 tmpStr.sprintf("%i", anEvent->transparency());
993 addPropValue(vevent, VCTranspProp, tmpStr.toUtf8());
994
995 // related event
996 if (!anEvent->relatedTo().isEmpty()) {
997 addPropValue(vevent, VCRelatedToProp, anEvent->relatedTo().toUtf8());
998 }
999
1000 QString pilotId = anEvent->nonKDECustomProperty(KPilotIdProp);
1001 if (!pilotId.isEmpty()) {
1002 // pilot sync stuff
1003 addPropValue(vevent, KPilotIdProp, pilotId.toUtf8());
1004 addPropValue(vevent, KPilotStatusProp,
1005 anEvent->nonKDECustomProperty(KPilotStatusProp).toUtf8());
1006 }
1007
1008#if defined(KCALCORE_FOR_SYMBIAN)
1009 if (anEvent->nonKDECustomProperty(EPOCAgendaEntryTypeProp).isEmpty()) {
1010 // Propagate braindeath by setting this property also so that
1011 // S60 is happy
1012 if (anEvent->allDay()) {
1013 addPropValue(vevent, EPOCAgendaEntryTypeProp, "EVENT");
1014 } else {
1015 addPropValue(vevent, EPOCAgendaEntryTypeProp, "APPOINTMENT");
1016 }
1017 }
1018
1019 if (anEvent->hasRecurrenceId()) {
1020 tmpStr = kDateTimeToISO(anEvent->recurrenceId(), true);
1021 addPropValue(vevent, VCRecurrenceIdProp, tmpStr.toUtf8());
1022 }
1023 writeCustomProperties(vevent, anEvent);
1024#endif
1025
1026 return vevent;
1027}
1028
1029Todo::Ptr VCalFormat::VTodoToEvent(VObject *vtodo)
1030{
1031 VObject *vo;
1032 VObjectIterator voi;
1033 char *s;
1034
1035 Todo::Ptr anEvent(new Todo);
1036
1037 // creation date
1038 if ((vo = isAPropertyOf(vtodo, VCDCreatedProp)) != 0) {
1039 anEvent->setCreated(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo))));
1040 deleteStr(s);
1041 }
1042
1043 // unique id
1044 vo = isAPropertyOf(vtodo, VCUniqueStringProp);
1045 // while the UID property is preferred, it is not required. We'll use the
1046 // default Event UID if none is given.
1047 if (vo) {
1048 anEvent->setUid(s = fakeCString(vObjectUStringZValue(vo)));
1049 deleteStr(s);
1050 }
1051
1052 // last modification date
1053 if ((vo = isAPropertyOf(vtodo, VCLastModifiedProp)) != 0) {
1054 anEvent->setLastModified(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo))));
1055 deleteStr(s);
1056 } else {
1057 anEvent->setLastModified(KDateTime::currentUtcDateTime());
1058 }
1059
1060 // organizer
1061 // if our extension property for the event's ORGANIZER exists, add it.
1062 if ((vo = isAPropertyOf(vtodo, ICOrganizerProp)) != 0) {
1063 anEvent->setOrganizer(s = fakeCString(vObjectUStringZValue(vo)));
1064 deleteStr(s);
1065 } else {
1066 if (d->mCalendar->owner()->name() != QLatin1String("Unknown Name")) {
1067 anEvent->setOrganizer(d->mCalendar->owner());
1068 }
1069 }
1070
1071 // attendees.
1072 initPropIterator(&voi, vtodo);
1073 while (moreIteration(&voi)) {
1074 vo = nextVObject(&voi);
1075 if (strcmp(vObjectName(vo), VCAttendeeProp) == 0) {
1076 Attendee::Ptr a;
1077 VObject *vp;
1078 s = fakeCString(vObjectUStringZValue(vo));
1079 QString tmpStr = QString::fromUtf8(s);
1080 deleteStr(s);
1081 tmpStr = tmpStr.simplified();
1082 int emailPos1, emailPos2;
1083 if ((emailPos1 = tmpStr.indexOf(QLatin1Char('<'))) > 0) {
1084 // both email address and name
1085 emailPos2 = tmpStr.lastIndexOf(QLatin1Char('>'));
1086 a = Attendee::Ptr(new Attendee(tmpStr.left(emailPos1 - 1),
1087 tmpStr.mid(emailPos1 + 1,
1088 emailPos2 - (emailPos1 + 1))));
1089 } else if (tmpStr.indexOf(QLatin1Char('@')) > 0) {
1090 // just an email address
1091 a = Attendee::Ptr(new Attendee(0, tmpStr));
1092 } else {
1093 // just a name
1094 // WTF??? Replacing the spaces of a name and using this as email?
1095 QString email = tmpStr.replace(' ', '.');
1096 a = Attendee::Ptr(new Attendee(tmpStr, email));
1097 }
1098
1099 // is there an RSVP property?
1100 if ((vp = isAPropertyOf(vo, VCRSVPProp)) != 0) {
1101 a->setRSVP(vObjectStringZValue(vp));
1102 }
1103 // is there a status property?
1104 if ((vp = isAPropertyOf(vo, VCStatusProp)) != 0) {
1105 a->setStatus(readStatus(vObjectStringZValue(vp)));
1106 }
1107 // add the attendee
1108 anEvent->addAttendee(a);
1109 }
1110 }
1111
1112 // description for todo
1113 if ((vo = isAPropertyOf(vtodo, VCDescriptionProp)) != 0) {
1114 s = fakeCString(vObjectUStringZValue(vo));
1115 anEvent->setDescription(QString::fromUtf8(s), Qt::mightBeRichText(s));
1116 deleteStr(s);
1117 }
1118
1119 // summary
1120 if ((vo = isAPropertyOf(vtodo, VCSummaryProp))) {
1121 s = fakeCString(vObjectUStringZValue(vo));
1122 anEvent->setSummary(QString::fromUtf8(s), Qt::mightBeRichText(s));
1123 deleteStr(s);
1124 }
1125
1126 // location
1127 if ((vo = isAPropertyOf(vtodo, VCLocationProp)) != 0) {
1128 s = fakeCString(vObjectUStringZValue(vo));
1129 anEvent->setLocation(QString::fromUtf8(s), Qt::mightBeRichText(s));
1130 deleteStr(s);
1131 }
1132
1133 // completed
1134 // was: status
1135 if ((vo = isAPropertyOf(vtodo, VCStatusProp)) != 0) {
1136 s = fakeCString(vObjectUStringZValue(vo));
1137 if (s && strcmp(s, "COMPLETED") == 0) {
1138 anEvent->setCompleted(true);
1139 } else {
1140 anEvent->setCompleted(false);
1141 }
1142 deleteStr(s);
1143 } else {
1144 anEvent->setCompleted(false);
1145 }
1146
1147 // completion date
1148 if ((vo = isAPropertyOf(vtodo, VCCompletedProp)) != 0) {
1149 anEvent->setCompleted(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo))));
1150 deleteStr(s);
1151 }
1152
1153 // priority
1154 if ((vo = isAPropertyOf(vtodo, VCPriorityProp))) {
1155 s = fakeCString(vObjectUStringZValue(vo));
1156 if (s) {
1157 anEvent->setPriority(atoi(s));
1158 deleteStr(s);
1159 }
1160 }
1161
1162 anEvent->setAllDay(false);
1163
1164 // due date
1165 if ((vo = isAPropertyOf(vtodo, VCDueProp)) != 0) {
1166 anEvent->setDtDue(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo))));
1167 deleteStr(s);
1168 if (anEvent->dtDue().time().hour() == 0 &&
1169 anEvent->dtDue().time().minute() == 0 &&
1170 anEvent->dtDue().time().second() == 0) {
1171#if defined(KCALCORE_FOR_MEEGO)
1172 QDate dueDate = anEvent->dtDue().date();
1173 anEvent->setDtDue(KDateTime(dueDate, KDateTime::ClockTime));
1174#endif
1175 anEvent->setAllDay(true);
1176 }
1177 } else {
1178 anEvent->setDtDue(KDateTime());
1179 }
1180
1181 // start time
1182 if ((vo = isAPropertyOf(vtodo, VCDTstartProp)) != 0) {
1183 anEvent->setDtStart(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo))));
1184 deleteStr(s);
1185 if (anEvent->dtStart().time().hour() == 0 &&
1186 anEvent->dtStart().time().minute() == 0 &&
1187 anEvent->dtStart().time().second() == 0) {
1188#if defined(KCALCORE_FOR_MEEGO)
1189 QDate startDate = anEvent->dtStart().date();
1190 anEvent->setDtStart(KDateTime(startDate, KDateTime::ClockTime));
1191#endif
1192 anEvent->setAllDay(true);
1193 }
1194 } else {
1195 anEvent->setDtStart(KDateTime());
1196 }
1197
1198 // repeat stuff
1199 if ((vo = isAPropertyOf(vtodo, VCRRuleProp)) != 0) {
1200 QString tmpStr = (s = fakeCString(vObjectUStringZValue(vo)));
1201 deleteStr(s);
1202 tmpStr = tmpStr.simplified();
1203 tmpStr = tmpStr.toUpper();
1204 // first, read the type of the recurrence
1205 int typelen = 1;
1206 uint type = Recurrence::rNone;
1207 if (tmpStr.left(1) == "D") {
1208 type = Recurrence::rDaily;
1209 } else if (tmpStr.left(1) == "W") {
1210 type = Recurrence::rWeekly;
1211 } else {
1212 typelen = 2;
1213 if (tmpStr.left(2) == "MP") {
1214 type = Recurrence::rMonthlyPos;
1215 } else if (tmpStr.left(2) == "MD") {
1216 type = Recurrence::rMonthlyDay;
1217 } else if (tmpStr.left(2) == "YM") {
1218 type = Recurrence::rYearlyMonth;
1219 } else if (tmpStr.left(2) == "YD") {
1220 type = Recurrence::rYearlyDay;
1221 }
1222 }
1223
1224 if (type != Recurrence::rNone) {
1225
1226 // Immediately after the type is the frequency
1227 int index = tmpStr.indexOf(' ');
1228 int last = tmpStr.lastIndexOf(' ') + 1; // find last entry
1229 int rFreq = tmpStr.mid(typelen, (index - 1)).toInt();
1230 ++index; // advance to beginning of stuff after freq
1231
1232 // Read the type-specific settings
1233 switch (type) {
1234 case Recurrence::rDaily:
1235 anEvent->recurrence()->setDaily(rFreq);
1236 break;
1237
1238 case Recurrence::rWeekly:
1239 {
1240 QBitArray qba(7);
1241 QString dayStr;
1242 if (index == last) {
1243 // e.g. W1 #0
1244 qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
1245 } else {
1246 // e.g. W1 SU #0
1247 while (index < last) {
1248 dayStr = tmpStr.mid(index, 3);
1249 int dayNum = numFromDay(dayStr);
1250 if (dayNum >= 0) {
1251 qba.setBit(dayNum);
1252 }
1253 index += 3; // advance to next day, or possibly "#"
1254 }
1255 }
1256 anEvent->recurrence()->setWeekly(rFreq, qba);
1257 break;
1258 }
1259
1260 case Recurrence::rMonthlyPos:
1261 {
1262 anEvent->recurrence()->setMonthly(rFreq);
1263
1264 QBitArray qba(7);
1265 short tmpPos;
1266 if (index == last) {
1267 // e.g. MP1 #0
1268 tmpPos = anEvent->dtStart().date().day() / 7 + 1;
1269 if (tmpPos == 5) {
1270 tmpPos = -1;
1271 }
1272 qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
1273 anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
1274 } else {
1275 // e.g. MP1 1+ SU #0
1276 while (index < last) {
1277 tmpPos = tmpStr.mid(index, 1).toShort();
1278 index += 1;
1279 if (tmpStr.mid(index, 1) == "-") {
1280 // convert tmpPos to negative
1281 tmpPos = 0 - tmpPos;
1282 }
1283 index += 2; // advance to day(s)
1284 while (numFromDay(tmpStr.mid(index, 3)) >= 0) {
1285 int dayNum = numFromDay(tmpStr.mid(index, 3));
1286 qba.setBit(dayNum);
1287 index += 3; // advance to next day, or possibly pos or "#"
1288 }
1289 anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
1290 qba.detach();
1291 qba.fill(false); // clear out
1292 } // while != "#"
1293 }
1294 break;
1295 }
1296
1297 case Recurrence::rMonthlyDay:
1298 anEvent->recurrence()->setMonthly(rFreq);
1299 if (index == last) {
1300 // e.g. MD1 #0
1301 short tmpDay = anEvent->dtStart().date().day();
1302 anEvent->recurrence()->addMonthlyDate(tmpDay);
1303 } else {
1304 // e.g. MD1 3 #0
1305 while (index < last) {
1306 int index2 = tmpStr.indexOf(' ', index);
1307 if ((tmpStr.mid((index2 - 1), 1) == "-") ||
1308 (tmpStr.mid((index2 - 1), 1) == "+")) {
1309 index2 = index2 - 1;
1310 }
1311 short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
1312 index = index2;
1313 if (tmpStr.mid(index, 1) == "-") {
1314 tmpDay = 0 - tmpDay;
1315 }
1316 index += 2; // advance the index;
1317 anEvent->recurrence()->addMonthlyDate(tmpDay);
1318 } // while != #
1319 }
1320 break;
1321
1322 case Recurrence::rYearlyMonth:
1323 anEvent->recurrence()->setYearly(rFreq);
1324
1325 if (index == last) {
1326 // e.g. YM1 #0
1327 short tmpMonth = anEvent->dtStart().date().month();
1328 anEvent->recurrence()->addYearlyMonth(tmpMonth);
1329 } else {
1330 // e.g. YM1 3 #0
1331 while (index < last) {
1332 int index2 = tmpStr.indexOf(' ', index);
1333 short tmpMonth = tmpStr.mid(index, (index2 - index)).toShort();
1334 index = index2 + 1;
1335 anEvent->recurrence()->addYearlyMonth(tmpMonth);
1336 } // while != #
1337 }
1338 break;
1339
1340 case Recurrence::rYearlyDay:
1341 anEvent->recurrence()->setYearly(rFreq);
1342
1343 if (index == last) {
1344 // e.g. YD1 #0
1345 short tmpDay = anEvent->dtStart().date().dayOfYear();
1346 anEvent->recurrence()->addYearlyDay(tmpDay);
1347 } else {
1348 // e.g. YD1 123 #0
1349 while (index < last) {
1350 int index2 = tmpStr.indexOf(' ', index);
1351 short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
1352 index = index2 + 1;
1353 anEvent->recurrence()->addYearlyDay(tmpDay);
1354 } // while != #
1355 }
1356 break;
1357
1358 default:
1359 break;
1360 }
1361
1362 // find the last field, which is either the duration or the end date
1363 index = last;
1364 if (tmpStr.mid(index, 1) == "#") {
1365 // Nr of occurrences
1366 index++;
1367 int rDuration = tmpStr.mid(index, tmpStr.length() - index).toInt();
1368 if (rDuration > 0) {
1369 anEvent->recurrence()->setDuration(rDuration);
1370 }
1371 } else if (tmpStr.indexOf('T', index) != -1) {
1372 KDateTime rEndDate = ISOToKDateTime(tmpStr.mid(index, tmpStr.length() - index));
1373 anEvent->recurrence()->setEndDateTime(rEndDate);
1374 }
1375 } else {
1376 kDebug() << "we don't understand this type of recurrence!";
1377 } // if known recurrence type
1378 } // repeats
1379
1380 // recurrence exceptions
1381 if ((vo = isAPropertyOf(vtodo, VCExpDateProp)) != 0) {
1382 s = fakeCString(vObjectUStringZValue(vo));
1383 QStringList exDates = QString::fromUtf8(s).split(',');
1384 QStringList::ConstIterator it;
1385 for (it = exDates.constBegin(); it != exDates.constEnd(); ++it) {
1386 KDateTime exDate = ISOToKDateTime(*it);
1387 if (exDate.time().hour() == 0 &&
1388 exDate.time().minute() == 0 &&
1389 exDate.time().second() == 0) {
1390 anEvent->recurrence()->addExDate(ISOToQDate(*it));
1391 } else {
1392 anEvent->recurrence()->addExDateTime(exDate);
1393 }
1394 }
1395 deleteStr(s);
1396 }
1397
1398 // alarm stuff
1399 if ((vo = isAPropertyOf(vtodo, VCDAlarmProp))) {
1400 Alarm::Ptr alarm;
1401 VObject *a;
1402 VObject *b;
1403 a = isAPropertyOf(vo, VCRunTimeProp);
1404 b = isAPropertyOf(vo, VCDisplayStringProp);
1405
1406 if (a || b) {
1407 alarm = anEvent->newAlarm();
1408 if (a) {
1409 alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a))));
1410 deleteStr(s);
1411 }
1412 alarm->setEnabled(true);
1413 if (b) {
1414 s = fakeCString(vObjectUStringZValue(b));
1415 alarm->setDisplayAlarm(QString(s));
1416 deleteStr(s);
1417 } else {
1418 alarm->setDisplayAlarm(QString());
1419 }
1420 }
1421 }
1422
1423 if ((vo = isAPropertyOf(vtodo, VCAAlarmProp))) {
1424 Alarm::Ptr alarm;
1425 VObject *a;
1426 VObject *b;
1427 a = isAPropertyOf(vo, VCRunTimeProp);
1428 b = isAPropertyOf(vo, VCAudioContentProp);
1429
1430 if (a || b) {
1431 alarm = anEvent->newAlarm();
1432 if (a) {
1433 alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a))));
1434 deleteStr(s);
1435 }
1436 alarm->setEnabled(true);
1437 if (b) {
1438 s = fakeCString(vObjectUStringZValue(b));
1439 alarm->setAudioAlarm(QFile::decodeName(s));
1440 deleteStr(s);
1441 } else {
1442 alarm->setAudioAlarm(QString());
1443 }
1444 }
1445 }
1446
1447 if ((vo = isAPropertyOf(vtodo, VCPAlarmProp))) {
1448 Alarm::Ptr alarm;
1449 VObject *a;
1450 VObject *b;
1451 a = isAPropertyOf(vo, VCRunTimeProp);
1452 b = isAPropertyOf(vo, VCProcedureNameProp);
1453
1454 if (a || b) {
1455 alarm = anEvent->newAlarm();
1456 if (a) {
1457 alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a))));
1458 deleteStr(s);
1459 }
1460 alarm->setEnabled(true);
1461
1462 if (b) {
1463 s = fakeCString(vObjectUStringZValue(b));
1464 alarm->setProcedureAlarm(QFile::decodeName(s));
1465 deleteStr(s);
1466 } else {
1467 alarm->setProcedureAlarm(QString());
1468 }
1469 }
1470 }
1471
1472 // related todo
1473 if ((vo = isAPropertyOf(vtodo, VCRelatedToProp)) != 0) {
1474 anEvent->setRelatedTo(s = fakeCString(vObjectUStringZValue(vo)));
1475 deleteStr(s);
1476 d->mTodosRelate.append(anEvent);
1477 }
1478
1479 // secrecy
1480 Incidence::Secrecy secrecy = Incidence::SecrecyPublic;
1481 if ((vo = isAPropertyOf(vtodo, VCClassProp)) != 0) {
1482 s = fakeCString(vObjectUStringZValue(vo));
1483 if (s && strcmp(s, "PRIVATE") == 0) {
1484 secrecy = Incidence::SecrecyPrivate;
1485 } else if (s && strcmp(s, "CONFIDENTIAL") == 0) {
1486 secrecy = Incidence::SecrecyConfidential;
1487 }
1488 deleteStr(s);
1489 }
1490 anEvent->setSecrecy(secrecy);
1491
1492 // categories
1493 if ((vo = isAPropertyOf(vtodo, VCCategoriesProp)) != 0) {
1494 s = fakeCString(vObjectUStringZValue(vo));
1495 QString categories = QString::fromUtf8(s);
1496 deleteStr(s);
1497 QStringList tmpStrList = categories.split(';');
1498 anEvent->setCategories(tmpStrList);
1499 }
1500
1501 /* PILOT SYNC STUFF */
1502 if ((vo = isAPropertyOf(vtodo, KPilotIdProp))) {
1503 anEvent->setNonKDECustomProperty(
1504 KPilotIdProp, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
1505 deleteStr(s);
1506 if ((vo = isAPropertyOf(vtodo, KPilotStatusProp))) {
1507 anEvent->setNonKDECustomProperty(
1508 KPilotStatusProp, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
1509 deleteStr(s);
1510 } else {
1511 anEvent->setNonKDECustomProperty(KPilotStatusProp, QString::number(int(SYNCMOD)));
1512 }
1513 }
1514
1515 return anEvent;
1516}
1517
1518Event::Ptr VCalFormat::VEventToEvent(VObject *vevent)
1519{
1520 VObject *vo;
1521 VObjectIterator voi;
1522 char *s;
1523
1524 Event::Ptr anEvent(new Event);
1525
1526 // creation date
1527 if ((vo = isAPropertyOf(vevent, VCDCreatedProp)) != 0) {
1528 anEvent->setCreated(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo))));
1529 deleteStr(s);
1530 }
1531
1532 // unique id
1533 vo = isAPropertyOf(vevent, VCUniqueStringProp);
1534 // while the UID property is preferred, it is not required. We'll use the
1535 // default Event UID if none is given.
1536 if (vo) {
1537 anEvent->setUid(s = fakeCString(vObjectUStringZValue(vo)));
1538 deleteStr(s);
1539 }
1540
1541#if defined(KCALCORE_FOR_SYMBIAN)
1542 // recurrence id
1543 vo = isAPropertyOf(vevent, VCRecurrenceIdProp);
1544 if (vo) {
1545 anEvent->setRecurrenceId(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo))));
1546 deleteStr(s);
1547 }
1548#endif
1549
1550 // revision
1551 // again NSCAL doesn't give us much to work with, so we improvise...
1552 anEvent->setRevision(0);
1553 if ((vo = isAPropertyOf(vevent, VCSequenceProp)) != 0) {
1554 s = fakeCString(vObjectUStringZValue(vo));
1555 if (s) {
1556 anEvent->setRevision(atoi(s));
1557 deleteStr(s);
1558 }
1559 }
1560
1561 // last modification date
1562 if ((vo = isAPropertyOf(vevent, VCLastModifiedProp)) != 0) {
1563 anEvent->setLastModified(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo))));
1564 deleteStr(s);
1565 } else {
1566 anEvent->setLastModified(KDateTime::currentUtcDateTime());
1567 }
1568
1569 // organizer
1570 // if our extension property for the event's ORGANIZER exists, add it.
1571 if ((vo = isAPropertyOf(vevent, ICOrganizerProp)) != 0) {
1572 // FIXME: Also use the full name, not just the email address
1573 anEvent->setOrganizer(s = fakeCString(vObjectUStringZValue(vo)));
1574 deleteStr(s);
1575 } else {
1576 if (d->mCalendar->owner()->name() != "Unknown Name") {
1577 anEvent->setOrganizer(d->mCalendar->owner());
1578 }
1579 }
1580
1581 // deal with attendees.
1582 initPropIterator(&voi, vevent);
1583 while (moreIteration(&voi)) {
1584 vo = nextVObject(&voi);
1585 if (strcmp(vObjectName(vo), VCAttendeeProp) == 0) {
1586 Attendee::Ptr a;
1587 VObject *vp;
1588 s = fakeCString(vObjectUStringZValue(vo));
1589 QString tmpStr = QString::fromUtf8(s);
1590 deleteStr(s);
1591 tmpStr = tmpStr.simplified();
1592 int emailPos1, emailPos2;
1593 if ((emailPos1 = tmpStr.indexOf('<')) > 0) {
1594 // both email address and name
1595 emailPos2 = tmpStr.lastIndexOf('>');
1596 a = Attendee::Ptr(new Attendee(tmpStr.left(emailPos1 - 1),
1597 tmpStr.mid(emailPos1 + 1,
1598 emailPos2 - (emailPos1 + 1))));
1599 } else if (tmpStr.indexOf('@') > 0) {
1600 // just an email address
1601 a = Attendee::Ptr(new Attendee(0, tmpStr));
1602 } else {
1603 // just a name
1604 QString email = tmpStr.replace(' ', '.');
1605 a = Attendee::Ptr(new Attendee(tmpStr, email));
1606 }
1607
1608 // is there an RSVP property?
1609 if ((vp = isAPropertyOf(vo, VCRSVPProp)) != 0) {
1610 a->setRSVP(vObjectStringZValue(vp));
1611 }
1612 // is there a status property?
1613 if ((vp = isAPropertyOf(vo, VCStatusProp)) != 0) {
1614 a->setStatus(readStatus(vObjectStringZValue(vp)));
1615 }
1616 // add the attendee
1617 anEvent->addAttendee(a);
1618 }
1619 }
1620
1621 // This isn't strictly true. An event that doesn't have a start time
1622 // or an end time isn't all-day, it has an anchor in time but it doesn't
1623 // "take up" any time.
1624 /*if ((isAPropertyOf(vevent, VCDTstartProp) == 0) ||
1625 (isAPropertyOf(vevent, VCDTendProp) == 0)) {
1626 anEvent->setAllDay(true);
1627 } else {
1628 }*/
1629
1630 anEvent->setAllDay(false);
1631
1632 // start time
1633 if ((vo = isAPropertyOf(vevent, VCDTstartProp)) != 0) {
1634 anEvent->setDtStart(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo))));
1635 deleteStr(s);
1636
1637 if (anEvent->dtStart().time().hour() == 0 &&
1638 anEvent->dtStart().time().minute() == 0 &&
1639 anEvent->dtStart().time().second() == 0) {
1640#if defined(KCALCORE_FOR_MEEGO)
1641 QDate startDate = anEvent->dtStart().date();
1642 anEvent->setDtStart(KDateTime(startDate, KDateTime::ClockTime));
1643#endif
1644 anEvent->setAllDay(true);
1645 }
1646 }
1647
1648 // stop time
1649 if ((vo = isAPropertyOf(vevent, VCDTendProp)) != 0) {
1650 anEvent->setDtEnd(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(vo))));
1651 deleteStr(s);
1652
1653 if (anEvent->dtEnd().time().hour() == 0 &&
1654 anEvent->dtEnd().time().minute() == 0 &&
1655 anEvent->dtEnd().time().second() == 0) {
1656#if defined(KCALCORE_FOR_MEEGO)
1657 QDate endDate = anEvent->dtEnd().date();
1658 anEvent->setDtEnd(KDateTime(endDate, KDateTime::ClockTime));
1659#endif
1660 anEvent->setAllDay(true);
1661 }
1662 }
1663#if defined(KCALCORE_FOR_MEEGO)
1664 if (anEvent->allDay()) {
1665 if (anEvent->dtEnd() == anEvent->dtStart()) {
1666 anEvent->setDtEnd(anEvent->dtEnd().addDays(1));
1667 }
1668 }
1669#endif
1670
1671 // at this point, there should be at least a start or end time.
1672 // fix up for events that take up no time but have a time associated
1673 if (!isAPropertyOf(vevent, VCDTstartProp)) {
1674 anEvent->setDtStart(anEvent->dtEnd());
1675 }
1676 if (! isAPropertyOf(vevent, VCDTendProp)) {
1677 anEvent->setDtEnd(anEvent->dtStart());
1678 }
1679
1680 ///////////////////////////////////////////////////////////////////////////
1681
1682 // repeat stuff
1683 if ((vo = isAPropertyOf(vevent, VCRRuleProp)) != 0) {
1684 QString tmpStr = (s = fakeCString(vObjectUStringZValue(vo)));
1685 deleteStr(s);
1686 tmpStr = tmpStr.simplified();
1687 tmpStr = tmpStr.toUpper();
1688 // first, read the type of the recurrence
1689 int typelen = 1;
1690 uint type = Recurrence::rNone;
1691 if (tmpStr.left(1) == "D") {
1692 type = Recurrence::rDaily;
1693 } else if (tmpStr.left(1) == "W") {
1694 type = Recurrence::rWeekly;
1695 } else {
1696 typelen = 2;
1697 if (tmpStr.left(2) == "MP") {
1698 type = Recurrence::rMonthlyPos;
1699 } else if (tmpStr.left(2) == "MD") {
1700 type = Recurrence::rMonthlyDay;
1701 } else if (tmpStr.left(2) == "YM") {
1702 type = Recurrence::rYearlyMonth;
1703 } else if (tmpStr.left(2) == "YD") {
1704 type = Recurrence::rYearlyDay;
1705 }
1706 }
1707
1708 if (type != Recurrence::rNone) {
1709
1710 // Immediately after the type is the frequency
1711 int index = tmpStr.indexOf(' ');
1712 int last = tmpStr.lastIndexOf(' ') + 1; // find last entry
1713 int rFreq = tmpStr.mid(typelen, (index - 1)).toInt();
1714 ++index; // advance to beginning of stuff after freq
1715
1716 // Read the type-specific settings
1717 switch (type) {
1718 case Recurrence::rDaily:
1719 anEvent->recurrence()->setDaily(rFreq);
1720 break;
1721
1722 case Recurrence::rWeekly:
1723 {
1724 QBitArray qba(7);
1725 QString dayStr;
1726 if (index == last) {
1727 // e.g. W1 #0
1728 qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
1729 } else {
1730 // e.g. W1 SU #0
1731 while (index < last) {
1732 dayStr = tmpStr.mid(index, 3);
1733 int dayNum = numFromDay(dayStr);
1734 if (dayNum >= 0) {
1735 qba.setBit(dayNum);
1736 }
1737 index += 3; // advance to next day, or possibly "#"
1738 }
1739 }
1740 anEvent->recurrence()->setWeekly(rFreq, qba);
1741 break;
1742 }
1743
1744 case Recurrence::rMonthlyPos:
1745 {
1746 anEvent->recurrence()->setMonthly(rFreq);
1747
1748 QBitArray qba(7);
1749 short tmpPos;
1750 if (index == last) {
1751 // e.g. MP1 #0
1752 tmpPos = anEvent->dtStart().date().day() / 7 + 1;
1753 if (tmpPos == 5) {
1754 tmpPos = -1;
1755 }
1756 qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
1757 anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
1758 } else {
1759 // e.g. MP1 1+ SU #0
1760 while (index < last) {
1761 tmpPos = tmpStr.mid(index, 1).toShort();
1762 index += 1;
1763 if (tmpStr.mid(index, 1) == "-") {
1764 // convert tmpPos to negative
1765 tmpPos = 0 - tmpPos;
1766 }
1767 index += 2; // advance to day(s)
1768 while (numFromDay(tmpStr.mid(index, 3)) >= 0) {
1769 int dayNum = numFromDay(tmpStr.mid(index, 3));
1770 qba.setBit(dayNum);
1771 index += 3; // advance to next day, or possibly pos or "#"
1772 }
1773 anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
1774 qba.detach();
1775 qba.fill(false); // clear out
1776 } // while != "#"
1777 }
1778 break;
1779 }
1780
1781 case Recurrence::rMonthlyDay:
1782 anEvent->recurrence()->setMonthly(rFreq);
1783 if (index == last) {
1784 // e.g. MD1 #0
1785 short tmpDay = anEvent->dtStart().date().day();
1786 anEvent->recurrence()->addMonthlyDate(tmpDay);
1787 } else {
1788 // e.g. MD1 3 #0
1789 while (index < last) {
1790 int index2 = tmpStr.indexOf(' ', index);
1791 if ((tmpStr.mid((index2 - 1), 1) == "-") ||
1792 (tmpStr.mid((index2 - 1), 1) == "+")) {
1793 index2 = index2 - 1;
1794 }
1795 short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
1796 index = index2;
1797 if (tmpStr.mid(index, 1) == "-") {
1798 tmpDay = 0 - tmpDay;
1799 }
1800 index += 2; // advance the index;
1801 anEvent->recurrence()->addMonthlyDate(tmpDay);
1802 } // while != #
1803 }
1804 break;
1805
1806 case Recurrence::rYearlyMonth:
1807 anEvent->recurrence()->setYearly(rFreq);
1808
1809 if (index == last) {
1810 // e.g. YM1 #0
1811 short tmpMonth = anEvent->dtStart().date().month();
1812 anEvent->recurrence()->addYearlyMonth(tmpMonth);
1813 } else {
1814 // e.g. YM1 3 #0
1815 while (index < last) {
1816 int index2 = tmpStr.indexOf(' ', index);
1817 short tmpMonth = tmpStr.mid(index, (index2 - index)).toShort();
1818 index = index2 + 1;
1819 anEvent->recurrence()->addYearlyMonth(tmpMonth);
1820 } // while != #
1821 }
1822 break;
1823
1824 case Recurrence::rYearlyDay:
1825 anEvent->recurrence()->setYearly(rFreq);
1826
1827 if (index == last) {
1828 // e.g. YD1 #0
1829 short tmpDay = anEvent->dtStart().date().dayOfYear();
1830 anEvent->recurrence()->addYearlyDay(tmpDay);
1831 } else {
1832 // e.g. YD1 123 #0
1833 while (index < last) {
1834 int index2 = tmpStr.indexOf(' ', index);
1835 short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
1836 index = index2 + 1;
1837 anEvent->recurrence()->addYearlyDay(tmpDay);
1838 } // while != #
1839 }
1840 break;
1841
1842 default:
1843 break;
1844 }
1845
1846 // find the last field, which is either the duration or the end date
1847 index = last;
1848 if (tmpStr.mid(index, 1) == "#") {
1849 // Nr of occurrences
1850 index++;
1851 int rDuration = tmpStr.mid(index, tmpStr.length() - index).toInt();
1852 if (rDuration > 0) {
1853 anEvent->recurrence()->setDuration(rDuration);
1854 }
1855 } else if (tmpStr.indexOf('T', index) != -1) {
1856 KDateTime rEndDate = ISOToKDateTime(tmpStr.mid(index, tmpStr.length() - index));
1857 anEvent->recurrence()->setEndDateTime(rEndDate);
1858 }
1859// anEvent->recurrence()->dump();
1860
1861 } else {
1862 kDebug() << "we don't understand this type of recurrence!";
1863 } // if known recurrence type
1864 } // repeats
1865
1866 // recurrence exceptions
1867 if ((vo = isAPropertyOf(vevent, VCExpDateProp)) != 0) {
1868 s = fakeCString(vObjectUStringZValue(vo));
1869 QStringList exDates = QString::fromUtf8(s).split(',');
1870 QStringList::ConstIterator it;
1871 for (it = exDates.constBegin(); it != exDates.constEnd(); ++it) {
1872 KDateTime exDate = ISOToKDateTime(*it);
1873 if (exDate.time().hour() == 0 &&
1874 exDate.time().minute() == 0 &&
1875 exDate.time().second() == 0) {
1876 anEvent->recurrence()->addExDate(ISOToQDate(*it));
1877 } else {
1878 anEvent->recurrence()->addExDateTime(exDate);
1879 }
1880 }
1881 deleteStr(s);
1882 }
1883
1884 // summary
1885 if ((vo = isAPropertyOf(vevent, VCSummaryProp))) {
1886 s = fakeCString(vObjectUStringZValue(vo));
1887 anEvent->setSummary(QString::fromUtf8(s), Qt::mightBeRichText(s));
1888 deleteStr(s);
1889 }
1890
1891 // description
1892 if ((vo = isAPropertyOf(vevent, VCDescriptionProp)) != 0) {
1893 s = fakeCString(vObjectUStringZValue(vo));
1894 bool isRich = Qt::mightBeRichText(s);
1895 if (!anEvent->description().isEmpty()) {
1896 anEvent->setDescription(
1897 anEvent->description() + '\n' + QString::fromUtf8(s), isRich);
1898 } else {
1899 anEvent->setDescription(QString::fromUtf8(s), isRich);
1900 }
1901 deleteStr(s);
1902 }
1903
1904 // location
1905 if ((vo = isAPropertyOf(vevent, VCLocationProp)) != 0) {
1906 s = fakeCString(vObjectUStringZValue(vo));
1907 anEvent->setLocation(QString::fromUtf8(s), Qt::mightBeRichText(s));
1908 deleteStr(s);
1909 }
1910
1911 // some stupid vCal exporters ignore the standard and use Description
1912 // instead of Summary for the default field. Correct for this.
1913 if (anEvent->summary().isEmpty() && !(anEvent->description().isEmpty())) {
1914 QString tmpStr = anEvent->description().simplified();
1915 anEvent->setDescription("");
1916 anEvent->setSummary(tmpStr);
1917 }
1918
1919#if 0
1920 // status
1921 if ((vo = isAPropertyOf(vevent, VCStatusProp)) != 0) {
1922 QString tmpStr(s = fakeCString(vObjectUStringZValue(vo)));
1923 deleteStr(s);
1924// TODO: Define Event status
1925// anEvent->setStatus( tmpStr );
1926 } else {
1927// anEvent->setStatus( "NEEDS ACTION" );
1928 }
1929#endif
1930
1931 // secrecy
1932 Incidence::Secrecy secrecy = Incidence::SecrecyPublic;
1933 if ((vo = isAPropertyOf(vevent, VCClassProp)) != 0) {
1934 s = fakeCString(vObjectUStringZValue(vo));
1935 if (s && strcmp(s, "PRIVATE") == 0) {
1936 secrecy = Incidence::SecrecyPrivate;
1937 } else if (s && strcmp(s, "CONFIDENTIAL") == 0) {
1938 secrecy = Incidence::SecrecyConfidential;
1939 }
1940 deleteStr(s);
1941 }
1942 anEvent->setSecrecy(secrecy);
1943
1944 // categories
1945 if ((vo = isAPropertyOf(vevent, VCCategoriesProp)) != 0) {
1946 s = fakeCString(vObjectUStringZValue(vo));
1947 QString categories = QString::fromUtf8(s);
1948 deleteStr(s);
1949 QStringList tmpStrList = categories.split(',');
1950 anEvent->setCategories(tmpStrList);
1951 }
1952
1953 // attachments
1954 initPropIterator(&voi, vevent);
1955 while (moreIteration(&voi)) {
1956 vo = nextVObject(&voi);
1957 if (strcmp(vObjectName(vo), VCAttachProp) == 0) {
1958 s = fakeCString(vObjectUStringZValue(vo));
1959 anEvent->addAttachment(Attachment::Ptr(new Attachment(QString(s))));
1960 deleteStr(s);
1961 }
1962 }
1963
1964 // resources
1965 if ((vo = isAPropertyOf(vevent, VCResourcesProp)) != 0) {
1966 QString resources = (s = fakeCString(vObjectUStringZValue(vo)));
1967 deleteStr(s);
1968 QStringList tmpStrList = resources.split(';');
1969 anEvent->setResources(tmpStrList);
1970 }
1971
1972 // alarm stuff
1973 if ((vo = isAPropertyOf(vevent, VCDAlarmProp))) {
1974 Alarm::Ptr alarm;
1975 VObject *a;
1976 VObject *b;
1977 a = isAPropertyOf(vo, VCRunTimeProp);
1978 b = isAPropertyOf(vo, VCDisplayStringProp);
1979
1980 if (a || b) {
1981 alarm = anEvent->newAlarm();
1982 if (a) {
1983 alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a))));
1984 deleteStr(s);
1985 }
1986 alarm->setEnabled(true);
1987
1988 if (b) {
1989 s = fakeCString(vObjectUStringZValue(b));
1990 alarm->setDisplayAlarm(QString(s));
1991 deleteStr(s);
1992 } else {
1993 alarm->setDisplayAlarm(QString());
1994 }
1995 }
1996 }
1997
1998 if ((vo = isAPropertyOf(vevent, VCAAlarmProp))) {
1999 Alarm::Ptr alarm;
2000 VObject *a;
2001 VObject *b;
2002 a = isAPropertyOf(vo, VCRunTimeProp);
2003 b = isAPropertyOf(vo, VCAudioContentProp);
2004
2005 if (a || b) {
2006 alarm = anEvent->newAlarm();
2007 if (a) {
2008 alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a))));
2009 deleteStr(s);
2010 }
2011 alarm->setEnabled(true);
2012
2013 if (b) {
2014 s = fakeCString(vObjectUStringZValue(b));
2015 alarm->setAudioAlarm(QFile::decodeName(s));
2016 deleteStr(s);
2017 } else {
2018 alarm->setAudioAlarm(QString());
2019 }
2020 }
2021 }
2022
2023 if ((vo = isAPropertyOf(vevent, VCPAlarmProp))) {
2024 Alarm::Ptr alarm;
2025 VObject *a;
2026 VObject *b;
2027 a = isAPropertyOf(vo, VCRunTimeProp);
2028 b = isAPropertyOf(vo, VCProcedureNameProp);
2029
2030 if (a || b) {
2031 alarm = anEvent->newAlarm();
2032 if (a) {
2033 alarm->setTime(ISOToKDateTime(s = fakeCString(vObjectUStringZValue(a))));
2034 deleteStr(s);
2035 }
2036 alarm->setEnabled(true);
2037
2038 if (b) {
2039 s = fakeCString(vObjectUStringZValue(b));
2040 alarm->setProcedureAlarm(QFile::decodeName(s));
2041 deleteStr(s);
2042 } else {
2043 alarm->setProcedureAlarm(QString());
2044 }
2045 }
2046 }
2047
2048 // priority
2049 if ((vo = isAPropertyOf(vevent, VCPriorityProp))) {
2050 s = fakeCString(vObjectUStringZValue(vo));
2051 if (s) {
2052 anEvent->setPriority(atoi(s));
2053 deleteStr(s);
2054 }
2055 }
2056
2057 // transparency
2058 if ((vo = isAPropertyOf(vevent, VCTranspProp)) != 0) {
2059 s = fakeCString(vObjectUStringZValue(vo));
2060 if (s) {
2061 int i = atoi(s);
2062 anEvent->setTransparency(i == 1 ? Event::Transparent : Event::Opaque);
2063 deleteStr(s);
2064 }
2065 }
2066
2067 // related event
2068 if ((vo = isAPropertyOf(vevent, VCRelatedToProp)) != 0) {
2069 anEvent->setRelatedTo(s = fakeCString(vObjectUStringZValue(vo)));
2070 deleteStr(s);
2071 d->mEventsRelate.append(anEvent);
2072 }
2073
2074 /* PILOT SYNC STUFF */
2075 if ((vo = isAPropertyOf(vevent, KPilotIdProp))) {
2076 anEvent->setNonKDECustomProperty(
2077 KPilotIdProp, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
2078 deleteStr(s);
2079 if ((vo = isAPropertyOf(vevent, KPilotStatusProp))) {
2080 anEvent->setNonKDECustomProperty(
2081 KPilotStatusProp, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
2082 deleteStr(s);
2083 } else {
2084 anEvent->setNonKDECustomProperty(KPilotStatusProp, QString::number(int(SYNCMOD)));
2085 }
2086 }
2087
2088 /* Rest of the custom properties */
2089 readCustomProperties(vevent, anEvent);
2090
2091 return anEvent;
2092}
2093
2094QString VCalFormat::parseTZ(const QByteArray &timezone) const
2095{
2096 // kDebug() << timezone;
2097 QString pZone = timezone.mid(timezone.indexOf("TZID:VCAL") + 9);
2098 return pZone.mid(0, pZone.indexOf(QChar(QLatin1Char('\n'))));
2099}
2100
2101QString VCalFormat::parseDst(QByteArray &timezone) const
2102{
2103 if (!timezone.contains("BEGIN:DAYLIGHT")) {
2104 return QString();
2105 }
2106
2107 timezone = timezone.mid(timezone.indexOf("BEGIN:DAYLIGHT"));
2108 timezone = timezone.mid(timezone.indexOf("TZNAME:") + 7);
2109 QString sStart = timezone.mid(0, (timezone.indexOf("COMMENT:")));
2110 sStart.chop(2);
2111 timezone = timezone.mid(timezone.indexOf("TZOFFSETTO:") + 11);
2112 QString sOffset = timezone.mid(0, (timezone.indexOf("DTSTART:")));
2113 sOffset.chop(2);
2114 sOffset.insert(3, QString(":"));
2115 timezone = timezone.mid(timezone.indexOf("TZNAME:") + 7);
2116 QString sEnd = timezone.mid(0, (timezone.indexOf("COMMENT:")));
2117 sEnd.chop(2);
2118
2119 return "TRUE;" + sOffset + ';' + sStart + ';' + sEnd + ";;";
2120}
2121
2122QString VCalFormat::qDateToISO(const QDate &qd)
2123{
2124 QString tmpStr;
2125
2126 if (!qd.isValid()) {
2127 return QString();
2128 }
2129
2130 tmpStr.sprintf("%.2d%.2d%.2d", qd.year(), qd.month(), qd.day());
2131 return tmpStr;
2132
2133}
2134
2135QString VCalFormat::kDateTimeToISO(const KDateTime &dt, bool zulu)
2136{
2137 QString tmpStr;
2138
2139 if (!dt.isValid()) {
2140 return QString();
2141 }
2142
2143 QDateTime tmpDT;
2144 if (zulu) {
2145 tmpDT = dt.toUtc().dateTime();
2146 } else {
2147#if !defined(KCALCORE_FOR_MEEGO)
2148 tmpDT = dt.toTimeSpec(d->mCalendar->timeSpec()).dateTime();
2149#else
2150 tmpDT = dt.dateTime();
2151#endif
2152 }
2153 tmpStr.sprintf("%.2d%.2d%.2dT%.2d%.2d%.2d",
2154 tmpDT.date().year(), tmpDT.date().month(),
2155 tmpDT.date().day(), tmpDT.time().hour(),
2156 tmpDT.time().minute(), tmpDT.time().second());
2157 if (zulu || dt.isUtc()) {
2158 tmpStr += 'Z';
2159 }
2160 return tmpStr;
2161}
2162
2163KDateTime VCalFormat::ISOToKDateTime(const QString &dtStr)
2164{
2165 QDate tmpDate;
2166 QTime tmpTime;
2167 QString tmpStr;
2168 int year, month, day, hour, minute, second;
2169
2170 tmpStr = dtStr;
2171 year = tmpStr.left(4).toInt();
2172 month = tmpStr.mid(4, 2).toInt();
2173 day = tmpStr.mid(6, 2).toInt();
2174 hour = tmpStr.mid(9, 2).toInt();
2175 minute = tmpStr.mid(11, 2).toInt();
2176 second = tmpStr.mid(13, 2).toInt();
2177 tmpDate.setYMD(year, month, day);
2178 tmpTime.setHMS(hour, minute, second);
2179
2180 if (tmpDate.isValid() && tmpTime.isValid()) {
2181 // correct for GMT if string is in Zulu format
2182 if (dtStr.at(dtStr.length() - 1) == 'Z') {
2183 return KDateTime(tmpDate, tmpTime, KDateTime::UTC);
2184 } else {
2185 return KDateTime(tmpDate, tmpTime, d->mCalendar->timeSpec());
2186 }
2187 } else {
2188 return KDateTime();
2189 }
2190}
2191
2192QDate VCalFormat::ISOToQDate(const QString &dateStr)
2193{
2194 int year, month, day;
2195
2196 year = dateStr.left(4).toInt();
2197 month = dateStr.mid(4, 2).toInt();
2198 day = dateStr.mid(6, 2).toInt();
2199
2200 return QDate(year, month, day);
2201}
2202
2203bool VCalFormat::parseTZOffsetISO8601(const QString &s, int &result)
2204{
2205 // ISO8601 format(s):
2206 // +- hh : mm
2207 // +- hh mm
2208 // +- hh
2209
2210 // We also accept broken one without +
2211 int mod = 1;
2212 int v = 0;
2213 QString str = s.trimmed();
2214 int ofs = 0;
2215 result = 0;
2216
2217 // Check for end
2218 if (str.size() <= ofs) {
2219 return false;
2220 }
2221 if (str[ofs] == '-') {
2222 mod = -1;
2223 ofs++;
2224 } else if (str[ofs] == '+') {
2225 ofs++;
2226 }
2227 if (str.size() <= ofs) {
2228 return false;
2229 }
2230
2231 // Make sure next two values are numbers
2232 bool ok;
2233
2234 if (str.size() < (ofs + 2)) {
2235 return false;
2236 }
2237
2238 v = str.mid(ofs, 2).toInt(&ok) * 60;
2239 if (!ok) {
2240 return false;
2241 }
2242 ofs += 2;
2243
2244 if (str.size() > ofs) {
2245 if (str[ofs] == ':') {
2246 ofs++;
2247 }
2248 if (str.size() > ofs) {
2249 if (str.size() < (ofs + 2)) {
2250 return false;
2251 }
2252 v += str.mid(ofs, 2).toInt(&ok);
2253 if (!ok) {
2254 return false;
2255 }
2256 }
2257 }
2258 result = v * mod * 60;
2259 return true;
2260}
2261
2262// take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc.
2263// and break it down from it's tree-like format into the dictionary format
2264// that is used internally in the VCalFormat.
2265void VCalFormat::populate(VObject *vcal, bool deleted, const QString &notebook)
2266{
2267 Q_UNUSED(notebook);
2268 // this function will populate the caldict dictionary and other event
2269 // lists. It turns vevents into Events and then inserts them.
2270
2271 VObjectIterator i;
2272 VObject *curVO, *curVOProp;
2273 Event::Ptr anEvent;
2274 bool hasTimeZone = false; //The calendar came with a TZ and not UTC
2275 KDateTime::Spec previousSpec; //If we add a new TZ we should leave the spec as it was before
2276
2277 if ((curVO = isAPropertyOf(vcal, ICMethodProp)) != 0) {
2278 char *methodType = 0;
2279 methodType = fakeCString(vObjectUStringZValue(curVO));
2280 // kDebug() << "This calendar is an iTIP transaction of type '" << methodType << "'";
2281 deleteStr(methodType);
2282 }
2283
2284 // warn the user that we might have trouble reading non-known calendar.
2285 if ((curVO = isAPropertyOf(vcal, VCProdIdProp)) != 0) {
2286 char *s = fakeCString(vObjectUStringZValue(curVO));
2287 if (!s || strcmp(productId().toUtf8(), s) != 0) {
2288 kDebug() << "This vCalendar file was not created by KOrganizer or"
2289 << "any other product we support. Loading anyway...";
2290 }
2291 setLoadedProductId(s);
2292 deleteStr(s);
2293 }
2294
2295 // warn the user we might have trouble reading this unknown version.
2296 if ((curVO = isAPropertyOf(vcal, VCVersionProp)) != 0) {
2297 char *s = fakeCString(vObjectUStringZValue(curVO));
2298 if (!s || strcmp(_VCAL_VERSION, s) != 0) {
2299 kDebug() << "This vCalendar file has version" << s
2300 << "We only support" << _VCAL_VERSION;
2301 }
2302 deleteStr(s);
2303 }
2304
2305 // set the time zone (this is a property of the view, so just discard!)
2306 if ((curVO = isAPropertyOf(vcal, VCTimeZoneProp)) != 0) {
2307 char *s = fakeCString(vObjectUStringZValue(curVO));
2308 QString ts(s);
2309 QString name = QLatin1String("VCAL") + ts;
2310 deleteStr(s);
2311
2312 // TODO: While using the timezone-offset + vcal as timezone is is
2313 // most likely unique, we should REALLY actually create something
2314 // like vcal-tzoffset-daylightoffsets, or better yet,
2315 // vcal-hash<the former>
2316
2317 QStringList tzList;
2318 QString tz;
2319 int utcOffset;
2320 int utcOffsetDst;
2321 if (parseTZOffsetISO8601(ts, utcOffset)) {
2322 // kDebug() << "got standard offset" << ts << utcOffset;
2323 // standard from tz
2324 // starting date for now 01011900
2325 KDateTime dt = KDateTime(QDateTime(QDate(1900, 1, 1), QTime(0, 0, 0)));
2326 tz = QString("STD;%1;false;%2").arg(QString::number(utcOffset)).arg(dt.toString());
2327 tzList.append(tz);
2328
2329 // go through all the daylight tags
2330 initPropIterator(&i, vcal);
2331 while (moreIteration(&i)) {
2332 curVO = nextVObject(&i);
2333 if (strcmp(vObjectName(curVO), VCDayLightProp) == 0) {
2334 char *s = fakeCString(vObjectUStringZValue(curVO));
2335 QString dst = QLatin1String(s);
2336 QStringList argl = dst.split(QLatin1Char(','));
2337 deleteStr(s);
2338
2339 // Too short -> not interesting
2340 if (argl.size() < 4) {
2341 continue;
2342 }
2343
2344 // We don't care about the non-DST periods
2345 if (argl[0] != QLatin1String("TRUE")) {
2346 continue;
2347 }
2348
2349 if (parseTZOffsetISO8601(argl[1], utcOffsetDst)) {
2350
2351 // kDebug() << "got DST offset" << argl[1] << utcOffsetDst;
2352 // standard
2353 QString strEndDate = argl[3];
2354 KDateTime endDate = ISOToKDateTime(strEndDate);
2355 // daylight
2356 QString strStartDate = argl[2];
2357 KDateTime startDate = ISOToKDateTime(strStartDate);
2358
2359 QString strRealEndDate = strEndDate;
2360 QString strRealStartDate = strStartDate;
2361 KDateTime realEndDate = endDate;
2362 KDateTime realStartDate = startDate;
2363 // if we get dates for some reason in wrong order, earlier is used for dst
2364 if (endDate < startDate) {
2365 strRealEndDate = strStartDate;
2366 strRealStartDate = strEndDate;
2367 realEndDate = startDate;
2368 realStartDate = endDate;
2369 }
2370 tz = QString::fromLatin1("%1;%2;false;%3").
2371 arg(strRealEndDate).
2372 arg(QString::number(utcOffset)).
2373 arg(realEndDate.toString());
2374 tzList.append(tz);
2375
2376 tz = QString::fromLatin1("%1;%2;true;%3").
2377 arg(strRealStartDate).
2378 arg(QString::number(utcOffsetDst)).
2379 arg(realStartDate.toString());
2380 tzList.append(tz);
2381 } else {
2382 kDebug() << "unable to parse dst" << argl[1];
2383 }
2384 }
2385 }
2386 ICalTimeZones *tzlist = d->mCalendar->timeZones();
2387 ICalTimeZoneSource tzs;
2388 ICalTimeZone zone = tzs.parse(name, tzList, *tzlist);
2389 if (!zone.isValid()) {
2390 kDebug() << "zone is not valid, parsing error" << tzList;
2391 } else {
2392 previousSpec = d->mCalendar->timeSpec();
2393 d->mCalendar->setTimeZoneId(name);
2394 hasTimeZone = true;
2395 }
2396 } else {
2397 kDebug() << "unable to parse tzoffset" << ts;
2398 }
2399 }
2400
2401 // Store all events with a relatedTo property in a list for post-processing
2402 d->mEventsRelate.clear();
2403 d->mTodosRelate.clear();
2404
2405 initPropIterator(&i, vcal);
2406
2407 // go through all the vobjects in the vcal
2408 while (moreIteration(&i)) {
2409 curVO = nextVObject(&i);
2410
2411 /************************************************************************/
2412
2413 // now, check to see that the object is an event or todo.
2414 if (strcmp(vObjectName(curVO), VCEventProp) == 0) {
2415
2416 if ((curVOProp = isAPropertyOf(curVO, KPilotStatusProp)) != 0) {
2417 char *s;
2418 s = fakeCString(vObjectUStringZValue(curVOProp));
2419 // check to see if event was deleted by the kpilot conduit
2420 if (s) {
2421 if (atoi(s) == SYNCDEL) {
2422 deleteStr(s);
2423 kDebug() << "skipping pilot-deleted event";
2424 goto SKIP;
2425 }
2426 deleteStr(s);
2427 }
2428 }
2429
2430 if (!isAPropertyOf(curVO, VCDTstartProp) &&
2431 !isAPropertyOf(curVO, VCDTendProp)) {
2432 kDebug() << "found a VEvent with no DTSTART and no DTEND! Skipping...";
2433 goto SKIP;
2434 }
2435
2436 anEvent = VEventToEvent(curVO);
2437 if (anEvent) {
2438 if (hasTimeZone && !anEvent->allDay() && anEvent->dtStart().isUtc()) {
2439 //This sounds stupid but is how others are doing it, so here
2440 //we go. If there is a TZ in the VCALENDAR even if the dtStart
2441 //and dtend are in UTC, clients interpret it using also the TZ defined
2442 //in the Calendar. I know it sounds braindead but oh well
2443 int utcOffSet = anEvent->dtStart().utcOffset();
2444 KDateTime dtStart(anEvent->dtStart().dateTime().addSecs(utcOffSet),
2445 d->mCalendar->timeSpec());
2446 KDateTime dtEnd(anEvent->dtEnd().dateTime().addSecs(utcOffSet),
2447 d->mCalendar->timeSpec());
2448 anEvent->setDtStart(dtStart);
2449 anEvent->setDtEnd(dtEnd);
2450 }
2451 Event::Ptr old = !anEvent->hasRecurrenceId() ?
2452 d->mCalendar->event(anEvent->uid()) :
2453 d->mCalendar->event(anEvent->uid(), anEvent->recurrenceId());
2454
2455 if (old) {
2456 if (deleted) {
2457 d->mCalendar->deleteEvent(old); // move old to deleted
2458 removeAllVCal(d->mEventsRelate, old);
2459 } else if (anEvent->revision() > old->revision()) {
2460 d->mCalendar->deleteEvent(old); // move old to deleted
2461 removeAllVCal(d->mEventsRelate, old);
2462 d->mCalendar->addEvent(anEvent); // and replace it with this one
2463 }
2464 } else if (deleted) {
2465 old = !anEvent->hasRecurrenceId() ?
2466 d->mCalendar->deletedEvent(anEvent->uid()) :
2467 d->mCalendar->deletedEvent(anEvent->uid(), anEvent->recurrenceId());
2468 if (!old) {
2469 d->mCalendar->addEvent(anEvent); // add this one
2470 d->mCalendar->deleteEvent(anEvent); // and move it to deleted
2471 }
2472 } else {
2473 d->mCalendar->addEvent(anEvent); // just add this one
2474 }
2475 }
2476 } else if (strcmp(vObjectName(curVO), VCTodoProp) == 0) {
2477 Todo::Ptr aTodo = VTodoToEvent(curVO);
2478 if (aTodo) {
2479 if (hasTimeZone && !aTodo->allDay() && aTodo->dtStart().isUtc()) {
2480 //This sounds stupid but is how others are doing it, so here
2481 //we go. If there is a TZ in the VCALENDAR even if the dtStart
2482 //and dtend are in UTC, clients interpret it usint alse the TZ defined
2483 //in the Calendar. I know it sounds braindead but oh well
2484 int utcOffSet = aTodo->dtStart().utcOffset();
2485 KDateTime dtStart(aTodo->dtStart().dateTime().addSecs(utcOffSet),
2486 d->mCalendar->timeSpec());
2487 aTodo->setDtStart(dtStart);
2488 if (aTodo->hasDueDate()) {
2489 KDateTime dtDue(aTodo->dtDue().dateTime().addSecs(utcOffSet),
2490 d->mCalendar->timeSpec());
2491 aTodo->setDtDue(dtDue);
2492 }
2493 }
2494 Todo::Ptr old = !aTodo->hasRecurrenceId() ?
2495 d->mCalendar->todo(aTodo->uid()) :
2496 d->mCalendar->todo(aTodo->uid(), aTodo->recurrenceId());
2497 if (old) {
2498 if (deleted) {
2499 d->mCalendar->deleteTodo(old); // move old to deleted
2500 removeAllVCal(d->mTodosRelate, old);
2501 } else if (aTodo->revision() > old->revision()) {
2502 d->mCalendar->deleteTodo(old); // move old to deleted
2503 removeAllVCal(d->mTodosRelate, old);
2504 d->mCalendar->addTodo(aTodo); // and replace it with this one
2505 }
2506 } else if (deleted) {
2507 old = d->mCalendar->deletedTodo(aTodo->uid(), aTodo->recurrenceId());
2508 if (!old) {
2509 d->mCalendar->addTodo(aTodo); // add this one
2510 d->mCalendar->deleteTodo(aTodo); // and move it to deleted
2511 }
2512 } else {
2513 d->mCalendar->addTodo(aTodo); // just add this one
2514 }
2515 }
2516 } else if ((strcmp(vObjectName(curVO), VCVersionProp) == 0) ||
2517 (strcmp(vObjectName(curVO), VCProdIdProp) == 0) ||
2518 (strcmp(vObjectName(curVO), VCTimeZoneProp) == 0)) {
2519 // do nothing, we know these properties and we want to skip them.
2520 // we have either already processed them or are ignoring them.
2521 ;
2522 } else if (strcmp(vObjectName(curVO), VCDayLightProp) == 0) {
2523 // do nothing daylights are already processed
2524 ;
2525 } else {
2526 kDebug() << "Ignoring unknown vObject \"" << vObjectName(curVO) << "\"";
2527 }
2528SKIP:
2529 ;
2530 } // while
2531
2532 // Post-Process list of events with relations, put Event objects in relation
2533 Event::List::ConstIterator eIt;
2534 for (eIt = d->mEventsRelate.constBegin(); eIt != d->mEventsRelate.constEnd(); ++eIt) {
2535 (*eIt)->setRelatedTo((*eIt)->relatedTo());
2536 }
2537 Todo::List::ConstIterator tIt;
2538 for (tIt = d->mTodosRelate.constBegin(); tIt != d->mTodosRelate.constEnd(); ++tIt) {
2539 (*tIt)->setRelatedTo((*tIt)->relatedTo());
2540 }
2541
2542 //Now lets put the TZ back as it was if we have changed it.
2543 if (hasTimeZone) {
2544 d->mCalendar->setTimeSpec(previousSpec);
2545 }
2546
2547}
2548
2549const char *VCalFormat::dayFromNum(int day)
2550{
2551 const char *days[7] = { "MO ", "TU ", "WE ", "TH ", "FR ", "SA ", "SU " };
2552
2553 return days[day];
2554}
2555
2556int VCalFormat::numFromDay(const QString &day)
2557{
2558 if (day == QLatin1String("MO ")) {
2559 return 0;
2560 }
2561 if (day == QLatin1String("TU ")) {
2562 return 1;
2563 }
2564 if (day == QLatin1String("WE ")) {
2565 return 2;
2566 }
2567 if (day == QLatin1String("TH ")) {
2568 return 3;
2569 }
2570 if (day == QLatin1String("FR ")) {
2571 return 4;
2572 }
2573 if (day == QLatin1String("SA ")) {
2574 return 5;
2575 }
2576 if (day == QLatin1String("SU ")) {
2577 return 6;
2578 }
2579
2580 return -1; // something bad happened. :)
2581}
2582
2583Attendee::PartStat VCalFormat::readStatus(const char *s) const
2584{
2585 QString statStr = s;
2586 statStr = statStr.toUpper();
2587 Attendee::PartStat status;
2588
2589 if (statStr == QLatin1String("X-ACTION")) {
2590 status = Attendee::NeedsAction;
2591 } else if (statStr == QLatin1String("NEEDS ACTION")) {
2592 status = Attendee::NeedsAction;
2593 } else if (statStr == QLatin1String("ACCEPTED")) {
2594 status = Attendee::Accepted;
2595 } else if (statStr == QLatin1String("SENT")) {
2596 status = Attendee::NeedsAction;
2597 } else if (statStr == QLatin1String("TENTATIVE")) {
2598 status = Attendee::Tentative;
2599 } else if (statStr == QLatin1String("CONFIRMED")) {
2600 status = Attendee::Accepted;
2601 } else if (statStr == QLatin1String("DECLINED")) {
2602 status = Attendee::Declined;
2603 } else if (statStr == QLatin1String("COMPLETED")) {
2604 status = Attendee::Completed;
2605 } else if (statStr == QLatin1String("DELEGATED")) {
2606 status = Attendee::Delegated;
2607 } else {
2608 kDebug() << "error setting attendee mStatus, unknown mStatus!";
2609 status = Attendee::NeedsAction;
2610 }
2611
2612 return status;
2613}
2614
2615QByteArray VCalFormat::writeStatus(Attendee::PartStat status) const
2616{
2617 switch (status) {
2618 default:
2619 case Attendee::NeedsAction:
2620 return "NEEDS ACTION";
2621 break;
2622 case Attendee::Accepted:
2623 return "ACCEPTED";
2624 break;
2625 case Attendee::Declined:
2626 return "DECLINED";
2627 break;
2628 case Attendee::Tentative:
2629 return "TENTATIVE";
2630 break;
2631 case Attendee::Delegated:
2632 return "DELEGATED";
2633 break;
2634 case Attendee::Completed:
2635 return "COMPLETED";
2636 break;
2637 case Attendee::InProcess:
2638 return "NEEDS ACTION";
2639 break;
2640 }
2641}
2642
2643void VCalFormat::readCustomProperties(VObject *o, const Incidence::Ptr &i)
2644{
2645 VObjectIterator iter;
2646 VObject *cur;
2647 const char *curname;
2648 char *s;
2649
2650 initPropIterator(&iter, o);
2651 while (moreIteration(&iter)) {
2652 cur = nextVObject(&iter);
2653 curname = vObjectName(cur);
2654 Q_ASSERT(curname);
2655 if ((curname[0] == 'X' && curname[1] == '-') &&
2656 strcmp(curname, ICOrganizerProp) != 0) {
2657 // TODO - for the time being, we ignore the parameters part
2658 // and just do the value handling here
2659 i->setNonKDECustomProperty(
2660 curname, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(cur))));
2661 deleteStr(s);
2662 }
2663 }
2664}
2665
2666void VCalFormat::writeCustomProperties(VObject *o, const Incidence::Ptr &i)
2667{
2668 const QMap<QByteArray, QString> custom = i->customProperties();
2669 for (QMap<QByteArray, QString>::ConstIterator c = custom.begin();
2670 c != custom.end(); ++c) {
2671 if (d->mManuallyWrittenExtensionFields.contains(c.key()) ||
2672 c.key().startsWith("X-KDE-VOLATILE")) { //krazy:exclude=strings
2673 continue;
2674 }
2675
2676 addPropValue(o, c.key(), c.value().toUtf8());
2677 }
2678}
2679
2680void VCalFormat::virtual_hook(int id, void *data)
2681{
2682 Q_UNUSED(id);
2683 Q_UNUSED(data);
2684 Q_ASSERT(false);
2685}
2686