00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "scheduler.h"
00023 #include "stringify.h"
00024
00025 #include <kcalcore/icalformat.h>
00026 #include <kcalcore/freebusycache.h>
00027 using namespace KCalCore;
00028
00029 #include <KDebug>
00030 #include <KLocale>
00031 #include <KMessageBox>
00032
00033 using namespace KCalUtils;
00034
00035
00036 struct KCalUtils::Scheduler::Private
00037 {
00038 public:
00039 Private() : mFreeBusyCache( 0 )
00040 {
00041 }
00042 FreeBusyCache *mFreeBusyCache;
00043 };
00044
00045
00046 Scheduler::Scheduler( const Calendar::Ptr &calendar ) : d( new KCalUtils::Scheduler::Private )
00047 {
00048 mCalendar = calendar;
00049 mFormat = new ICalFormat();
00050 mFormat->setTimeSpec( calendar->timeSpec() );
00051 }
00052
00053 Scheduler::~Scheduler()
00054 {
00055 delete mFormat;
00056 delete d;
00057 }
00058
00059 void Scheduler::setFreeBusyCache( FreeBusyCache *c )
00060 {
00061 d->mFreeBusyCache = c;
00062 }
00063
00064 FreeBusyCache *Scheduler::freeBusyCache() const
00065 {
00066 return d->mFreeBusyCache;
00067 }
00068
00069 bool Scheduler::acceptTransaction( const IncidenceBase::Ptr &incidence, iTIPMethod method,
00070 ScheduleMessage::Status status, const QString &email )
00071 {
00072 kDebug() << "method=" << ScheduleMessage::methodName( method );
00073
00074 switch ( method ) {
00075 case iTIPPublish:
00076 return acceptPublish( incidence, status, method );
00077 case iTIPRequest:
00078 return acceptRequest( incidence, status, email );
00079 case iTIPAdd:
00080 return acceptAdd( incidence, status );
00081 case iTIPCancel:
00082 return acceptCancel( incidence, status, email );
00083 case iTIPDeclineCounter:
00084 return acceptDeclineCounter( incidence, status );
00085 case iTIPReply:
00086 return acceptReply( incidence, status, method );
00087 case iTIPRefresh:
00088 return acceptRefresh( incidence, status );
00089 case iTIPCounter:
00090 return acceptCounter( incidence, status );
00091 default:
00092 break;
00093 }
00094 deleteTransaction( incidence );
00095 return false;
00096 }
00097
00098 bool Scheduler::deleteTransaction( const IncidenceBase::Ptr & )
00099 {
00100 return true;
00101 }
00102
00103 bool Scheduler::acceptPublish( const IncidenceBase::Ptr &newIncBase, ScheduleMessage::Status status,
00104 iTIPMethod method )
00105 {
00106 if ( newIncBase->type() == IncidenceBase::TypeFreeBusy ) {
00107 return acceptFreeBusy( newIncBase, method );
00108 }
00109
00110 bool res = false;
00111
00112 kDebug() << "status=" << Stringify::scheduleMessageStatus( status );
00113
00114 Incidence::Ptr newInc = newIncBase.staticCast<Incidence>() ;
00115 Incidence::Ptr calInc = mCalendar->incidence( newIncBase->uid() );
00116 switch ( status ) {
00117 case ScheduleMessage::Unknown:
00118 case ScheduleMessage::PublishNew:
00119 case ScheduleMessage::PublishUpdate:
00120 if ( calInc && newInc ) {
00121 if ( ( newInc->revision() > calInc->revision() ) ||
00122 ( newInc->revision() == calInc->revision() &&
00123 newInc->lastModified() > calInc->lastModified() ) ) {
00124 const QString oldUid = calInc->uid();
00125
00126 if ( calInc->type() != newInc->type() ) {
00127 kError() << "assigning different incidence types";
00128 } else {
00129 IncidenceBase *ci = calInc.data();
00130 IncidenceBase *ni = newInc.data();
00131 *ci = *ni;
00132 calInc->setSchedulingID( newInc->uid(), oldUid );
00133 res = true;
00134 }
00135 }
00136 }
00137 break;
00138 case ScheduleMessage::Obsolete:
00139 res = true;
00140 break;
00141 default:
00142 break;
00143 }
00144 deleteTransaction( newIncBase );
00145 return res;
00146 }
00147
00148 bool Scheduler::acceptRequest( const IncidenceBase::Ptr &incidence,
00149 ScheduleMessage::Status status,
00150 const QString &email )
00151 {
00152 Incidence::Ptr inc = incidence.staticCast<Incidence>() ;
00153 if ( !inc ) {
00154 kWarning() << "Accept what?";
00155 return false;
00156 }
00157 if ( inc->type() == IncidenceBase::TypeFreeBusy ) {
00158
00159 return true;
00160 }
00161
00162 const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() );
00163 kDebug() << "status=" << Stringify::scheduleMessageStatus( status )
00164 << ": found " << existingIncidences.count()
00165 << " incidences with schedulingID " << inc->schedulingID()
00166 << "; uid was = " << inc->uid();
00167
00168 if ( existingIncidences.isEmpty() ) {
00169
00170
00171 kDebug() << "incidence not found; calendar = " << mCalendar.data()
00172 << "; incidence count = " << mCalendar->incidences().count();
00173 }
00174 Incidence::List::ConstIterator incit = existingIncidences.begin();
00175 for ( ; incit != existingIncidences.end() ; ++incit ) {
00176 Incidence::Ptr existingIncidence = *incit;
00177 kDebug() << "Considering this found event ("
00178 << ( existingIncidence->isReadOnly() ? "readonly" : "readwrite" )
00179 << ") :" << mFormat->toString( existingIncidence );
00180
00181 if ( existingIncidence->isReadOnly() ) {
00182 continue;
00183 }
00184 if ( existingIncidence->revision() <= inc->revision() ) {
00185
00186 bool isUpdate = true;
00187
00188
00189
00190
00191
00192 kDebug() << "looking in " << existingIncidence->uid() << "'s attendees";
00193
00194
00195
00196 const Attendee::List attendees = existingIncidence->attendees();
00197 Attendee::List::ConstIterator ait;
00198 for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) {
00199 if( (*ait)->email() == email && (*ait)->status() == Attendee::NeedsAction ) {
00200
00201
00202 kDebug() << "ignoring " << existingIncidence->uid() << " since I'm still NeedsAction there";
00203 isUpdate = false;
00204 break;
00205 }
00206 }
00207 if ( isUpdate ) {
00208 if ( existingIncidence->revision() == inc->revision() &&
00209 existingIncidence->lastModified() > inc->lastModified() ) {
00210
00211 kDebug() << "This isn't an update - the found incidence was modified more recently";
00212 deleteTransaction( existingIncidence );
00213 return false;
00214 }
00215 kDebug() << "replacing existing incidence " << existingIncidence->uid();
00216 bool res = true;
00217 const QString oldUid = existingIncidence->uid();
00218 if ( existingIncidence->type() != inc->type() ) {
00219 kError() << "assigning different incidence types";
00220 res = false;
00221 } else {
00222 IncidenceBase *existingIncidenceBase = existingIncidence.data();
00223 IncidenceBase *incBase = inc.data();
00224 *existingIncidenceBase = *incBase;
00225 existingIncidence->setSchedulingID( inc->uid(), oldUid );
00226 }
00227 deleteTransaction( incidence );
00228 return res;
00229 }
00230 } else {
00231
00232 kDebug() << "This isn't an update - the found incidence has a bigger revision number";
00233 deleteTransaction( incidence );
00234 return false;
00235 }
00236 }
00237
00238
00239 inc->setSchedulingID( inc->uid(), CalFormat::createUniqueId() );
00240
00241 if ( existingIncidences.count() == 0 && inc->revision() > 0 ) {
00242 KMessageBox::information(
00243 0,
00244 i18nc( "@info",
00245 "<para>You accepted an invitation update, but an earlier version of the "
00246 "item could not be found in your calendar.</para>"
00247 "<para>This may have occurred because:<list>"
00248 "<item>the organizer did not include you in the original invitation</item>"
00249 "<item>you did not accept the original invitation yet</item>"
00250 "<item>you deleted the original invitation from your calendar</item>"
00251 "<item>you no longer have access to the calendar containing the invitation</item>"
00252 "</list></para>"
00253 "<para>This is not a problem, but we thought you should know.</para>" ),
00254 i18nc( "@title", "Cannot find invitation to be updated" ), "AcceptCantFindIncidence" );
00255 }
00256 kDebug() << "Storing new incidence with scheduling uid=" << inc->schedulingID()
00257 << " and uid=" << inc->uid();
00258 mCalendar->addIncidence( inc );
00259
00260 deleteTransaction( incidence );
00261 return true;
00262 }
00263
00264 bool Scheduler::acceptAdd( const IncidenceBase::Ptr &incidence,
00265 ScheduleMessage::Status )
00266 {
00267 deleteTransaction( incidence );
00268 return false;
00269 }
00270
00271 bool Scheduler::acceptCancel( const IncidenceBase::Ptr &incidence,
00272 ScheduleMessage::Status status,
00273 const QString &attendee )
00274 {
00275 Incidence::Ptr inc = incidence.staticCast<Incidence>();
00276 if ( !inc ) {
00277 return false;
00278 }
00279
00280 if ( inc->type() == IncidenceBase::TypeFreeBusy ) {
00281
00282 return true;
00283 }
00284
00285 const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() );
00286 kDebug() << "Scheduler::acceptCancel="
00287 << Stringify::scheduleMessageStatus( status )
00288 << ": found " << existingIncidences.count()
00289 << " incidences with schedulingID " << inc->schedulingID();
00290
00291 bool ret = false;
00292 Incidence::List::ConstIterator incit = existingIncidences.begin();
00293 for ( ; incit != existingIncidences.end() ; ++incit ) {
00294 Incidence::Ptr i = *incit;
00295 kDebug() << "Considering this found event ("
00296 << ( i->isReadOnly() ? "readonly" : "readwrite" )
00297 << ") :" << mFormat->toString( i );
00298
00299
00300 if ( i->isReadOnly() ) {
00301 continue;
00302 }
00303
00304
00305
00306
00307
00308
00309
00310 kDebug() << "looking in " << i->uid() << "'s attendees";
00311
00312
00313
00314
00315 bool isMine = true;
00316 const Attendee::List attendees = i->attendees();
00317 Attendee::List::ConstIterator ait;
00318 for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) {
00319 if ( (*ait)->email() == attendee &&
00320 (*ait)->status() == Attendee::NeedsAction ) {
00321
00322
00323 kDebug() << "ignoring " << i->uid()
00324 << " since I'm still NeedsAction there";
00325 isMine = false;
00326 break;
00327 }
00328 }
00329
00330 if ( isMine ) {
00331 kDebug() << "removing existing incidence " << i->uid();
00332 if ( i->type() == IncidenceBase::TypeEvent ) {
00333 Event::Ptr event = mCalendar->event( i->uid() );
00334 ret = ( event && mCalendar->deleteEvent( event ) );
00335 } else if ( i->type() == IncidenceBase::TypeTodo ) {
00336 Todo::Ptr todo = mCalendar->todo( i->uid() );
00337 ret = ( todo && mCalendar->deleteTodo( todo ) );
00338 }
00339 deleteTransaction( incidence );
00340 return ret;
00341 }
00342 }
00343
00344
00345 if ( existingIncidences.count() > 0 && inc->revision() > 0 ) {
00346 KMessageBox::error(
00347 0,
00348 i18nc( "@info",
00349 "The event or task could not be removed from your calendar. "
00350 "Maybe it has already been deleted or is not owned by you. "
00351 "Or it might belong to a read-only or disabled calendar." ) );
00352 }
00353 deleteTransaction( incidence );
00354 return ret;
00355 }
00356
00357 bool Scheduler::acceptDeclineCounter( const IncidenceBase::Ptr &incidence,
00358 ScheduleMessage::Status status )
00359 {
00360 Q_UNUSED( status );
00361 deleteTransaction( incidence );
00362 return false;
00363 }
00364
00365 bool Scheduler::acceptReply( const IncidenceBase::Ptr &incidence, ScheduleMessage::Status status,
00366 iTIPMethod method )
00367 {
00368 Q_UNUSED( status );
00369 if ( incidence->type() == IncidenceBase::TypeFreeBusy ) {
00370 return acceptFreeBusy( incidence, method );
00371 }
00372 bool ret = false;
00373 Event::Ptr ev = mCalendar->event( incidence->uid() );
00374 Todo::Ptr to = mCalendar->todo( incidence->uid() );
00375
00376
00377 if ( !ev && !to ) {
00378 const Incidence::List list = mCalendar->incidences();
00379 for ( Incidence::List::ConstIterator it=list.constBegin(), end=list.constEnd();
00380 it != end; ++it ) {
00381 if ( (*it)->schedulingID() == incidence->uid() ) {
00382 ev = ( *it ).dynamicCast<Event>();
00383 to = ( *it ).dynamicCast<Todo>();
00384 break;
00385 }
00386 }
00387 }
00388
00389 if ( ev || to ) {
00390
00391 kDebug() << "match found!";
00392 Attendee::List attendeesIn = incidence->attendees();
00393 Attendee::List attendeesEv;
00394 Attendee::List attendeesNew;
00395 if ( ev ) {
00396 attendeesEv = ev->attendees();
00397 }
00398 if ( to ) {
00399 attendeesEv = to->attendees();
00400 }
00401 Attendee::List::ConstIterator inIt;
00402 Attendee::List::ConstIterator evIt;
00403 for ( inIt = attendeesIn.constBegin(); inIt != attendeesIn.constEnd(); ++inIt ) {
00404 Attendee::Ptr attIn = *inIt;
00405 bool found = false;
00406 for ( evIt = attendeesEv.constBegin(); evIt != attendeesEv.constEnd(); ++evIt ) {
00407 Attendee::Ptr attEv = *evIt;
00408 if ( attIn->email().toLower() == attEv->email().toLower() ) {
00409
00410 kDebug() << "update attendee";
00411 attEv->setStatus( attIn->status() );
00412 attEv->setDelegate( attIn->delegate() );
00413 attEv->setDelegator( attIn->delegator() );
00414 ret = true;
00415 found = true;
00416 }
00417 }
00418 if ( !found && attIn->status() != Attendee::Declined ) {
00419 attendeesNew.append( attIn );
00420 }
00421 }
00422
00423 bool attendeeAdded = false;
00424 for ( Attendee::List::ConstIterator it = attendeesNew.constBegin();
00425 it != attendeesNew.constEnd(); ++it ) {
00426 Attendee::Ptr attNew = *it;
00427 QString msg =
00428 i18nc( "@info", "%1 wants to attend %2 but was not invited.",
00429 attNew->fullName(),
00430 ( ev ? ev->summary() : to->summary() ) );
00431 if ( !attNew->delegator().isEmpty() ) {
00432 msg = i18nc( "@info", "%1 wants to attend %2 on behalf of %3.",
00433 attNew->fullName(),
00434 ( ev ? ev->summary() : to->summary() ), attNew->delegator() );
00435 }
00436 if ( KMessageBox::questionYesNo(
00437 0, msg, i18nc( "@title", "Uninvited attendee" ),
00438 KGuiItem( i18nc( "@option", "Accept Attendance" ) ),
00439 KGuiItem( i18nc( "@option", "Reject Attendance" ) ) ) != KMessageBox::Yes ) {
00440 Incidence::Ptr cancel = incidence.dynamicCast<Incidence>();
00441 if ( cancel ) {
00442 cancel->addComment(
00443 i18nc( "@info",
00444 "The organizer rejected your attendance at this meeting." ) );
00445 }
00446 performTransaction( incidence, iTIPCancel, attNew->fullName() );
00447
00448
00449
00450 continue;
00451 }
00452
00453 Attendee::Ptr a( new Attendee( attNew->name(), attNew->email(), attNew->RSVP(),
00454 attNew->status(), attNew->role(), attNew->uid() ) );
00455
00456 a->setDelegate( attNew->delegate() );
00457 a->setDelegator( attNew->delegator() );
00458 if ( ev ) {
00459 ev->addAttendee( a );
00460 } else if ( to ) {
00461 to->addAttendee( a );
00462 }
00463 ret = true;
00464 attendeeAdded = true;
00465 }
00466
00467
00468 if ( attendeeAdded ) {
00469 bool sendMail = false;
00470 if ( ev || to ) {
00471 if ( KMessageBox::questionYesNo(
00472 0,
00473 i18nc( "@info",
00474 "An attendee was added to the incidence. "
00475 "Do you want to email the attendees an update message?" ),
00476 i18nc( "@title", "Attendee Added" ),
00477 KGuiItem( i18nc( "@option", "Send Messages" ) ),
00478 KGuiItem( i18nc( "@option", "Do Not Send" ) ) ) == KMessageBox::Yes ) {
00479 sendMail = true;
00480 }
00481 }
00482
00483 if ( ev ) {
00484 ev->setRevision( ev->revision() + 1 );
00485 if ( sendMail ) {
00486 performTransaction( ev, iTIPRequest );
00487 }
00488 }
00489 if ( to ) {
00490 to->setRevision( to->revision() + 1 );
00491 if ( sendMail ) {
00492 performTransaction( to, iTIPRequest );
00493 }
00494 }
00495 }
00496
00497 if ( ret ) {
00498
00499
00500 if ( ev ) {
00501 ev->updated();
00502 } else if ( to ) {
00503 to->updated();
00504 }
00505 }
00506 if ( to ) {
00507
00508
00509 Todo::Ptr update = incidence.dynamicCast<Todo>();
00510 Q_ASSERT( update );
00511 if ( update && ( to->percentComplete() != update->percentComplete() ) ) {
00512 to->setPercentComplete( update->percentComplete() );
00513 to->updated();
00514 }
00515 }
00516 } else {
00517 kError() << "No incidence for scheduling.";
00518 }
00519
00520 if ( ret ) {
00521 deleteTransaction( incidence );
00522 }
00523 return ret;
00524 }
00525
00526 bool Scheduler::acceptRefresh( const IncidenceBase::Ptr &incidence, ScheduleMessage::Status status )
00527 {
00528 Q_UNUSED( status );
00529
00530 deleteTransaction( incidence );
00531 return false;
00532 }
00533
00534 bool Scheduler::acceptCounter( const IncidenceBase::Ptr &incidence, ScheduleMessage::Status status )
00535 {
00536 Q_UNUSED( status );
00537 deleteTransaction( incidence );
00538 return false;
00539 }
00540
00541 bool Scheduler::acceptFreeBusy( const IncidenceBase::Ptr &incidence, iTIPMethod method )
00542 {
00543 if ( !d->mFreeBusyCache ) {
00544 kError() << "Scheduler: no FreeBusyCache.";
00545 return false;
00546 }
00547
00548 FreeBusy::Ptr freebusy = incidence.staticCast<FreeBusy>();
00549
00550 kDebug() << "freeBusyDirName:" << freeBusyDir();
00551
00552 Person::Ptr from;
00553 if( method == iTIPPublish ) {
00554 from = freebusy->organizer();
00555 }
00556 if ( ( method == iTIPReply ) && ( freebusy->attendeeCount() == 1 ) ) {
00557 Attendee::Ptr attendee = freebusy->attendees().first();
00558 from->setName( attendee->name() );
00559 from->setEmail( attendee->email() );
00560 }
00561
00562 if ( !d->mFreeBusyCache->saveFreeBusy( freebusy, from ) ) {
00563 return false;
00564 }
00565
00566 deleteTransaction( incidence );
00567 return true;
00568 }