diff options
Diffstat (limited to 'korganizer/kogroupware.cpp')
-rw-r--r-- | korganizer/kogroupware.cpp | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/korganizer/kogroupware.cpp b/korganizer/kogroupware.cpp new file mode 100644 index 000000000..d788abddd --- /dev/null +++ b/korganizer/kogroupware.cpp @@ -0,0 +1,354 @@ +/* + This file is part of the Groupware/KOrganizer integration. + + Requires the Qt and KDE widget libraries, available at no cost at + http://www.trolltech.com and http://www.kde.org respectively + + Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB + <info@klaralvdalens-datakonsult.se> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "kogroupware.h" +#include "freebusymanager.h" +#include "calendarview.h" +#include "mailscheduler.h" +#include "koprefs.h" +#include "koincidenceeditor.h" +#include <libemailfunctions/email.h> +#include <libkcal/attendee.h> +#include <libkcal/journal.h> +#include <libkcal/incidenceformatter.h> +#include <kdebug.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <kdirwatch.h> +#include <qfile.h> +#include <qregexp.h> +#include <qdir.h> +#include <qtimer.h> + +FreeBusyManager *KOGroupware::mFreeBusyManager = 0; + +KOGroupware *KOGroupware::mInstance = 0; + +KOGroupware *KOGroupware::create( CalendarView *view, + KCal::CalendarResources *calendar ) +{ + if( !mInstance ) + mInstance = new KOGroupware( view, calendar ); + return mInstance; +} + +KOGroupware *KOGroupware::instance() +{ + // Doesn't create, that is the task of create() + Q_ASSERT( mInstance ); + return mInstance; +} + + + KOGroupware::KOGroupware( CalendarView* view, KCal::CalendarResources* cal ) + : QObject( 0, "kmgroupware_instance" ), mView( view ), mCalendar( cal ) +{ + // Set up the dir watch of the three incoming dirs + KDirWatch* watcher = KDirWatch::self(); + watcher->addDir( locateLocal( "data", "korganizer/income.accepted/" ) ); + watcher->addDir( locateLocal( "data", "korganizer/income.tentative/" ) ); + watcher->addDir( locateLocal( "data", "korganizer/income.counter/" ) ); + watcher->addDir( locateLocal( "data", "korganizer/income.cancel/" ) ); + watcher->addDir( locateLocal( "data", "korganizer/income.reply/" ) ); + watcher->addDir( locateLocal( "data", "korganizer/income.delegated/" ) ); + connect( watcher, SIGNAL( dirty( const QString& ) ), + this, SLOT( incomingDirChanged( const QString& ) ) ); + // Now set the ball rolling + QTimer::singleShot( 0, this, SLOT(initialCheckForChanges()) ); +} + +void KOGroupware::initialCheckForChanges() +{ + incomingDirChanged( locateLocal( "data", "korganizer/income.accepted/" ) ); + incomingDirChanged( locateLocal( "data", "korganizer/income.tentative/" ) ); + incomingDirChanged( locateLocal( "data", "korganizer/income.counter/" ) ); + incomingDirChanged( locateLocal( "data", "korganizer/income.cancel/" ) ); + incomingDirChanged( locateLocal( "data", "korganizer/income.reply/" ) ); + incomingDirChanged( locateLocal( "data", "korganizer/income.delegated/" ) ); +} + +void KOGroupware::slotViewNewIncidenceChanger( IncidenceChangerBase* changer ) +{ + // Call slot perhapsUploadFB if an incidence was added, changed or removed + connect( changer, SIGNAL( incidenceAdded( Incidence* ) ), + mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) ); + connect( changer, SIGNAL( incidenceChanged( Incidence*, Incidence*, int ) ), + mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) ); + connect( changer, SIGNAL( incidenceChanged( Incidence*, Incidence* ) ), + mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) ) ; + connect( changer, SIGNAL( incidenceDeleted( Incidence * ) ), + mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) ); +} + +FreeBusyManager *KOGroupware::freeBusyManager() +{ + if ( !mFreeBusyManager ) { + mFreeBusyManager = new FreeBusyManager( this, "freebusymanager" ); + mFreeBusyManager->setCalendar( mCalendar ); + connect( mCalendar, SIGNAL( calendarChanged() ), + mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) ); + connect( mView, SIGNAL( newIncidenceChanger( IncidenceChangerBase* ) ), + this, SLOT( slotViewNewIncidenceChanger( IncidenceChangerBase* ) ) ); + slotViewNewIncidenceChanger( mView->incidenceChanger() ); + } + + return mFreeBusyManager; +} + +void KOGroupware::incomingDirChanged( const QString& path ) +{ + const QString incomingDirName = locateLocal( "data","korganizer/" ) + + "income."; + if ( !path.startsWith( incomingDirName ) ) { + kdDebug(5850) << "incomingDirChanged: Wrong dir " << path << endl; + return; + } + QString action = path.mid( incomingDirName.length() ); + while ( action.length() > 0 && action[ action.length()-1 ] == '/' ) + // Strip slashes at the end + action.truncate( action.length()-1 ); + + // Handle accepted invitations + QDir dir( path ); + const QStringList files = dir.entryList( QDir::Files ); + if ( files.isEmpty() ) + // No more files here + return; + + // Read the file and remove it + QFile f( path + "/" + files[0] ); + if (!f.open(IO_ReadOnly)) { + kdError(5850) << "Can't open file '" << files[0] << "'" << endl; + return; + } + QTextStream t(&f); + t.setEncoding( QTextStream::UnicodeUTF8 ); + QString receiver = KPIM::getFirstEmailAddress( t.readLine() ); + QString iCal = t.read(); + + f.remove(); + + ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar, iCal ); + if ( !message ) { + QString errorMessage; + if (mFormat.exception()) + errorMessage = i18n( "Error message: %1" ).arg( mFormat.exception()->message() ); + kdDebug(5850) << "MailScheduler::retrieveTransactions() Error parsing " + << errorMessage << endl; + KMessageBox::detailedError( mView, + i18n("Error while processing an invitation or update."), + errorMessage ); + return; + } + + KCal::Scheduler::Method method = + static_cast<KCal::Scheduler::Method>( message->method() ); + KCal::ScheduleMessage::Status status = message->status(); + KCal::Incidence* incidence = + dynamic_cast<KCal::Incidence*>( message->event() ); + KCal::MailScheduler scheduler( mCalendar ); + if ( action.startsWith( "accepted" ) || action.startsWith( "tentative" ) + || action.startsWith( "delegated" ) || action.startsWith( "counter" ) ) { + // Find myself and set my status. This can't be done in the scheduler, + // since this does not know the choice I made in the KMail bpf + KCal::Attendee::List attendees = incidence->attendees(); + KCal::Attendee::List::ConstIterator it; + for ( it = attendees.begin(); it != attendees.end(); ++it ) { + if( (*it)->email() == receiver ) { + if ( action.startsWith( "accepted" ) ) + (*it)->setStatus( KCal::Attendee::Accepted ); + else if ( action.startsWith( "tentative" ) ) + (*it)->setStatus( KCal::Attendee::Tentative ); + else if ( KOPrefs::instance()->outlookCompatCounterProposals() && action.startsWith( "counter" ) ) + (*it)->setStatus( KCal::Attendee::Tentative ); + else if ( action.startsWith( "delegated" ) ) + (*it)->setStatus( KCal::Attendee::Delegated ); + break; + } + } + if ( KOPrefs::instance()->outlookCompatCounterProposals() || !action.startsWith( "counter" ) ) + scheduler.acceptTransaction( incidence, method, status ); + } else if ( action.startsWith( "cancel" ) ) + // Delete the old incidence, if one is present + scheduler.acceptTransaction( incidence, KCal::Scheduler::Cancel, status ); + else if ( action.startsWith( "reply" ) ) { + if ( method != Scheduler::Counter ) { + scheduler.acceptTransaction( incidence, method, status ); + } else { + // accept counter proposal + scheduler.acceptCounterProposal( incidence ); + // send update to all attendees + sendICalMessage( mView, Scheduler::Request, incidence ); + } + } else + kdError(5850) << "Unknown incoming action " << action << endl; + + if ( action.startsWith( "counter" ) ) { + mView->editIncidence( incidence, true ); + KOIncidenceEditor *tmp = mView->editorDialog( incidence ); + tmp->selectInvitationCounterProposal( true ); + } + mView->updateView(); +} + +class KOInvitationFormatterHelper : public InvitationFormatterHelper +{ + public: + virtual QString generateLinkURL( const QString &id ) { return "kmail:groupware_request_" + id; } +}; + +/* This function sends mails if necessary, and makes sure the user really + * want to change his calendar. + * + * Return true means accept the changes + * Return false means revert the changes + */ +bool KOGroupware::sendICalMessage( QWidget* parent, + KCal::Scheduler::Method method, + Incidence* incidence, bool isDeleting, + bool statusChanged ) +{ + // If there are no attendees, don't bother + if( incidence->attendees().isEmpty() ) + return true; + + bool isOrganizer = KOPrefs::instance()->thatIsMe( incidence->organizer().email() ); + int rc = 0; + /* + * There are two scenarios: + * o "we" are the organizer, where "we" means any of the identities or mail + * addresses known to Kontact/PIM. If there are attendees, we need to mail + * them all, even if one or more of them are also "us". Otherwise there + * would be no way to invite a resource or our boss, other identities we + * also manage. + * o "we: are not the organizer, which means we changed the completion status + * of a todo, or we changed our attendee status from, say, tentative to + * accepted. In both cases we only mail the organizer. All other changes + * bring us out of sync with the organizer, so we won't mail, if the user + * insists on applying them. + */ + + if ( isOrganizer ) { + /* We are the organizer. If there is more than one attendee, or if there is + * only one, and it's not the same as the organizer, ask the user to send + * mail. */ + if ( incidence->attendees().count() > 1 + || incidence->attendees().first()->email() != incidence->organizer().email() ) { + QString type; + if( incidence->type() == "Event") type = i18n("event"); + else if( incidence->type() == "Todo" ) type = i18n("task"); + else if( incidence->type() == "Journal" ) type = i18n("journal entry"); + else type = incidence->type(); + QString txt = i18n( "This %1 includes other people. " + "Should email be sent out to the attendees?" ) + .arg( type ); + rc = KMessageBox::questionYesNoCancel( parent, txt, + i18n("Group Scheduling Email"), i18n("Send Email"), i18n("Do Not Send") ); + } else { + return true; + } + } else if( incidence->type() == "Todo" ) { + if( method == Scheduler::Request ) + // This is an update to be sent to the organizer + method = Scheduler::Reply; + + // Ask if the user wants to tell the organizer about the current status + QString txt = i18n( "Do you want to send a status update to the " + "organizer of this task?"); + rc = KMessageBox::questionYesNo( parent, txt, QString::null, i18n("Send Update"), i18n("Do Not Send") ); + } else if( incidence->type() == "Event" ) { + QString txt; + if ( statusChanged && method == Scheduler::Request ) { + txt = i18n( "Your status as an attendee of this event " + "changed. Do you want to send a status update to the " + "organizer of this event?" ); + method = Scheduler::Reply; + rc = KMessageBox::questionYesNo( parent, txt, QString::null, i18n("Send Update"), i18n("Do Not Send") ); + } else { + if( isDeleting ) + txt = i18n( "You are not the organizer of this event. " + "Deleting it will bring your calendar out of sync " + "with the organizers calendar. Do you really want " + "to delete it?" ); + else + txt = i18n( "You are not the organizer of this event. " + "Editing it will bring your calendar out of sync " + "with the organizers calendar. Do you really want " + "to edit it?" ); + rc = KMessageBox::warningYesNo( parent, txt ); + return ( rc == KMessageBox::Yes ); + } + } else { + kdWarning(5850) << "Groupware messages for Journals are not implemented yet!" << endl; + return true; + } + + if( rc == KMessageBox::Yes ) { + // We will be sending out a message here. Now make sure there is + // some summary + if( incidence->summary().isEmpty() ) + incidence->setSummary( i18n("<No summary given>") ); + + // Send the mail + KCal::MailScheduler scheduler( mCalendar ); + scheduler.performTransaction( incidence, method ); + + return true; + } else if( rc == KMessageBox::No ) + return true; + else + return false; +} + +void KOGroupware::sendCounterProposal(KCal::Calendar *calendar, KCal::Event * oldEvent, KCal::Event * newEvent) const +{ + if ( !oldEvent || !newEvent || *oldEvent == *newEvent || !KOPrefs::instance()->mUseGroupwareCommunication ) + return; + if ( KOPrefs::instance()->outlookCompatCounterProposals() ) { + Incidence* tmp = oldEvent->clone(); + tmp->setSummary( i18n("Counter proposal: %1").arg( newEvent->summary() ) ); + tmp->setDescription( newEvent->description() ); + tmp->addComment( i18n("Proposed new meeting time: %1 - %2").arg( newEvent->dtStartStr(), newEvent->dtEndStr() ) ); + KCal::MailScheduler scheduler( calendar ); + scheduler.performTransaction( tmp, Scheduler::Reply ); + delete tmp; + } else { + KCal::MailScheduler scheduler( calendar ); + scheduler.performTransaction( newEvent, Scheduler::Counter ); + } +} + +#include "kogroupware.moc" |