|
|
|
/*
|
|
|
|
This file is part of libkpimexchange
|
|
|
|
Copyright (c) 2002 Jan-Pascal van Best <janpascal@vanbest.org>
|
|
|
|
|
|
|
|
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 <tqstring.h>
|
|
|
|
#include <tqregexp.h>
|
|
|
|
#include <tqsocketdevice.h>
|
|
|
|
#include <tqsocketnotifier.h>
|
|
|
|
#include <tqtextstream.h>
|
|
|
|
|
|
|
|
#include <kurl.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <krfcdate.h>
|
|
|
|
#include <kextsock.h>
|
|
|
|
|
|
|
|
#include <tdeio/job.h>
|
|
|
|
#include <tdeio/slave.h>
|
|
|
|
#include <tdeio/scheduler.h>
|
|
|
|
#include <tdeio/slavebase.h>
|
|
|
|
#include <tdeio/davjob.h>
|
|
|
|
#include <tdeio/http.h>
|
|
|
|
|
|
|
|
#include <libkcal/event.h>
|
|
|
|
#include <libkcal/icalformat.h>
|
|
|
|
#include <libkcal/icalformatimpl.h>
|
|
|
|
#include <libkcal/recurrence.h>
|
|
|
|
#include <libkcal/incidence.h>
|
|
|
|
#include <libkcal/event.h>
|
|
|
|
|
|
|
|
#include "exchangemonitor.h"
|
|
|
|
#include "exchangeclient.h"
|
|
|
|
#include "exchangeaccount.h"
|
|
|
|
#include "utils.h"
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
#include <unistd.h>
|
|
|
|
}
|
|
|
|
|
|
|
|
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, TQ_SIGNAL(activated( int )), this, TQ_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, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotPollTimer()) );
|
|
|
|
mPollTimer->start( 60000 ); // 1 minute timer
|
|
|
|
}
|
|
|
|
|
|
|
|
mRenewTimer = new TQTimer( this, "mRenewTimer" );
|
|
|
|
connect( mRenewTimer, TQ_SIGNAL(timeout()), this, TQ_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;
|
|
|
|
TDEIO::DavJob *job = new TDEIO::DavJob( mAccount->calendarURL(), (int) TDEIO::DAV_UNSUBSCRIBE, TQString(), false );
|
|
|
|
job->addMetaData( "customHTTPHeader", headers );
|
|
|
|
// Can't do, this is a destructor!
|
|
|
|
// job->addMetaData( "PropagateHttpHeader", "true" );
|
|
|
|
// connect(job, TQ_SIGNAL(result( TDEIO::Job * )), this, TQ_SLOT(slotUnsubscribeResult(TDEIO::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/<http://schemas.microsoft.com/exchange/newmail>\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 );
|
|
|
|
TDEIO::DavJob *job = new TDEIO::DavJob( myURL, (int) TDEIO::DAV_SUBSCRIBE, TQString(), false );
|
|
|
|
job->addMetaData( "customHTTPHeader", headers );
|
|
|
|
job->addMetaData( "PropagateHttpHeader", "true" );
|
|
|
|
connect(job, TQ_SIGNAL(result( TDEIO::Job * )), this, TQ_SLOT(slotSubscribeResult(TDEIO::Job *)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeMonitor::removeWatch( const KURL &url )
|
|
|
|
{
|
|
|
|
KURL myURL = toDAV( url );
|
|
|
|
TQMap<ID,KURL>::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 )
|
|
|
|
{
|
|
|
|
TDEIO::DavJob *job = new TDEIO::DavJob( mAccount->calendarURL(), (int) TDEIO::DAV_UNSUBSCRIBE, TQString(), false );
|
|
|
|
job->addMetaData( "customHTTPHeader", "Subscription-id: " + TQString::number( id ));
|
|
|
|
job->addMetaData( "PropagateHttpHeader", "true" );
|
|
|
|
connect(job, TQ_SIGNAL(result( TDEIO::Job * )), this, TQ_SLOT(slotUnsubscribeResult(TDEIO::Job *)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeMonitor::slotSubscribeResult( TDEIO::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).find( ": " );
|
|
|
|
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( TDEIO::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<TDEIO::DavJob *>( 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().contains( "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
|
|
|
|
TDEIO::DavJob *job = new TDEIO::DavJob( mAccount->calendarURL(), (int) TDEIO::DAV_POLL, TQString(), false );
|
|
|
|
job->addMetaData( "customHTTPHeader", "Subscription-ID: " + makeIDString( IDs ) );
|
|
|
|
connect(job, TQ_SIGNAL(result( TDEIO::Job * )), this, TQ_SLOT(slotPollResult(TDEIO::Job *)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeMonitor::slotPollResult( TDEIO::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<TDEIO::DavJob *>( 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<responses.count(); i++ ) {
|
|
|
|
TQDomElement item = responses.item( i ).toElement();
|
|
|
|
TQDomElement status = item.namedItem( "status" ).toElement();
|
|
|
|
TQDomElement subscriptionID = item.namedItem( "subscriptionID" ).toElement();
|
|
|
|
if ( status.text().contains( "200" ) ) {
|
|
|
|
kdDebug() << "subscriptionID: " << subscriptionID.text() << endl;
|
|
|
|
IDList IDs = makeIDList( subscriptionID.text() );
|
|
|
|
TQValueList<KURL> urls;
|
|
|
|
IDList::ConstIterator it;
|
|
|
|
for ( it = IDs.begin(); it != IDs.end(); ++it ) {
|
|
|
|
urls += mSubscriptionMap[ *it ];
|
|
|
|
}
|
|
|
|
emit notify( IDs, urls );
|
|
|
|
} else if ( ! status.text().contains( "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;
|
|
|
|
|
|
|
|
TDEIO::DavJob *job = new TDEIO::DavJob( mAccount->calendarURL(), (int) TDEIO::DAV_SUBSCRIBE, TQString(), false );
|
|
|
|
job->addMetaData( "customHTTPHeader", "Subscription-id: " + makeIDString( mSubscriptionMap.keys() ) );
|
|
|
|
connect(job, TQ_SIGNAL(result( TDEIO::Job * )), this, TQ_SLOT(slotRenewResult(TDEIO::Job *)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExchangeMonitor::slotRenewResult( TDEIO::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"
|