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 | |
40 | using namespace KCal; |
41 | |
42 | //@cond PRIVATE |
43 | class 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 | |
55 | ScheduleMessage::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 | |
65 | ScheduleMessage::~ScheduleMessage() |
66 | { |
67 | delete d; |
68 | } |
69 | |
70 | IncidenceBase *ScheduleMessage::event() |
71 | { |
72 | return d->mIncidence; |
73 | } |
74 | |
75 | iTIPMethod ScheduleMessage::method() |
76 | { |
77 | return d->mMethod; |
78 | } |
79 | |
80 | ScheduleMessage::Status ScheduleMessage::status() |
81 | { |
82 | return d->mStatus; |
83 | } |
84 | |
85 | QString 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 | |
107 | QString ScheduleMessage::error() |
108 | { |
109 | return d->mError; |
110 | } |
111 | |
112 | //@cond PRIVATE |
113 | struct KCal::Scheduler::Private |
114 | { |
115 | Private() |
116 | : mFreeBusyCache( 0 ) |
117 | { |
118 | } |
119 | FreeBusyCache *mFreeBusyCache; |
120 | }; |
121 | //@endcond |
122 | |
123 | Scheduler::Scheduler( Calendar *calendar ) : d( new KCal::Scheduler::Private ) |
124 | { |
125 | mCalendar = calendar; |
126 | mFormat = new ICalFormat(); |
127 | mFormat->setTimeSpec( calendar->timeSpec() ); |
128 | } |
129 | |
130 | Scheduler::~Scheduler() |
131 | { |
132 | delete mFormat; |
133 | delete d; |
134 | } |
135 | |
136 | void Scheduler::setFreeBusyCache( FreeBusyCache *c ) |
137 | { |
138 | d->mFreeBusyCache = c; |
139 | } |
140 | |
141 | FreeBusyCache *Scheduler::freeBusyCache() const |
142 | { |
143 | return d->mFreeBusyCache; |
144 | } |
145 | |
146 | bool Scheduler::acceptTransaction( IncidenceBase *incidence, |
147 | iTIPMethod method, |
148 | ScheduleMessage::Status status ) |
149 | { |
150 | return acceptTransaction( incidence, method, status, QString() ); |
151 | } |
152 | |
153 | bool 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 | |
184 | QString 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 | |
208 | QString 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 | |
233 | bool Scheduler::deleteTransaction( IncidenceBase * ) |
234 | { |
235 | return true; |
236 | } |
237 | |
238 | bool 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 | |
282 | bool Scheduler::acceptRequest( IncidenceBase *incidence, |
283 | ScheduleMessage::Status status ) |
284 | { |
285 | return acceptRequest( incidence, status, QString() ); |
286 | } |
287 | |
288 | bool 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 | |
462 | bool Scheduler::acceptAdd( IncidenceBase *incidence, ScheduleMessage::Status /* status */) |
463 | { |
464 | deleteTransaction( incidence ); |
465 | return false; |
466 | } |
467 | |
468 | bool 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 | |
554 | bool 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 | |
591 | bool Scheduler::acceptDeclineCounter( IncidenceBase *incidence, |
592 | ScheduleMessage::Status status ) |
593 | { |
594 | Q_UNUSED( status ); |
595 | deleteTransaction( incidence ); |
596 | return false; |
597 | } |
598 | |
599 | bool 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 | |
760 | bool 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 | |
768 | bool Scheduler::acceptCounter( IncidenceBase *incidence, ScheduleMessage::Status status ) |
769 | { |
770 | Q_UNUSED( status ); |
771 | deleteTransaction( incidence ); |
772 | return false; |
773 | } |
774 | |
775 | bool 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 | |