00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
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;
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
00100
00101
00102
00103
00104
00105
00106 kdDebug() << "Port: " << mSocket->port() << endl;
00107 kdDebug() << "Host: " << mSocket->address().toString() << endl;
00108
00109 }
00110
00111 if ( mPollMode == Poll ) {
00112 mPollTimer = new QTimer( this );
00113 connect( mPollTimer, SIGNAL(timeout()), this, SLOT(slotPollTimer()) );
00114 mPollTimer->start( 60000 );
00115 }
00116
00117 mRenewTimer = new QTimer( this );
00118 connect( mRenewTimer, SIGNAL(timeout()), this, SLOT(slotRenewTimer()) );
00119 mRenewTimer->start( mSubscriptionLifetime * 900 );
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
00135
00136
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
00319
00320
00321
00322
00323
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
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
00384 }
00385
00386 #include "exchangemonitor.moc"