1/*
2 This file is part of the kcal library.
3
4 Copyright (c) 2001,2004 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
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#include "scheduler.h"
24#include "calendar.h"
25#ifndef KDEPIM_NO_KRESOURCES
26#include "calendarresources.h"
27#endif
28#include "event.h"
29#include "todo.h"
30#include "freebusy.h"
31#include "freebusycache.h"
32#include "icalformat.h"
33#include "assignmentvisitor.h"
34
35#include <klocalizedstring.h>
36#include <kdebug.h>
37#include <kmessagebox.h>
38#include <kstandarddirs.h>
39
40using namespace KCal;
41
42//@cond PRIVATE
43class KCal::ScheduleMessage::Private
44{
45 public:
46 Private() {}
47
48 IncidenceBase *mIncidence;
49 iTIPMethod mMethod;
50 Status mStatus;
51 QString mError;
52};
53//@endcond
54
55ScheduleMessage::ScheduleMessage( IncidenceBase *incidence,
56 iTIPMethod method,
57 ScheduleMessage::Status status )
58 : d( new KCal::ScheduleMessage::Private )
59{
60 d->mIncidence = incidence;
61 d->mMethod = method;
62 d->mStatus = status;
63}
64
65ScheduleMessage::~ScheduleMessage()
66{
67 delete d;
68}
69
70IncidenceBase *ScheduleMessage::event()
71{
72 return d->mIncidence;
73}
74
75iTIPMethod ScheduleMessage::method()
76{
77 return d->mMethod;
78}
79
80ScheduleMessage::Status ScheduleMessage::status()
81{
82 return d->mStatus;
83}
84
85QString ScheduleMessage::statusName( ScheduleMessage::Status status )
86{
87 switch( status ) {
88 case PublishNew:
89 return i18nc( "@item this is a new scheduling message",
90 "New Scheduling Message" );
91 case PublishUpdate:
92 return i18nc( "@item this is an update to an existing scheduling message",
93 "Updated Scheduling Message" );
94 case Obsolete:
95 return i18nc( "@item obsolete status", "Obsolete" );
96 case RequestNew:
97 return i18nc( "@item this is a request for a new scheduling message",
98 "New Scheduling Message Request" );
99 case RequestUpdate:
100 return i18nc( "@item this is a request for an update to an existing scheduling message",
101 "Updated Scheduling Message Request" );
102 default:
103 return i18nc( "@item unknown status", "Unknown Status: %1", int( status ) );
104 }
105}
106
107QString ScheduleMessage::error()
108{
109 return d->mError;
110}
111
112//@cond PRIVATE
113struct KCal::Scheduler::Private
114{
115 Private()
116 : mFreeBusyCache( 0 )
117 {
118 }
119 FreeBusyCache *mFreeBusyCache;
120};
121//@endcond
122
123Scheduler::Scheduler( Calendar *calendar ) : d( new KCal::Scheduler::Private )
124{
125 mCalendar = calendar;
126 mFormat = new ICalFormat();
127 mFormat->setTimeSpec( calendar->timeSpec() );
128}
129
130Scheduler::~Scheduler()
131{
132 delete mFormat;
133 delete d;
134}
135
136void Scheduler::setFreeBusyCache( FreeBusyCache *c )
137{
138 d->mFreeBusyCache = c;
139}
140
141FreeBusyCache *Scheduler::freeBusyCache() const
142{
143 return d->mFreeBusyCache;
144}
145
146bool Scheduler::acceptTransaction( IncidenceBase *incidence,
147 iTIPMethod method,
148 ScheduleMessage::Status status )
149{
150 return acceptTransaction( incidence, method, status, QString() );
151}
152
153bool Scheduler::acceptTransaction( IncidenceBase *incidence,
154 iTIPMethod method,
155 ScheduleMessage::Status status,
156 const QString &email )
157{
158 kDebug() << "method=" << methodName( method );
159
160 switch ( method ) {
161 case iTIPPublish:
162 return acceptPublish( incidence, status, method );
163 case iTIPRequest:
164 return acceptRequest( incidence, status, email );
165 case iTIPAdd:
166 return acceptAdd( incidence, status );
167 case iTIPCancel:
168 return acceptCancel( incidence, status, email );
169 case iTIPDeclineCounter:
170 return acceptDeclineCounter( incidence, status );
171 case iTIPReply:
172 return acceptReply( incidence, status, method );
173 case iTIPRefresh:
174 return acceptRefresh( incidence, status );
175 case iTIPCounter:
176 return acceptCounter( incidence, status );
177 default:
178 break;
179 }
180 deleteTransaction( incidence );
181 return false;
182}
183
184QString Scheduler::methodName( iTIPMethod method )
185{
186 switch ( method ) {
187 case iTIPPublish:
188 return QLatin1String( "Publish" );
189 case iTIPRequest:
190 return QLatin1String( "Request" );
191 case iTIPRefresh:
192 return QLatin1String( "Refresh" );
193 case iTIPCancel:
194 return QLatin1String( "Cancel" );
195 case iTIPAdd:
196 return QLatin1String( "Add" );
197 case iTIPReply:
198 return QLatin1String( "Reply" );
199 case iTIPCounter:
200 return QLatin1String( "Counter" );
201 case iTIPDeclineCounter:
202 return QLatin1String( "Decline Counter" );
203 default:
204 return QLatin1String( "Unknown" );
205 }
206}
207
208QString Scheduler::translatedMethodName( iTIPMethod method )
209{
210 switch ( method ) {
211 case iTIPPublish:
212 return i18nc( "@item event, to-do, journal or freebusy posting", "Publish" );
213 case iTIPRequest:
214 return i18nc( "@item event, to-do or freebusy scheduling requests", "Request" );
215 case iTIPReply:
216 return i18nc( "@item event, to-do or freebusy reply to request", "Reply" );
217 case iTIPAdd:
218 return i18nc(
219 "@item event, to-do or journal additional property request", "Add" );
220 case iTIPCancel:
221 return i18nc( "@item event, to-do or journal cancellation notice", "Cancel" );
222 case iTIPRefresh:
223 return i18nc( "@item event or to-do description update request", "Refresh" );
224 case iTIPCounter:
225 return i18nc( "@item event or to-do submit counter proposal", "Counter" );
226 case iTIPDeclineCounter:
227 return i18nc( "@item event or to-do decline a counter proposal", "Decline Counter" );
228 default:
229 return i18nc( "@item no method", "Unknown" );
230 }
231}
232
233bool Scheduler::deleteTransaction( IncidenceBase * )
234{
235 return true;
236}
237
238bool Scheduler::acceptPublish( IncidenceBase *newIncBase,
239 ScheduleMessage::Status status,
240 iTIPMethod method )
241{
242 if( newIncBase->type() == "FreeBusy" ) {
243 return acceptFreeBusy( newIncBase, method );
244 }
245
246 bool res = false;
247
248 kDebug() << "status=" << ScheduleMessage::statusName( status );
249
250 Incidence *newInc = static_cast<Incidence *>( newIncBase );
251 Incidence *calInc = mCalendar->incidence( newIncBase->uid() );
252 switch ( status ) {
253 case ScheduleMessage::Unknown:
254 case ScheduleMessage::PublishNew:
255 case ScheduleMessage::PublishUpdate:
256 if ( calInc && newInc ) {
257 if ( ( newInc->revision() > calInc->revision() ) ||
258 ( newInc->revision() == calInc->revision() &&
259 newInc->lastModified() > calInc->lastModified() ) ) {
260 AssignmentVisitor visitor;
261 const QString oldUid = calInc->uid();
262 if ( !visitor.assign( calInc, newInc ) ) {
263 kError() << "assigning different incidence types";
264 } else {
265 calInc->setSchedulingID( newInc->uid() );
266 calInc->setUid( oldUid );
267 res = true;
268 }
269 }
270 }
271 break;
272 case ScheduleMessage::Obsolete:
273 res = true;
274 break;
275 default:
276 break;
277 }
278 deleteTransaction( newIncBase );
279 return res;
280}
281
282bool Scheduler::acceptRequest( IncidenceBase *incidence,
283 ScheduleMessage::Status status )
284{
285 return acceptRequest( incidence, status, QString() );
286}
287
288bool Scheduler::acceptRequest( IncidenceBase *incidence,
289 ScheduleMessage::Status status,
290 const QString &email )
291{
292 Incidence *inc = static_cast<Incidence *>( incidence );
293 if ( !inc ) {
294 return false;
295 }
296 if ( inc->type() == "FreeBusy" ) {
297 // reply to this request is handled in korganizer's incomingdialog
298 return true;
299 }
300
301 const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() );
302 kDebug() << "status=" << ScheduleMessage::statusName( status )
303 << ": found " << existingIncidences.count()
304 << " incidences with schedulingID " << inc->schedulingID();
305 Incidence::List::ConstIterator incit = existingIncidences.begin();
306 for ( ; incit != existingIncidences.end() ; ++incit ) {
307 Incidence *i = *incit;
308 kDebug() << "Considering this found event ("
309 << ( i->isReadOnly() ? "readonly" : "readwrite" )
310 << ") :" << mFormat->toString( i );
311 // If it's readonly, we can't possible update it.
312 if ( i->isReadOnly() ) {
313 continue;
314 }
315 if ( i->revision() <= inc->revision() ) {
316 // The new incidence might be an update for the found one
317 bool isUpdate = true;
318 // Code for new invitations:
319 // If you think we could check the value of "status" to be RequestNew: we can't.
320 // It comes from a similar check inside libical, where the event is compared to
321 // other events in the calendar. But if we have another version of the event around
322 // (e.g. shared folder for a group), the status could be RequestNew, Obsolete or Updated.
323 kDebug() << "looking in " << i->uid() << "'s attendees";
324 // This is supposed to be a new request, not an update - however we want to update
325 // the existing one to handle the "clicking more than once on the invitation" case.
326 // So check the attendee status of the attendee.
327 const KCal::Attendee::List attendees = i->attendees();
328 KCal::Attendee::List::ConstIterator ait;
329 for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) {
330 if( (*ait)->email() == email && (*ait)->status() == Attendee::NeedsAction ) {
331 // This incidence wasn't created by me - it's probably in a shared folder
332 // and meant for someone else, ignore it.
333 kDebug() << "ignoring " << i->uid() << " since I'm still NeedsAction there";
334 isUpdate = false;
335 break;
336 }
337 }
338 if ( isUpdate ) {
339 if ( i->revision() == inc->revision() &&
340 i->lastModified() > inc->lastModified() ) {
341 // This isn't an update - the found incidence was modified more recently
342 kDebug() << "This isn't an update - the found incidence was modified more recently";
343 deleteTransaction( i );
344 return false;
345 }
346 kDebug() << "replacing existing incidence " << i->uid();
347 bool res = true;
348 AssignmentVisitor visitor;
349 const QString oldUid = i->uid();
350 if ( !visitor.assign( i, inc ) ) {
351 kError() << "assigning different incidence types";
352 res = false;
353 } else {
354 i->setUid( oldUid );
355 i->setSchedulingID( inc->uid() );
356 }
357 deleteTransaction( incidence );
358 return res;
359 }
360 } else {
361 // This isn't an update - the found incidence has a bigger revision number
362 kDebug() << "This isn't an update - the found incidence has a bigger revision number";
363 deleteTransaction( incidence );
364 return false;
365 }
366 }
367
368 // Move the uid to be the schedulingID and make a unique UID
369 inc->setSchedulingID( inc->uid() );
370 inc->setUid( CalFormat::createUniqueId() );
371 // in case this is an update and we didn't find the to-be-updated incidence,
372 // ask whether we should create a new one, or drop the update
373 if ( existingIncidences.count() > 0 || inc->revision() == 0 ||
374 KMessageBox::questionYesNo(
375 0,
376 i18nc( "@info",
377 "The event, to-do or journal to be updated could not be found. "
378 "Maybe it has already been deleted, or the calendar that "
379 "contains it is disabled. Press 'Store' to create a new "
380 "one or 'Throw away' to discard this update." ),
381 i18nc( "@title", "Discard this update?" ),
382 KGuiItem( i18nc( "@option", "Store" ) ),
383 KGuiItem( i18nc( "@option", "Throw away" ) ),
384 "AcceptCantFindIncidence" ) == KMessageBox::Yes ) {
385 kDebug() << "Storing new incidence with scheduling uid=" << inc->schedulingID()
386 << " and uid=" << inc->uid();
387
388#ifndef KDEPIM_NO_KRESOURCES
389 CalendarResources *stdcal = dynamic_cast<CalendarResources *>( mCalendar );
390 if( stdcal && !stdcal->hasCalendarResources() ) {
391 KMessageBox::sorry(
392 0,
393 i18nc( "@info", "No calendars found, unable to save the invitation." ) );
394 return false;
395 }
396
397 // FIXME: This is a nasty hack, since we need to set a parent for the
398 // resource selection dialog. However, we don't have any UI methods
399 // in the calendar, only in the CalendarResources::DestinationPolicy
400 // So we need to type-cast it and extract it from the CalendarResources
401 if ( stdcal ) {
402 stdcal->setDialogParentWidget( 0 );
403 }
404#endif
405
406 TryAgain:
407 bool success = false;
408#ifndef KDEPIM_NO_KRESOURCES
409 if ( stdcal )
410 success = stdcal->addIncidence( inc );
411 else
412#endif
413 success = mCalendar->addIncidence( inc );
414
415 if ( !success ) {
416#ifndef KDEPIM_NO_KRESOURCES
417 ErrorFormat *e = stdcal ? stdcal->exception() : 0;
418#else
419 ErrorFormat *e = 0;
420#endif
421
422 if ( e && e->errorCode() == KCal::ErrorFormat::UserCancel &&
423 KMessageBox::warningYesNo(
424 0,
425 i18nc( "@info",
426 "You canceled the save operation. Therefore, the appointment will not be "
427 "stored in your calendar even though you accepted the invitation. "
428 "Are you certain you want to discard this invitation? " ),
429 i18nc( "@title", "Discard this invitation?" ),
430 KGuiItem( i18nc( "@option", "Discard" ) ),
431 KGuiItem( i18nc( "@option", "Go Back to Folder Selection" ) ) ) == KMessageBox::Yes ) {
432 KMessageBox::information(
433 0,
434 i18nc( "@info",
435 "The invitation \"%1\" was not saved to your calendar "
436 "but you are still listed as an attendee for that appointment.\n"
437 "If you mistakenly accepted the invitation or do not plan to attend, please "
438 "notify the organizer %2 and ask them to remove you from the attendee list.",
439 inc->summary(), inc->organizer().fullName() ) );
440 deleteTransaction( incidence );
441 return true;
442 } else {
443 goto TryAgain;
444 }
445
446 // We can have a failure if the user pressed [cancel] in the resource
447 // selectdialog, so check the exception.
448 if ( !e ||
449 ( e && ( e->errorCode() != KCal::ErrorFormat::UserCancel &&
450 e->errorCode() != KCal::ErrorFormat::NoWritableFound ) ) ) {
451 QString errMessage = i18nc( "@info", "Unable to save %1 \"%2\".",
452 i18n( inc->type() ), inc->summary() );
453 KMessageBox::sorry( 0, errMessage );
454 }
455 return false;
456 }
457 }
458 deleteTransaction( incidence );
459 return true;
460}
461
462bool Scheduler::acceptAdd( IncidenceBase *incidence, ScheduleMessage::Status /* status */)
463{
464 deleteTransaction( incidence );
465 return false;
466}
467
468bool Scheduler::acceptCancel( IncidenceBase *incidence,
469 ScheduleMessage::Status status,
470 const QString &attendee )
471{
472 Incidence *inc = static_cast<Incidence *>( incidence );
473 if ( !inc ) {
474 return false;
475 }
476
477 if ( inc->type() == "FreeBusy" ) {
478 // reply to this request is handled in korganizer's incomingdialog
479 return true;
480 }
481
482 const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() );
483 kDebug() << "Scheduler::acceptCancel="
484 << ScheduleMessage::statusName( status )
485 << ": found " << existingIncidences.count()
486 << " incidences with schedulingID " << inc->schedulingID();
487
488 bool ret = false;
489 Incidence::List::ConstIterator incit = existingIncidences.begin();
490 for ( ; incit != existingIncidences.end() ; ++incit ) {
491 Incidence *i = *incit;
492 kDebug() << "Considering this found event ("
493 << ( i->isReadOnly() ? "readonly" : "readwrite" )
494 << ") :" << mFormat->toString( i );
495
496 // If it's readonly, we can't possible remove it.
497 if ( i->isReadOnly() ) {
498 continue;
499 }
500
501 // Code for new invitations:
502 // We cannot check the value of "status" to be RequestNew because
503 // "status" comes from a similar check inside libical, where the event
504 // is compared to other events in the calendar. But if we have another
505 // version of the event around (e.g. shared folder for a group), the
506 // status could be RequestNew, Obsolete or Updated.
507 kDebug() << "looking in " << i->uid() << "'s attendees";
508
509 // This is supposed to be a new request, not an update - however we want
510 // to update the existing one to handle the "clicking more than once
511 // on the invitation" case. So check the attendee status of the attendee.
512 bool isMine = true;
513 const KCal::Attendee::List attendees = i->attendees();
514 KCal::Attendee::List::ConstIterator ait;
515 for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) {
516 if ( (*ait)->email() == attendee &&
517 (*ait)->status() == Attendee::NeedsAction ) {
518 // This incidence wasn't created by me - it's probably in a shared
519 // folder and meant for someone else, ignore it.
520 kDebug() << "ignoring " << i->uid()
521 << " since I'm still NeedsAction there";
522 isMine = false;
523 break;
524 }
525 }
526
527 if ( isMine ) {
528 kDebug() << "removing existing incidence " << i->uid();
529 if ( i->type() == "Event" ) {
530 Event *event = mCalendar->event( i->uid() );
531 ret = ( event && mCalendar->deleteEvent( event ) );
532 } else if ( i->type() == "Todo" ) {
533 Todo *todo = mCalendar->todo( i->uid() );
534 ret = ( todo && mCalendar->deleteTodo( todo ) );
535 }
536 deleteTransaction( incidence );
537 return ret;
538 }
539 }
540
541 // in case we didn't find the to-be-removed incidence
542 if ( existingIncidences.count() > 0 && inc->revision() > 0 ) {
543 KMessageBox::information(
544 0,
545 i18nc( "@info",
546 "The event or task could not be removed from your calendar. "
547 "Maybe it has already been deleted or is not owned by you. "
548 "Or it might belong to a read-only or disabled calendar." ) );
549 }
550 deleteTransaction( incidence );
551 return ret;
552}
553
554bool Scheduler::acceptCancel( IncidenceBase *incidence,
555 ScheduleMessage::Status status )
556{
557 Q_UNUSED( status );
558
559 const IncidenceBase *toDelete = mCalendar->incidenceFromSchedulingID( incidence->uid() );
560
561 bool ret = true;
562 if ( toDelete ) {
563 if ( toDelete->type() == "Event" ) {
564 Event *event = mCalendar->event( toDelete->uid() );
565 ret = ( event && mCalendar->deleteEvent( event ) );
566 } else if ( toDelete->type() == "Todo" ) {
567 Todo *todo = mCalendar->todo( toDelete->uid() );
568 ret = ( todo && mCalendar->deleteTodo( todo ) );
569 }
570 } else {
571 // only complain if we failed to determine the toDelete incidence
572 // on non-initial request.
573 Incidence *inc = static_cast<Incidence *>( incidence );
574 if ( inc->revision() > 0 ) {
575 ret = false;
576 }
577 }
578
579 if ( !ret ) {
580 KMessageBox::information(
581 0,
582 i18nc( "@info",
583 "The event or task to be canceled could not be removed from your calendar. "
584 "Maybe it has already been deleted or is not owned by you. "
585 "Or it might belong to a read-only or disabled calendar." ) );
586 }
587 deleteTransaction( incidence );
588 return ret;
589}
590
591bool Scheduler::acceptDeclineCounter( IncidenceBase *incidence,
592 ScheduleMessage::Status status )
593{
594 Q_UNUSED( status );
595 deleteTransaction( incidence );
596 return false;
597}
598
599bool Scheduler::acceptReply( IncidenceBase *incidence,
600 ScheduleMessage::Status status,
601 iTIPMethod method )
602{
603 Q_UNUSED( status );
604 if ( incidence->type() == "FreeBusy" ) {
605 return acceptFreeBusy( incidence, method );
606 }
607 bool ret = false;
608 Event *ev = mCalendar->event( incidence->uid() );
609 Todo *to = mCalendar->todo( incidence->uid() );
610
611 // try harder to find the correct incidence
612 if ( !ev && !to ) {
613 const Incidence::List list = mCalendar->incidences();
614 for ( Incidence::List::ConstIterator it=list.constBegin(), end=list.constEnd();
615 it != end; ++it ) {
616 if ( (*it)->schedulingID() == incidence->uid() ) {
617 ev = dynamic_cast<Event*>( *it );
618 to = dynamic_cast<Todo*>( *it );
619 break;
620 }
621 }
622 }
623
624 if ( ev || to ) {
625 //get matching attendee in calendar
626 kDebug() << "match found!";
627 Attendee::List attendeesIn = incidence->attendees();
628 Attendee::List attendeesEv;
629 Attendee::List attendeesNew;
630 if ( ev ) {
631 attendeesEv = ev->attendees();
632 }
633 if ( to ) {
634 attendeesEv = to->attendees();
635 }
636 Attendee::List::ConstIterator inIt;
637 Attendee::List::ConstIterator evIt;
638 for ( inIt = attendeesIn.constBegin(); inIt != attendeesIn.constEnd(); ++inIt ) {
639 Attendee *attIn = *inIt;
640 bool found = false;
641 for ( evIt = attendeesEv.constBegin(); evIt != attendeesEv.constEnd(); ++evIt ) {
642 Attendee *attEv = *evIt;
643 if ( attIn->email().toLower() == attEv->email().toLower() ) {
644 //update attendee-info
645 kDebug() << "update attendee";
646 attEv->setStatus( attIn->status() );
647 attEv->setDelegate( attIn->delegate() );
648 attEv->setDelegator( attIn->delegator() );
649 ret = true;
650 found = true;
651 }
652 }
653 if ( !found && attIn->status() != Attendee::Declined ) {
654 attendeesNew.append( attIn );
655 }
656 }
657
658 bool attendeeAdded = false;
659 for ( Attendee::List::ConstIterator it = attendeesNew.constBegin();
660 it != attendeesNew.constEnd(); ++it ) {
661 Attendee *attNew = *it;
662 QString msg =
663 i18nc( "@info", "%1 wants to attend %2 but was not invited.",
664 attNew->fullName(),
665 ( ev ? ev->summary() : to->summary() ) );
666 if ( !attNew->delegator().isEmpty() ) {
667 msg = i18nc( "@info", "%1 wants to attend %2 on behalf of %3.",
668 attNew->fullName(),
669 ( ev ? ev->summary() : to->summary() ), attNew->delegator() );
670 }
671 if ( KMessageBox::questionYesNo(
672 0, msg, i18nc( "@title", "Uninvited attendee" ),
673 KGuiItem( i18nc( "@option", "Accept Attendance" ) ),
674 KGuiItem( i18nc( "@option", "Reject Attendance" ) ) ) != KMessageBox::Yes ) {
675 KCal::Incidence *cancel = dynamic_cast<Incidence*>( incidence );
676 if ( cancel ) {
677 cancel->addComment(
678 i18nc( "@info",
679 "The organizer rejected your attendance at this meeting." ) );
680 }
681 performTransaction( cancel ? cancel : incidence, iTIPCancel, attNew->fullName() );
682 // ### can't delete cancel here because it is aliased to incidence which
683 // is accessed in the next loop iteration (CID 4232)
684 // delete cancel;
685 continue;
686 }
687
688 Attendee *a = new Attendee( attNew->name(), attNew->email(), attNew->RSVP(),
689 attNew->status(), attNew->role(), attNew->uid() );
690 a->setDelegate( attNew->delegate() );
691 a->setDelegator( attNew->delegator() );
692 if ( ev ) {
693 ev->addAttendee( a );
694 } else if ( to ) {
695 to->addAttendee( a );
696 }
697 ret = true;
698 attendeeAdded = true;
699 }
700
701 // send update about new participants
702 if ( attendeeAdded ) {
703 bool sendMail = false;
704 if ( ev || to ) {
705 if ( KMessageBox::questionYesNo(
706 0,
707 i18nc( "@info",
708 "An attendee was added to the incidence. "
709 "Do you want to email the attendees an update message?" ),
710 i18nc( "@title", "Attendee Added" ),
711 KGuiItem( i18nc( "@option", "Send Messages" ) ),
712 KGuiItem( i18nc( "@option", "Do Not Send" ) ) ) == KMessageBox::Yes ) {
713 sendMail = true;
714 }
715 }
716
717 if ( ev ) {
718 ev->setRevision( ev->revision() + 1 );
719 if ( sendMail ) {
720 performTransaction( ev, iTIPRequest );
721 }
722 }
723 if ( to ) {
724 to->setRevision( to->revision() + 1 );
725 if ( sendMail ) {
726 performTransaction( to, iTIPRequest );
727 }
728 }
729 }
730
731 if ( ret ) {
732 // We set at least one of the attendees, so the incidence changed
733 // Note: This should not result in a sequence number bump
734 if ( ev ) {
735 ev->updated();
736 } else if ( to ) {
737 to->updated();
738 }
739 }
740 if ( to ) {
741 // for VTODO a REPLY can be used to update the completion status of
742 // a to-do. see RFC2446 3.4.3
743 Todo *update = dynamic_cast<Todo*> ( incidence );
744 Q_ASSERT( update );
745 if ( update && ( to->percentComplete() != update->percentComplete() ) ) {
746 to->setPercentComplete( update->percentComplete() );
747 to->updated();
748 }
749 }
750 } else {
751 kError() << "No incidence for scheduling.";
752 }
753
754 if ( ret ) {
755 deleteTransaction( incidence );
756 }
757 return ret;
758}
759
760bool Scheduler::acceptRefresh( IncidenceBase *incidence, ScheduleMessage::Status status )
761{
762 Q_UNUSED( status );
763 // handled in korganizer's IncomingDialog
764 deleteTransaction( incidence );
765 return false;
766}
767
768bool Scheduler::acceptCounter( IncidenceBase *incidence, ScheduleMessage::Status status )
769{
770 Q_UNUSED( status );
771 deleteTransaction( incidence );
772 return false;
773}
774
775bool Scheduler::acceptFreeBusy( IncidenceBase *incidence, iTIPMethod method )
776{
777 if ( !d->mFreeBusyCache ) {
778 kError() << "KCal::Scheduler: no FreeBusyCache.";
779 return false;
780 }
781
782 FreeBusy *freebusy = static_cast<FreeBusy *>(incidence);
783
784 kDebug() << "freeBusyDirName:" << freeBusyDir();
785
786 Person from;
787 if( method == iTIPPublish ) {
788 from = freebusy->organizer();
789 }
790 if ( ( method == iTIPReply ) && ( freebusy->attendeeCount() == 1 ) ) {
791 Attendee *attendee = freebusy->attendees().first();
792 from.setName( attendee->name() );
793 from.setEmail( attendee->email() );
794 }
795
796 if ( !d->mFreeBusyCache->saveFreeBusy( freebusy, from ) ) {
797 return false;
798 }
799
800 deleteTransaction( incidence );
801 return true;
802}
803