• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.8.3 API Reference
  • 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()
00203                    << " since I'm still NeedsAction there";
00204           isUpdate = false;
00205           break;
00206         }
00207       }
00208       if ( isUpdate ) {
00209         if ( existingIncidence->revision() == inc->revision() &&
00210              existingIncidence->lastModified() > inc->lastModified() ) {
00211           // This isn't an update - the found incidence was modified more recently
00212           kDebug() << "This isn't an update - the found incidence was modified more recently";
00213           deleteTransaction( existingIncidence );
00214           return false;
00215         }
00216         kDebug() << "replacing existing incidence " << existingIncidence->uid();
00217         bool res = true;
00218         const QString oldUid = existingIncidence->uid();
00219         if ( existingIncidence->type() != inc->type() ) {
00220           kError() << "assigning different incidence types";
00221           res = false;
00222         } else {
00223           IncidenceBase *existingIncidenceBase = existingIncidence.data();
00224           IncidenceBase *incBase = inc.data();
00225           *existingIncidenceBase = *incBase;
00226           existingIncidence->setSchedulingID( inc->uid(), oldUid );
00227         }
00228         deleteTransaction( incidence );
00229         return res;
00230       }
00231     } else {
00232       // This isn't an update - the found incidence has a bigger revision number
00233       kDebug() << "This isn't an update - the found incidence has a bigger revision number";
00234       deleteTransaction( incidence );
00235       return false;
00236     }
00237   }
00238 
00239   // Move the uid to be the schedulingID and make a unique UID
00240   inc->setSchedulingID( inc->uid(), CalFormat::createUniqueId() );
00241   // notify the user in case this is an update and we didn't find the to-be-updated incidence
00242   if ( existingIncidences.count() == 0 && inc->revision() > 0 ) {
00243     KMessageBox::information(
00244       0,
00245       i18nc( "@info",
00246              "<para>You accepted an invitation update, but an earlier version of the "
00247              "item could not be found in your calendar.</para>"
00248              "<para>This may have occurred because:<list>"
00249              "<item>the organizer did not include you in the original invitation</item>"
00250              "<item>you did not accept the original invitation yet</item>"
00251              "<item>you deleted the original invitation from your calendar</item>"
00252              "<item>you no longer have access to the calendar containing the invitation</item>"
00253              "</list></para>"
00254              "<para>This is not a problem, but we thought you should know.</para>" ),
00255       i18nc( "@title", "Cannot find invitation to be updated" ), "AcceptCantFindIncidence" );
00256   }
00257   kDebug() << "Storing new incidence with scheduling uid=" << inc->schedulingID()
00258            << " and uid=" << inc->uid();
00259   mCalendar->addIncidence( inc );
00260 
00261   deleteTransaction( incidence );
00262   return true;
00263 }
00264 
00265 bool Scheduler::acceptAdd( const IncidenceBase::Ptr &incidence,
00266                            ScheduleMessage::Status /* status */)
00267 {
00268   deleteTransaction( incidence );
00269   return false;
00270 }
00271 
00272 bool Scheduler::acceptCancel( const IncidenceBase::Ptr &incidence,
00273                               ScheduleMessage::Status status,
00274                               const QString &attendee )
00275 {
00276   Incidence::Ptr inc = incidence.staticCast<Incidence>();
00277   if ( !inc ) {
00278     return false;
00279   }
00280 
00281   if ( inc->type() == IncidenceBase::TypeFreeBusy ) {
00282     // reply to this request is handled in korganizer's incomingdialog
00283     return true;
00284   }
00285 
00286   const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() );
00287   kDebug() << "Scheduler::acceptCancel="
00288            << Stringify::scheduleMessageStatus( status ) //krazy2:exclude=kdebug
00289            << ": found " << existingIncidences.count()
00290            << " incidences with schedulingID " << inc->schedulingID();
00291 
00292   bool ret = false;
00293   Incidence::List::ConstIterator incit = existingIncidences.begin();
00294   for ( ; incit != existingIncidences.end() ; ++incit ) {
00295     Incidence::Ptr i = *incit;
00296     kDebug() << "Considering this found event ("
00297              << ( i->isReadOnly() ? "readonly" : "readwrite" )
00298              << ") :" << mFormat->toString( i );
00299 
00300     // If it's readonly, we can't possible remove it.
00301     if ( i->isReadOnly() ) {
00302       continue;
00303     }
00304 
00305     // Code for new invitations:
00306     // We cannot check the value of "status" to be RequestNew because
00307     // "status" comes from a similar check inside libical, where the event
00308     // is compared to other events in the calendar. But if we have another
00309     // version of the event around (e.g. shared folder for a group), the
00310     // status could be RequestNew, Obsolete or Updated.
00311     kDebug() << "looking in " << i->uid() << "'s attendees";
00312 
00313     // This is supposed to be a new request, not an update - however we want
00314     // to update the existing one to handle the "clicking more than once
00315     // on the invitation" case. So check the attendee status of the attendee.
00316     bool isMine = true;
00317     const Attendee::List attendees = i->attendees();
00318     Attendee::List::ConstIterator ait;
00319     for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) {
00320       if ( (*ait)->email() == attendee &&
00321            (*ait)->status() == Attendee::NeedsAction ) {
00322         // This incidence wasn't created by me - it's probably in a shared
00323         // folder and meant for someone else, ignore it.
00324         kDebug() << "ignoring " << i->uid()
00325                  << " since I'm still NeedsAction there";
00326         isMine = false;
00327         break;
00328       }
00329     }
00330 
00331     if ( isMine ) {
00332       kDebug() << "removing existing incidence " << i->uid();
00333       if ( i->type() == IncidenceBase::TypeEvent ) {
00334         Event::Ptr event = mCalendar->event( i->uid() );
00335         ret = ( event && mCalendar->deleteEvent( event ) );
00336       } else if ( i->type() == IncidenceBase::TypeTodo ) {
00337         Todo::Ptr todo = mCalendar->todo( i->uid() );
00338         ret = ( todo && mCalendar->deleteTodo( todo ) );
00339       }
00340       deleteTransaction( incidence );
00341       return ret;
00342     }
00343   }
00344 
00345   // in case we didn't find the to-be-removed incidence
00346   if ( existingIncidences.count() > 0 && inc->revision() > 0 ) {
00347     KMessageBox::error(
00348       0,
00349       i18nc( "@info",
00350              "The event or task could not be removed from your calendar. "
00351              "Maybe it has already been deleted or is not owned by you. "
00352              "Or it might belong to a read-only or disabled calendar." ) );
00353   }
00354   deleteTransaction( incidence );
00355   return ret;
00356 }
00357 
00358 bool Scheduler::acceptDeclineCounter( const IncidenceBase::Ptr &incidence,
00359                                       ScheduleMessage::Status status )
00360 {
00361   Q_UNUSED( status );
00362   deleteTransaction( incidence );
00363   return false;
00364 }
00365 
00366 bool Scheduler::acceptReply( const IncidenceBase::Ptr &incidence, ScheduleMessage::Status status,
00367                              iTIPMethod method )
00368 {
00369   Q_UNUSED( status );
00370   if ( incidence->type() == IncidenceBase::TypeFreeBusy ) {
00371     return acceptFreeBusy( incidence, method );
00372   }
00373   bool ret = false;
00374   Event::Ptr ev = mCalendar->event( incidence->uid() );
00375   Todo::Ptr to = mCalendar->todo( incidence->uid() );
00376 
00377   // try harder to find the correct incidence
00378   if ( !ev && !to ) {
00379     const Incidence::List list = mCalendar->incidences();
00380     for ( Incidence::List::ConstIterator it=list.constBegin(), end=list.constEnd();
00381           it != end; ++it ) {
00382       if ( (*it)->schedulingID() == incidence->uid() ) {
00383         ev =  ( *it ).dynamicCast<Event>();
00384         to = ( *it ).dynamicCast<Todo>();
00385         break;
00386       }
00387     }
00388   }
00389 
00390   if ( ev || to ) {
00391     //get matching attendee in calendar
00392     kDebug() << "match found!";
00393     Attendee::List attendeesIn = incidence->attendees();
00394     Attendee::List attendeesEv;
00395     Attendee::List attendeesNew;
00396     if ( ev ) {
00397       attendeesEv = ev->attendees();
00398     }
00399     if ( to ) {
00400       attendeesEv = to->attendees();
00401     }
00402     Attendee::List::ConstIterator inIt;
00403     Attendee::List::ConstIterator evIt;
00404     for ( inIt = attendeesIn.constBegin(); inIt != attendeesIn.constEnd(); ++inIt ) {
00405       Attendee::Ptr attIn = *inIt;
00406       bool found = false;
00407       for ( evIt = attendeesEv.constBegin(); evIt != attendeesEv.constEnd(); ++evIt ) {
00408         Attendee::Ptr attEv = *evIt;
00409         if ( attIn->email().toLower() == attEv->email().toLower() ) {
00410           //update attendee-info
00411           kDebug() << "update attendee";
00412           attEv->setStatus( attIn->status() );
00413           attEv->setDelegate( attIn->delegate() );
00414           attEv->setDelegator( attIn->delegator() );
00415           ret = true;
00416           found = true;
00417         }
00418       }
00419       if ( !found && attIn->status() != Attendee::Declined ) {
00420         attendeesNew.append( attIn );
00421       }
00422     }
00423 
00424     bool attendeeAdded = false;
00425     for ( Attendee::List::ConstIterator it = attendeesNew.constBegin();
00426           it != attendeesNew.constEnd(); ++it ) {
00427       Attendee::Ptr attNew = *it;
00428       QString msg =
00429         i18nc( "@info", "%1 wants to attend %2 but was not invited.",
00430                attNew->fullName(),
00431                ( ev ? ev->summary() : to->summary() ) );
00432       if ( !attNew->delegator().isEmpty() ) {
00433         msg = i18nc( "@info", "%1 wants to attend %2 on behalf of %3.",
00434                      attNew->fullName(),
00435                      ( ev ? ev->summary() : to->summary() ), attNew->delegator() );
00436       }
00437       if ( KMessageBox::questionYesNo(
00438              0, msg, i18nc( "@title", "Uninvited attendee" ),
00439              KGuiItem( i18nc( "@option", "Accept Attendance" ) ),
00440              KGuiItem( i18nc( "@option", "Reject Attendance" ) ) ) != KMessageBox::Yes ) {
00441         Incidence::Ptr cancel = incidence.dynamicCast<Incidence>();
00442         if ( cancel ) {
00443           cancel->addComment(
00444             i18nc( "@info",
00445                    "The organizer rejected your attendance at this meeting." ) );
00446         }
00447         performTransaction( incidence, iTIPCancel, attNew->fullName() );
00448         // ### can't delete cancel here because it is aliased to incidence which
00449         // is accessed in the next loop iteration (CID 4232)
00450         // delete cancel;
00451         continue;
00452       }
00453 
00454       Attendee::Ptr a( new Attendee( attNew->name(), attNew->email(), attNew->RSVP(),
00455                                      attNew->status(), attNew->role(), attNew->uid() ) );
00456 
00457       a->setDelegate( attNew->delegate() );
00458       a->setDelegator( attNew->delegator() );
00459       if ( ev ) {
00460         ev->addAttendee( a );
00461       } else if ( to ) {
00462         to->addAttendee( a );
00463       }
00464       ret = true;
00465       attendeeAdded = true;
00466     }
00467 
00468     // send update about new participants
00469     if ( attendeeAdded ) {
00470       bool sendMail = false;
00471       if ( ev || to ) {
00472         if ( KMessageBox::questionYesNo(
00473                0,
00474                i18nc( "@info",
00475                       "An attendee was added to the incidence. "
00476                       "Do you want to email the attendees an update message?" ),
00477                i18nc( "@title", "Attendee Added" ),
00478                KGuiItem( i18nc( "@option", "Send Messages" ) ),
00479                KGuiItem( i18nc( "@option", "Do Not Send" ) ) ) == KMessageBox::Yes ) {
00480           sendMail = true;
00481         }
00482       }
00483 
00484       if ( ev ) {
00485         ev->setRevision( ev->revision() + 1 );
00486         if ( sendMail ) {
00487           performTransaction( ev, iTIPRequest );
00488         }
00489       }
00490       if ( to ) {
00491         to->setRevision( to->revision() + 1 );
00492         if ( sendMail ) {
00493           performTransaction( to, iTIPRequest );
00494         }
00495       }
00496     }
00497 
00498     if ( ret ) {
00499       // We set at least one of the attendees, so the incidence changed
00500       // Note: This should not result in a sequence number bump
00501       if ( ev ) {
00502         ev->updated();
00503       } else if ( to ) {
00504         to->updated();
00505       }
00506     }
00507     if ( to ) {
00508       // for VTODO a REPLY can be used to update the completion status of
00509       // a to-do. see RFC2446 3.4.3
00510       Todo::Ptr update = incidence.dynamicCast<Todo>();
00511       Q_ASSERT( update );
00512       if ( update && ( to->percentComplete() != update->percentComplete() ) ) {
00513         to->setPercentComplete( update->percentComplete() );
00514         to->updated();
00515       }
00516     }
00517   } else {
00518     kError() << "No incidence for scheduling.";
00519   }
00520 
00521   if ( ret ) {
00522     deleteTransaction( incidence );
00523   }
00524   return ret;
00525 }
00526 
00527 bool Scheduler::acceptRefresh( const IncidenceBase::Ptr &incidence, ScheduleMessage::Status status )
00528 {
00529   Q_UNUSED( status );
00530   // handled in korganizer's IncomingDialog
00531   deleteTransaction( incidence );
00532   return false;
00533 }
00534 
00535 bool Scheduler::acceptCounter( const IncidenceBase::Ptr &incidence, ScheduleMessage::Status status )
00536 {
00537   Q_UNUSED( status );
00538   deleteTransaction( incidence );
00539   return false;
00540 }
00541 
00542 bool Scheduler::acceptFreeBusy( const IncidenceBase::Ptr &incidence, iTIPMethod method )
00543 {
00544   if ( !d->mFreeBusyCache ) {
00545     kError() << "Scheduler: no FreeBusyCache.";
00546     return false;
00547   }
00548 
00549   FreeBusy::Ptr freebusy = incidence.staticCast<FreeBusy>();
00550 
00551   kDebug() << "freeBusyDirName:" << freeBusyDir();
00552 
00553   Person::Ptr from;
00554   if( method == iTIPPublish ) {
00555     from = freebusy->organizer();
00556   }
00557   if ( ( method == iTIPReply ) && ( freebusy->attendeeCount() == 1 ) ) {
00558     Attendee::Ptr attendee = freebusy->attendees().first();
00559     from->setName( attendee->name() );
00560     from->setEmail( attendee->email() );
00561   }
00562 
00563   if ( !d->mFreeBusyCache->saveFreeBusy( freebusy, from ) ) {
00564     return false;
00565   }
00566 
00567   deleteTransaction( incidence );
00568   return true;
00569 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon May 14 2012 04:49:08 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KCalUtils Library

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

kdepimlibs-4.8.3 API Reference

Skip menu "kdepimlibs-4.8.3 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal