korganizer

freebusymanager.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�vdalens Datakonsult AB
00008         <info@klaralvdalens-datakonsult.se>
00009   Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
00010 
00011   This program is free software; you can redistribute it and/or modify
00012   it under the terms of the GNU General Public License as published by
00013   the Free Software Foundation; either version 2 of the License, or
00014   (at your option) any later version.
00015 
00016   This program is distributed in the hope that it will be useful,
00017   but WITHOUT ANY WARRANTY; without even the implied warranty of
00018   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00019   GNU General Public License for more details.
00020 
00021   You should have received a copy of the GNU General Public License
00022   along with this program; if not, write to the Free Software
00023   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
00024   MA  02110-1301, USA.
00025 
00026   In addition, as a special exception, the copyright holders give
00027   permission to link the code of this program with any edition of
00028   the Qt library by Trolltech AS, Norway (or with modified versions
00029   of Qt that use the same license as Qt), and distribute linked
00030   combinations including the two.  You must obey the GNU General
00031   Public License in all respects for all of the code used other than
00032   Qt.  If you modify this file, you may extend this exception to
00033   your version of the file, but you are not obligated to do so.  If
00034   you do not wish to do so, delete this exception statement from
00035   your version.
00036 */
00037 
00038 #include "freebusymanager.h"
00039 
00040 #include "koprefs.h"
00041 #include "mailscheduler.h"
00042 
00043 #include <libkcal/incidencebase.h>
00044 #include <libkcal/attendee.h>
00045 #include <libkcal/freebusy.h>
00046 #include <libkcal/journal.h>
00047 #include <libkcal/calendarlocal.h>
00048 #include <libkcal/icalformat.h>
00049 
00050 #include <kio/job.h>
00051 #include <kdebug.h>
00052 #include <kmessagebox.h>
00053 #include <ktempfile.h>
00054 #include <kio/netaccess.h>
00055 #include <kapplication.h>
00056 #include <kconfig.h>
00057 #include <klocale.h>
00058 #include <kstandarddirs.h>
00059 #include <kabc/stdaddressbook.h> 
00060 #include <kabc/addressee.h> 
00061 
00062 #include <qfile.h>
00063 #include <qbuffer.h>
00064 #include <qregexp.h>
00065 #include <qdir.h>
00066 
00067 using namespace KCal;
00068 
00069 FreeBusyDownloadJob::FreeBusyDownloadJob( const QString &email, const KURL &url,
00070                                           FreeBusyManager *manager,
00071                                           const char *name )
00072   : QObject( manager, name ), mManager( manager ), mEmail( email )
00073 {
00074   KIO::Job *job = KIO::get( url, false, false );
00075   connect( job, SIGNAL( result( KIO::Job * ) ),
00076            SLOT( slotResult( KIO::Job * ) ) );
00077   connect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
00078            SLOT( slotData( KIO::Job *, const QByteArray & ) ) );
00079 }
00080 
00081 FreeBusyDownloadJob::~FreeBusyDownloadJob()
00082 {
00083 }
00084 
00085 
00086 void FreeBusyDownloadJob::slotData( KIO::Job *, const QByteArray &data )
00087 {
00088   QByteArray tmp = data;
00089   tmp.resize( tmp.size() + 1 );
00090   tmp[tmp.size()-1] = 0;
00091   mFreeBusyData += tmp;
00092 }
00093 
00094 void FreeBusyDownloadJob::slotResult( KIO::Job *job )
00095 {
00096   kdDebug(5850) << "FreeBusyDownloadJob::slotResult() " << mEmail << endl;
00097 
00098   if( job->error() ) {
00099     kdDebug(5850) << "FreeBusyDownloadJob::slotResult() job error :-(" << endl;
00100   }
00101 
00102   FreeBusy *fb = mManager->iCalToFreeBusy( mFreeBusyData );
00103   if ( fb ) {
00104     Person p = fb->organizer();
00105     p.setEmail( mEmail );
00106     mManager->saveFreeBusy( fb, p );
00107   }
00108   emit freeBusyDownloaded( fb, mEmail );
00109   // PENDING(steffen): Is this safe?
00110   //job->deleteLater();
00111   delete this;
00112 }
00113 
00114 
00115 FreeBusyManager::FreeBusyManager( QObject *parent, const char *name )
00116   : QObject( parent, name ),
00117     mCalendar( 0 ), mTimerID( 0 ), mUploadingFreeBusy( false )
00118 {
00119 }
00120 
00121 void FreeBusyManager::setCalendar( KCal::Calendar *c )
00122 {
00123   mCalendar = c;
00124   if ( mCalendar ) {
00125     mFormat.setTimeZone( mCalendar->timeZoneId(), true );
00126   }
00127 }
00128 
00129 KCal::FreeBusy *FreeBusyManager::ownerFreeBusy()
00130 {
00131   QDateTime start = QDateTime::currentDateTime();
00132   QDateTime end = start.addDays( KOPrefs::instance()->mFreeBusyPublishDays );
00133 
00134   FreeBusy *freebusy = new FreeBusy( mCalendar, start, end );
00135   freebusy->setOrganizer( Person( KOPrefs::instance()->fullName(),
00136                           KOPrefs::instance()->email() ) );
00137 
00138   return freebusy;
00139 }
00140 
00141 QString FreeBusyManager::ownerFreeBusyAsString()
00142 {
00143   FreeBusy *freebusy = ownerFreeBusy();
00144 
00145   QString result = freeBusyToIcal( freebusy );
00146 
00147   delete freebusy;
00148 
00149   return result;
00150 }
00151 
00152 QString FreeBusyManager::freeBusyToIcal( KCal::FreeBusy *freebusy )
00153 {
00154   return mFormat.createScheduleMessage( freebusy, Scheduler::Publish );
00155 }
00156 
00157 void FreeBusyManager::slotPerhapsUploadFB()
00158 {
00159   // user has automtic uploading disabled, bail out
00160   if ( !KOPrefs::instance()->freeBusyPublishAuto() ||
00161        KOPrefs::instance()->freeBusyPublishUrl().isEmpty() )
00162      return;
00163   if( mTimerID != 0 )
00164     // A timer is already running, so we don't need to do anything
00165     return;
00166 
00167   int now = static_cast<int>( QDateTime::currentDateTime().toTime_t() );
00168   int eta = static_cast<int>( mNextUploadTime.toTime_t() ) - now;
00169 
00170   if( !mUploadingFreeBusy ) {
00171     // Not currently uploading
00172     if( mNextUploadTime.isNull() ||
00173         QDateTime::currentDateTime() > mNextUploadTime ) {
00174       // No uploading have been done in this session, or delay time is over
00175       publishFreeBusy();
00176       return;
00177     }
00178 
00179     // We're in the delay time and no timer is running. Start one
00180     if( eta <= 0 ) {
00181       // Sanity check failed - better do the upload
00182       publishFreeBusy();
00183       return;
00184     }
00185   } else {
00186     // We are currently uploading the FB list. Start the timer
00187     if( eta <= 0 ) {
00188       kdDebug(5850) << "This shouldn't happen! eta <= 0\n";
00189       eta = 10; // whatever
00190     }
00191   }
00192 
00193   // Start the timer
00194   mTimerID = startTimer( eta * 1000 );
00195 
00196   if( mTimerID == 0 )
00197     // startTimer failed - better do the upload
00198     publishFreeBusy();
00199 }
00200 
00201 // This is used for delayed Free/Busy list uploading
00202 void FreeBusyManager::timerEvent( QTimerEvent* )
00203 {
00204   publishFreeBusy();
00205 }
00206 
00211 void FreeBusyManager::publishFreeBusy()
00212 {
00213   // Already uploading? Skip this one then.
00214   if ( mUploadingFreeBusy )
00215     return;
00216   KURL targetURL ( KOPrefs::instance()->freeBusyPublishUrl() );
00217   if ( targetURL.isEmpty() )  {
00218     KMessageBox::sorry( 0,
00219       i18n( "<qt>No URL configured for uploading your free/busy list. Please "
00220             "set it in KOrganizer's configuration dialog, on the \"Free/Busy\" page. "
00221             "<br>Contact your system administrator for the exact URL and the "
00222             "account details."
00223             "</qt>" ), i18n("No Free/Busy Upload URL") );
00224     return;
00225   }
00226   targetURL.setUser( KOPrefs::instance()->mFreeBusyPublishUser );
00227   targetURL.setPass( KOPrefs::instance()->mFreeBusyPublishPassword );
00228   
00229   mUploadingFreeBusy = true;
00230 
00231   // If we have a timer running, it should be stopped now
00232   if( mTimerID != 0 ) {
00233     killTimer( mTimerID );
00234     mTimerID = 0;
00235   }
00236 
00237   // Save the time of the next free/busy uploading
00238   mNextUploadTime = QDateTime::currentDateTime();
00239   if( KOPrefs::instance()->mFreeBusyPublishDelay > 0 )
00240     mNextUploadTime = mNextUploadTime.addSecs(
00241         KOPrefs::instance()->mFreeBusyPublishDelay * 60 );
00242 
00243   QString messageText = ownerFreeBusyAsString();
00244 
00245   // We need to massage the list a bit so that Outlook understands
00246   // it.
00247   messageText = messageText.replace( QRegExp( "ORGANIZER\\s*:MAILTO:" ),
00248                                      "ORGANIZER:" );
00249 
00250   // Create a local temp file and save the message to it
00251   KTempFile tempFile;
00252   QTextStream *textStream = tempFile.textStream();
00253   if( textStream ) {
00254     *textStream << messageText;
00255     tempFile.close();
00256 
00257 #if 0
00258     QString defaultEmail = KOCore()::self()->email();
00259     QString emailHost = defaultEmail.mid( defaultEmail.find( '@' ) + 1 );
00260 
00261     // Put target string together
00262     KURL targetURL;
00263     if( KOPrefs::instance()->mPublishKolab ) {
00264       // we use Kolab
00265       QString server;
00266       if( KOPrefs::instance()->mPublishKolabServer == "%SERVER%" ||
00267       KOPrefs::instance()->mPublishKolabServer.isEmpty() )
00268     server = emailHost;
00269       else
00270     server = KOPrefs::instance()->mPublishKolabServer;
00271 
00272       targetURL.setProtocol( "webdavs" );
00273       targetURL.setHost( server );
00274 
00275       QString fbname = KOPrefs::instance()->mPublishUserName;
00276       int at = fbname.find('@');
00277       if( at > 1 && fbname.length() > (uint)at ) {
00278     fbname = fbname.left(at);
00279       }
00280       targetURL.setPath( "/freebusy/" + fbname + ".ifb" );
00281       targetURL.setUser( KOPrefs::instance()->mPublishUserName );
00282       targetURL.setPass( KOPrefs::instance()->mPublishPassword );
00283     } else {
00284       // we use something else
00285       targetURL = KOPrefs::instance()->mPublishAnyURL.replace( "%SERVER%",
00286                                                                emailHost );
00287       targetURL.setUser( KOPrefs::instance()->mPublishUserName );
00288       targetURL.setPass( KOPrefs::instance()->mPublishPassword );
00289     }
00290 #endif
00291 
00292 
00293     KURL src;
00294     src.setPath( tempFile.name() );
00295 
00296     kdDebug(5850) << "FreeBusyManager::publishFreeBusy(): " << targetURL << endl;
00297 
00298     KIO::Job * job = KIO::file_copy( src, targetURL, -1,
00299                                      true /*overwrite*/,
00300                                      false /*don't resume*/,
00301                                      false /*don't show progress info*/ );
00302     connect( job, SIGNAL( result( KIO::Job * ) ),
00303              SLOT( slotUploadFreeBusyResult( KIO::Job * ) ) );
00304   }
00305 }
00306 
00307 void FreeBusyManager::slotUploadFreeBusyResult(KIO::Job *_job)
00308 {
00309     KIO::FileCopyJob* job = static_cast<KIO::FileCopyJob *>(_job);
00310     if ( job->error() )
00311         KMessageBox::sorry( 0,
00312           i18n( "<qt>The software could not upload your free/busy list to the "
00313                 "URL '%1'. There might be a problem with the access rights, or "
00314                 "you specified an incorrect URL. The system said: <em>%2</em>."
00315                 "<br>Please check the URL or contact your system administrator."
00316                 "</qt>" ).arg( job->destURL().prettyURL() )
00317                          .arg( job->errorString() ) );
00318     // Delete temp file
00319     KURL src = job->srcURL();
00320     Q_ASSERT( src.isLocalFile() );
00321     if( src.isLocalFile() )
00322         QFile::remove(src.path());
00323     mUploadingFreeBusy = false;
00324 }
00325 
00326 bool FreeBusyManager::retrieveFreeBusy( const QString &email )
00327 {
00328   kdDebug(5850) << "FreeBusyManager::retrieveFreeBusy(): " << email << endl;
00329   if ( email.isEmpty() ) return false;
00330 
00331   if( KOPrefs::instance()->thatIsMe( email ) ) {
00332     // Don't download our own free-busy list from the net
00333     kdDebug(5850) << "freebusy of owner" << endl;
00334     emit freeBusyRetrieved( ownerFreeBusy(), email );
00335     return true;
00336   }
00337 
00338   // Check for cached copy of free/busy list
00339   KCal::FreeBusy *fb = loadFreeBusy( email );
00340   if ( fb ) {
00341     emit freeBusyRetrieved( fb, email );
00342   }
00343 
00344   // Don't download free/busy if the user does not want it.
00345   if( !KOPrefs::instance()->mFreeBusyRetrieveAuto )
00346     return false;
00347 
00348   mRetrieveQueue.append( email );
00349 
00350   if ( mRetrieveQueue.count() > 1 ) return true;
00351 
00352   return processRetrieveQueue();
00353 }
00354 
00355 bool FreeBusyManager::processRetrieveQueue()
00356 {
00357   if ( mRetrieveQueue.isEmpty() ) return true;
00358 
00359   QString email = mRetrieveQueue.first();
00360   mRetrieveQueue.pop_front();
00361 
00362   KURL sourceURL = freeBusyUrl( email );
00363 
00364   kdDebug(5850) << "FreeBusyManager::retrieveFreeBusy(): url: " << sourceURL.url()
00365             << endl;
00366 
00367   if ( !sourceURL.isValid() ) {
00368     kdDebug(5850) << "Invalid FB URL\n";
00369     return false;
00370   }
00371 
00372   FreeBusyDownloadJob *job = new FreeBusyDownloadJob( email, sourceURL, this,
00373                                                       "freebusy_download_job" );
00374   connect( job, SIGNAL( freeBusyDownloaded( KCal::FreeBusy *,
00375                                             const QString & ) ),
00376        SIGNAL( freeBusyRetrieved( KCal::FreeBusy *, const QString & ) ) );
00377   connect( job, SIGNAL( freeBusyDownloaded( KCal::FreeBusy *,
00378                                             const QString & ) ),
00379            SLOT( processRetrieveQueue() ) );
00380 
00381   return true;
00382 }
00383 
00384 void FreeBusyManager::cancelRetrieval()
00385 {
00386   mRetrieveQueue.clear();
00387 }
00388 
00389 KURL FreeBusyManager::freeBusyUrl( const QString &email )
00390 {
00391   kdDebug(5850) << "FreeBusyManager::freeBusyUrl(): " << email << endl;
00392 
00393   // First check if there is a specific FB url for this email
00394   QString configFile = locateLocal( "data", "korganizer/freebusyurls" );
00395   KConfig cfg( configFile );
00396 
00397   cfg.setGroup( email );
00398   QString url = cfg.readEntry( "url" );
00399   if ( !url.isEmpty() ) {
00400     return KURL( url );
00401   }
00402   // Try with the url configurated by preferred email in kaddressbook
00403   KABC::Addressee::List list= KABC::StdAddressBook::self( true )->findByEmail( email );
00404   KABC::Addressee::List::Iterator it;
00405   QString pref;
00406   for ( it = list.begin(); it != list.end(); ++it ) {
00407     pref = (*it).preferredEmail();
00408     if ( !pref.isEmpty() && pref != email ) {
00409       kdDebug( 5850 ) << "FreeBusyManager::freeBusyUrl():" <<
00410         "Preferred email of " << email << " is " << pref << endl;
00411       cfg.setGroup( pref );
00412       url = cfg.readEntry ( "url" );
00413       if ( !url.isEmpty() )
00414         kdDebug( 5850 ) << "FreeBusyManager::freeBusyUrl():" <<
00415           "Taken url from preferred email:" << url << endl;
00416         return KURL( url );
00417     }
00418   }
00419   // None found. Check if we do automatic FB retrieving then
00420   if ( !KOPrefs::instance()->mFreeBusyRetrieveAuto )
00421     // No, so no FB list here
00422     return KURL();
00423 
00424   // Sanity check: Don't download if it's not a correct email
00425   // address (this also avoids downloading for "(empty email)").
00426   int emailpos = email.find( '@' );
00427   if( emailpos == -1 )
00428     return KURL();
00429 
00430   // Cut off everything left of the @ sign to get the user name.
00431   const QString emailName = email.left( emailpos );
00432   const QString emailHost = email.mid( emailpos + 1 );
00433 
00434   // Build the URL
00435   KURL sourceURL;
00436   sourceURL = KOPrefs::instance()->mFreeBusyRetrieveUrl;
00437 
00438   // Don't try to fetch free/busy data for users not on the specified servers
00439   // This tests if the hostnames match, or one is a subset of the other
00440   const QString hostDomain = sourceURL.host();
00441   if ( hostDomain != emailHost && !hostDomain.endsWith( '.' + emailHost )
00442        && !emailHost.endsWith( '.' + hostDomain ) ) {
00443     // Host names do not match
00444     kdDebug(5850) << "Host '" << sourceURL.host() << "' doesn't match email '"
00445       << email << "'" << endl; 
00446     return KURL();
00447 }
00448 
00449   if ( KOPrefs::instance()->mFreeBusyFullDomainRetrieval )
00450     sourceURL.setFileName( email + ".ifb" );
00451   else
00452     sourceURL.setFileName( emailName + ".ifb" );
00453   sourceURL.setUser( KOPrefs::instance()->mFreeBusyRetrieveUser );
00454   sourceURL.setPass( KOPrefs::instance()->mFreeBusyRetrievePassword );
00455 
00456   return sourceURL;
00457 }
00458 
00459 KCal::FreeBusy *FreeBusyManager::iCalToFreeBusy( const QCString &data )
00460 {
00461   kdDebug(5850) << "FreeBusyManager::iCalToFreeBusy()" << endl;
00462   kdDebug(5850) << data << endl;
00463 
00464   QString freeBusyVCal = QString::fromUtf8( data );
00465   KCal::FreeBusy *fb = mFormat.parseFreeBusy( freeBusyVCal );
00466   if ( !fb ) {
00467     kdDebug(5850) << "FreeBusyManager::iCalToFreeBusy(): Error parsing free/busy"
00468               << endl;
00469     kdDebug(5850) << freeBusyVCal << endl;
00470   } 
00471   return fb;
00472 }
00473 
00474 QString FreeBusyManager::freeBusyDir()
00475 {
00476   return locateLocal( "data", "korganizer/freebusy" );
00477 }
00478 
00479 FreeBusy *FreeBusyManager::loadFreeBusy( const QString &email )
00480 {
00481   kdDebug(5850) << "FreeBusyManager::loadFreeBusy(): " << email << endl;
00482 
00483   QString fbd = freeBusyDir();
00484 
00485   QFile f( fbd + "/" + email + ".ifb" );
00486   if ( !f.exists() ) {
00487     kdDebug(5850) << "FreeBusyManager::loadFreeBusy() " << f.name()
00488               << " doesn't exist." << endl;
00489     return 0;
00490   }
00491 
00492   if ( !f.open( IO_ReadOnly ) ) {
00493     kdDebug(5850) << "FreeBusyManager::loadFreeBusy() Unable to open file "
00494               << f.name() << endl;
00495     return 0;
00496   }
00497 
00498   QTextStream ts( &f );
00499   QString str = ts.read();
00500 
00501   return iCalToFreeBusy( str.utf8() );
00502 }
00503 
00504 bool FreeBusyManager::saveFreeBusy( FreeBusy *freebusy, const Person &person )
00505 {
00506   kdDebug(5850) << "FreeBusyManager::saveFreeBusy(): " << person.fullName() << endl;
00507 
00508   QString fbd = freeBusyDir();
00509 
00510   QDir freeBusyDirectory( fbd );
00511   if ( !freeBusyDirectory.exists() ) {
00512     kdDebug(5850) << "Directory " << fbd << " does not exist!" << endl;
00513     kdDebug(5850) << "Creating directory: " << fbd << endl;
00514 
00515     if( !freeBusyDirectory.mkdir( fbd, true ) ) {
00516       kdDebug(5850) << "Could not create directory: " << fbd << endl;
00517       return false;
00518     }
00519   }
00520 
00521   QString filename( fbd );
00522   filename += "/";
00523   filename += person.email();
00524   filename += ".ifb";
00525   QFile f( filename );
00526 
00527   kdDebug(5850) << "FreeBusyManager::saveFreeBusy(): filename: " << filename
00528             << endl;
00529 
00530   freebusy->clearAttendees();
00531   freebusy->setOrganizer( person );
00532 
00533   QString messageText = mFormat.createScheduleMessage( freebusy,
00534                                                        Scheduler::Publish );
00535 
00536   if ( !f.open( IO_ReadWrite ) ) {
00537     kdDebug(5850) << "acceptFreeBusy: Can't open:" << filename << " for writing"
00538               << endl;
00539     return false;
00540   }
00541   QTextStream t( &f );
00542   t << messageText;
00543   f.close();
00544 
00545   return true;
00546 }
00547 
00548 #include "freebusymanager.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys