• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • Contact Us
 

KCalUtils Library

scheduler.cpp

00001 /*
00002   This file is part of the kcalutils library.
00003 
00004   Copyright (c) 2001,2004 Cornelius Schumacher <schumacher@kde.org>
00005   Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00006 
00007   This library is free software; you can redistribute it and/or
00008   modify it under the terms of the GNU Library General Public
00009   License as published by the Free Software Foundation; either
00010   version 2 of the License, or (at your option) any later version.
00011 
00012   This library is distributed in the hope that it will be useful,
00013   but WITHOUT ANY WARRANTY; without even the implied warranty of
00014   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015   Library General Public License for more details.
00016 
00017   You should have received a copy of the GNU Library General Public License
00018   along with this library; see the file COPYING.LIB.  If not, write to
00019   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00020   Boston, MA 02110-1301, USA.
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 //@cond PRIVATE
00036 struct KCalUtils::Scheduler::Private
00037 {
00038   public:
00039     Private() : mFreeBusyCache( 0 )
00040     {
00041     }
00042     FreeBusyCache *mFreeBusyCache;
00043 };
00044 //@endcond
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 ); //krazy:exclude=kdebug
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 ); //krazy:exclude=kdebug
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     // reply to this request is handled in korganizer's incomingdialog
00159     return true;
00160   }
00161 
00162   const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() );
00163   kDebug() << "status=" << Stringify::scheduleMessageStatus( status ) //krazy:exclude=kdebug
00164            << ": found " << existingIncidences.count()
00165            << " incidences with schedulingID " << inc->schedulingID()
00166            << "; uid was = " << inc->uid();
00167 
00168   if ( existingIncidences.isEmpty() ) {
00169     // Perfectly normal if the incidence doesn't exist. This is probably
00170     // a new invitation.
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     // If it's readonly, we can't possible update it.
00181     if ( existingIncidence->isReadOnly() ) {
00182       continue;
00183     }
00184     if ( existingIncidence->revision() <= inc->revision() ) {
00185       // The new incidence might be an update for the found one
00186       bool isUpdate = true;
00187       // Code for new invitations:
00188       // If you think we could check the value of "status" to be RequestNew:  we can't.
00189       // It comes from a similar check inside libical, where the event is compared to
00190       // other events in the calendar. But if we have another version of the event around
00191       // (e.g. shared folder for a group), the status could be RequestNew, Obsolete or Updated.
00192       kDebug() << "looking in " << existingIncidence->uid() << "'s attendees";
00193       // This is supposed to be a new request, not an update - however we want to update
00194       // the existing one to handle the "clicking more than once on the invitation" case.
00195       // So check the attendee status of the attendee.
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           // This incidence wasn't created by me - it's probably in a shared folder
00201           // and meant for someone else, ignore it.
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           // This isn't an update - the found incidence was modified more recently
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       // This isn't an update - the found incidence has a bigger revision number
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   // Move the uid to be the schedulingID and make a unique UID
00239   inc->setSchedulingID( inc->uid(), CalFormat::createUniqueId() );
00240   // notify the user in case this is an update and we didn't find the to-be-updated incidence
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 /* 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     // reply to this request is handled in korganizer's incomingdialog
00282     return true;
00283   }
00284 
00285   const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() );
00286   kDebug() << "Scheduler::acceptCancel="
00287            << Stringify::scheduleMessageStatus( status ) //krazy2:exclude=kdebug
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     // If it's readonly, we can't possible remove it.
00300     if ( i->isReadOnly() ) {
00301       continue;
00302     }
00303 
00304     // Code for new invitations:
00305     // We cannot check the value of "status" to be RequestNew because
00306     // "status" comes from a similar check inside libical, where the event
00307     // is compared to other events in the calendar. But if we have another
00308     // version of the event around (e.g. shared folder for a group), the
00309     // status could be RequestNew, Obsolete or Updated.
00310     kDebug() << "looking in " << i->uid() << "'s attendees";
00311 
00312     // This is supposed to be a new request, not an update - however we want
00313     // to update the existing one to handle the "clicking more than once
00314     // on the invitation" case. So check the attendee status of the attendee.
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         // This incidence wasn't created by me - it's probably in a shared
00322         // folder and meant for someone else, ignore it.
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   // in case we didn't find the to-be-removed incidence
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   // try harder to find the correct incidence
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     //get matching attendee in calendar
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           //update attendee-info
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         // ### can't delete cancel here because it is aliased to incidence which
00448         // is accessed in the next loop iteration (CID 4232)
00449         // delete cancel;
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     // send update about new participants
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       // We set at least one of the attendees, so the incidence changed
00499       // Note: This should not result in a sequence number bump
00500       if ( ev ) {
00501         ev->updated();
00502       } else if ( to ) {
00503         to->updated();
00504       }
00505     }
00506     if ( to ) {
00507       // for VTODO a REPLY can be used to update the completion status of
00508       // a to-do. see RFC2446 3.4.3
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   // handled in korganizer's IncomingDialog
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 }

KCalUtils Library

Skip menu "KCalUtils Library"
  • Main Page
  • Namespace List
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal