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