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