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