00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037 #include "kogroupware.h"
00038 #include "freebusymanager.h"
00039 #include "calendarview.h"
00040 #include "mailscheduler.h"
00041 #include "koprefs.h"
00042 #include "koincidenceeditor.h"
00043 #include <libemailfunctions/email.h>
00044 #include <libkcal/attendee.h>
00045 #include <libkcal/journal.h>
00046 #include <libkcal/incidenceformatter.h>
00047 #include <kdebug.h>
00048 #include <kmessagebox.h>
00049 #include <kstandarddirs.h>
00050 #include <kdirwatch.h>
00051 #include <qfile.h>
00052 #include <qregexp.h>
00053 #include <qdir.h>
00054 #include <qtimer.h>
00055
00056 FreeBusyManager *KOGroupware::mFreeBusyManager = 0;
00057
00058 KOGroupware *KOGroupware::mInstance = 0;
00059
00060 KOGroupware *KOGroupware::create( CalendarView *view,
00061 KCal::CalendarResources *calendar )
00062 {
00063 if( !mInstance )
00064 mInstance = new KOGroupware( view, calendar );
00065 return mInstance;
00066 }
00067
00068 KOGroupware *KOGroupware::instance()
00069 {
00070
00071 Q_ASSERT( mInstance );
00072 return mInstance;
00073 }
00074
00075
00076 KOGroupware::KOGroupware( CalendarView* view, KCal::CalendarResources* cal )
00077 : QObject( 0, "kmgroupware_instance" ), mView( view ), mCalendar( cal ), mDoNotNotify( false )
00078 {
00079
00080 KDirWatch* watcher = KDirWatch::self();
00081 watcher->addDir( locateLocal( "data", "korganizer/income.accepted/" ) );
00082 watcher->addDir( locateLocal( "data", "korganizer/income.tentative/" ) );
00083 watcher->addDir( locateLocal( "data", "korganizer/income.counter/" ) );
00084 watcher->addDir( locateLocal( "data", "korganizer/income.cancel/" ) );
00085 watcher->addDir( locateLocal( "data", "korganizer/income.reply/" ) );
00086 watcher->addDir( locateLocal( "data", "korganizer/income.delegated/" ) );
00087 connect( watcher, SIGNAL( dirty( const QString& ) ),
00088 this, SLOT( incomingDirChanged( const QString& ) ) );
00089
00090 QTimer::singleShot( 0, this, SLOT(initialCheckForChanges()) );
00091
00092
00093 lastUsedDialogAnswer = KMessageBox::Yes;
00094 }
00095
00096 void KOGroupware::initialCheckForChanges()
00097 {
00098 incomingDirChanged( locateLocal( "data", "korganizer/income.accepted/" ) );
00099 incomingDirChanged( locateLocal( "data", "korganizer/income.tentative/" ) );
00100 incomingDirChanged( locateLocal( "data", "korganizer/income.counter/" ) );
00101 incomingDirChanged( locateLocal( "data", "korganizer/income.cancel/" ) );
00102 incomingDirChanged( locateLocal( "data", "korganizer/income.reply/" ) );
00103 incomingDirChanged( locateLocal( "data", "korganizer/income.delegated/" ) );
00104 }
00105
00106 void KOGroupware::slotViewNewIncidenceChanger( IncidenceChangerBase* changer )
00107 {
00108
00109 connect( changer, SIGNAL( incidenceAdded( Incidence* ) ),
00110 mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00111 connect( changer, SIGNAL( incidenceChanged( Incidence*, Incidence*, KOGlobals::WhatChanged ) ),
00112 mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00113 connect( changer, SIGNAL( incidenceDeleted( Incidence * ) ),
00114 mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00115 }
00116
00117 FreeBusyManager *KOGroupware::freeBusyManager()
00118 {
00119 if ( !mFreeBusyManager ) {
00120 mFreeBusyManager = new FreeBusyManager( this, "freebusymanager" );
00121 mFreeBusyManager->setCalendar( mCalendar );
00122 connect( mCalendar, SIGNAL( calendarChanged() ),
00123 mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00124 connect( mView, SIGNAL( newIncidenceChanger( IncidenceChangerBase* ) ),
00125 this, SLOT( slotViewNewIncidenceChanger( IncidenceChangerBase* ) ) );
00126 slotViewNewIncidenceChanger( mView->incidenceChanger() );
00127 }
00128
00129 return mFreeBusyManager;
00130 }
00131
00132 void KOGroupware::incomingDirChanged( const QString& path )
00133 {
00134 const QString incomingDirName = locateLocal( "data","korganizer/" )
00135 + "income.";
00136 if ( !path.startsWith( incomingDirName ) ) {
00137 kdDebug(5850) << "incomingDirChanged: Wrong dir " << path << endl;
00138 return;
00139 }
00140 QString action = path.mid( incomingDirName.length() );
00141 while ( action.length() > 0 && action[ action.length()-1 ] == '/' )
00142
00143 action.truncate( action.length()-1 );
00144
00145
00146 QDir dir( path );
00147 const QStringList files = dir.entryList( QDir::Files );
00148 if ( files.isEmpty() )
00149
00150 return;
00151
00152
00153 QFile f( path + "/" + files[0] );
00154 if (!f.open(IO_ReadOnly)) {
00155 kdError(5850) << "Can't open file '" << files[0] << "'" << endl;
00156 return;
00157 }
00158 QTextStream t(&f);
00159 t.setEncoding( QTextStream::UnicodeUTF8 );
00160 QString receiver = KPIM::getFirstEmailAddress( t.readLine() );
00161 QString iCal = t.read();
00162
00163 f.remove();
00164
00165 ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar, iCal );
00166 if ( !message ) {
00167 QString errorMessage;
00168 if (mFormat.exception())
00169 errorMessage = i18n( "Error message: %1" ).arg( mFormat.exception()->message() );
00170 kdDebug(5850) << "MailScheduler::retrieveTransactions() Error parsing "
00171 << errorMessage << endl;
00172 KMessageBox::detailedError( mView,
00173 i18n("Error while processing an invitation or update."),
00174 errorMessage );
00175 return;
00176 }
00177
00178 KCal::Scheduler::Method method =
00179 static_cast<KCal::Scheduler::Method>( message->method() );
00180 KCal::ScheduleMessage::Status status = message->status();
00181 KCal::Incidence* incidence =
00182 dynamic_cast<KCal::Incidence*>( message->event() );
00183 if(!incidence) {
00184 delete message;
00185 return;
00186 }
00187 KCal::MailScheduler scheduler( mCalendar );
00188 if ( action.startsWith( "accepted" ) || action.startsWith( "tentative" )
00189 || action.startsWith( "delegated" ) || action.startsWith( "counter" ) ) {
00190
00191
00192 KCal::Attendee::List attendees = incidence->attendees();
00193 KCal::Attendee::List::ConstIterator it;
00194 for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00195 if( (*it)->email() == receiver ) {
00196 if ( action.startsWith( "accepted" ) )
00197 (*it)->setStatus( KCal::Attendee::Accepted );
00198 else if ( action.startsWith( "tentative" ) )
00199 (*it)->setStatus( KCal::Attendee::Tentative );
00200 else if ( KOPrefs::instance()->outlookCompatCounterProposals() && action.startsWith( "counter" ) )
00201 (*it)->setStatus( KCal::Attendee::Tentative );
00202 else if ( action.startsWith( "delegated" ) )
00203 (*it)->setStatus( KCal::Attendee::Delegated );
00204 break;
00205 }
00206 }
00207 if ( KOPrefs::instance()->outlookCompatCounterProposals() || !action.startsWith( "counter" ) )
00208 scheduler.acceptTransaction( incidence, method, status, receiver );
00209 } else if ( action.startsWith( "cancel" ) )
00210
00211 scheduler.acceptTransaction( incidence, KCal::Scheduler::Cancel, status, receiver );
00212 else if ( action.startsWith( "reply" ) ) {
00213 if ( method != Scheduler::Counter ) {
00214 scheduler.acceptTransaction( incidence, method, status );
00215 } else {
00216
00217 scheduler.acceptCounterProposal( incidence );
00218
00219 sendICalMessage( mView, Scheduler::Request, incidence, 0, KOGlobals::INCIDENCEEDITED, false );
00220 }
00221 } else
00222 kdError(5850) << "Unknown incoming action " << action << endl;
00223
00224 if ( action.startsWith( "counter" ) ) {
00225 mView->editIncidence( incidence, QDate(), true );
00226 KOIncidenceEditor *tmp = mView->editorDialog( incidence );
00227 tmp->selectInvitationCounterProposal( true );
00228 }
00229 mView->updateView();
00230 }
00231
00232 class KOInvitationFormatterHelper : public InvitationFormatterHelper
00233 {
00234 public:
00235 virtual QString generateLinkURL( const QString &id ) { return "kmail:groupware_request_" + id; }
00236 };
00237
00238
00239 static bool stringCompare( const QString &s1, const QString &s2 )
00240 {
00241 return ( s1.isEmpty() && s2.isEmpty() ) || ( s1 == s2 );
00242 }
00243
00244 static bool compareIncsExceptAttendees( Incidence *i1, Incidence *i2 )
00245 {
00246 if( i1->alarms().count() != i2->alarms().count() ) {
00247 return false;
00248 }
00249
00250 Alarm::List::ConstIterator a1 = i1->alarms().begin();
00251 Alarm::List::ConstIterator a2 = i2->alarms().begin();
00252 for( ; a1 != i1->alarms().end() && a2 != i2->alarms().end(); ++a1, ++a2 ) {
00253 if( **a1 == **a2 ) {
00254 continue;
00255 } else {
00256 return false;
00257 }
00258 }
00259
00260 bool recurrenceEqual = ( i1->recurrence() == 0 && i2->recurrence() == 0 );
00261 if ( !recurrenceEqual ) {
00262 recurrenceEqual = i1->recurrence() != 0 &&
00263 i2->recurrence() != 0 &&
00264 i2->recurrence() == i2->recurrence();
00265 }
00266
00267 return
00268 recurrenceEqual &&
00269 i1->dtStart() == i2->dtStart() &&
00270 i1->organizer() == i2->organizer() &&
00271 i1->doesFloat() == i2->doesFloat() &&
00272 i1->duration() == i2->duration() &&
00273 i2->hasDuration() == i2->hasDuration() &&
00274 stringCompare( i1->description(), i2->description() ) &&
00275 stringCompare( i1->summary(), i2->summary() ) &&
00276 i1->categories() == i2->categories() &&
00277 i1->attachments() == i2->attachments() &&
00278 i1->secrecy() == i2->secrecy() &&
00279 i1->priority() == i2->priority() &&
00280 stringCompare( i1->location(), i2->location() );
00281 }
00282
00283
00284
00285
00286
00287
00288
00289 bool KOGroupware::sendICalMessage( QWidget* parent,
00290 KCal::Scheduler::Method method,
00291 Incidence *incidence,
00292 Incidence *oldincidence,
00293 KOGlobals::HowChanged action,
00294 bool attendeeStatusChanged,
00295 bool useLastDialogAnswer )
00296 {
00297
00298 if ( incidence->attendees().isEmpty() )
00299 return true;
00300
00301 bool isOrganizer = KOPrefs::instance()->thatIsMe( incidence->organizer().email() );
00302 int rc = 0;
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317 if ( isOrganizer ) {
00318
00319
00320 if ( incidence->attendees().count() > 1
00321 || incidence->attendees().first()->email() != incidence->organizer().email() ) {
00322
00323 QString txt;
00324 switch( action ) {
00325 case KOGlobals::INCIDENCEEDITED:
00326 {
00327 bool sendUpdate = true;
00328
00329 Attendee::List attendees = incidence->attendees();
00330 Attendee::List::ConstIterator it;
00331
00332
00333 Attendee::List sameAtts;
00334 for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00335 if ( (*it)->email() != incidence->organizer().email() ) {
00336 sameAtts << *it;
00337 }
00338 }
00339
00340 if ( incidence->summary().isEmpty() ) {
00341 incidence->setSummary( i18n( "<No summary given>" ) );
00342 }
00343
00344 if ( oldincidence ) {
00345
00346
00347
00348
00349
00350 Attendee::List oldattendees = oldincidence->attendees();
00351 Attendee::List::ConstIterator ot;
00352
00353 Attendee::List newAtts;
00354 Attendee::List remAtts;
00355 sameAtts.clear();
00356
00357
00358 for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00359 bool found = false;
00360 for ( ot = oldattendees.begin(); ot != oldattendees.end(); ++ot ) {
00361 if ( (*it)->email() == (*ot)->email() ) {
00362 found = true;
00363 break;
00364 }
00365 }
00366 if ( !found ) {
00367 newAtts << *it;
00368 } else {
00369 if ( (*it)->email() != incidence->organizer().email() ) {
00370 sameAtts << *it;
00371 }
00372 }
00373 }
00374
00375
00376 for ( ot = oldattendees.begin(); ot != oldattendees.end(); ++ot ) {
00377 bool found = false;
00378 for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00379 if ( (*it)->email() == (*ot)->email() ) {
00380 found = true;
00381 break;
00382 }
00383 }
00384 if ( !found ) {
00385 remAtts << *ot;
00386 }
00387 }
00388
00389
00390 if ( compareIncsExceptAttendees( incidence, oldincidence ) ) {
00391
00392 sendUpdate = false;
00393 }
00394
00395
00396 if ( newAtts.count() > 0 ) {
00397 QStringList attStrList;
00398 for ( it = newAtts.begin(); it != newAtts.end(); ++it ) {
00399 attStrList << (*it)->fullName();
00400 }
00401 const QString recipients = attStrList.join( "," );
00402
00403 int newMail = KMessageBox::questionYesNoList(
00404 parent,
00405 i18n( "You are adding new attendees to the invitation \"%1\".\n"
00406 "Do you want to email an invitation to these new attendees?" ).
00407 arg( incidence->summary() ),
00408 attStrList,
00409 i18n( "New Attendees" ),
00410 KGuiItem( i18n( "Send Email" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
00411 if ( newMail == KMessageBox::Yes ) {
00412 KCal::MailScheduler scheduler( mCalendar );
00413 incidence->setRevision( 0 );
00414 scheduler.performTransaction( incidence, Scheduler::Request, recipients );
00415 }
00416 }
00417
00418
00419 if ( remAtts.count() > 0 ) {
00420 QStringList attStrList;
00421 for ( it = remAtts.begin(); it != remAtts.end(); ++it ) {
00422 attStrList << (*it)->fullName();
00423 }
00424 const QString recipients = attStrList.join( "," );
00425
00426 int newMail = KMessageBox::questionYesNoList(
00427 parent,
00428 i18n( "You removed attendees from the invitation \"%1\".\n"
00429 "Do you want to email a cancellation message to these attendees?" ).
00430 arg( incidence->summary() ),
00431 attStrList,
00432 i18n( "Removed Attendees" ),
00433 KGuiItem( i18n( "Send Email" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
00434 if ( newMail == KMessageBox::Yes ) {
00435 KCal::MailScheduler scheduler( mCalendar );
00436 scheduler.performTransaction( incidence, Scheduler::Cancel, recipients );
00437 }
00438 }
00439 }
00440
00441
00442 if ( sameAtts.count() > 0 ) {
00443 QStringList attStrList;
00444 Attendee::List::ConstIterator it;
00445 for ( it = sameAtts.begin(); it != sameAtts.end(); ++it ) {
00446 attStrList << (*it)->fullName();
00447 }
00448 const QString recipients = attStrList.join( "," );
00449 int newMail;
00450 if ( sendUpdate ) {
00451 newMail = KMessageBox::questionYesNoList(
00452 parent,
00453 i18n( "You changed the invitation \"%1\".\n"
00454 "Do you want to email an updated invitation to these attendees?" ).
00455 arg( incidence->summary() ),
00456 attStrList,
00457 i18n( "Send Invitation Update" ),
00458 KGuiItem( i18n( "Send Email" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
00459 } else {
00460 newMail = KMessageBox::questionYesNoList(
00461 parent,
00462 i18n( "You changed the invitation attendee list only.\n"
00463 "Do you want to email an updated invitation showing the new attendees?" ),
00464 attStrList,
00465 i18n( "Send Invitation Update" ),
00466 KGuiItem( i18n( "Send Email" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
00467 }
00468 if ( newMail == KMessageBox::Yes ) {
00469 KCal::MailScheduler scheduler( mCalendar );
00470 scheduler.performTransaction( incidence, Scheduler::Request, recipients );
00471 }
00472 }
00473 return true;
00474 }
00475
00476 case KOGlobals::INCIDENCEDELETED:
00477 Q_ASSERT( incidence->type() == "Event" || incidence->type() == "Todo" );
00478 if ( incidence->type() == "Event" ) {
00479 txt = i18n( "You removed the invitation \"%1\".\n"
00480 "Do you want to email the attendees that the event is canceled?" ).
00481 arg( incidence->summary() );
00482 } else if ( incidence->type() == "Todo" ) {
00483 txt = i18n( "You removed the invitation \"%1\".\n"
00484 "Do you want to email the attendees that the todo is canceled?" ).
00485 arg( incidence->summary() );
00486 }
00487 break;
00488
00489 case KOGlobals::INCIDENCEADDED:
00490 if ( incidence->type() == "Event" ) {
00491 txt = i18n( "The event \"%1\" includes other people.\n"
00492 "Do you want to email the invitation to the attendees?" ).
00493 arg( incidence->summary() );
00494 } else if ( incidence->type() == "Todo" ) {
00495 txt = i18n( "The todo \"%1\" includes other people.\n"
00496 "Do you want to email the invitation to the attendees?" ).
00497 arg( incidence->summary() );
00498 } else {
00499 txt = i18n( "This incidence includes other people. "
00500 "Should an email be sent to the attendees?" );
00501 }
00502 break;
00503
00504 default:
00505 kdError() << "Unsupported HowChanged action" << int( action ) << endl;
00506 break;
00507 }
00508
00509 if ( useLastDialogAnswer ) {
00510 rc = lastUsedDialogAnswer;
00511 } else {
00512 lastUsedDialogAnswer = rc = KMessageBox::questionYesNo(
00513 parent, txt, i18n( "Group Scheduling Email" ),
00514 KGuiItem( i18n( "Send Email" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
00515 }
00516 } else {
00517 return true;
00518 }
00519 } else if( incidence->type() == "Todo" ) {
00520 if( method == Scheduler::Request )
00521
00522 method = Scheduler::Reply;
00523
00524 if ( useLastDialogAnswer ) {
00525 rc = lastUsedDialogAnswer;
00526 } else {
00527
00528 const QString txt =
00529 i18n( "Do you want to send a status update to the organizer of this task?" );
00530 lastUsedDialogAnswer = rc = KMessageBox::questionYesNo(
00531 parent, txt, i18n( "Group Scheduling Email" ),
00532 KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
00533 }
00534 } else if ( incidence->type() == "Event" ) {
00535 QString txt;
00536 if ( attendeeStatusChanged && method == Scheduler::Request ) {
00537 txt = i18n( "Your status as an attendee of this event changed. "
00538 "Do you want to send a status update to the event organizer?" );
00539 method = Scheduler::Reply;
00540 if ( useLastDialogAnswer ) {
00541 rc = lastUsedDialogAnswer;
00542 } else {
00543 lastUsedDialogAnswer = rc = KMessageBox::questionYesNo(
00544 parent, txt, i18n( "Group Scheduling Email" ),
00545 KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
00546 }
00547 } else {
00548 if( action == KOGlobals::INCIDENCEDELETED ) {
00549 const QStringList myEmails = KOPrefs::instance()->allEmails();
00550 bool askConfirmation = false;
00551 for ( QStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it ) {
00552 QString email = *it;
00553 Attendee *me = incidence->attendeeByMail(email);
00554 if ( me &&
00555 ( me->status() == KCal::Attendee::Accepted ||
00556 me->status() == KCal::Attendee::Delegated ) ) {
00557 askConfirmation = true;
00558 break;
00559 }
00560 }
00561
00562 if ( !askConfirmation ) {
00563 return true;
00564 }
00565
00566 txt = i18n( "You had previously accepted an invitation to this event. "
00567 "Do you want to send an updated response to the organizer "
00568 "declining the invitation?" );
00569 if ( useLastDialogAnswer ) {
00570 rc = lastUsedDialogAnswer;
00571 } else {
00572 lastUsedDialogAnswer = rc = KMessageBox::questionYesNo(
00573 parent, txt, i18n( "Group Scheduling Email" ),
00574 KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
00575 setDoNotNotify( rc == KMessageBox::No );
00576 }
00577 } else {
00578 if ( useLastDialogAnswer ) {
00579 rc = lastUsedDialogAnswer;
00580 } else {
00581 txt = i18n( "You are not the organizer of this event. Editing it will "
00582 "bring your calendar out of sync with the organizer's calendar. "
00583 "Do you really want to edit it?" );
00584 lastUsedDialogAnswer = rc = KMessageBox::warningYesNo( parent, txt );
00585 }
00586 return ( rc == KMessageBox::Yes );
00587 }
00588 }
00589 } else {
00590 kdWarning(5850) << "Groupware messages for Journals are not implemented yet!" << endl;
00591 return true;
00592 }
00593
00594 if ( rc == KMessageBox::Yes ) {
00595
00596
00597 if( incidence->summary().isEmpty() )
00598 incidence->setSummary( i18n("<No summary given>") );
00599
00600
00601 KCal::MailScheduler scheduler( mCalendar );
00602 scheduler.performTransaction( incidence, method );
00603
00604 return true;
00605 } else if ( rc == KMessageBox::No ) {
00606 return true;
00607 } else {
00608 return false;
00609 }
00610 }
00611
00612 void KOGroupware::sendCounterProposal(KCal::Calendar *calendar, KCal::Event * oldEvent, KCal::Event * newEvent) const
00613 {
00614 if ( !oldEvent || !newEvent || *oldEvent == *newEvent || !KOPrefs::instance()->mUseGroupwareCommunication )
00615 return;
00616 if ( KOPrefs::instance()->outlookCompatCounterProposals() ) {
00617 Incidence* tmp = oldEvent->clone();
00618 tmp->setSummary( i18n("Counter proposal: %1").arg( newEvent->summary() ) );
00619 tmp->setDescription( newEvent->description() );
00620 tmp->addComment( i18n("Proposed new meeting time: %1 - %2").
00621 arg( IncidenceFormatter::dateToString( newEvent->dtStart() ),
00622 IncidenceFormatter::dateToString( newEvent->dtEnd() ) ) );
00623 KCal::MailScheduler scheduler( calendar );
00624 scheduler.performTransaction( tmp, Scheduler::Reply );
00625 delete tmp;
00626 } else {
00627 KCal::MailScheduler scheduler( calendar );
00628 scheduler.performTransaction( newEvent, Scheduler::Counter );
00629 }
00630 }
00631
00632 #include "kogroupware.moc"