00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 #include "scheduler.h"
00024 #include "calendar.h"
00025 #include "event.h"
00026 #include "todo.h"
00027 #include "freebusy.h"
00028 #include "freebusycache.h"
00029 #include "icalformat.h"
00030 #include "assignmentvisitor.h"
00031
00032 #include <klocale.h>
00033 #include <kdebug.h>
00034 #include <kmessagebox.h>
00035 #include <kstandarddirs.h>
00036
00037 using namespace KCal;
00038
00039
00040 class KCal::ScheduleMessage::Private
00041 {
00042 public:
00043 Private() {}
00044
00045 IncidenceBase *mIncidence;
00046 iTIPMethod mMethod;
00047 Status mStatus;
00048 QString mError;
00049 };
00050
00051
00052 ScheduleMessage::ScheduleMessage( IncidenceBase *incidence,
00053 iTIPMethod method,
00054 ScheduleMessage::Status status )
00055 : d( new KCal::ScheduleMessage::Private )
00056 {
00057 d->mIncidence = incidence;
00058 d->mMethod = method;
00059 d->mStatus = status;
00060 }
00061
00062 ScheduleMessage::~ScheduleMessage()
00063 {
00064 delete d;
00065 }
00066
00067 IncidenceBase *ScheduleMessage::event()
00068 {
00069 return d->mIncidence;
00070 }
00071
00072 iTIPMethod ScheduleMessage::method()
00073 {
00074 return d->mMethod;
00075 }
00076
00077 ScheduleMessage::Status ScheduleMessage::status()
00078 {
00079 return d->mStatus;
00080 }
00081
00082 QString ScheduleMessage::statusName( ScheduleMessage::Status status )
00083 {
00084 switch( status ) {
00085 case PublishNew:
00086 return i18nc( "@item new message posting", "New Message Publish" );
00087 case PublishUpdate:
00088 return i18nc( "@item updated message", "Updated Message Published" );
00089 case Obsolete:
00090 return i18nc( "@item obsolete status", "Obsolete" );
00091 case RequestNew:
00092 return i18nc( "@item request new message posting", "Request New Message" );
00093 case RequestUpdate:
00094 return i18nc( "@item request updated posting", "Request Updated Message" );
00095 default:
00096 return i18nc( "@item unknown status", "Unknown Status: %1", int( status ) );
00097 }
00098 }
00099
00100 QString ScheduleMessage::error()
00101 {
00102 return d->mError;
00103 }
00104
00105
00106 struct KCal::Scheduler::Private
00107 {
00108 Private()
00109 : mFreeBusyCache( 0 )
00110 {
00111 }
00112 FreeBusyCache *mFreeBusyCache;
00113 };
00114
00115
00116 Scheduler::Scheduler( Calendar *calendar ) : d( new KCal::Scheduler::Private )
00117 {
00118 mCalendar = calendar;
00119 mFormat = new ICalFormat();
00120 mFormat->setTimeSpec( calendar->timeSpec() );
00121 }
00122
00123 Scheduler::~Scheduler()
00124 {
00125 delete mFormat;
00126 delete d;
00127 }
00128
00129 void Scheduler::setFreeBusyCache( FreeBusyCache *c )
00130 {
00131 d->mFreeBusyCache = c;
00132 }
00133
00134 FreeBusyCache *Scheduler::freeBusyCache() const
00135 {
00136 return d->mFreeBusyCache;
00137 }
00138
00139 bool Scheduler::acceptTransaction( IncidenceBase *incidence,
00140 iTIPMethod method,
00141 ScheduleMessage::Status status )
00142 {
00143 return acceptTransaction( incidence, method, status, QString() );
00144 }
00145
00146 bool Scheduler::acceptTransaction( IncidenceBase *incidence,
00147 iTIPMethod method,
00148 ScheduleMessage::Status status,
00149 const QString &email )
00150 {
00151 kDebug() << "method=" << methodName( method );
00152
00153 switch ( method ) {
00154 case iTIPPublish:
00155 return acceptPublish( incidence, status, method );
00156 case iTIPRequest:
00157 return acceptRequest( incidence, status, email );
00158 case iTIPAdd:
00159 return acceptAdd( incidence, status );
00160 case iTIPCancel:
00161 return acceptCancel( incidence, status, email );
00162 case iTIPDeclineCounter:
00163 return acceptDeclineCounter( incidence, status );
00164 case iTIPReply:
00165 return acceptReply( incidence, status, method );
00166 case iTIPRefresh:
00167 return acceptRefresh( incidence, status );
00168 case iTIPCounter:
00169 return acceptCounter( incidence, status );
00170 default:
00171 break;
00172 }
00173 deleteTransaction( incidence );
00174 return false;
00175 }
00176
00177 QString Scheduler::methodName( iTIPMethod method )
00178 {
00179 switch ( method ) {
00180 case iTIPPublish:
00181 return QLatin1String( "Publish" );
00182 case iTIPRequest:
00183 return QLatin1String( "Request" );
00184 case iTIPRefresh:
00185 return QLatin1String( "Refresh" );
00186 case iTIPCancel:
00187 return QLatin1String( "Cancel" );
00188 case iTIPAdd:
00189 return QLatin1String( "Add" );
00190 case iTIPReply:
00191 return QLatin1String( "Reply" );
00192 case iTIPCounter:
00193 return QLatin1String( "Counter" );
00194 case iTIPDeclineCounter:
00195 return QLatin1String( "Decline Counter" );
00196 default:
00197 return QLatin1String( "Unknown" );
00198 }
00199 }
00200
00201 QString Scheduler::translatedMethodName( iTIPMethod method )
00202 {
00203 switch ( method ) {
00204 case iTIPPublish:
00205 return i18nc( "@item event, to-do, journal or freebusy posting", "Publish" );
00206 case iTIPRequest:
00207 return i18nc( "@item event, to-do or freebusy scheduling requests", "Request" );
00208 case iTIPReply:
00209 return i18nc( "@item event, to-do or freebusy reply to request", "Reply" );
00210 case iTIPAdd:
00211 return i18nc(
00212 "@item event, to-do or journal additional property request", "Add" );
00213 case iTIPCancel:
00214 return i18nc( "@item event, to-do or journal cancellation notice", "Cancel" );
00215 case iTIPRefresh:
00216 return i18nc( "@item event or to-do description update request", "Refresh" );
00217 case iTIPCounter:
00218 return i18nc( "@item event or to-do submit counter proposal", "Counter" );
00219 case iTIPDeclineCounter:
00220 return i18nc( "@item event or to-do decline a counter proposal", "Decline Counter" );
00221 default:
00222 return i18nc( "@item no method", "Unknown" );
00223 }
00224 }
00225
00226 bool Scheduler::deleteTransaction( IncidenceBase * )
00227 {
00228 return true;
00229 }
00230
00231 bool Scheduler::acceptPublish( IncidenceBase *newIncBase,
00232 ScheduleMessage::Status status,
00233 iTIPMethod method )
00234 {
00235 if( newIncBase->type() == "FreeBusy" ) {
00236 return acceptFreeBusy( newIncBase, method );
00237 }
00238
00239 bool res = false;
00240
00241 kDebug() << "status=" << ScheduleMessage::statusName( status );
00242
00243 Incidence *newInc = static_cast<Incidence *>( newIncBase );
00244 Incidence *calInc = mCalendar->incidence( newIncBase->uid() );
00245 switch ( status ) {
00246 case ScheduleMessage::Unknown:
00247 case ScheduleMessage::PublishNew:
00248 case ScheduleMessage::PublishUpdate:
00249 if ( calInc && newInc ) {
00250 if ( ( newInc->revision() > calInc->revision() ) ||
00251 ( newInc->revision() == calInc->revision() &&
00252 newInc->lastModified() > calInc->lastModified() ) ) {
00253 AssignmentVisitor visitor;
00254 const QString oldUid = calInc->uid();
00255 if ( !visitor.assign( calInc, newInc ) ) {
00256 kError() << "assigning different incidence types";
00257 } else {
00258 calInc->setUid( oldUid );
00259 calInc->setSchedulingID( newInc->uid() );
00260 res = true;
00261 }
00262 }
00263 }
00264 break;
00265 case ScheduleMessage::Obsolete:
00266 res = true;
00267 break;
00268 default:
00269 break;
00270 }
00271 deleteTransaction( newIncBase );
00272 return res;
00273 }
00274
00275 bool Scheduler::acceptRequest( IncidenceBase *incidence,
00276 ScheduleMessage::Status status )
00277 {
00278 return acceptRequest( incidence, status, QString() );
00279 }
00280
00281 bool Scheduler::acceptRequest( IncidenceBase *incidence,
00282 ScheduleMessage::Status status,
00283 const QString &email )
00284 {
00285 Incidence *inc = static_cast<Incidence *>( incidence );
00286 if ( !inc ) {
00287 return false;
00288 }
00289 if ( inc->type() == "FreeBusy" ) {
00290
00291 return true;
00292 }
00293
00294 const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() );
00295 kDebug() << "status=" << ScheduleMessage::statusName( status )
00296 << ": found " << existingIncidences.count()
00297 << " incidences with schedulingID " << inc->schedulingID();
00298 Incidence::List::ConstIterator incit = existingIncidences.begin();
00299 for ( ; incit != existingIncidences.end() ; ++incit ) {
00300 Incidence *i = *incit;
00301 kDebug() << "Considering this found event ("
00302 << ( i->isReadOnly() ? "readonly" : "readwrite" )
00303 << ") :" << mFormat->toString( i );
00304
00305 if ( i->isReadOnly() ) {
00306 continue;
00307 }
00308 if ( i->revision() <= inc->revision() ) {
00309
00310 bool isUpdate = true;
00311
00312
00313
00314
00315
00316 kDebug() << "looking in " << i->uid() << "'s attendees";
00317
00318
00319
00320 const KCal::Attendee::List attendees = i->attendees();
00321 KCal::Attendee::List::ConstIterator ait;
00322 for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) {
00323 if( (*ait)->email() == email && (*ait)->status() == Attendee::NeedsAction ) {
00324
00325
00326 kDebug() << "ignoring " << i->uid() << " since I'm still NeedsAction there";
00327 isUpdate = false;
00328 break;
00329 }
00330 }
00331 if ( isUpdate ) {
00332 if ( i->revision() == inc->revision() &&
00333 i->lastModified() > inc->lastModified() ) {
00334
00335 kDebug() << "This isn't an update - the found incidence was modified more recently";
00336 deleteTransaction( i );
00337 return false;
00338 }
00339 kDebug() << "replacing existing incidence " << i->uid();
00340 bool res = true;
00341 AssignmentVisitor visitor;
00342 const QString oldUid = i->uid();
00343 if ( !visitor.assign( i, inc ) ) {
00344 kError() << "assigning different incidence types";
00345 res = false;
00346 } else {
00347 i->setUid( oldUid );
00348 i->setSchedulingID( inc->uid() );
00349 }
00350 deleteTransaction( incidence );
00351 return res;
00352 }
00353 } else {
00354
00355 kDebug() << "This isn't an update - the found incidence has a bigger revision number";
00356 deleteTransaction( incidence );
00357 return false;
00358 }
00359 }
00360
00361
00362 inc->setSchedulingID( inc->uid() );
00363 inc->setUid( CalFormat::createUniqueId() );
00364
00365
00366 if ( existingIncidences.count() > 0 || inc->revision() == 0 ||
00367 KMessageBox::warningYesNo(
00368 0,
00369 i18nc( "@info",
00370 "The event, to-do or journal to be updated could not be found. "
00371 "Maybe it has already been deleted, or the calendar that "
00372 "contains it is disabled. Press 'Store' to create a new "
00373 "one or 'Throw away' to discard this update." ),
00374 i18nc( "@title", "Discard this update?" ),
00375 KGuiItem( i18nc( "@option", "Store" ) ),
00376 KGuiItem( i18nc( "@option", "Throw away" ) ) ) == KMessageBox::Yes ) {
00377 kDebug() << "Storing new incidence with scheduling uid=" << inc->schedulingID()
00378 << " and uid=" << inc->uid();
00379 mCalendar->addIncidence( inc );
00380 }
00381 deleteTransaction( incidence );
00382 return true;
00383 }
00384
00385 bool Scheduler::acceptAdd( IncidenceBase *incidence, ScheduleMessage::Status )
00386 {
00387 deleteTransaction( incidence );
00388 return false;
00389 }
00390
00391 bool Scheduler::acceptCancel( IncidenceBase *incidence,
00392 ScheduleMessage::Status status,
00393 const QString &attendee )
00394 {
00395 Incidence *inc = static_cast<Incidence *>( incidence );
00396 if ( !inc ) {
00397 return false;
00398 }
00399
00400 if ( inc->type() == "FreeBusy" ) {
00401
00402 return true;
00403 }
00404
00405 const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() );
00406 kDebug() << "Scheduler::acceptCancel="
00407 << ScheduleMessage::statusName( status )
00408 << ": found " << existingIncidences.count()
00409 << " incidences with schedulingID " << inc->schedulingID();
00410
00411 bool ret = false;
00412 Incidence::List::ConstIterator incit = existingIncidences.begin();
00413 for ( ; incit != existingIncidences.end() ; ++incit ) {
00414 Incidence *i = *incit;
00415 kDebug() << "Considering this found event ("
00416 << ( i->isReadOnly() ? "readonly" : "readwrite" )
00417 << ") :" << mFormat->toString( i );
00418
00419
00420 if ( i->isReadOnly() ) {
00421 continue;
00422 }
00423
00424
00425
00426
00427
00428
00429
00430 kDebug() << "looking in " << i->uid() << "'s attendees";
00431
00432
00433
00434
00435 bool isMine = true;
00436 const KCal::Attendee::List attendees = i->attendees();
00437 KCal::Attendee::List::ConstIterator ait;
00438 for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) {
00439 if ( (*ait)->email() == attendee &&
00440 (*ait)->status() == Attendee::NeedsAction ) {
00441
00442
00443 kDebug() << "ignoring " << i->uid()
00444 << " since I'm still NeedsAction there";
00445 isMine = false;
00446 break;
00447 }
00448 }
00449
00450 if ( isMine ) {
00451 kDebug() << "removing existing incidence " << i->uid();
00452 if ( i->type() == "Event" ) {
00453 Event *event = mCalendar->event( i->uid() );
00454 ret = ( event && mCalendar->deleteEvent( event ) );
00455 } else if ( i->type() == "Todo" ) {
00456 Todo *todo = mCalendar->todo( i->uid() );
00457 ret = ( todo && mCalendar->deleteTodo( todo ) );
00458 }
00459 deleteTransaction( incidence );
00460 return ret;
00461 }
00462 }
00463
00464
00465 if ( existingIncidences.count() > 0 && inc->revision() > 0 ) {
00466 KMessageBox::information(
00467 0,
00468 i18nc( "@info",
00469 "The event or task could not be removed from your calendar. "
00470 "Maybe it has already been deleted or is not owned by you. "
00471 "Or it might belong to a read-only or disabled calendar." ) );
00472 }
00473 deleteTransaction( incidence );
00474 return ret;
00475 }
00476
00477 bool Scheduler::acceptCancel( IncidenceBase *incidence,
00478 ScheduleMessage::Status status )
00479 {
00480 Q_UNUSED( status );
00481
00482 const IncidenceBase *toDelete = mCalendar->incidenceFromSchedulingID( incidence->uid() );
00483
00484 bool ret = true;
00485 if ( toDelete ) {
00486 if ( toDelete->type() == "Event" ) {
00487 Event *event = mCalendar->event( toDelete->uid() );
00488 ret = ( event && mCalendar->deleteEvent( event ) );
00489 } else if ( toDelete->type() == "Todo" ) {
00490 Todo *todo = mCalendar->todo( toDelete->uid() );
00491 ret = ( todo && mCalendar->deleteTodo( todo ) );
00492 }
00493 } else {
00494
00495
00496 Incidence *inc = static_cast<Incidence *>( incidence );
00497 if ( inc->revision() > 0 ) {
00498 ret = false;
00499 }
00500 }
00501
00502 if ( !ret ) {
00503 KMessageBox::information(
00504 0,
00505 i18nc( "@info",
00506 "The event or task to be canceled could not be removed from your calendar. "
00507 "Maybe it has already been deleted or is not owned by you. "
00508 "Or it might belong to a read-only or disabled calendar." ) );
00509 }
00510 deleteTransaction( incidence );
00511 return ret;
00512 }
00513
00514 bool Scheduler::acceptDeclineCounter( IncidenceBase *incidence,
00515 ScheduleMessage::Status status )
00516 {
00517 Q_UNUSED( status );
00518 deleteTransaction( incidence );
00519 return false;
00520 }
00521
00522 bool Scheduler::acceptReply( IncidenceBase *incidence,
00523 ScheduleMessage::Status status,
00524 iTIPMethod method )
00525 {
00526 Q_UNUSED( status );
00527 if ( incidence->type() == "FreeBusy" ) {
00528 return acceptFreeBusy( incidence, method );
00529 }
00530 bool ret = false;
00531 Event *ev = mCalendar->event( incidence->uid() );
00532 Todo *to = mCalendar->todo( incidence->uid() );
00533
00534
00535 if ( !ev && !to ) {
00536 const Incidence::List list = mCalendar->incidences();
00537 for ( Incidence::List::ConstIterator it=list.constBegin(), end=list.constEnd();
00538 it != end; ++it ) {
00539 if ( (*it)->schedulingID() == incidence->uid() ) {
00540 ev = dynamic_cast<Event*>( *it );
00541 to = dynamic_cast<Todo*>( *it );
00542 break;
00543 }
00544 }
00545 }
00546
00547 if ( ev || to ) {
00548
00549 kDebug() << "match found!";
00550 Attendee::List attendeesIn = incidence->attendees();
00551 Attendee::List attendeesEv;
00552 Attendee::List attendeesNew;
00553 if ( ev ) {
00554 attendeesEv = ev->attendees();
00555 }
00556 if ( to ) {
00557 attendeesEv = to->attendees();
00558 }
00559 Attendee::List::ConstIterator inIt;
00560 Attendee::List::ConstIterator evIt;
00561 for ( inIt = attendeesIn.constBegin(); inIt != attendeesIn.constEnd(); ++inIt ) {
00562 Attendee *attIn = *inIt;
00563 bool found = false;
00564 for ( evIt = attendeesEv.constBegin(); evIt != attendeesEv.constEnd(); ++evIt ) {
00565 Attendee *attEv = *evIt;
00566 if ( attIn->email().toLower() == attEv->email().toLower() ) {
00567
00568 kDebug() << "update attendee";
00569 attEv->setStatus( attIn->status() );
00570 attEv->setDelegate( attIn->delegate() );
00571 attEv->setDelegator( attIn->delegator() );
00572 ret = true;
00573 found = true;
00574 }
00575 }
00576 if ( !found && attIn->status() != Attendee::Declined ) {
00577 attendeesNew.append( attIn );
00578 }
00579 }
00580
00581 bool attendeeAdded = false;
00582 for ( Attendee::List::ConstIterator it = attendeesNew.constBegin();
00583 it != attendeesNew.constEnd(); ++it ) {
00584 Attendee *attNew = *it;
00585 QString msg =
00586 i18nc( "@info", "%1 wants to attend %2 but was not invited.",
00587 attNew->fullName(),
00588 ( ev ? ev->summary() : to->summary() ) );
00589 if ( !attNew->delegator().isEmpty() ) {
00590 msg = i18nc( "@info", "%1 wants to attend %2 on behalf of %3.",
00591 attNew->fullName(),
00592 ( ev ? ev->summary() : to->summary() ), attNew->delegator() );
00593 }
00594 if ( KMessageBox::questionYesNo(
00595 0, msg, i18nc( "@title", "Uninvited attendee" ),
00596 KGuiItem( i18nc( "@option", "Accept Attendance" ) ),
00597 KGuiItem( i18nc( "@option", "Reject Attendance" ) ) ) != KMessageBox::Yes ) {
00598 KCal::Incidence *cancel = dynamic_cast<Incidence*>( incidence );
00599 if ( cancel ) {
00600 cancel->addComment(
00601 i18nc( "@info",
00602 "The organizer rejected your attendance at this meeting." ) );
00603 }
00604 performTransaction( cancel ? cancel : incidence, iTIPCancel, attNew->fullName() );
00605
00606
00607
00608 continue;
00609 }
00610
00611 Attendee *a = new Attendee( attNew->name(), attNew->email(), attNew->RSVP(),
00612 attNew->status(), attNew->role(), attNew->uid() );
00613 a->setDelegate( attNew->delegate() );
00614 a->setDelegator( attNew->delegator() );
00615 if ( ev ) {
00616 ev->addAttendee( a );
00617 } else if ( to ) {
00618 to->addAttendee( a );
00619 }
00620 ret = true;
00621 attendeeAdded = true;
00622 }
00623
00624
00625 if ( attendeeAdded ) {
00626 if ( ev ) {
00627 ev->setRevision( ev->revision() + 1 );
00628 performTransaction( ev, iTIPRequest );
00629 }
00630 if ( to ) {
00631 to->setRevision( to->revision() + 1 );
00632 performTransaction( to, iTIPRequest );
00633 }
00634 }
00635
00636 if ( ret ) {
00637
00638
00639 if ( ev ) {
00640 ev->updated();
00641 } else if ( to ) {
00642 to->updated();
00643 }
00644 }
00645 if ( to ) {
00646
00647
00648 Todo *update = dynamic_cast<Todo*> ( incidence );
00649 Q_ASSERT( update );
00650 if ( update && ( to->percentComplete() != update->percentComplete() ) ) {
00651 to->setPercentComplete( update->percentComplete() );
00652 to->updated();
00653 }
00654 }
00655 } else {
00656 kError() << "No incidence for scheduling.";
00657 }
00658
00659 if ( ret ) {
00660 deleteTransaction( incidence );
00661 }
00662 return ret;
00663 }
00664
00665 bool Scheduler::acceptRefresh( IncidenceBase *incidence, ScheduleMessage::Status status )
00666 {
00667 Q_UNUSED( status );
00668
00669 deleteTransaction( incidence );
00670 return false;
00671 }
00672
00673 bool Scheduler::acceptCounter( IncidenceBase *incidence, ScheduleMessage::Status status )
00674 {
00675 Q_UNUSED( status );
00676 deleteTransaction( incidence );
00677 return false;
00678 }
00679
00680 bool Scheduler::acceptFreeBusy( IncidenceBase *incidence, iTIPMethod method )
00681 {
00682 if ( !d->mFreeBusyCache ) {
00683 kError() << "KCal::Scheduler: no FreeBusyCache.";
00684 return false;
00685 }
00686
00687 FreeBusy *freebusy = static_cast<FreeBusy *>(incidence);
00688
00689 kDebug() << "freeBusyDirName:" << freeBusyDir();
00690
00691 Person from;
00692 if( method == iTIPPublish ) {
00693 from = freebusy->organizer();
00694 }
00695 if ( ( method == iTIPReply ) && ( freebusy->attendeeCount() == 1 ) ) {
00696 Attendee *attendee = freebusy->attendees().first();
00697 from.setName( attendee->name() );
00698 from.setEmail( attendee->email() );
00699 }
00700
00701 if ( !d->mFreeBusyCache->saveFreeBusy( freebusy, from ) ) {
00702 return false;
00703 }
00704
00705 deleteTransaction( incidence );
00706 return true;
00707 }