/* This file is part of libkpimexchange Copyright (c) 2002 Jan-Pascal van Best This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "exchangemonitor.h" #include "exchangeclient.h" #include "exchangeaccount.h" #include "utils.h" extern "C" { #include } using namespace KPIM; TQString makeIDString( const ExchangeMonitor::IDList& IDs ) { TQString result; ExchangeMonitor::IDList::ConstIterator it; for ( it = IDs.begin(); it != IDs.end(); ++it ) { if ( it == IDs.begin() ) result += TQString::number( (*it) ); else result += "," + TQString::number( (*it) ); } return result; } ExchangeMonitor::IDList makeIDList( const TQString& input ) { ExchangeMonitor::IDList IDs; TQStringList numbers = TQStringList::split( ",", input ); TQStringList::iterator j; for ( j = numbers.begin(); j != numbers.end(); ++j ) { ExchangeMonitor::ID id = (*j).toLong(); IDs.append( id ); } return IDs; } ExchangeMonitor::ExchangeMonitor( ExchangeAccount* account, int pollMode, const TQHostAddress& ownInterface ) { kdDebug() << "Called ExchangeMonitor" << endl; mAccount = account; mSubscriptionLifetime = 3600; // by default, renew subscription every 3600 seconds or one hour mPollMode = pollMode; mPollTimer = 0; if ( pollMode == CallBack ) { mSocket = new TQSocketDevice( TQSocketDevice::Datagram ); if ( ! mSocket->bind( ownInterface, 0 ) ) kdDebug() << "bind() returned false" << endl; mSocket->setBlocking( false ); mNotifier = new TQSocketNotifier( mSocket->socket(), TQSocketNotifier::Read ); connect( mNotifier, TQT_SIGNAL(activated( int )), this, TQT_SLOT( slotActivated(int))); //mSocket.setSocketFlags( KExtendedSocket::inetSocket | KExtendedSocket::passiveSocket | KExtendedSocket::datagramSocket | KExtendedSocket::bufferedSocket ); //mSocket.setHost( "jupiter.tbm.tudelft.nl" ); // Does this work? //mSocket.setPort( 0 ); // setting port to 0 will make us bind to a random, free port // UDP server socket: no listen //if ( int code = mSocket.listen() ) // kdError() << "Error in socket listen: " << code << endl; //mSocket.enableRead( true ); kdDebug() << "Port: " << mSocket->port() << endl; kdDebug() << "Host: " << TQString(mSocket->address().toString()) << endl; // mStream = new TQTextStream( mSocket ); } if ( mPollMode == Poll ) { mPollTimer = new TQTimer( this, "mPollTimer" ); connect( mPollTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotPollTimer()) ); mPollTimer->start( 60000 ); // 1 minute timer } mRenewTimer = new TQTimer( this, "mRenewTimer" ); connect( mRenewTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotRenewTimer()) ); mRenewTimer->start( mSubscriptionLifetime * 900 ); // 10% early so as to be in time } ExchangeMonitor::~ExchangeMonitor() { kdDebug() << "Entering ExchangeMonitor destructor" << endl; delete mNotifier; delete mSocket; if ( mPollTimer ) delete mPollTimer; if ( mRenewTimer ) delete mRenewTimer; if ( ! mSubscriptionMap.isEmpty() ) { TQString headers = "Subscription-ID: " + makeIDString( mSubscriptionMap.keys() ); kdDebug() << "Subsubscribing all watches, headers:" << endl << headers << endl; KIO::DavJob *job = new KIO::DavJob( mAccount->calendarURL(), (int) KIO::DAV_UNSUBSCRIBE, TQString(), false ); job->addMetaData( "customHTTPHeader", headers ); // Can't do, this is a destructor! // job->addMetaData( "PropagateHttpHeader", "true" ); // connect(job, TQT_SIGNAL(result( KIO::Job * )), this, TQT_SLOT(slotUnsubscribeResult(KIO::Job *))); } kdDebug() << "Finished ExchangeMonitor destructor" << endl; } void ExchangeMonitor::addWatch( const KURL &url, int mode, int depth ) { TQString headers = "Notification-type: "; switch( mode ) { case Delete: headers += "delete\r\n"; break; case Move: headers += "move\r\n"; break; case Newmail: headers += "pragma/\r\n"; break; case Update: headers += "update\r\n"; break; case UpdateNewMember: headers += "update/newmember\r\n"; break; } headers += "Depth: " + TQString::number( depth ); if (mPollMode == CallBack ) headers += "\r\nCall-Back: httpu://" + mSocket->address().toString() + ":" + TQString::number(mSocket->port()); kdDebug() << "Headers: " << headers << endl; KURL myURL = toDAV( url ); KIO::DavJob *job = new KIO::DavJob( myURL, (int) KIO::DAV_SUBSCRIBE, TQString(), false ); job->addMetaData( "customHTTPHeader", headers ); job->addMetaData( "PropagateHttpHeader", "true" ); connect(job, TQT_SIGNAL(result( KIO::Job * )), this, TQT_SLOT(slotSubscribeResult(KIO::Job *))); } void ExchangeMonitor::removeWatch( const KURL &url ) { KURL myURL = toDAV( url ); TQMap::Iterator it; for ( it = mSubscriptionMap.begin(); it != mSubscriptionMap.end(); ++it ) { if ( it.data() == myURL ) { removeWatch( it.key() ); return; } } kdWarning() << "Trying to remove unknown watch " << myURL.prettyURL() << ", failed." << endl; } void ExchangeMonitor::removeWatch( ID id ) { KIO::DavJob *job = new KIO::DavJob( mAccount->calendarURL(), (int) KIO::DAV_UNSUBSCRIBE, TQString(), false ); job->addMetaData( "customHTTPHeader", "Subscription-id: " + TQString::number( id )); job->addMetaData( "PropagateHttpHeader", "true" ); connect(job, TQT_SIGNAL(result( KIO::Job * )), this, TQT_SLOT(slotUnsubscribeResult(KIO::Job *))); } void ExchangeMonitor::slotSubscribeResult( KIO::Job * job ) { if ( job->error() ) { job->showErrorDialog( 0L ); emit error( ExchangeClient::CommunicationError, "IO Error: " + TQString::number(job->error()) + ":" + job->errorString() ); return; } ID id; KURL url; bool gotID = false; bool gotURL = false; TQStringList headers = TQStringList::split( "\n", job->queryMetaData( "HTTP-Headers" ) ); for ( TQStringList::Iterator it = headers.begin(); it != headers.end(); ++it ) { int colon = (*it).tqfind( ": " ); if ( colon<0 ) continue; TQString tag = (*it).left( colon ).stripWhiteSpace().lower(); TQString value = (*it).mid( colon+1 ).stripWhiteSpace(); if ( tag == "subscription-lifetime" ) { int lifetime = value.toInt(); if ( lifetime < mSubscriptionLifetime ) { mSubscriptionLifetime = lifetime; mRenewTimer->changeInterval( lifetime * 900 ); slotRenewTimer(); } } else if ( tag == "subscription-id" ) { id = value.toLong(); gotID = true; } else if ( tag == "content-location" ) { url = toDAV( KURL( value ) ); gotURL = true; } } if ( mSubscriptionLifetime < 60 ) { kdWarning() << "Exchange server gave subscription a lifetime of " << mSubscriptionLifetime << ", changing to 60 seconds." << endl; mSubscriptionLifetime = 60; return; } if ( ! gotID ) { kdError() << "Error: Exchange server didn't give a subscription ID" << endl; emit error( ExchangeClient::ServerResponseError, "No subscription ID in SUBSCRIBE response headers: " + headers.join(", ") ); return; } if ( ! gotURL ) { kdError() << "Error: Exchange server didn't return content-location" << endl; emit error( ExchangeClient::ServerResponseError, "No content-location in SUBSCRIBE response headers: " + headers.join(", ") ); return; } kdDebug() << "Lifetime: " << mSubscriptionLifetime << endl; kdDebug() << "ID: " << id << endl; kdDebug() << "URL: " << url.prettyURL() << endl; mSubscriptionMap.insert( id, url ); } void ExchangeMonitor::slotUnsubscribeResult( KIO::Job * job ) { if ( job->error() ) { job->showErrorDialog( 0L ); emit error( ExchangeClient::CommunicationError, "IO Error: " + TQString::number(job->error()) + ":" + job->errorString() ); return; } TQDomDocument& response = static_cast( job )->response(); kdDebug() << "UNSUBSCRIBE result: " << endl << response.toString() << endl; TQDomElement status = response.documentElement().namedItem( "response" ).namedItem( "status" ).toElement(); TQDomElement subscriptionID = response.documentElement().namedItem( "response" ).namedItem( "subscriptionID" ).toElement(); kdDebug() << "Subscription ID.text(): " << subscriptionID.text() << endl; bool ok; ID id = subscriptionID.text().toLong( &ok ); if ( ! status.text().tqcontains( "200" ) || !ok) { kdError() << "UNSUBSCRIBE result is not 200 or no subscription ID found" << endl; emit error( ExchangeClient::ServerResponseError, "UNSUBSCRIBE yields an error response: \n" + response.toString() ); } mSubscriptionMap.remove( id ); } void ExchangeMonitor::slotPollTimer() { kdDebug() << "ExchangeMonitor::slotPollTimer()" << endl; poll( mSubscriptionMap.keys() ); } void ExchangeMonitor::slotActivated( int ) { kdDebug() << "ExchangeMonitor::slotActivated()" << endl; kdDebug() << "Bytes available: " << mSocket->bytesAvailable() << endl; int maxLen = mSocket->bytesAvailable(); if ( maxLen == 0 ) return; TQCString response( maxLen+2 ); TQ_LONG len = mSocket->readBlock ( response.data(), maxLen+1 ); if ( len <= 0 ) { kdDebug() << "Error: len<=0" << endl; kdDebug() << "Error: " << mSocket->error() << endl; return; } kdDebug() << "Got data of " << len << " bytes." << endl; kdDebug() << response << endl; TQString s(response); IDList IDs; TQStringList lines = TQStringList::split( "\n", s ); TQStringList::iterator it; for ( it = lines.begin(); it != lines.end(); ++it ) { TQString line = (*it).stripWhiteSpace().lower(); if ( line.startsWith( "subscription-id: " ) ) IDs = makeIDList( TQString(line.section(":",1)).stripWhiteSpace() ); } if ( IDs.isEmpty() ) { kdWarning() << "Did not find any subscriptions in NOTIFY!" << response << endl; } else { poll( IDs ); } } void ExchangeMonitor::poll( const IDList& IDs ) { // FIXME: Check what did subscription means // if ( id != mSubscriptionId ) { // kdDebug() << "Don't know subscription id " << id << endl; // } // confirm it KIO::DavJob *job = new KIO::DavJob( mAccount->calendarURL(), (int) KIO::DAV_POLL, TQString(), false ); job->addMetaData( "customHTTPHeader", "Subscription-ID: " + makeIDString( IDs ) ); connect(job, TQT_SIGNAL(result( KIO::Job * )), this, TQT_SLOT(slotPollResult(KIO::Job *))); } void ExchangeMonitor::slotPollResult( KIO::Job * job ) { if ( job->error() ) { job->showErrorDialog( 0L ); emit error( ExchangeClient::CommunicationError, "IO Error: " + TQString::number(job->error()) + ":" + job->errorString() ); return; } TQDomDocument& response = static_cast( job )->response(); kdDebug() << "POLL result: " << endl << response.toString() << endl; // Multiple results! TQDomNodeList responses = response.documentElement().elementsByTagName( "response" ); if ( responses.count() == 0 ) { emit error( ExchangeClient::ServerResponseError, "Poll result is wrong: \n" + response.toString() ); return; } for( uint i=0; i urls; IDList::ConstIterator it; for ( it = IDs.begin(); it != IDs.end(); ++it ) { urls += mSubscriptionMap[ *it ]; } emit notify( IDs, urls ); } else if ( ! status.text().tqcontains( "204" ) ) { kdWarning() << "POLL result is not 200 or 204, what's up?" << endl; emit error( ExchangeClient::ServerResponseError, "Poll result is wrong: \n" + response.toString() ); } } } void ExchangeMonitor::slotRenewTimer() { kdDebug() << "ExchangeMonitor::slotRenewTimer()" << endl; KIO::DavJob *job = new KIO::DavJob( mAccount->calendarURL(), (int) KIO::DAV_SUBSCRIBE, TQString(), false ); job->addMetaData( "customHTTPHeader", "Subscription-id: " + makeIDString( mSubscriptionMap.keys() ) ); connect(job, TQT_SIGNAL(result( KIO::Job * )), this, TQT_SLOT(slotRenewResult(KIO::Job *))); } void ExchangeMonitor::slotRenewResult( KIO::Job* job ) { if ( job->error() ) { job->showErrorDialog( 0L ); emit error( ExchangeClient::CommunicationError, "IO Error: " + TQString::number(job->error()) + ":" + job->errorString() ); return; } kdDebug() << "ExchangeMonitor::slotRenewResult()" << endl; // FIXME: check for new subscription lifetime } #include "exchangemonitor.moc"