summaryrefslogtreecommitdiffstats
path: root/korganizer/freebusymanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'korganizer/freebusymanager.cpp')
-rw-r--r--korganizer/freebusymanager.cpp587
1 files changed, 587 insertions, 0 deletions
diff --git a/korganizer/freebusymanager.cpp b/korganizer/freebusymanager.cpp
new file mode 100644
index 000000000..94fe320cf
--- /dev/null
+++ b/korganizer/freebusymanager.cpp
@@ -0,0 +1,587 @@
+/*
+ This file is part of the Groupware/KOrganizer integration.
+
+ Requires the Qt and KDE widget libraries, available at no cost at
+ http://www.trolltech.com and http://www.kde.org respectively
+
+ Copyright (c) 2002-2004 Klar�vdalens Datakonsult AB
+ <info@klaralvdalens-datakonsult.se>
+ Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "freebusymanager.h"
+
+#include "koprefs.h"
+#include "mailscheduler.h"
+
+#include <libkcal/incidencebase.h>
+#include <libkcal/attendee.h>
+#include <libkcal/freebusy.h>
+#include <libkcal/journal.h>
+#include <libkcal/calendarlocal.h>
+#include <libkcal/icalformat.h>
+
+#include <kio/job.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+#include <ktempfile.h>
+#include <kio/jobclasses.h>
+#include <kio/netaccess.h>
+#include <kio/scheduler.h>
+#include <kapplication.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kabc/stdaddressbook.h>
+#include <kabc/addressee.h>
+
+#include <qfile.h>
+#include <qbuffer.h>
+#include <qregexp.h>
+#include <qdir.h>
+
+using namespace KCal;
+
+FreeBusyDownloadJob::FreeBusyDownloadJob( const QString &email, const KURL &url,
+ FreeBusyManager *manager,
+ const char *name )
+ : QObject( manager, name ), mManager( manager ), mEmail( email )
+{
+ KIO::TransferJob *job = KIO::get( url, false, false );
+ connect( job, SIGNAL( result( KIO::Job * ) ),
+ SLOT( slotResult( KIO::Job * ) ) );
+ connect( job, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
+ SLOT( slotData( KIO::Job *, const QByteArray & ) ) );
+ KIO::Scheduler::scheduleJob( job );
+}
+
+FreeBusyDownloadJob::~FreeBusyDownloadJob()
+{
+}
+
+
+void FreeBusyDownloadJob::slotData( KIO::Job *, const QByteArray &data )
+{
+ QByteArray tmp = data;
+ tmp.resize( tmp.size() + 1 );
+ tmp[tmp.size()-1] = 0;
+ mFreeBusyData += tmp;
+}
+
+void FreeBusyDownloadJob::slotResult( KIO::Job *job )
+{
+ kdDebug(5850) << "FreeBusyDownloadJob::slotResult() " << mEmail << endl;
+
+ if( job->error() ) {
+ kdDebug(5850) << "FreeBusyDownloadJob::slotResult() job error for " << mEmail << endl;
+ emit freeBusyDownloadError( mEmail );
+ } else {
+ FreeBusy *fb = mManager->iCalToFreeBusy( mFreeBusyData );
+ if ( fb ) {
+ Person p = fb->organizer();
+ p.setEmail( mEmail );
+ mManager->saveFreeBusy( fb, p );
+ }
+ emit freeBusyDownloaded( fb, mEmail );
+ }
+ deleteLater();
+}
+
+////
+
+FreeBusyManager::FreeBusyManager( QObject *parent, const char *name )
+ : QObject( parent, name ),
+ mCalendar( 0 ), mTimerID( 0 ), mUploadingFreeBusy( false ),
+ mBrokenUrl( false )
+{
+}
+
+void FreeBusyManager::setCalendar( KCal::Calendar *c )
+{
+ mCalendar = c;
+ if ( mCalendar ) {
+ mFormat.setTimeZone( mCalendar->timeZoneId(), true );
+ }
+}
+
+KCal::FreeBusy *FreeBusyManager::ownerFreeBusy()
+{
+ QDateTime start = QDateTime::currentDateTime();
+ QDateTime end = start.addDays( KOPrefs::instance()->mFreeBusyPublishDays );
+
+ FreeBusy *freebusy = new FreeBusy( mCalendar, start, end );
+ freebusy->setOrganizer( Person( KOPrefs::instance()->fullName(),
+ KOPrefs::instance()->email() ) );
+
+ return freebusy;
+}
+
+QString FreeBusyManager::ownerFreeBusyAsString()
+{
+ FreeBusy *freebusy = ownerFreeBusy();
+
+ QString result = freeBusyToIcal( freebusy );
+
+ delete freebusy;
+
+ return result;
+}
+
+QString FreeBusyManager::freeBusyToIcal( KCal::FreeBusy *freebusy )
+{
+ return mFormat.createScheduleMessage( freebusy, Scheduler::Publish );
+}
+
+void FreeBusyManager::slotPerhapsUploadFB()
+{
+ // user has automatic uploading disabled, bail out
+ if ( !KOPrefs::instance()->freeBusyPublishAuto() ||
+ KOPrefs::instance()->freeBusyPublishUrl().isEmpty() )
+ return;
+ if( mTimerID != 0 )
+ // A timer is already running, so we don't need to do anything
+ return;
+
+ int now = static_cast<int>( QDateTime::currentDateTime().toTime_t() );
+ int eta = static_cast<int>( mNextUploadTime.toTime_t() ) - now;
+
+ if( !mUploadingFreeBusy ) {
+ // Not currently uploading
+ if( mNextUploadTime.isNull() ||
+ QDateTime::currentDateTime() > mNextUploadTime ) {
+ // No uploading have been done in this session, or delay time is over
+ publishFreeBusy();
+ return;
+ }
+
+ // We're in the delay time and no timer is running. Start one
+ if( eta <= 0 ) {
+ // Sanity check failed - better do the upload
+ publishFreeBusy();
+ return;
+ }
+ } else {
+ // We are currently uploading the FB list. Start the timer
+ if( eta <= 0 ) {
+ kdDebug(5850) << "This shouldn't happen! eta <= 0\n";
+ eta = 10; // whatever
+ }
+ }
+
+ // Start the timer
+ mTimerID = startTimer( eta * 1000 );
+
+ if( mTimerID == 0 )
+ // startTimer failed - better do the upload
+ publishFreeBusy();
+}
+
+// This is used for delayed Free/Busy list uploading
+void FreeBusyManager::timerEvent( QTimerEvent* )
+{
+ publishFreeBusy();
+}
+
+void FreeBusyManager::setBrokenUrl( bool isBroken )
+{
+ mBrokenUrl = isBroken;
+}
+
+/*!
+ This method is called when the user has selected to publish its
+ free/busy list or when the delay have passed.
+*/
+void FreeBusyManager::publishFreeBusy()
+{
+ // Already uploading? Skip this one then.
+ if ( mUploadingFreeBusy )
+ return;
+ KURL targetURL ( KOPrefs::instance()->freeBusyPublishUrl() );
+ if ( targetURL.isEmpty() ) {
+ KMessageBox::sorry( 0,
+ i18n( "<qt>No URL configured for uploading your free/busy list. Please "
+ "set it in KOrganizer's configuration dialog, on the \"Free/Busy\" page. "
+ "<br>Contact your system administrator for the exact URL and the "
+ "account details."
+ "</qt>" ), i18n("No Free/Busy Upload URL") );
+ return;
+ }
+ if ( mBrokenUrl ) // Url is invalid, don't try again
+ return;
+ if ( !targetURL.isValid() ) {
+ KMessageBox::sorry( 0,
+ i18n( "<qt>The target URL '%1' provided is invalid."
+ "</qt>" ).arg( targetURL.prettyURL() ), i18n("Invalid URL") );
+ mBrokenUrl = true;
+ return;
+ }
+ targetURL.setUser( KOPrefs::instance()->mFreeBusyPublishUser );
+ targetURL.setPass( KOPrefs::instance()->mFreeBusyPublishPassword );
+
+ mUploadingFreeBusy = true;
+
+ // If we have a timer running, it should be stopped now
+ if( mTimerID != 0 ) {
+ killTimer( mTimerID );
+ mTimerID = 0;
+ }
+
+ // Save the time of the next free/busy uploading
+ mNextUploadTime = QDateTime::currentDateTime();
+ if( KOPrefs::instance()->mFreeBusyPublishDelay > 0 )
+ mNextUploadTime = mNextUploadTime.addSecs(
+ KOPrefs::instance()->mFreeBusyPublishDelay * 60 );
+
+ QString messageText = ownerFreeBusyAsString();
+
+ // We need to massage the list a bit so that Outlook understands
+ // it.
+ messageText = messageText.replace( QRegExp( "ORGANIZER\\s*:MAILTO:" ),
+ "ORGANIZER:" );
+
+ // Create a local temp file and save the message to it
+ KTempFile tempFile;
+ QTextStream *textStream = tempFile.textStream();
+ if( textStream ) {
+ *textStream << messageText;
+ tempFile.close();
+
+#if 0
+ QString defaultEmail = KOCore()::self()->email();
+ QString emailHost = defaultEmail.mid( defaultEmail.find( '@' ) + 1 );
+
+ // Put target string together
+ KURL targetURL;
+ if( KOPrefs::instance()->mPublishKolab ) {
+ // we use Kolab
+ QString server;
+ if( KOPrefs::instance()->mPublishKolabServer == "%SERVER%" ||
+ KOPrefs::instance()->mPublishKolabServer.isEmpty() )
+ server = emailHost;
+ else
+ server = KOPrefs::instance()->mPublishKolabServer;
+
+ targetURL.setProtocol( "webdavs" );
+ targetURL.setHost( server );
+
+ QString fbname = KOPrefs::instance()->mPublishUserName;
+ int at = fbname.find('@');
+ if( at > 1 && fbname.length() > (uint)at ) {
+ fbname = fbname.left(at);
+ }
+ targetURL.setPath( "/freebusy/" + fbname + ".ifb" );
+ targetURL.setUser( KOPrefs::instance()->mPublishUserName );
+ targetURL.setPass( KOPrefs::instance()->mPublishPassword );
+ } else {
+ // we use something else
+ targetURL = KOPrefs::instance()->mPublishAnyURL.replace( "%SERVER%",
+ emailHost );
+ targetURL.setUser( KOPrefs::instance()->mPublishUserName );
+ targetURL.setPass( KOPrefs::instance()->mPublishPassword );
+ }
+#endif
+
+
+ KURL src;
+ src.setPath( tempFile.name() );
+
+ kdDebug(5850) << "FreeBusyManager::publishFreeBusy(): " << targetURL << endl;
+
+ KIO::Job * job = KIO::file_copy( src, targetURL, -1,
+ true /*overwrite*/,
+ false /*don't resume*/,
+ false /*don't show progress info*/ );
+ connect( job, SIGNAL( result( KIO::Job * ) ),
+ SLOT( slotUploadFreeBusyResult( KIO::Job * ) ) );
+ }
+}
+
+void FreeBusyManager::slotUploadFreeBusyResult(KIO::Job *_job)
+{
+ KIO::FileCopyJob* job = static_cast<KIO::FileCopyJob *>(_job);
+ if ( job->error() )
+ KMessageBox::sorry( 0,
+ i18n( "<qt>The software could not upload your free/busy list to the "
+ "URL '%1'. There might be a problem with the access rights, or "
+ "you specified an incorrect URL. The system said: <em>%2</em>."
+ "<br>Please check the URL or contact your system administrator."
+ "</qt>" ).arg( job->destURL().prettyURL() )
+ .arg( job->errorString() ) );
+ // Delete temp file
+ KURL src = job->srcURL();
+ Q_ASSERT( src.isLocalFile() );
+ if( src.isLocalFile() )
+ QFile::remove(src.path());
+ mUploadingFreeBusy = false;
+}
+
+bool FreeBusyManager::retrieveFreeBusy( const QString &email, bool forceDownload )
+{
+ kdDebug(5850) << "FreeBusyManager::retrieveFreeBusy(): " << email << endl;
+ if ( email.isEmpty() ) return false;
+
+ // Check for cached copy of free/busy list
+ KCal::FreeBusy *fb = loadFreeBusy( email );
+ if ( fb ) {
+ emit freeBusyRetrieved( fb, email );
+ }
+
+ // Don't download free/busy if the user does not want it.
+ if( !KOPrefs::instance()->mFreeBusyRetrieveAuto && !forceDownload) {
+ slotFreeBusyDownloadError( email ); // fblist
+ return false;
+ }
+
+ mRetrieveQueue.append( email );
+
+ if ( mRetrieveQueue.count() > 1 ) return true;
+
+ return processRetrieveQueue();
+}
+
+bool FreeBusyManager::processRetrieveQueue()
+{
+ if ( mRetrieveQueue.isEmpty() ) return true;
+
+ QString email = mRetrieveQueue.first();
+ mRetrieveQueue.pop_front();
+
+ KURL sourceURL = freeBusyUrl( email );
+
+ kdDebug(5850) << "FreeBusyManager::processRetrieveQueue(): url: " << sourceURL
+ << endl;
+
+ if ( !sourceURL.isValid() ) {
+ kdDebug(5850) << "Invalid FB URL\n";
+ slotFreeBusyDownloadError( email );
+ return false;
+ }
+
+ FreeBusyDownloadJob *job = new FreeBusyDownloadJob( email, sourceURL, this,
+ "freebusy_download_job" );
+ connect( job, SIGNAL( freeBusyDownloaded( KCal::FreeBusy *,
+ const QString & ) ),
+ SIGNAL( freeBusyRetrieved( KCal::FreeBusy *, const QString & ) ) );
+ connect( job, SIGNAL( freeBusyDownloaded( KCal::FreeBusy *,
+ const QString & ) ),
+ SLOT( processRetrieveQueue() ) );
+
+ connect( job, SIGNAL( freeBusyDownloadError( const QString& ) ),
+ this, SLOT( slotFreeBusyDownloadError( const QString& ) ) );
+
+ return true;
+}
+
+void FreeBusyManager::slotFreeBusyDownloadError( const QString& email )
+{
+ if( KOPrefs::instance()->thatIsMe( email ) ) {
+ // We tried to download our own free-busy list from the net, but it failed
+ // so use local version instead.
+ // The reason we try to download even our own free-busy list is that
+ // this allows to avoid showing as busy the folders that are "fb relevant for nobody"
+ // like shared resources (meeting rooms etc.)
+ kdDebug(5850) << "freebusy of owner, falling back to local list" << endl;
+ emit freeBusyRetrieved( ownerFreeBusy(), email );
+ }
+
+}
+
+void FreeBusyManager::cancelRetrieval()
+{
+ mRetrieveQueue.clear();
+}
+
+KURL FreeBusyManager::freeBusyUrl( const QString &email )
+{
+ kdDebug(5850) << "FreeBusyManager::freeBusyUrl(): " << email << endl;
+
+ // First check if there is a specific FB url for this email
+ QString configFile = locateLocal( "data", "korganizer/freebusyurls" );
+ KConfig cfg( configFile );
+
+ cfg.setGroup( email );
+ QString url = cfg.readEntry( "url" );
+ if ( !url.isEmpty() ) {
+ kdDebug(5850) << "found cached url: " << url << endl;
+ return KURL( url );
+ }
+ // Try with the url configurated by preferred email in kaddressbook
+ KABC::Addressee::List list= KABC::StdAddressBook::self( true )->findByEmail( email );
+ KABC::Addressee::List::Iterator it;
+ QString pref;
+ for ( it = list.begin(); it != list.end(); ++it ) {
+ pref = (*it).preferredEmail();
+ if ( !pref.isEmpty() && pref != email ) {
+ kdDebug( 5850 ) << "FreeBusyManager::freeBusyUrl():" <<
+ "Preferred email of " << email << " is " << pref << endl;
+ cfg.setGroup( pref );
+ url = cfg.readEntry ( "url" );
+ if ( !url.isEmpty() ) {
+ kdDebug( 5850 ) << "FreeBusyManager::freeBusyUrl():" <<
+ "Taken url from preferred email:" << url << endl;
+ return KURL( url );
+ }
+ }
+ }
+ // None found. Check if we do automatic FB retrieving then
+ if ( !KOPrefs::instance()->mFreeBusyRetrieveAuto ) {
+ kdDebug( 5850 ) << "no auto retrieving" << endl;
+ // No, so no FB list here
+ return KURL();
+ }
+
+ // Sanity check: Don't download if it's not a correct email
+ // address (this also avoids downloading for "(empty email)").
+ int emailpos = email.find( '@' );
+ if( emailpos == -1 )
+ return KURL();
+
+ // Cut off everything left of the @ sign to get the user name.
+ const QString emailName = email.left( emailpos );
+ const QString emailHost = email.mid( emailpos + 1 );
+
+ // Build the URL
+ KURL sourceURL;
+ sourceURL = KOPrefs::instance()->mFreeBusyRetrieveUrl;
+
+ if ( KOPrefs::instance()->mFreeBusyCheckHostname ) {
+ // Don't try to fetch free/busy data for users not on the specified servers
+ // This tests if the hostnames match, or one is a subset of the other
+ const QString hostDomain = sourceURL.host();
+ if ( hostDomain != emailHost && !hostDomain.endsWith( '.' + emailHost )
+ && !emailHost.endsWith( '.' + hostDomain ) ) {
+ // Host names do not match
+ kdDebug(5850) << "Host '" << sourceURL.host() << "' doesn't match email '"
+ << email << '\'' << endl;
+ return KURL();
+ }
+ }
+
+ kdDebug(5850) << "Server FreeBusy url: " << sourceURL << endl;
+ if ( KOPrefs::instance()->mFreeBusyFullDomainRetrieval )
+ sourceURL.setFileName( email + ".ifb" );
+ else
+ sourceURL.setFileName( emailName + ".ifb" );
+ sourceURL.setUser( KOPrefs::instance()->mFreeBusyRetrieveUser );
+ sourceURL.setPass( KOPrefs::instance()->mFreeBusyRetrievePassword );
+
+ kdDebug(5850) << "Results in generated: " << sourceURL << endl;
+ return sourceURL;
+}
+
+KCal::FreeBusy *FreeBusyManager::iCalToFreeBusy( const QCString &data )
+{
+ kdDebug(5850) << "FreeBusyManager::iCalToFreeBusy()" << endl;
+ kdDebug(5850) << data << endl;
+
+ QString freeBusyVCal = QString::fromUtf8( data );
+ KCal::FreeBusy *fb = mFormat.parseFreeBusy( freeBusyVCal );
+ if ( !fb ) {
+ kdDebug(5850) << "FreeBusyManager::iCalToFreeBusy(): Error parsing free/busy"
+ << endl;
+ kdDebug(5850) << freeBusyVCal << endl;
+ }
+ return fb;
+}
+
+QString FreeBusyManager::freeBusyDir()
+{
+ return locateLocal( "data", "korganizer/freebusy" );
+}
+
+FreeBusy *FreeBusyManager::loadFreeBusy( const QString &email )
+{
+ kdDebug(5850) << "FreeBusyManager::loadFreeBusy(): " << email << endl;
+
+ QString fbd = freeBusyDir();
+
+ QFile f( fbd + "/" + email + ".ifb" );
+ if ( !f.exists() ) {
+ kdDebug(5850) << "FreeBusyManager::loadFreeBusy() " << f.name()
+ << " doesn't exist." << endl;
+ return 0;
+ }
+
+ if ( !f.open( IO_ReadOnly ) ) {
+ kdDebug(5850) << "FreeBusyManager::loadFreeBusy() Unable to open file "
+ << f.name() << endl;
+ return 0;
+ }
+
+ QTextStream ts( &f );
+ QString str = ts.read();
+
+ return iCalToFreeBusy( str.utf8() );
+}
+
+bool FreeBusyManager::saveFreeBusy( FreeBusy *freebusy, const Person &person )
+{
+ kdDebug(5850) << "FreeBusyManager::saveFreeBusy(): " << person.fullName() << endl;
+
+ QString fbd = freeBusyDir();
+
+ QDir freeBusyDirectory( fbd );
+ if ( !freeBusyDirectory.exists() ) {
+ kdDebug(5850) << "Directory " << fbd << " does not exist!" << endl;
+ kdDebug(5850) << "Creating directory: " << fbd << endl;
+
+ if( !freeBusyDirectory.mkdir( fbd, true ) ) {
+ kdDebug(5850) << "Could not create directory: " << fbd << endl;
+ return false;
+ }
+ }
+
+ QString filename( fbd );
+ filename += "/";
+ filename += person.email();
+ filename += ".ifb";
+ QFile f( filename );
+
+ kdDebug(5850) << "FreeBusyManager::saveFreeBusy(): filename: " << filename
+ << endl;
+
+ freebusy->clearAttendees();
+ freebusy->setOrganizer( person );
+
+ QString messageText = mFormat.createScheduleMessage( freebusy,
+ Scheduler::Publish );
+
+ if ( !f.open( IO_ReadWrite ) ) {
+ kdDebug(5850) << "acceptFreeBusy: Can't open:" << filename << " for writing"
+ << endl;
+ return false;
+ }
+ QTextStream t( &f );
+ t << messageText;
+ f.close();
+
+ return true;
+}
+
+#include "freebusymanager.moc"