korganizer

kogroupware.cpp

00001 /*
00002   This file is part of the Groupware/KOrganizer integration.
00003 
00004   Requires the Qt and KDE widget libraries, available at no cost at
00005   http://www.trolltech.com and http://www.kde.org respectively
00006 
00007   Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB
00008         <info@klaralvdalens-datakonsult.se>
00009 
00010   This program is free software; you can redistribute it and/or modify
00011   it under the terms of the GNU General Public License as published by
00012   the Free Software Foundation; either version 2 of the License, or
00013   (at your option) any later version.
00014 
00015   This program is distributed in the hope that it will be useful,
00016   but WITHOUT ANY WARRANTY; without even the implied warranty of
00017   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00018   GNU General Public License for more details.
00019 
00020   You should have received a copy of the GNU General Public License
00021   along with this program; if not, write to the Free Software
00022   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00023   MA  02110-1301, USA.
00024 
00025   In addition, as a special exception, the copyright holders give
00026   permission to link the code of this program with any edition of
00027   the Qt library by Trolltech AS, Norway (or with modified versions
00028   of Qt that use the same license as Qt), and distribute linked
00029   combinations including the two.  You must obey the GNU General
00030   Public License in all respects for all of the code used other than
00031   Qt.  If you modify this file, you may extend this exception to
00032   your version of the file, but you are not obligated to do so.  If
00033   you do not wish to do so, delete this exception statement from
00034   your version.
00035 */
00036 
00037 #include "kogroupware.h"
00038 #include "freebusymanager.h"
00039 #include "calendarview.h"
00040 #include "mailscheduler.h"
00041 #include "koprefs.h"
00042 #include <libemailfunctions/email.h>
00043 #include <libkcal/attendee.h>
00044 #include <libkcal/journal.h>
00045 #include <libkcal/incidenceformatter.h>
00046 #include <kdebug.h>
00047 #include <kmessagebox.h>
00048 #include <kstandarddirs.h>
00049 #include <kdirwatch.h>
00050 #include <qfile.h>
00051 #include <qregexp.h>
00052 #include <qdir.h>
00053 
00054 FreeBusyManager *KOGroupware::mFreeBusyManager = 0;
00055 
00056 KOGroupware *KOGroupware::mInstance = 0;
00057 
00058 KOGroupware *KOGroupware::create( CalendarView *view,
00059                                   KCal::CalendarResources *calendar )
00060 {
00061   if( !mInstance )
00062     mInstance = new KOGroupware( view, calendar );
00063   return mInstance;
00064 }
00065 
00066 KOGroupware *KOGroupware::instance()
00067 {
00068   // Doesn't create, that is the task of create()
00069   Q_ASSERT( mInstance );
00070   return mInstance;
00071 }
00072 
00073 
00074  KOGroupware::KOGroupware( CalendarView* view, KCal::CalendarResources* cal )
00075    : QObject( 0, "kmgroupware_instance" ), mView( view ), mCalendar( cal )
00076 {
00077   // Set up the dir watch of the three incoming dirs
00078   KDirWatch* watcher = KDirWatch::self();
00079   watcher->addDir( locateLocal( "data", "korganizer/income.accepted/" ) );
00080   watcher->addDir( locateLocal( "data", "korganizer/income.tentative/" ) );
00081   watcher->addDir( locateLocal( "data", "korganizer/income.cancel/" ) );
00082   watcher->addDir( locateLocal( "data", "korganizer/income.reply/" ) );
00083   connect( watcher, SIGNAL( dirty( const QString& ) ),
00084            this, SLOT( incomingDirChanged( const QString& ) ) );
00085   // Now set the ball rolling
00086   incomingDirChanged( locateLocal( "data", "korganizer/income.accepted/" ) );
00087   incomingDirChanged( locateLocal( "data", "korganizer/income.tentative/" ) );
00088   incomingDirChanged( locateLocal( "data", "korganizer/income.cancel/" ) );
00089   incomingDirChanged( locateLocal( "data", "korganizer/income.reply/" ) );
00090 
00091   if ( !mFreeBusyManager ) {
00092     mFreeBusyManager = new FreeBusyManager( this, "freebusymanager" );
00093     mFreeBusyManager->setCalendar( mCalendar );
00094     connect( mCalendar, SIGNAL( calendarChanged() ),
00095              mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00096     connect( mView, SIGNAL( newIncidenceChanger( IncidenceChangerBase* ) ),
00097              this, SLOT( slotViewNewIncidenceChanger( IncidenceChangerBase* ) ) );
00098     slotViewNewIncidenceChanger( mView->incidenceChanger() );
00099   }
00100 
00101 }
00102 
00103 void KOGroupware::slotViewNewIncidenceChanger( IncidenceChangerBase* changer )
00104 {
00105     // Call slot perhapsUploadFB if an incidence was added, changed or removed
00106     connect( changer, SIGNAL( incidenceAdded( Incidence* ) ),
00107              mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00108     connect( changer, SIGNAL( incidenceChanged( Incidence*, Incidence*, int ) ),
00109              mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00110     connect( changer, SIGNAL( incidenceChanged( Incidence*, Incidence* ) ),
00111              mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) ) ;
00112     connect( changer, SIGNAL( incidenceDeleted( Incidence * ) ),
00113              mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00114 }
00115 
00116 FreeBusyManager *KOGroupware::freeBusyManager()
00117 {
00118   return mFreeBusyManager;
00119 }
00120 
00121 void KOGroupware::incomingDirChanged( const QString& path )
00122 {
00123   const QString incomingDirName = locateLocal( "data","korganizer/" )
00124                                   + "income.";
00125   if ( !path.startsWith( incomingDirName ) ) {
00126     kdDebug(5850) << "incomingDirChanged: Wrong dir " << path << endl;
00127     return;
00128   }
00129   QString action = path.mid( incomingDirName.length() );
00130   while ( action.length() > 0 && action[ action.length()-1 ] == '/' )
00131     // Strip slashes at the end
00132     action.truncate( action.length()-1 );
00133 
00134   // Handle accepted invitations
00135   QDir dir( path );
00136   const QStringList files = dir.entryList( QDir::Files );
00137   if ( files.isEmpty() )
00138     // No more files here
00139     return;
00140 
00141   // Read the file and remove it
00142   QFile f( path + "/" + files[0] );
00143   if (!f.open(IO_ReadOnly)) {
00144     kdError(5850) << "Can't open file '" << files[0] << "'" << endl;
00145     return;
00146   }
00147   QTextStream t(&f);
00148   t.setEncoding( QTextStream::UnicodeUTF8 );
00149   QString receiver = KPIM::getFirstEmailAddress( t.readLine() );
00150   QString iCal = t.read();
00151 
00152   f.remove();
00153 
00154   ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar, iCal );
00155   if ( !message ) {
00156     QString errorMessage;
00157     if (mFormat.exception())
00158       errorMessage = i18n( "Error message: %1" ).arg( mFormat.exception()->message() );
00159     kdDebug(5850) << "MailScheduler::retrieveTransactions() Error parsing "
00160                   << errorMessage << endl;
00161     KMessageBox::detailedError( mView,
00162         i18n("Error while processing an invitation or update."),
00163         errorMessage );
00164     return;
00165   }
00166 
00167   KCal::Scheduler::Method method =
00168     static_cast<KCal::Scheduler::Method>( message->method() );
00169   KCal::ScheduleMessage::Status status = message->status();
00170   KCal::Incidence* incidence =
00171     dynamic_cast<KCal::Incidence*>( message->event() );
00172   KCal::MailScheduler scheduler( mCalendar );
00173   if ( action.startsWith( "accepted" ) || action.startsWith( "tentative" ) ) {
00174     // Find myself and set my status. This can't be done in the scheduler,
00175     // since this does not know the choice I made in the KMail bpf
00176     KCal::Attendee::List attendees = incidence->attendees();
00177     KCal::Attendee::List::ConstIterator it;
00178     for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00179       if( (*it)->email() == receiver ) {
00180         if ( action.startsWith( "accepted" ) )
00181           (*it)->setStatus( KCal::Attendee::Accepted );
00182         else
00183           (*it)->setStatus( KCal::Attendee::Tentative );
00184         break;
00185       }
00186     }
00187     scheduler.acceptTransaction( incidence, method, status );
00188   } else if ( action.startsWith( "cancel" ) )
00189     // Delete the old incidence, if one is present
00190     scheduler.acceptTransaction( incidence, KCal::Scheduler::Cancel, status );
00191   else if ( action.startsWith( "reply" ) )
00192     scheduler.acceptTransaction( incidence, method, status );
00193   else
00194     kdError(5850) << "Unknown incoming action " << action << endl;
00195   mView->updateView();
00196 }
00197 
00198 class KOInvitationFormatterHelper : public InvitationFormatterHelper
00199 {
00200   public:
00201     virtual QString generateLinkURL( const QString &id ) { return "kmail:groupware_request_" + id; }
00202 };
00203 
00204 /* This function sends mails if necessary, and makes sure the user really
00205  * want to change his calendar.
00206  *
00207  * Return true means accept the changes
00208  * Return false means revert the changes
00209  */
00210 bool KOGroupware::sendICalMessage( QWidget* parent,
00211                                    KCal::Scheduler::Method method,
00212                                    Incidence* incidence, bool isDeleting,
00213                                    bool statusChanged )
00214 {
00215   // If there are no attendees, don't bother
00216   if( incidence->attendees().isEmpty() )
00217     return true;
00218 
00219   bool isOrganizer = KOPrefs::instance()->thatIsMe( incidence->organizer().email() );
00220   int rc = 0;
00221   /*
00222    * There are two scenarios:
00223    * o "we" are the organizer, where "we" means any of the identities or mail
00224    *   addresses known to Kontact/PIM. If there are attendees, we need to mail
00225    *   them all, even if one or more of them are also "us". Otherwise there
00226    *   would be no way to invite a resource or our boss, other identities we
00227    *   also manage.
00228    * o "we: are not the organizer, which means we changed the completion status
00229    *   of a todo, or we changed our attendee status from, say, tentative to
00230    *   accepted. In both cases we only mail the organizer. All other changes
00231    *   bring us out of sync with the organizer, so we won't mail, if the user
00232    *   insists on applying them.
00233    */
00234 
00235   if ( isOrganizer ) {
00236     /* We are the organizer. If there is more than one attendee, or if there is
00237      * only one, and it's not the same as the organizer, ask the user to send
00238      * mail. */
00239     if ( incidence->attendees().count() > 1
00240         || incidence->attendees().first()->email() != incidence->organizer().email() ) {
00241       QString type;
00242       if( incidence->type() == "Event") type = i18n("event");
00243       else if( incidence->type() == "Todo" ) type = i18n("task");
00244       else if( incidence->type() == "Journal" ) type = i18n("journal entry");
00245       else type = incidence->type();
00246       QString txt = i18n( "This %1 includes other people. "
00247           "Should email be sent out to the attendees?" )
00248         .arg( type );
00249       rc = KMessageBox::questionYesNoCancel( parent, txt,
00250           i18n("Group Scheduling Email"), i18n("Send Email"), i18n("Do Not Send") );
00251     } else {
00252       return true;
00253     }
00254   } else if( incidence->type() == "Todo" ) {
00255     if( method == Scheduler::Request )
00256       // This is an update to be sent to the organizer
00257       method = Scheduler::Reply;
00258 
00259     // Ask if the user wants to tell the organizer about the current status
00260     QString txt = i18n( "Do you want to send a status update to the "
00261                         "organizer of this task?");
00262     rc = KMessageBox::questionYesNo( parent, txt, QString::null, i18n("Send Update"), i18n("Do Not Send") );
00263   } else if( incidence->type() == "Event" ) {
00264     QString txt;
00265     if ( statusChanged && method == Scheduler::Request ) {
00266       txt = i18n( "Your status as an attendee of this event "
00267           "changed. Do you want to send a status update to the "
00268           "organizer of this event?" );
00269       method = Scheduler::Reply;
00270       rc = KMessageBox::questionYesNo( parent, txt, QString::null, i18n("Send Update"), i18n("Do Not Send") );
00271     } else {
00272       if( isDeleting )
00273         txt = i18n( "You are not the organizer of this event. "
00274             "Deleting it will bring your calendar out of sync "
00275             "with the organizers calendar. Do you really want "
00276             "to delete it?" );
00277       else
00278         txt = i18n( "You are not the organizer of this event. "
00279             "Editing it will bring your calendar out of sync "
00280             "with the organizers calendar. Do you really want "
00281             "to edit it?" );
00282       rc = KMessageBox::warningYesNo( parent, txt );
00283       return ( rc == KMessageBox::Yes );
00284     }
00285   } else {
00286     kdWarning(5850) << "Groupware messages for Journals are not implemented yet!" << endl;
00287     return true;
00288   }
00289 
00290   if( rc == KMessageBox::Yes ) {
00291     // We will be sending out a message here. Now make sure there is
00292     // some summary
00293     if( incidence->summary().isEmpty() )
00294       incidence->setSummary( i18n("<No summary given>") );
00295 
00296     // Send the mail
00297     KCal::MailScheduler scheduler( mCalendar );
00298     scheduler.performTransaction( incidence, method );
00299 
00300     return true;
00301   } else if( rc == KMessageBox::No )
00302     return true;
00303   else
00304     return false;
00305 }
00306 
00307 
00308 #include "kogroupware.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys