libkpimexchange Library API Documentation

exchangemonitor.cpp

00001 /*
00002     This file is part of libkpimexchange
00003     Copyright (c) 2002 Jan-Pascal van Best <janpascal@vanbest.org>
00004 
00005     This library is free software; you can redistribute it and/or
00006     modify it under the terms of the GNU Library General Public
00007     License as published by the Free Software Foundation; either
00008     version 2 of the License, or (at your option) any later version.
00009 
00010     This library is distributed in the hope that it will be useful,
00011     but WITHOUT ANY WARRANTY; without even the implied warranty of
00012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013     Library General Public License for more details.
00014 
00015     You should have received a copy of the GNU Library General Public License
00016     along with this library; see the file COPYING.LIB.  If not, write to
00017     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018     Boston, MA 02111-1307, USA.
00019 */
00020 
00021 #include <qstring.h>
00022 #include <qregexp.h>
00023 #include <qsocketdevice.h>
00024 #include <qsocketnotifier.h>
00025 #include <qtextstream.h>
00026 
00027 #include <kurl.h>
00028 #include <kdebug.h>
00029 #include <krfcdate.h>
00030 #include <kextsock.h>
00031 
00032 #include <kio/job.h>
00033 #include <kio/slave.h>
00034 #include <kio/scheduler.h>
00035 #include <kio/slavebase.h>
00036 #include <kio/davjob.h>
00037 #include <kio/http.h>
00038 
00039 #include <libkcal/event.h>
00040 #include <libkcal/icalformat.h>
00041 #include <libkcal/icalformatimpl.h>
00042 #include <libkcal/recurrence.h>
00043 #include <libkcal/incidence.h>
00044 #include <libkcal/event.h>
00045 
00046 #include "exchangemonitor.h"
00047 #include "exchangeclient.h"
00048 #include "exchangeaccount.h"
00049 #include "utils.h"
00050 
00051 extern "C" {
00052   #include <unistd.h>
00053 }
00054 
00055 using namespace KPIM;
00056 
00057 QString makeIDString( const ExchangeMonitor::IDList& IDs )
00058 {
00059   QString result;
00060   ExchangeMonitor::IDList::ConstIterator it;
00061   for ( it = IDs.begin(); it != IDs.end(); ++it ) {
00062     if ( it == IDs.begin() )
00063       result += QString::number( (*it) );
00064     else
00065       result += "," + QString::number( (*it) );
00066   }
00067   return result;
00068 }
00069 
00070 ExchangeMonitor::IDList makeIDList( const QString& input )
00071 {
00072   ExchangeMonitor::IDList IDs;
00073   QStringList numbers = QStringList::split( ",", input );
00074   QStringList::iterator j;
00075   for ( j = numbers.begin(); j != numbers.end(); ++j ) {
00076     ExchangeMonitor::ID id = (*j).toLong();
00077     IDs.append( id );
00078   }
00079   return IDs;
00080 }
00081 
00082 ExchangeMonitor::ExchangeMonitor( ExchangeAccount* account, int pollMode, const QHostAddress& ownInterface )
00083 {
00084   kdDebug() << "Called ExchangeMonitor" << endl;
00085 
00086   mAccount = account;
00087   mSubscriptionLifetime = 3600; // by default, renew subscription every 3600 seconds or one hour
00088   mPollMode = pollMode;
00089   mPollTimer = 0;
00090 
00091   if ( pollMode == CallBack ) {
00092     mSocket = new QSocketDevice( QSocketDevice::Datagram );
00093     if ( ! mSocket->bind( ownInterface, 0 ) )
00094       kdDebug() << "bind() returned false" << endl;
00095     mSocket->setBlocking( false );
00096     mNotifier = new QSocketNotifier( mSocket->socket(), QSocketNotifier::Read );
00097     connect( mNotifier, SIGNAL(activated( int )), this, SLOT( slotActivated(int)));
00098 
00099     //mSocket.setSocketFlags( KExtendedSocket::inetSocket | KExtendedSocket::passiveSocket | KExtendedSocket::datagramSocket | KExtendedSocket::bufferedSocket );
00100     //mSocket.setHost( "jupiter.tbm.tudelft.nl" );    // Does this work?
00101     //mSocket.setPort( 0 );                // setting port to 0 will make us bind to a random, free port
00102     // UDP server socket: no listen
00103     //if (  int code = mSocket.listen() )
00104     //   kdError() << "Error in socket listen: " << code << endl;
00105     //mSocket.enableRead( true );
00106     kdDebug() << "Port: " << mSocket->port() << endl;
00107     kdDebug() << "Host: " << mSocket->address().toString() << endl;
00108     //  mStream = new QTextStream( mSocket );
00109   }
00110 
00111   if ( mPollMode == Poll ) {
00112     mPollTimer = new QTimer( this );
00113     connect( mPollTimer, SIGNAL(timeout()), this, SLOT(slotPollTimer()) );
00114     mPollTimer->start( 60000 ); // 1 minute timer
00115   }
00116 
00117   mRenewTimer = new QTimer( this );
00118   connect( mRenewTimer, SIGNAL(timeout()), this, SLOT(slotRenewTimer()) );
00119   mRenewTimer->start( mSubscriptionLifetime * 900 ); // 10% early so as to be in time
00120 }
00121 
00122 ExchangeMonitor::~ExchangeMonitor()
00123 {
00124   kdDebug() << "Entering ExchangeMonitor destructor" << endl;
00125   delete mNotifier;
00126   delete mSocket;
00127   if ( mPollTimer ) delete mPollTimer;
00128   if ( mRenewTimer ) delete mRenewTimer;
00129   if ( ! mSubscriptionMap.isEmpty() ) {
00130     QString headers = "Subscription-ID: " + makeIDString( mSubscriptionMap.keys() );
00131     kdDebug() << "Subsubscribing all watches, headers:" << endl << headers << endl;
00132     KIO::DavJob *job = new KIO::DavJob( mAccount->calendarURL(), (int) KIO::DAV_UNSUBSCRIBE, QString::null, false );
00133     job->addMetaData( "customHTTPHeader", headers );
00134     // Can't do, this is a destructor!
00135     // job->addMetaData( "PropagateHttpHeader", "true" );
00136     // connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotUnsubscribeResult(KIO::Job *)));
00137   }
00138   kdDebug() << "Finished ExchangeMonitor destructor" << endl;
00139 
00140 }
00141 
00142 void ExchangeMonitor::addWatch( const KURL &url, int mode, int depth )
00143 {
00144   QString headers = "Notification-type: ";
00145   switch( mode ) {
00146     case Delete: headers += "delete\r\n"; break;
00147     case Move: headers += "move\r\n"; break;
00148     case Newmail: headers += "pragma/<http://schemas.microsoft.com/exchange/newmail>\r\n"; break;
00149     case Update: headers += "update\r\n"; break;
00150     case UpdateNewMember: headers += "update/newmember\r\n"; break;
00151   }
00152 
00153   headers += "Depth: " + QString::number( depth );
00154 
00155   if (mPollMode == CallBack )
00156     headers += "\r\nCall-Back: httpu://" + mSocket->address().toString() + ":" + QString::number(mSocket->port());
00157 
00158   kdDebug() << "Headers: " << headers << endl;
00159 
00160   KURL myURL = toDAV( url );
00161   KIO::DavJob *job = new KIO::DavJob( myURL, (int) KIO::DAV_SUBSCRIBE, QString::null, false );
00162   job->addMetaData( "customHTTPHeader", headers );
00163   job->addMetaData( "PropagateHttpHeader", "true" );
00164   connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotSubscribeResult(KIO::Job *)));
00165 }
00166 
00167 void ExchangeMonitor::removeWatch( const KURL &url ) 
00168 {
00169   KURL myURL = toDAV( url );
00170   QMap<ID,KURL>::Iterator it;
00171   for ( it = mSubscriptionMap.begin(); it != mSubscriptionMap.end(); ++it ) {
00172     if ( it.data() == myURL ) {
00173       removeWatch( it.key() );
00174       return;
00175     }
00176   }
00177   kdWarning() << "Trying to remove unknown watch " << myURL.prettyURL() << ", failed." << endl;
00178 }
00179 
00180 void ExchangeMonitor::removeWatch( ID id )
00181 {
00182   KIO::DavJob *job = new KIO::DavJob( mAccount->calendarURL(), (int) KIO::DAV_UNSUBSCRIBE, QString::null, false );
00183   job->addMetaData( "customHTTPHeader", "Subscription-id: " + QString::number( id ));
00184   job->addMetaData( "PropagateHttpHeader", "true" );
00185   connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotUnsubscribeResult(KIO::Job *)));
00186 }
00187 
00188 void ExchangeMonitor::slotSubscribeResult( KIO::Job * job )
00189 {
00190   if ( job->error() ) {
00191     job->showErrorDialog( 0L );
00192     emit error( ExchangeClient::CommunicationError, "IO Error: " + QString::number(job->error()) + ":" + job->errorString() );
00193     return;
00194   }
00195 
00196   ID id;
00197   KURL url;
00198   bool gotID = false;
00199   bool gotURL = false;
00200 
00201   QStringList headers = QStringList::split( "\n", job->queryMetaData( "HTTP-Headers" ) );
00202   for ( QStringList::Iterator it = headers.begin(); it != headers.end(); ++it ) {
00203     int colon = (*it).find( ": " );
00204     if ( colon<0 ) continue;
00205     QString tag = (*it).left( colon ).stripWhiteSpace().lower();
00206     QString value = (*it).mid( colon+1 ).stripWhiteSpace();
00207     if ( tag == "subscription-lifetime" ) {
00208       int lifetime = value.toInt();
00209       if ( lifetime < mSubscriptionLifetime ) {
00210         mSubscriptionLifetime = lifetime;
00211         mRenewTimer->changeInterval( lifetime * 900 );
00212         slotRenewTimer();
00213       }
00214     } else if ( tag == "subscription-id" ) {
00215       id = value.toLong();
00216       gotID = true;
00217     } else if ( tag == "content-location" ) {
00218       url = toDAV( KURL( value ) );
00219       gotURL = true;
00220     }
00221   }
00222 
00223   if ( mSubscriptionLifetime < 60 ) {
00224     kdWarning() << "Exchange server gave subscription a lifetime of " << mSubscriptionLifetime << ", changing to 60 seconds." << endl;
00225     mSubscriptionLifetime = 60;
00226     return;
00227   }
00228 
00229   if ( ! gotID ) {
00230     kdError() << "Error: Exchange server didn't give a subscription ID" << endl;
00231     emit error( ExchangeClient::ServerResponseError, "No subscription ID in SUBSCRIBE response headers: " + headers.join(", ") );
00232     return;
00233   }
00234 
00235   if ( ! gotURL ) {
00236     kdError() << "Error: Exchange server didn't return content-location" << endl;
00237     emit error( ExchangeClient::ServerResponseError, "No content-location in SUBSCRIBE response headers: " + headers.join(", ") );
00238     return;
00239   }
00240 
00241   kdDebug() << "Lifetime: " << mSubscriptionLifetime << endl;
00242   kdDebug() << "ID: " << id << endl;
00243   kdDebug() << "URL: " << url.prettyURL() << endl;
00244 
00245   mSubscriptionMap.insert( id, url );
00246 }
00247 
00248 void ExchangeMonitor::slotUnsubscribeResult( KIO::Job * job )
00249 {
00250   if ( job->error() ) {
00251     job->showErrorDialog( 0L );
00252     emit error( ExchangeClient::CommunicationError, "IO Error: " + QString::number(job->error()) + ":" + job->errorString() );
00253     return;
00254   }
00255 
00256   QDomDocument& response = static_cast<KIO::DavJob *>( job )->response();
00257   kdDebug() << "UNSUBSCRIBE result: " << endl << response.toString() << endl;
00258 
00259   QDomElement status = response.documentElement().namedItem( "response" ).namedItem( "status" ).toElement();
00260   QDomElement subscriptionID = response.documentElement().namedItem( "response" ).namedItem( "subscriptionID" ).toElement();
00261   kdDebug() << "Subscription ID.text(): " << subscriptionID.text() << endl;
00262   bool ok;
00263   ID id = subscriptionID.text().toLong( &ok );
00264   if ( ! status.text().contains( "200" ) || !ok) {
00265     kdError() << "UNSUBSCRIBE result is not 200 or no subscription ID found" << endl;
00266     emit error( ExchangeClient::ServerResponseError, "UNSUBSCRIBE yields an error response: \n" + response.toString() );
00267   }
00268 
00269   mSubscriptionMap.remove( id );
00270 }
00271 
00272 void ExchangeMonitor::slotPollTimer()
00273 {
00274   kdDebug() << "ExchangeMonitor::slotPollTimer()" << endl;
00275   poll( mSubscriptionMap.keys() );
00276 }
00277 
00278 void ExchangeMonitor::slotActivated( int )
00279 {
00280   kdDebug() << "ExchangeMonitor::slotActivated()" << endl;
00281 
00282   kdDebug() << "Bytes available: " << mSocket->bytesAvailable() << endl;
00283   int maxLen = mSocket->bytesAvailable();
00284   if ( maxLen == 0 )
00285     return;
00286 
00287   QCString response( maxLen+2 );
00288   Q_LONG len = mSocket->readBlock ( response.data(), maxLen+1 );
00289 
00290   if ( len <= 0 ) {
00291     kdDebug() << "Error: len<=0" << endl;
00292     kdDebug() << "Error: " << mSocket->error() << endl;
00293     return;
00294   }
00295   kdDebug() << "Got data of " << len << " bytes." << endl;
00296   kdDebug() << response << endl;
00297 
00298   QString s(response);
00299   IDList IDs;
00300 
00301   QStringList lines = QStringList::split( "\n", s );
00302   QStringList::iterator it;
00303   for ( it = lines.begin(); it != lines.end(); ++it ) {
00304     QString line = (*it).stripWhiteSpace().lower();
00305     if ( line.startsWith( "subscription-id: " ) )
00306       IDs = makeIDList( line.section(":",1).stripWhiteSpace() );
00307   }
00308 
00309   if ( IDs.isEmpty() ) {
00310     kdWarning() << "Did not find any subscriptions in NOTIFY!" << response << endl;
00311   } else {
00312     poll( IDs );
00313   }
00314 
00315 }
00316 
00317 void ExchangeMonitor::poll( const IDList& IDs ) {
00318   // FIXME: Check what did subscription means
00319 //  if ( id != mSubscriptionId ) {
00320 //    kdDebug() << "Don't know subscription id " << id << endl;
00321 //  }
00322 
00323   // confirm it
00324   KIO::DavJob *job = new KIO::DavJob( mAccount->calendarURL(), (int) KIO::DAV_POLL, QString::null, false );
00325   job->addMetaData( "customHTTPHeader", "Subscription-ID: " + makeIDString( IDs ) );
00326   connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotPollResult(KIO::Job *)));
00327 }
00328 
00329 void ExchangeMonitor::slotPollResult( KIO::Job * job )
00330 {
00331   if ( job->error() ) {
00332     job->showErrorDialog( 0L );
00333     emit error( ExchangeClient::CommunicationError, "IO Error: " + QString::number(job->error()) + ":" + job->errorString() );
00334     return;
00335   }
00336   QDomDocument& response = static_cast<KIO::DavJob *>( job )->response();
00337   kdDebug() << "POLL result: " << endl << response.toString() << endl;
00338 
00339   // Multiple results!
00340   QDomNodeList responses = response.documentElement().elementsByTagName( "response" );
00341   if ( responses.count() == 0 ) {
00342     emit error( ExchangeClient::ServerResponseError, "Poll result is wrong: \n" +  response.toString() );
00343     return;
00344   }
00345   for( uint i=0; i<responses.count(); i++ ) {
00346     QDomElement item = responses.item( i ).toElement();
00347     QDomElement status = item.namedItem( "status" ).toElement();
00348     QDomElement subscriptionID = item.namedItem( "subscriptionID" ).toElement();
00349     if ( status.text().contains( "200" ) ) {
00350       kdDebug() << "subscriptionID: " << subscriptionID.text() << endl;
00351       IDList IDs = makeIDList( subscriptionID.text() );
00352       QValueList<KURL> urls;
00353       IDList::ConstIterator it;
00354       for ( it = IDs.begin(); it != IDs.end(); ++it ) {
00355         urls += mSubscriptionMap[ *it ];
00356       }
00357       emit notify( IDs, urls );
00358     } else if ( ! status.text().contains( "204" ) ) {
00359       kdWarning() << "POLL result is not 200 or 204, what's up?" << endl;
00360       emit error( ExchangeClient::ServerResponseError, "Poll result is wrong: \n" +  response.toString() );
00361     }
00362   }
00363 }
00364 
00365 void ExchangeMonitor::slotRenewTimer()
00366 {
00367   kdDebug() << "ExchangeMonitor::slotRenewTimer()" << endl;
00368 
00369   KIO::DavJob *job = new KIO::DavJob( mAccount->calendarURL(), (int) KIO::DAV_SUBSCRIBE, QString::null, false );
00370   job->addMetaData( "customHTTPHeader", "Subscription-id: " + makeIDString( mSubscriptionMap.keys() ) );
00371   connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotRenewResult(KIO::Job *)));
00372 }
00373 
00374 void ExchangeMonitor::slotRenewResult( KIO::Job* job )
00375 {
00376   if ( job->error() ) {
00377     job->showErrorDialog( 0L );
00378     emit error( ExchangeClient::CommunicationError, "IO Error: " + QString::number(job->error()) + ":" + job->errorString() );
00379     return;
00380   }
00381   kdDebug() << "ExchangeMonitor::slotRenewResult()" << endl;
00382 
00383   // FIXME: check for new subscription lifetime
00384 }
00385 
00386 #include "exchangemonitor.moc"
KDE Logo
This file is part of the documentation for libkpimexchange Library Version 3.2.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat May 1 11:38:09 2004 by doxygen 1.2.15 written by Dimitri van Heesch, © 1997-2003