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