diff options
Diffstat (limited to 'libkpimexchange/core')
-rw-r--r-- | libkpimexchange/core/Makefile.am | 27 | ||||
-rw-r--r-- | libkpimexchange/core/README.download | 63 | ||||
-rw-r--r-- | libkpimexchange/core/exchangeaccount.cpp | 339 | ||||
-rw-r--r-- | libkpimexchange/core/exchangeaccount.h | 99 | ||||
-rw-r--r-- | libkpimexchange/core/exchangeclient.cpp | 292 | ||||
-rw-r--r-- | libkpimexchange/core/exchangeclient.h | 134 | ||||
-rw-r--r-- | libkpimexchange/core/exchangedelete.cpp | 121 | ||||
-rw-r--r-- | libkpimexchange/core/exchangedelete.h | 59 | ||||
-rw-r--r-- | libkpimexchange/core/exchangedownload.cpp | 643 | ||||
-rw-r--r-- | libkpimexchange/core/exchangedownload.h | 89 | ||||
-rw-r--r-- | libkpimexchange/core/exchangemonitor.cpp | 386 | ||||
-rw-r--r-- | libkpimexchange/core/exchangemonitor.h | 106 | ||||
-rw-r--r-- | libkpimexchange/core/exchangeprogress.cpp | 72 | ||||
-rw-r--r-- | libkpimexchange/core/exchangeprogress.h | 54 | ||||
-rw-r--r-- | libkpimexchange/core/exchangeupload.cpp | 371 | ||||
-rw-r--r-- | libkpimexchange/core/exchangeupload.h | 66 | ||||
-rw-r--r-- | libkpimexchange/core/utils.cpp | 100 | ||||
-rw-r--r-- | libkpimexchange/core/utils.h | 56 |
18 files changed, 3077 insertions, 0 deletions
diff --git a/libkpimexchange/core/Makefile.am b/libkpimexchange/core/Makefile.am new file mode 100644 index 000000000..cc19fd79c --- /dev/null +++ b/libkpimexchange/core/Makefile.am @@ -0,0 +1,27 @@ +# $Id$ + +SUBDIRS= + +METASOURCES = AUTO + +INCLUDES = -I$(top_srcdir)/korganizer/interfaces -I$(top_srcdir) \ + -I$(top_srcdir)/libkcal/libical/src/libical -I$(top_srcdir)/libkcal/libical/src/libicalss \ + -I$(top_builddir)/libkcal/libical/src/libical -I$(top_builddir)/libkcal/libical/src/libicalss \ + $(all_includes) + +# -I$(top_builddir)/libkdepim/resources -I$(top_builddir)/libkdepim/resources/calendar + +noinst_LTLIBRARIES = libkpimexchangecore.la + +libkpimexchangecore_la_SOURCES = exchangeclient.cpp exchangeaccount.cpp \ + exchangedownload.cpp exchangeupload.cpp exchangedelete.cpp \ + utils.cpp exchangeprogress.cpp exchangemonitor.cpp + +#libkpimexchange_la_LDFLAGS = $(all_libraries) -no-undefined -version-info 1:0:0 -module +#libkpimexchange_la_LIBADD = $(LIB_KIO) $(top_builddir)/libkcal/libkcal.la + +kdepimincludedir = $(includedir)/kdepim +kdepiminclude_HEADERS = exchangeclient.h exchangeaccount.h + +noinst_HEADERS = exchangedownload.h exchangeupload.h exchangedelete.h exchangeprogress.h utils.h + diff --git a/libkpimexchange/core/README.download b/libkpimexchange/core/README.download new file mode 100644 index 000000000..07540db85 --- /dev/null +++ b/libkpimexchange/core/README.download @@ -0,0 +1,63 @@ +$Id$ +This document describes what happens during the download of +appointments from an exchange server in exchangedownload.cpp. +Error handling, user interface ignored for clarity + +Author: Jan-Pascal van Best, janpascal@vanbest.org + +NOTES: +- You can only use an ExchangeDownload object for a single download + It uses internal state member variables and such. + +DATA STRUCTURES: +QMap<QString,int> m_uids is in fact a set of known uids telling us +whether we're already busy or finished reading the Master event +for this UID. The map contains the UID as key, with a value of 1, +if UID is either being or finished downloading. + +QMap<QString,DwString *> m_transferJobs maps URLs being downloaded +to strings of data already received. A URL is removed from the map +if all data has been received + +METHODS: +download() +- Provides authentication info to the KDE authentication service +- Creates an SQL query using dateSelectQuery() +- Starts a SEARCH job, connects the result() signal + to the slotSearchResult() slot + +slotSearchResult() +- Calls handleAppointments() with recurrence enabled + +handleAppointments() +- Examines all events returned by the SEARCH +- If recurrence is enabled, for Master, Instance or Exception events, + and if we havent't handled this particular UID yet, call + handleRecurrence() with the UID of the event +- If recurrence is disabled, or for Single events, start a TransferJob. + Connect the data() signal to the slotData() slot and the result() + signal to the slotTransferResult() slot. +- Note that this method may start many new jobs for transferring + appointments and for finding recurrent events! + +handleRecurrence() +- Start a new SEARCH job, looking for the Master event of the UID +- Connect the result() signal to the slotMasterResult() slot + +slotMasterResult() +- Call handleAppointment() with recurrence disabled + +slotData() +- If the URL of the data we're receiving is already in m_transferJobs, + append the data to the string related to this URL. Else, create a new + string, and place a new URL,string pair in m_transferJobs + +slotTransferResult() +- Parse the data received for this URL as a MIME message +- call handlePart() for every MIME part in the message +- Remove the URL from m_transferJobs and free the string + +handlePart() +- If this is a text/calendar part, read iCalendar data from the part and + insert it into the calendar. + diff --git a/libkpimexchange/core/exchangeaccount.cpp b/libkpimexchange/core/exchangeaccount.cpp new file mode 100644 index 000000000..9142c9db3 --- /dev/null +++ b/libkpimexchange/core/exchangeaccount.cpp @@ -0,0 +1,339 @@ +/* + This file is part of libkpimexchange + + Copyright (c) 2002 Jan-Pascal van Best <janpascal@vanbest.org> + Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.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 <qstring.h> +#include <qtextstream.h> +#include <qapplication.h> +#include <qdom.h> +#include <qwidgetlist.h> +#include <qwidget.h> +#include <qfile.h> + +#include <kurl.h> +#include <kapplication.h> +#include <kdebug.h> +#include <kconfig.h> +#include <dcopclient.h> +#include <kcursor.h> +#include <kmessagebox.h> +#include <klocale.h> + +#include <kio/authinfo.h> +#include <kio/davjob.h> +#include <kio/job.h> +#include <kio/netaccess.h> + +#include "exchangeaccount.h" +#include "utils.h" + +using namespace KPIM; + +ExchangeAccount::ExchangeAccount( const QString &host, const QString &port, + const QString &account, + const QString &password, + const QString &mailbox ) + : mError( false ) +{ + KURL url( "webdav://" + host + "/exchange/" + account ); + + if ( !port.isEmpty() ) + { + url.setPort( port.toInt() ); + } + + mHost = host; + mPort = port; + mAccount = account; + mPassword = password; + + if ( mailbox.isEmpty() ) { + mMailbox = url.url(); + kdDebug() << "#!#!#!#!#!#!# mailbox url: " << mMailbox << endl; + } else + mMailbox = mailbox; + + kdDebug() << "ExchangeAccount: mMailbox: " << mMailbox << endl; + + mCalendarURL = 0; +} + +ExchangeAccount::ExchangeAccount( const QString& group ) +{ + load( group ); +} + +ExchangeAccount::~ExchangeAccount() +{ +} + +QString endecryptStr( const QString &aStr ) +{ + QString result; + for (uint i = 0; i < aStr.length(); i++) + result += (aStr[i].unicode() < 0x20) ? aStr[i] : + QChar(0x1001F - aStr[i].unicode()); + return result; +} + +void ExchangeAccount::save( QString const &group ) +{ + kapp->config()->setGroup( group ); + kapp->config()->writeEntry( "host", mHost ); + kapp->config()->writeEntry( "user", mAccount ); + kapp->config()->writeEntry( "mailbox", mMailbox ); + kapp->config()->writeEntry( "MS-ID", endecryptStr( mPassword ) ); + kapp->config()->sync(); +} + +void ExchangeAccount::load( QString const &group ) +{ + kapp->config()->setGroup( group ); + + QString host = kapp->config()->readEntry( "host" ); + if ( ! host.isNull() ) { + mHost = host; + } else { + mHost = "mail.company.com"; + } + + QString user = kapp->config()->readEntry( "user" ); + if ( ! user.isNull() ) { + mAccount = user; + } else { + mAccount = "username"; + } + + QString mailbox = kapp->config()->readEntry( "mailbox" ); + if ( ! mailbox.isNull() ) { + mMailbox = mailbox; + } else { + mMailbox = "webdav://" + host + "/exchange/" + mAccount; + } + + QString password = endecryptStr( kapp->config()->readEntry( "MS-ID" ) ); + if ( ! password.isNull() ) { + mPassword = password; + } +} + +KURL ExchangeAccount::baseURL() +{ + KURL url = KURL( mMailbox ); + return url; +} + +KURL ExchangeAccount::calendarURL() +{ + if ( mCalendarURL ) { + return *mCalendarURL; + } else { + KURL url = baseURL(); + url.addPath( "Calendar" ); + return url; + } +} + +bool ExchangeAccount::authenticate( QWidget *window ) +{ + if ( window ) + return authenticate( window->winId() ); + else + return authenticate(); +} + +bool ExchangeAccount::authenticate() +{ + long windowId; + QWidgetList *widgets = QApplication::topLevelWidgets(); + if ( widgets->isEmpty() ) + windowId = 0; + else + windowId = widgets->first()->winId(); + delete widgets; + + return authenticate( windowId ); +} + +bool ExchangeAccount::authenticate( int windowId ) +{ + kdDebug() << "Entering ExchangeAccount::authenticate( windowId=" << windowId << " )" << endl; + + kdDebug() << "Authenticating to base URL: " << baseURL().prettyURL() << endl; + + KIO::AuthInfo info; + info.url = baseURL(); + info.username = mAccount; + info.password = mPassword; + info.realmValue = mHost; + info.digestInfo = "Basic"; + + DCOPClient *dcopClient = new DCOPClient(); + dcopClient->attach(); + + QByteArray params; + QDataStream stream(params, IO_WriteOnly); + stream << info << windowId; + + dcopClient->send( "kded", "kpasswdserver", + "addAuthInfo(KIO::AuthInfo, long int)", params ); + + dcopClient->detach(); + delete dcopClient; + + mCalendarURL = 0; + + calcFolderURLs(); + + // TODO: Remove this busy loop + QApplication::setOverrideCursor( KCursor::waitCursor() ); + do { + qApp->processEvents(); + } while ( !mCalendarURL && !mError ); + QApplication::restoreOverrideCursor(); + + return !mError; +} + +void ExchangeAccount::calcFolderURLs() +{ + kdDebug() << "ExchangeAccount::calcFolderURLs" << endl; + QDomDocument doc; + QDomElement root = addElement( doc, doc, "DAV:", "propfind" ); + QDomElement prop = addElement( doc, root, "DAV:", "prop" ); + addElement( doc, prop, "urn:schemas:httpmail:", "calendar" ); +// For later use: +// urn:schemas:httpmail:contacts Contacts +// urn:schemas:httpmail:deleteditems Deleted Items +// urn:schemas:httpmail:drafts Drafts +// urn:schemas:httpmail:inbox Inbox +// urn:schemas:httpmail:journal Journal +// urn:schemas:httpmail:notes Notes +// urn:schemas:httpmail:outbox Outbox +// urn:schemas:httpmail:sentitems Sent Items +// urn:schemas:httpmail:tasks Tasks +// urn:schemas:httpmail:sendmsg Exchange Mail Submission URI +// urn:schemas:httpmail:msgfolderroot Mailbox folder (root) + + kdDebug() << "calcFolderUrls(): " << baseURL() << endl; + + mError = false; + + KIO::DavJob* job = KIO::davPropFind( baseURL(), doc, "1", false ); + job->addMetaData( "errorPage", "false" ); + connect( job, SIGNAL( result( KIO::Job * ) ), + SLOT( slotFolderResult( KIO::Job * ) ) ); +} + +void ExchangeAccount::slotFolderResult( KIO::Job *job ) +{ + kdDebug() << "ExchangeAccount::slotFolderResult()" << endl; + if ( job->error() ) { + kdError() << "Error: Cannot get well-know folder names; " << job->error() << endl; + QString text = i18n("ExchangeAccount\nError accessing '%1': %2") + .arg( baseURL().prettyURL() ).arg( job->errorString() ); + KMessageBox::error( 0, text ); + mError = true; + return; + } + QDomDocument &response = static_cast<KIO::DavJob *>( job )->response(); + + QDomElement prop = response.documentElement().namedItem( "response" ) + .namedItem( "propstat" ).namedItem( "prop" ).toElement(); + + QDomElement calElement = prop.namedItem( "calendar" ).toElement(); + if ( calElement.isNull() ) { + kdError() << "Error: no calendar URL in Exchange server reply" << endl; + mError = true; + return; + } + QString calendar = calElement.text(); + + kdDebug() << "ExchangeAccount: response calendarURL: " << calendar << endl; + + mCalendarURL = toDAV( new KURL( calendar ) ); + kdDebug() << "Calendar URL: " << mCalendarURL->url() << endl; +} + +QString ExchangeAccount::tryFindMailbox( const QString& host, const QString& port, const QString& user, const QString& password ) +{ + kdDebug() << "Entering ExchangeAccount::tryFindMailbox()" << endl; + + KURL url("http://" + host + "/exchange"); + if (!port.isEmpty()) url.setPort(port.toInt()); + + QString result = tryMailbox( url.url(), user, password ); + if ( result.isNull() ) + { + url.setProtocol("https"); + result = tryMailbox( url.url(), user, password ); + } + return result; +} + +QString ExchangeAccount::tryMailbox( const QString &_url, const QString &user, + const QString &password ) +{ + KURL url = KURL( _url ); + url.setUser( user ); + url.setPass( password ); + + QString tmpFile; + if ( !KIO::NetAccess::download( url, tmpFile, 0 ) ) { + kdWarning() << "Trying to find mailbox failed: not able to download " << url.prettyURL() << endl; + return QString::null; + } + QFile file( tmpFile ); + if ( !file.open( IO_ReadOnly ) ) { + kdWarning() << "Trying to find mailbox failed: not able to open temp file " << tmpFile << endl; + KIO::NetAccess::removeTempFile( tmpFile ); + return QString::null; + } + + QTextStream stream( &file ); + QString line; + QString result; + while ( !stream.eof() ) { + line = stream.readLine(); // line of text excluding '\n' + int pos = line.find( "<BASE href=\"", 0, FALSE ); + if ( pos < 0 ) + continue; + int end = line.find( "\"", pos+12, FALSE ); + if ( pos < 0 ) { + kdWarning() << "Strange, found no closing quote in " << line << endl; + continue; + } + QString mailboxString = line.mid( pos+12, end-pos-12 ); + KURL mailbox( mailboxString ); + if ( mailbox.isEmpty() ) { + kdWarning() << "Strange, could not get URL from " << mailboxString << " in line " << line << endl; + continue; + } + result = toDAV( mailbox ).prettyURL( -1 ); // Strip ending slash from URL, if present + kdDebug() << "Found mailbox: " << result << endl; + } + file.close(); + + KIO::NetAccess::removeTempFile( tmpFile ); + return result; +} + +#include "exchangeaccount.moc" diff --git a/libkpimexchange/core/exchangeaccount.h b/libkpimexchange/core/exchangeaccount.h new file mode 100644 index 000000000..505232c88 --- /dev/null +++ b/libkpimexchange/core/exchangeaccount.h @@ -0,0 +1,99 @@ +/* + This file is part of libkpimexchange. + + Copyright (c) 2002 Jan-Pascal van Best <janpascal@vanbest.org> + Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.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. + +*/ +#ifndef EXCHANGE_ACCOUNT_H +#define EXCHANGE_ACCOUNT_H + +#include <qobject.h> +#include <qstring.h> + +#include <kdepimmacros.h> +#include <kurl.h> +#include <kio/job.h> + +namespace KPIM { + +class KDE_EXPORT ExchangeAccount : public QObject +{ + Q_OBJECT + public: + ExchangeAccount( const QString &host, const QString &port, + const QString &account, const QString &password, + const QString &mailbox = QString::null ); + /** + Create a new account object, read data from group app data + */ + ExchangeAccount( const QString &group ); + ~ExchangeAccount(); + + void save( QString const &group ); + void load( QString const &group ); + + QString host() { return mHost; } + QString port() { return mPort; } + QString account() { return mAccount; } + QString mailbox() { return mMailbox; } + QString password() { return mPassword; } + + void setHost( QString host ) { mHost = host; } + void setPort( QString port ) { mPort = port; } + void setAccount( QString account ) { mAccount = account; } + void setMailbox( QString mailbox ) { mMailbox = mailbox; } + void setPassword( QString password ) { mPassword = password; } + + KURL baseURL(); + KURL calendarURL(); + + // Returns the mailbox URL of this user. QString::null if unsuccessful + static QString tryFindMailbox( const QString &host, const QString &port, + const QString &user, + const QString &password ); + + // Put authentication info in KDE password store for auto-authentication + // with later webdav access. Also calculates the calendar URL. + bool authenticate(); + bool authenticate( QWidget *window ); + + private: + bool authenticate( int windowId ); + void calcFolderURLs(); + static QString tryMailbox( const QString &_url, const QString &user, + const QString &password ); + + private slots: + void slotFolderResult( KIO::Job * ); + + private: + QString mHost; + QString mPort; + QString mAccount; + QString mMailbox; + QString mPassword; + + KURL *mCalendarURL; + bool mError; +}; + +} + +#endif + diff --git a/libkpimexchange/core/exchangeclient.cpp b/libkpimexchange/core/exchangeclient.cpp new file mode 100644 index 000000000..337e540fd --- /dev/null +++ b/libkpimexchange/core/exchangeclient.cpp @@ -0,0 +1,292 @@ +/* + 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 <kapplication.h> +#include <kurl.h> +#include <kdebug.h> +#include <kcursor.h> +#include <klocale.h> + +// These for test() method +#include <kio/http.h> +#include <kio/davjob.h> +// #include "libkdepim/resources/resourcemanager.h" +// #include "libkdepim/resources/calendar/resourcecalendar.h" + + +#include "exchangeclient.h" +#include "exchangeaccount.h" +#include "exchangeprogress.h" +#include "exchangeupload.h" +#include "exchangedownload.h" +#include "exchangedelete.h" +//#include "exchangemonitor.h" +#include "utils.h" + +using namespace KPIM; + +ExchangeClient::ExchangeClient( ExchangeAccount *account, + const QString &timeZoneId ) + : mWindow( 0 ), mTimeZoneId( timeZoneId ) +{ + kdDebug() << "Creating ExchangeClient...\n"; + mAccount = account; + if ( timeZoneId.isNull() ) { + setTimeZoneId( "UTC" ); + } +} + +ExchangeClient::~ExchangeClient() +{ + kdDebug() << "ExchangeClient destructor" << endl; +} + +void ExchangeClient::setWindow(QWidget *window) +{ + mWindow = window; +} + +QWidget *ExchangeClient::window() const +{ + return mWindow; +} + +void ExchangeClient::setTimeZoneId( const QString& timeZoneId ) +{ + mTimeZoneId = timeZoneId; +} + +QString ExchangeClient::timeZoneId() +{ + return mTimeZoneId; +} + +void ExchangeClient::test() +{ +// if ( !mAccount->authenticate( mWindow ) ) return; + kdDebug() << "Entering test()" << endl; + KURL baseURL = KURL( "http://mail.tbm.tudelft.nl/janb/Calendar" ); + KURL url( "webdav://mail.tbm.tudelft.nl/exchange/" ); + +/* + KRES::Manager<KCal::ResourceCalendar>* manager = new KRES::Manager<KCal::ResourceCalendar>( "calendar" ); + KCal::ResourceCalendar* resource = manager->standardResource(); + + kdDebug(5800) << "Opening resource " + resource->resourceName() << endl; + bool result = resource->open(); + kdDebug() << "Result: " << result << endl; + + resource->subscribeEvents( QDate( 2002, 12, 18 ), QDate( 2002, 12, 19 ) ); +*/ +// mAccount->tryFindMailbox(); +/* + QString query = + "<propfind xmlns=\"DAV:\" xmlns:h=\"urn:schemas:httpmail:\">\r\n" + " <allprop/>\r\n" + "</propfind>\r\n"; + + KIO::DavJob* job = new KIO::DavJob( url, (int) KIO::DAV_PROPFIND, query, false ); + job->addMetaData( "davDepth", "0" ); +*/ +// ExchangeMonitor* monitor = new ExchangeMonitor( mAccount ); +} + +void ExchangeClient::test2() +{ + kdDebug() << "Entering test2()" << endl; +} +/* +ExchangeMonitor* ExchangeClient::monitor( int pollMode, const QHostAddress& ownInterface ) +{ + return new ExchangeMonitor( mAccount, pollMode, ownInterface ); +} +*/ +void ExchangeClient::download( KCal::Calendar *calendar, const QDate &start, + const QDate &end, bool showProgress ) +{ + kdDebug() << "ExchangeClient::download1()" << endl; + + if ( !mAccount->authenticate( mWindow ) ) { + emit downloadFinished( 0, i18n("Authentication error") ); + return; + } + + ExchangeDownload *worker = new ExchangeDownload( mAccount, mWindow ); + worker->download( calendar, start, end, showProgress ); + connect( worker, + SIGNAL( finished( ExchangeDownload *, int, const QString & ) ), + SLOT( slotDownloadFinished( ExchangeDownload *, int, + const QString & ) ) ); +} + +void ExchangeClient::download( const QDate &start, const QDate &end, + bool showProgress ) +{ + kdDebug() << "ExchangeClient::download2()" << endl; + + if ( !mAccount->authenticate( mWindow ) ) { + emit downloadFinished( 0, i18n("Authentication error") ); + return; + } + + ExchangeDownload *worker = new ExchangeDownload( mAccount, mWindow ); + worker->download( start, end, showProgress ); + connect( worker, + SIGNAL( finished( ExchangeDownload *, int, const QString & ) ), + SLOT( slotDownloadFinished( ExchangeDownload *, int, + const QString & ) ) ); + connect( worker, SIGNAL( gotEvent( KCal::Event *, const KURL & ) ), + SIGNAL( event( KCal::Event *, const KURL & ) ) ); +} + +void ExchangeClient::upload( KCal::Event *event ) +{ + kdDebug() << "ExchangeClient::upload()" << endl; + + if ( !mAccount->authenticate( mWindow ) ) { + emit uploadFinished( 0, i18n("Authentication error") ); + return; + } + + ExchangeUpload *worker = new ExchangeUpload( event, mAccount, mTimeZoneId, + mWindow ); + connect( worker, SIGNAL( finished( ExchangeUpload *, int, const QString & ) ), + SLOT( slotUploadFinished( ExchangeUpload *, int, const QString & ) ) ); +} + +void ExchangeClient::remove( KCal::Event *event ) +{ + if ( !mAccount->authenticate( mWindow ) ) { + emit removeFinished( 0, i18n("Authentication error") ); + return; + } + + ExchangeDelete *worker = new ExchangeDelete( event, mAccount, mWindow ); + connect( worker, SIGNAL( finished( ExchangeDelete *, int, const QString & ) ), + SLOT( slotRemoveFinished( ExchangeDelete *, int, const QString & ) ) ); +} + +void ExchangeClient::slotDownloadFinished( ExchangeDownload *worker, + int result, const QString &moreInfo ) +{ + emit downloadFinished( result, moreInfo ); + worker->deleteLater(); +} + +void ExchangeClient::slotDownloadFinished( ExchangeDownload* worker, int result, const QString& moreInfo, QPtrList<KCal::Event>& events ) +{ + emit downloadFinished( result, moreInfo, events ); + worker->deleteLater(); +} + +void ExchangeClient::slotUploadFinished( ExchangeUpload* worker, int result, const QString& moreInfo ) +{ + kdDebug() << "ExchangeClient::slotUploadFinished()" << endl; + emit uploadFinished( result, moreInfo ); + worker->deleteLater(); +} + +void ExchangeClient::slotRemoveFinished( ExchangeDelete* worker, int result, const QString& moreInfo ) +{ + kdDebug() << "ExchangeClient::slotRemoveFinished()" << endl; + emit removeFinished( result, moreInfo ); + worker->deleteLater(); +} + +int ExchangeClient::downloadSynchronous( KCal::Calendar *calendar, + const QDate &start, const QDate &end, + bool showProgress ) +{ + kdDebug() << "ExchangeClient::downloadSynchronous()" << endl; + + mClientState = WaitingForResult; + connect( this, SIGNAL( downloadFinished( int, const QString & ) ), + SLOT( slotSyncFinished( int, const QString & ) ) ); + + download( calendar, start, end, showProgress ); + + // TODO: Remove this busy loop + QApplication::setOverrideCursor + ( KCursor::waitCursor() ); + do { + qApp->processEvents(); + } while ( mClientState == WaitingForResult ); + QApplication::restoreOverrideCursor(); + + disconnect( this, SIGNAL( downloadFinished( int, const QString & ) ), + this, SLOT( slotSyncFinished( int, const QString & ) ) ); + + return mSyncResult; +} + +int ExchangeClient::uploadSynchronous( KCal::Event* event ) +{ + mClientState = WaitingForResult; + connect( this, SIGNAL( uploadFinished( int, const QString & ) ), + SLOT( slotSyncFinished( int, const QString & ) ) ); + + upload( event ); + + // TODO: Remove this busy loop + QApplication::setOverrideCursor( KCursor::waitCursor() ); + do { + qApp->processEvents(); + } while ( mClientState == WaitingForResult ); + QApplication::restoreOverrideCursor(); + disconnect( this, SIGNAL( uploadFinished( int, const QString & ) ), + this, SLOT( slotSyncFinished( int, const QString & ) ) ); + return mSyncResult; +} + +int ExchangeClient::removeSynchronous( KCal::Event* event ) +{ + mClientState = WaitingForResult; + connect( this, SIGNAL( removeFinished( int, const QString & ) ), + SLOT( slotSyncFinished( int, const QString & ) ) ); + + remove( event ); + + // TODO: Remove this busy loop + QApplication::setOverrideCursor( KCursor::waitCursor() ); + do { + qApp->processEvents(); + } while ( mClientState == WaitingForResult ); + QApplication::restoreOverrideCursor(); + disconnect( this, SIGNAL( removeFinished( int, const QString & ) ), + this, SLOT( slotSyncFinished( int, const QString & ) ) ); + return mSyncResult; +} + +void ExchangeClient::slotSyncFinished( int result, const QString &moreInfo ) +{ + kdDebug() << "Exchangeclient::slotSyncFinished("<<result<<","<<moreInfo<<")" << endl; + if ( mClientState == WaitingForResult ) { + mClientState = HaveResult; + mSyncResult = result; + mDetailedErrorString = moreInfo; + } +} + +QString ExchangeClient::detailedErrorString() +{ + return mDetailedErrorString; +} + +#include "exchangeclient.moc" diff --git a/libkpimexchange/core/exchangeclient.h b/libkpimexchange/core/exchangeclient.h new file mode 100644 index 000000000..b887dac50 --- /dev/null +++ b/libkpimexchange/core/exchangeclient.h @@ -0,0 +1,134 @@ +/* + 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. +*/ +#ifndef KDEPIM_EXCHANGE_CLIENT_H +#define KDEPIM_EXCHANGE_CLIENT_H + +#include <qstring.h> +#include <qdatetime.h> +#include <qobject.h> +#include <qhostaddress.h> +#include <qptrlist.h> + +#include <kdepimmacros.h> + +namespace KCal { + class Event; + class Calendar; +} + +namespace KIO { + class Job; +} + +namespace KPIM { + +class ExchangeAccount; +class ExchangeDownload; +class ExchangeUpload; +class ExchangeDelete; +//class ExchangeMonitor; + +class KDE_EXPORT ExchangeClient : public QObject { + Q_OBJECT + public: + ExchangeClient( ExchangeAccount* account, const QString& mTimeZoneId=QString::null ); + ~ExchangeClient(); + + /** + * Associate this client with a window given by @p window. + */ + void setWindow(QWidget *window); + + /** + * Returns the window this client is associated with. + */ + QWidget *window() const; + + /** + * Set the time zone to use + */ + void setTimeZoneId( const QString& timeZoneId ); + QString timeZoneId(); + + // synchronous functions + enum { + ResultOK, /** No problem */ + UnknownError, /** Something else happened */ + CommunicationError, /** IO Error, the server could not be reached or returned an HTTP error */ + ServerResponseError, /** Server did not give a useful response. For download, this + means that a SEARCH did not result in anything like an appointment */ + IllegalAppointmentError, /** Reading appointment data from server response failed */ + NonEventError, /** The Incidence that is to be uplaoded to the server is not an Event */ + EventWriteError, /** When writing an event to the server, an error occurred */ + DeleteUnknownEventError /** The event to be deleted does not exist on the server */ + }; + + int downloadSynchronous( KCal::Calendar* calendar, const QDate& start, const QDate& end, bool showProgress=false); + int uploadSynchronous( KCal::Event* event ); + int removeSynchronous( KCal::Event* event ); + + // ExchangeMonitor* monitor( int pollMode, const QHostAddress& ownInterface ); + + QString detailedErrorString(); + + public slots: + // Asynchronous functions, wait for "finished" signals for result + // Deprecated: use download() without the Calendar* argument instead + void download( KCal::Calendar* calendar, const QDate& start, const QDate& end, bool showProgress=false); + void download( const QDate& start, const QDate& end, bool showProgress=false); + void upload( KCal::Event* event ); + void remove( KCal::Event* event ); + void test(); + + private slots: + void slotDownloadFinished( ExchangeDownload* worker, int result, const QString& moreInfo ); + void slotDownloadFinished( ExchangeDownload* worker, int result, const QString& moreInfo, QPtrList<KCal::Event>& ); + void slotUploadFinished( ExchangeUpload* worker, int result, const QString& moreInfo ); + void slotRemoveFinished( ExchangeDelete* worker, int result, const QString& moreInfo ); + void slotSyncFinished( int result, const QString& moreInfo ); + + signals: + // Useful for progress dialogs, shows how much still needs to be done. + // Not used right now, since ExchangeDownload provides its own progress dialog + void startDownload(); + void finishDownload(); + + void downloadFinished( int result, const QString& moreInfo ); + void event( KCal::Event* event, const KURL& url); + void downloadFinished( int result, const QString& moreInfo, QPtrList<KCal::Event>& events ); + void uploadFinished( int result, const QString& moreInfo ); + void removeFinished( int result, const QString& moreInfo ); + + private: + void test2(); + + enum { WaitingForResult, HaveResult, Error }; + + int mClientState; + int mSyncResult; + QString mDetailedErrorString; + QWidget* mWindow; + ExchangeAccount* mAccount; + QString mTimeZoneId; +}; + +} + +#endif diff --git a/libkpimexchange/core/exchangedelete.cpp b/libkpimexchange/core/exchangedelete.cpp new file mode 100644 index 000000000..8495afa36 --- /dev/null +++ b/libkpimexchange/core/exchangedelete.cpp @@ -0,0 +1,121 @@ +/* + 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 <qstring.h> +#include <qregexp.h> + +#include <kurl.h> +#include <kdebug.h> +#include <krfcdate.h> +#include <kio/job.h> +#include <kio/jobclasses.h> + +#include <kio/slave.h> +#include <kio/scheduler.h> +#include <kio/slavebase.h> +#include <kio/davjob.h> +#include <kio/http.h> + +#include "exchangeclient.h" +#include "exchangeprogress.h" +#include "exchangedelete.h" +#include "exchangeaccount.h" +#include "utils.h" + +using namespace KPIM; + +// Delete: +// - Find URL for uid +// - Delete URL +// - Can there be multipe URLs, for instance when dealing with +// recurrent appointments? Maybe, so we just look for Master or Single +// instancetypes + +ExchangeDelete::ExchangeDelete( KCal::Event* event, ExchangeAccount* account, QWidget* window ) : + mWindow( window ) +{ + kdDebug() << "Created ExchangeDelete" << endl; + + mAccount = account; + + findUidSingleMaster( event->uid() ); +} + +ExchangeDelete::~ExchangeDelete() +{ + kdDebug() << "ExchangeDelete destructor" << endl; +} + +void ExchangeDelete::findUidSingleMaster( QString const& uid ) +{ + QString query = + "SELECT \"DAV:href\", \"urn:schemas:calendar:uid\"\r\n" + "FROM Scope('shallow traversal of \"\"')\r\n" + "WHERE \"urn:schemas:calendar:uid\" = '" + uid + "'\r\n" + " AND (\"urn:schemas:calendar:instancetype\" = 0\r\n" + " OR \"urn:schemas:calendar:instancetype\" = 1)\r\n"; + + KIO::DavJob* job = KIO::davSearch( mAccount->calendarURL(), "DAV:", "sql", query, false ); + job->setWindow( mWindow ); + connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotFindUidResult(KIO::Job *))); +} + +void ExchangeDelete::slotFindUidResult( KIO::Job * job ) +{ + if ( job->error() ) { + job->showErrorDialog( 0L ); + emit finished( this, ExchangeClient::CommunicationError, "IO Error: " + QString::number(job->error()) + ":" + job->errorString() ); + return; + } + QDomDocument& response = static_cast<KIO::DavJob *>( job )->response(); + + QDomElement item = response.documentElement().firstChild().toElement(); + QDomElement hrefElement = item.namedItem( "href" ).toElement(); + if ( item.isNull() || hrefElement.isNull() ) { + // Not found + emit finished( this, ExchangeClient::DeleteUnknownEventError, "UID of event to be deleted not found on server\n"+response.toString() ); + return; + } + // Found the appointment's URL + QString href = hrefElement.text(); + KURL url(href); + + startDelete( toDAV( url ) ); +} + +void ExchangeDelete::startDelete( const KURL& url ) +{ + KIO::SimpleJob* job = KIO::file_delete( url, false ); // no GUI + job->setWindow( mWindow ); + connect( job, SIGNAL( result( KIO::Job * ) ), this, SLOT( slotDeleteResult( KIO::Job * ) ) ); +} + +void ExchangeDelete::slotDeleteResult( KIO::Job* job ) +{ + kdDebug() << "Finished Delete" << endl; + if ( job->error() ) { + job->showErrorDialog( 0L ); + emit finished( this, ExchangeClient::CommunicationError, "IO Error: " + QString::number(job->error()) + ":" + job->errorString() ); + return; + } + emit finished( this, ExchangeClient::ResultOK, QString::null ); +} + +#include "exchangedelete.moc" diff --git a/libkpimexchange/core/exchangedelete.h b/libkpimexchange/core/exchangedelete.h new file mode 100644 index 000000000..a9d933254 --- /dev/null +++ b/libkpimexchange/core/exchangedelete.h @@ -0,0 +1,59 @@ +/* + 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. +*/ +#ifndef KDEPIM_EXCHANGE_DELETE_H +#define KDEPIM_EXCHANGE_DELETE_H + +#include <qstring.h> +#include <qwidget.h> + +#include <kio/job.h> +#include <kdepimmacros.h> + +#include <libkcal/calendar.h> +#include <libkcal/event.h> + +namespace KPIM { + +class ExchangeAccount; + +class KDE_EXPORT ExchangeDelete : public QObject { + Q_OBJECT + public: + ExchangeDelete( KCal::Event* event, ExchangeAccount* account, QWidget* window=0 ); + ~ExchangeDelete(); + + private slots: + void slotDeleteResult( KIO::Job * ); + void slotFindUidResult( KIO::Job * ); + + signals: + void finished( ExchangeDelete* worker, int result, const QString& moreInfo ); + + private: + void findUidSingleMaster( QString const& uid ); + void startDelete( const KURL& url ); + + ExchangeAccount* mAccount; + QWidget* mWindow; +}; + +} + +#endif diff --git a/libkpimexchange/core/exchangedownload.cpp b/libkpimexchange/core/exchangedownload.cpp new file mode 100644 index 000000000..dcf469549 --- /dev/null +++ b/libkpimexchange/core/exchangedownload.cpp @@ -0,0 +1,643 @@ +/* + 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 <qfile.h> +#include <qtextstream.h> +#include <qdatastream.h> +#include <qcstring.h> +#include <qregexp.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kstandarddirs.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <kaction.h> +#include <kurl.h> +#include <kdebug.h> +#include <krfcdate.h> + +#include <kio/slave.h> +#include <kio/scheduler.h> +#include <kio/slavebase.h> +#include <kio/davjob.h> +#include <kio/http.h> +#include <kio/job.h> + +#include <libkcal/incidence.h> +#include <libkcal/event.h> +#include <libkcal/recurrence.h> +#include <libkcal/icalformat.h> +#include <libkcal/icalformatimpl.h> +#include <libkcal/calendarlocal.h> + +extern "C" { + #include <ical.h> +} + +#include "exchangeclient.h" +#include "exchangeaccount.h" +#include "exchangeprogress.h" +#include "utils.h" + +#include "exchangedownload.h" + +using namespace KPIM; + +ExchangeDownload::ExchangeDownload( ExchangeAccount *account, QWidget *window ) + : mWindow( window ) +{ + kdDebug() << "ExchangeDownload()" << endl; + + mAccount = account; + mDownloadsBusy = 0; + mProgress = 0; + mCalendar = 0; + mFormat = new KCal::ICalFormat(); +} + +ExchangeDownload::~ExchangeDownload() +{ + kdDebug() << "ExchangeDownload destructor" << endl; + delete mFormat; + if ( mEvents ) delete mEvents; +} + +void ExchangeDownload::download( KCal::Calendar *calendar, const QDate &start, + const QDate &end, bool showProgress ) +{ + mCalendar = calendar; + mEvents = 0; + + if( showProgress ) { +#if 0 + //kdDebug() << "Creating progress dialog" << endl; + mProgress = new ExchangeProgress(); + mProgress->show(); + + connect( this, SIGNAL( startDownload() ), mProgress, + SLOT( slotTransferStarted() ) ); + connect( this, SIGNAL(finishDownload() ), mProgress, + SLOT( slotTransferFinished() ) ); +#endif + } + + QString sql = dateSelectQuery( start, end.addDays( 1 ) ); + + kdDebug() << "Exchange download query: " << endl << sql << endl; + + increaseDownloads(); + + kdDebug() << "ExchangeDownload::download() davSearch URL: " + << mAccount->calendarURL() << endl; + + KIO::DavJob *job = KIO::davSearch( mAccount->calendarURL(), "DAV:", "sql", + sql, false ); + KIO::Scheduler::scheduleJob( job ); + job->setWindow( mWindow ); + connect( job, SIGNAL( result( KIO::Job * ) ), + SLOT( slotSearchResult( KIO::Job *) ) ); +} + +void ExchangeDownload::download( const QDate& start, const QDate& end, bool showProgress ) +{ + mCalendar = 0; + mEvents = new QPtrList<KCal::Event>; + + if( showProgress ) { + //kdDebug() << "Creating progress dialog" << endl; + mProgress = new ExchangeProgress(); + mProgress->show(); + + connect( this, SIGNAL(startDownload()), mProgress, SLOT(slotTransferStarted()) ); + connect( this, SIGNAL(finishDownload()), mProgress, SLOT(slotTransferFinished()) ); + } + + QString sql = dateSelectQuery( start, end.addDays( 1 ) ); + + increaseDownloads(); + + KIO::DavJob *job = KIO::davSearch( mAccount->calendarURL(), "DAV:", "sql", sql, false ); + KIO::Scheduler::scheduleJob(job); + job->setWindow( mWindow ); + connect( job, SIGNAL( result( KIO::Job * ) ), + SLOT( slotSearchResult( KIO::Job * ) ) ); +} + +// Original query TODO: make query configurable +QString ExchangeDownload::dateSelectQuery( const QDate& start, const QDate& end ) +{ + QString startString; + startString.sprintf("%04i/%02i/%02i",start.year(),start.month(),start.day()); + QString endString; + endString.sprintf("%04i/%02i/%02i",end.year(),end.month(),end.day()); + QString sql = + "SELECT \"DAV:href\", \"urn:schemas:calendar:instancetype\", \"urn:schemas:calendar:uid\"\r\n" + "FROM Scope('shallow traversal of \"\"')\r\n" + "WHERE \"urn:schemas:calendar:dtend\" > '" + startString + "'\r\n" + "AND \"urn:schemas:calendar:dtstart\" < '" + endString + "'"; + return sql; +} + +#if 0 +// That's the "new" code that breaks with Exchange. It was meant for Opengroupware, but that got its own resource anyway +QString ExchangeDownload::dateSelectQuery( const QDate& start, const QDate& end ) +{ + QString startString; + startString.sprintf( "%04i-%02i-%02iT00:00:00Z", start.year(), + start.month(), start.day() ); + QString endString; + endString.sprintf( "%04i-%02i-%02iT23:59:59Z", end.year(), end.month(), + end.day() ); + QString sql = + "SELECT \"DAV:href\", \"urn:schemas:calendar:instancetype\", " + "\"urn:schemas:calendar:uid\"\r\n" + "FROM Scope('shallow traversal of \"\"')\r\n" + "WHERE \"urn:schemas:calendar:dtend\" > '" + startString + "'\r\n" + "AND \"urn:schemas:calendar:dtstart\" < '" + endString + "'"; + return sql; +} +#endif + +void ExchangeDownload::slotSearchResult( KIO::Job *job ) +{ + if ( job->error() ) { + kdError() << "ExchangeDownload::slotSearchResult() error: " + << job->error() << endl; + QString text = i18n("ExchangeDownload\nError accessing '%1': %2") + .arg( mAccount->calendarURL().prettyURL() ) + .arg( job->errorString() ); + KMessageBox::error( 0, text ); + finishUp( ExchangeClient::CommunicationError, job ); + return; + } + QDomDocument &response = static_cast<KIO::DavJob *>( job )->response(); + + kdDebug() << "Search result: " << endl << response.toString() << endl; + + handleAppointments( response, true ); + + decreaseDownloads(); +} + +void ExchangeDownload::slotMasterResult( KIO::Job *job ) +{ + if ( job->error() ) { + kdError() << "Error result for Master search: " << job->error() << endl; + job->showErrorDialog( 0 ); + finishUp( ExchangeClient::CommunicationError, job ); + return; + } + QDomDocument &response = static_cast<KIO::DavJob *>( job )->response(); + + kdDebug() << "Search (master) result: " << endl << response.toString() << endl; + + handleAppointments( response, false ); + + decreaseDownloads(); +} + +void ExchangeDownload::handleAppointments( const QDomDocument &response, + bool recurrence ) +{ + kdDebug() << "Entering handleAppointments" << endl; + int successCount = 0; + + if ( response.documentElement().firstChild().toElement().isNull() ) { + // Got an empty response, but no error. This would mean there are + // no appointments in this time period. + return; + } + + for( QDomElement item = response.documentElement().firstChild().toElement(); + !item.isNull(); + item = item.nextSibling().toElement() ) { + //kdDebug() << "Current item:" << item.tagName() << endl; + QDomNodeList propstats = item.elementsByTagNameNS( "DAV:", "propstat" ); + // kdDebug() << "Item has " << propstats.count() << " propstat children" << endl; + for( uint i=0; i < propstats.count(); i++ ) { + QDomElement propstat = propstats.item(i).toElement(); + QDomElement prop = propstat.namedItem( "prop" ).toElement(); + if ( prop.isNull() ) { + kdError() << "Error: no <prop> in response" << endl; + continue; + } + + QDomElement instancetypeElement = prop.namedItem( "instancetype" ).toElement(); + if ( instancetypeElement.isNull() ) { + kdError() << "Error: no instance type in Exchange server reply" << endl; + continue; + } + int instanceType = instancetypeElement.text().toInt(); + //kdDebug() << "Instance type: " << instanceType << endl; + + if ( recurrence && instanceType > 0 ) { + QDomElement uidElement = prop.namedItem( "uid" ).toElement(); + if ( uidElement.isNull() ) { + kdError() << "Error: no uid in Exchange server reply" << endl; + continue; + } + QString uid = uidElement.text(); + if ( ! m_uids.contains( uid ) ) { + m_uids[uid] = 1; + handleRecurrence(uid); + successCount++; + } + continue; + } + + QDomElement hrefElement = prop.namedItem( "href" ).toElement(); + if ( hrefElement.isNull() ) { + kdError() << "Error: no href in Exchange server reply" << endl; + continue; + } + QString href = hrefElement.text(); + KURL url(href); + + kdDebug() << "Getting appointment from url: " << url.prettyURL() << endl; + + readAppointment( toDAV( url ) ); + successCount++; + } + } + if ( !successCount ) { + finishUp( ExchangeClient::ServerResponseError, + "WebDAV SEARCH response:\n" + response.toString() ); + } +} + +void ExchangeDownload::handleRecurrence( QString uid ) +{ + // kdDebug() << "Handling recurrence info for uid=" << uid << endl; + QString query = + "SELECT \"DAV:href\", \"urn:schemas:calendar:instancetype\"\r\n" + "FROM Scope('shallow traversal of \"\"')\r\n" + "WHERE \"urn:schemas:calendar:uid\" = '" + uid + "'\r\n" + " AND (\"urn:schemas:calendar:instancetype\" = 1)\r\n"; +// " OR \"urn:schemas:calendar:instancetype\" = 3)\r\n" // FIXME: exception are not handled + + // kdDebug() << "Exchange master query: " << endl << query << endl; + + increaseDownloads(); + + KIO::DavJob* job = KIO::davSearch( mAccount->calendarURL(), "DAV:", "sql", + query, false ); + KIO::Scheduler::scheduleJob( job ); + job->setWindow( mWindow ); + connect( job, SIGNAL( result( KIO::Job * ) ), + SLOT( slotMasterResult( KIO::Job * ) ) ); +} + +void ExchangeDownload::readAppointment( const KURL& url ) +{ + QDomDocument doc; + QDomElement root = addElement( doc, doc, "DAV:", "propfind" ); + QDomElement prop = addElement( doc, root, "DAV:", "prop" ); + addElement( doc, prop, "urn:schemas:calendar:", "uid" ); + addElement( doc, prop, "urn:schemas:calendar:", "timezoneid" ); + addElement( doc, prop, "urn:schemas:calendar:", "timezone" ); + addElement( doc, prop, "urn:schemas:calendar:", "lastmodified" ); + addElement( doc, prop, "urn:schemas:calendar:", "organizer" ); + addElement( doc, prop, "urn:schemas:calendar:", "contact" ); + addElement( doc, prop, "urn:schemas:httpmail:", "to" ); + addElement( doc, prop, "urn:schemas:calendar:", "attendeestatus" ); + addElement( doc, prop, "urn:schemas:calendar:", "attendeerole" ); + addElement( doc, prop, "DAV:", "isreadonly" ); + addElement( doc, prop, "urn:schemas:calendar:", "instancetype" ); + addElement( doc, prop, "urn:schemas:calendar:", "created" ); + addElement( doc, prop, "urn:schemas:calendar:", "dtstart" ); + addElement( doc, prop, "urn:schemas:calendar:", "dtend" ); + addElement( doc, prop, "urn:schemas:calendar:", "alldayevent" ); + addElement( doc, prop, "urn:schemas:calendar:", "transparent" ); + addElement( doc, prop, "urn:schemas:httpmail:", "textdescription" ); + addElement( doc, prop, "urn:schemas:httpmail:", "subject" ); + addElement( doc, prop, "urn:schemas:calendar:", "location" ); + addElement( doc, prop, "urn:schemas:calendar:", "rrule" ); + addElement( doc, prop, "urn:schemas:calendar:", "exdate" ); + addElement( doc, prop, "urn:schemas:mailheader:", "sensitivity" ); + addElement( doc, prop, "urn:schemas:calendar:", "reminderoffset" ); + + addElement( doc, prop, "urn:schemas-microsoft-com:office:office", + "Keywords" ); + +// addElement( doc, prop, "", "" ); +// addElement( doc, prop, "DAV:", "" ); +// addElement( doc, prop, "urn:schemas:calendar:", "" ); +// addElement( doc, prop, "urn:content-classes:appointment", "" ); +// addElement( doc, prop, "urn:schemas:httpmail:", "" ); + + increaseDownloads(); + + KIO::DavJob* job = KIO::davPropFind( url, doc, "0", false ); + KIO::Scheduler::scheduleJob( job ); + job->setWindow( mWindow ); + job->addMetaData( "errorPage", "false" ); + connect( job, SIGNAL( result( KIO::Job * ) ), + SLOT( slotPropFindResult( KIO::Job * ) ) ); +} + +void ExchangeDownload::slotPropFindResult( KIO::Job *job ) +{ + kdDebug() << "slotPropFindResult" << endl; + + int error = job->error(); + if ( error ) { + job->showErrorDialog( 0 ); + finishUp( ExchangeClient::CommunicationError, job ); + return; + } + + QDomDocument response = static_cast<KIO::DavJob *>( job )->response(); + kdDebug() << "Response: " << endl; + kdDebug() << response.toString() << endl; + + QDomElement prop = response.documentElement().namedItem( "response" ) + .namedItem( "propstat" ).namedItem( "prop" ).toElement(); + + KCal::Event* event = new KCal::Event(); + + QDomElement uidElement = prop.namedItem( "uid" ).toElement(); + if ( uidElement.isNull() ) { + kdError() << "Error: no uid in Exchange server reply" << endl; + finishUp( ExchangeClient::IllegalAppointmentError, + "WebDAV server response:\n" + response.toString() ); + return; + } + event->setUid( uidElement.text() ); + // kdDebug() << "Got UID: " << uidElement.text() << endl; + + QString timezoneid = prop.namedItem( "timezoneid" ).toElement().text(); + // kdDebug() << "DEBUG: timezoneid = " << timezoneid << endl; + + QString timezone = prop.namedItem( "timezone" ).toElement().text(); + // kdDebug() << "DEBUG: timezone = " << timezone << endl; + + // mFormat is used for parsing recurrence rules. + QString localTimeZoneId; + if ( mCalendar ) { + mFormat->setTimeZone( mCalendar->timeZoneId(), !mCalendar->isLocalTime() ); + localTimeZoneId = mCalendar->timeZoneId(); + } else { + localTimeZoneId = "UTC"; + // If no mCalendar, stay in UTC + } + + QString lastModified = prop.namedItem( "lastmodified" ).toElement().text(); + if ( !lastModified.isEmpty() ) { + QDateTime dt = utcAsZone( QDateTime::fromString( lastModified, Qt::ISODate ), localTimeZoneId ); + event->setLastModified( dt ); + kdDebug() << "Got lastModified:" << lastModified << ", " << dt.toString() << endl; + } + + QString organizer = prop.namedItem( "organizer" ).toElement().text(); + // TODO: Does outlook have a common name? Or does the organizer already contain both? + event->setOrganizer( organizer ); + // kdDebug() << "Got organizer: " << organizer << endl; + + // Trying to find attendees, not working yet + QString contact = prop.namedItem( "contact" ).toElement().text(); +// event->setOrganizer( organizer ); + // kdDebug() << "DEBUG: Got contact: " << contact << endl; + + // This looks promising for finding attendees + // FIXME: get this to work + QString to = prop.namedItem( "to" ).toElement().text(); + // kdDebug() << "DEBUG: Got to: " << to << endl; + QStringList attn = QStringList::split( ",", to ); // This doesn't work: there can be commas between "" + QStringList::iterator it; + for ( it = attn.begin(); it != attn.end(); ++it ) { + // kdDebug() << " attendee: " << (*it) << endl; + QString name = ""; + // KCal::Attendee* a = new KCal::Attendee( name, email ); + + // event->addAttendee( a ); + } + + QString readonly = prop.namedItem( "isreadonly" ).toElement().text(); + event->setReadOnly( readonly == "1" ); + kdDebug() << "Got readonly: " << readonly << ":" << (readonly != "0") << endl; + + QString created = prop.namedItem( "created" ).toElement().text(); + if ( !created.isEmpty() ) { + QDateTime dt = utcAsZone( QDateTime::fromString( created, Qt::ISODate ), + localTimeZoneId ); + event->setCreated( dt ); + kdDebug() << "got created: " << dt.toString() << endl; + } + + QString dtstart = prop.namedItem( "dtstart" ).toElement().text(); + if ( !dtstart.isEmpty() ) { + QDateTime dt = utcAsZone( QDateTime::fromString( dtstart, Qt::ISODate ), + localTimeZoneId ); + event->setDtStart( dt ); + kdDebug() << "got dtstart: " << dtstart << " becomes in timezone " << dt.toString() << endl; + } + + QString alldayevent = prop.namedItem( "alldayevent" ).toElement().text(); + bool floats = alldayevent.toInt() != 0; + event->setFloats( floats ); + kdDebug() << "Got alldayevent: \"" << alldayevent << "\":" << floats << endl; + + QString dtend = prop.namedItem( "dtend" ).toElement().text(); + if ( !dtend.isEmpty() ) { + QDateTime dt = utcAsZone( QDateTime::fromString( dtend, Qt::ISODate ), + localTimeZoneId ); + // Outlook thinks differently about floating event timing than libkcal + if ( floats ) dt = dt.addDays( -1 ); + event->setDtEnd( dt ); + kdDebug() << "got dtend: " << dtend << " becomes in timezone " << dt.toString() << endl; + } + + QString transparent = prop.namedItem( "transparent" ).toElement().text(); + event->setTransparency( transparent.toInt() > 0 ? KCal::Event::Transparent + : KCal::Event::Opaque ); + // kdDebug() << "Got transparent: " << transparent << endl; + + QString description = prop.namedItem( "textdescription" ).toElement().text(); + event->setDescription( description ); + kdDebug() << "Got description: " << description << endl; + + QString subject = prop.namedItem( "subject" ).toElement().text(); + event->setSummary( subject ); + kdDebug() << "Got summary: " << subject << endl; + + QString location = prop.namedItem( "location" ).toElement().text(); + event->setLocation( location ); + // kdDebug() << "Got location: " << location << endl; + + QString rrule = prop.namedItem( "rrule" ).toElement().text(); + kdDebug() << "Got rrule: " << rrule << endl; + if ( !rrule.isEmpty() ) { + // Timezone should be handled automatically + // because we used mFormat->setTimeZone() earlier + KCal::RecurrenceRule *rr = event->recurrence()->defaultRRule( true ); + + if ( !rr || !mFormat->fromString( rr, rrule ) ) { + kdError() << "ERROR parsing rrule " << rrule << endl; + } + } + + QDomElement keywords = prop.namedItem( "Keywords" ).toElement(); + QStringList categories; + QDomNodeList list = keywords.elementsByTagNameNS( "xml:", "v" ); + for( uint i=0; i < list.count(); i++ ) { + QDomElement item = list.item(i).toElement(); + categories.append( item.text() ); + } + event->setCategories( categories ); + // kdDebug() << "Got categories: " << categories.join( ", " ) << endl; + + + QDomElement exdate = prop.namedItem( "exdate" ).toElement(); + KCal::DateList exdates; + list = exdate.elementsByTagNameNS( "xml:", "v" ); + for( uint i=0; i < list.count(); i++ ) { + QDomElement item = list.item(i).toElement(); + QDate date = utcAsZone( QDateTime::fromString( item.text(), Qt::ISODate ), localTimeZoneId ).date(); + exdates.append( date ); + // kdDebug() << "Got exdate: " << date.toString() << endl; + } + event->recurrence()->setExDates( exdates ); + + // Exchange sentitivity values: + // 0 None + // 1 Personal + // 2 Private + // 3 Company Confidential + QString sensitivity = prop.namedItem( "sensitivity" ).toElement().text(); + if ( ! sensitivity.isNull() ) + switch( sensitivity.toInt() ) { + case 0: event->setSecrecy( KCal::Incidence::SecrecyPublic ); break; + case 1: event->setSecrecy( KCal::Incidence::SecrecyPrivate ); break; + case 2: event->setSecrecy( KCal::Incidence::SecrecyPrivate ); break; + case 3: event->setSecrecy( KCal::Incidence::SecrecyConfidential ); break; + default: kdWarning() << "Unknown sensitivity: " << sensitivity << endl; + } + // kdDebug() << "Got sensitivity: " << sensitivity << endl; + + + QString reminder = prop.namedItem( "reminderoffset" ).toElement().text(); + // kdDebug() << "Reminder offset: " << reminder << endl; + if ( !reminder.isEmpty() ) { + // Duration before event in seconds + KCal::Duration offset( - reminder.toInt() ); + KCal::Alarm *alarm = event->newAlarm(); + alarm->setStartOffset( offset ); + alarm->setDisplayAlarm(""); + alarm->setEnabled( true ); + // TODO: multiple alarms; alarm->setType( KCal::Alarm::xxxx ); + } + /** Create a new alarm which is associated with this incidence */ + //Alarm* newAlarm(); + /** Add an alarm which is associated with this incidence */ + //void addAlarm(Alarm*); + + /** point at some other event to which the event relates */ + //void setRelatedTo(Incidence *relatedTo); + /** Add an event which is related to this event */ + //void addRelation(Incidence *); + + /** set the list of attachments/associated files for this event */ + //void setAttachments(const QStringList &attachments); + + /** set resources used, such as Office, Car, etc. */ + //void setResources(const QStringList &resources); + + /** set the event's priority, 0 is undefined, 1 highest (decreasing order) */ + //void setPriority(int priority); + + /** + Add Attendee to this incidence. IncidenceBase takes ownership of the + Attendee object. + */ + + //void addAttendee(Attendee *a, bool doupdate=true ); + + // THE FOLLOWING EVENT PROPERTIES ARE NOT READ + + // Revision ID in webdav is a String, not an int + /** set the number of revisions this event has seen */ + //void setRevision(int rev); + + // Problem: When you sync Outlook to a Palm, the conduit splits up + // multi-day events into single-day events WITH ALL THE SAME UID + // Grrrrrrr. + if ( mCalendar ) { + KCal::Event *oldEvent = mCalendar->event( event->uid() ); + if ( oldEvent ) { + kdWarning() << "Already got his event, replace it..." << endl; + mCalendar->deleteEvent( oldEvent ); + } + kdDebug() << "ADD EVENT" << endl; + mCalendar->addEvent( event ); + } else { + kdDebug() << "EMIT gotEvent" << endl; + emit gotEvent( event, static_cast<KIO::DavJob *>( job )->url() ); +// mEvents->append( event ); + } + + decreaseDownloads(); +} + +void ExchangeDownload::increaseDownloads() +{ + mDownloadsBusy++; + emit startDownload(); +} + +void ExchangeDownload::decreaseDownloads() +{ + mDownloadsBusy--; + // kdDebug() << "Download finished, waiting for " << mDownloadsBusy << " more" << endl; + emit finishDownload(); + if ( mDownloadsBusy == 0 ) { + kdDebug() << "All downloads finished" << endl; + finishUp( ExchangeClient::ResultOK ); + } +} + +void ExchangeDownload::finishUp( int result, const QString &moreInfo ) +{ + kdDebug() << "ExchangeDownload::finishUp() " << result << " " + << moreInfo << endl; + + if ( mCalendar ) mCalendar->setModified( true ); + // Disconnect from progress bar + if ( mProgress ) { + disconnect( this, 0, mProgress, 0 ); + disconnect( mProgress, 0, this, 0 ); + mProgress->delayedDestruct(); + } + +// if ( mEvents ) { +// emit finished( this, result, moreInfo, *mEvents ); +// } else { + emit finished( this, result, moreInfo ); +// } +} + +void ExchangeDownload::finishUp( int result, KIO::Job *job ) +{ + finishUp( result, QString("WebDAV job error code = ") + + QString::number( job->error() ) + ";\n" + "\"" + + job->errorString() + "\"" ); +} + +#include "exchangedownload.moc" diff --git a/libkpimexchange/core/exchangedownload.h b/libkpimexchange/core/exchangedownload.h new file mode 100644 index 000000000..a43d82692 --- /dev/null +++ b/libkpimexchange/core/exchangedownload.h @@ -0,0 +1,89 @@ +/* + 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. +*/ +#ifndef KDEPIM_EXCHANGE_DOWNLOAD_H +#define KDEPIM_EXCHANGE_DOWNLOAD_H + +#include <qstring.h> +#include <qptrlist.h> +#include <qdatetime.h> +#include <qdom.h> +#include <qmap.h> +#include <kio/job.h> + +#include <libkcal/calendar.h> +#include <libkcal/icalformat.h> + +namespace KPIM { + +class ExchangeProgress; +class ExchangeAccount; + +class ExchangeDownload : public QObject { + Q_OBJECT + public: + ExchangeDownload( ExchangeAccount* account, QWidget* window=0 ); + ~ExchangeDownload(); + + void download( KCal::Calendar* calendar, + const QDate& start, const QDate& end, bool showProgress ); + void download( const QDate& start, const QDate& end, bool showProgress ); + + signals: + void startDownload(); + void finishDownload(); + + void gotEvent( KCal::Event* event, const KURL& url ); + void finished( ExchangeDownload*, int result, const QString& moreInfo ); + void finished( ExchangeDownload*, int result, const QString& moreInfo, QPtrList<KCal::Event>& events ); + + private slots: + void slotSearchResult( KIO::Job *job ); + void slotMasterResult( KIO::Job* job ); + void slotPropFindResult( KIO::Job * ); + + private: + void handleAppointments( const QDomDocument &, bool recurrence ); + void readAppointment( const KURL& url ); + void handleRecurrence( QString uid ); + void finishUp( int result, const QString& moreInfo=QString::null ); + void finishUp( int result, KIO::Job* job ); + + void increaseDownloads(); + void decreaseDownloads(); + + QString dateSelectQuery( const QDate& start, const QDate& end ); + + KCal::Calendar *mCalendar; + KCal::ICalFormat *mFormat; + QPtrList<KCal::Event> *mEvents; + ExchangeAccount *mAccount; + ExchangeProgress *mProgress; + int mDownloadsBusy; + QDomDocument mResponse; + + QMap<QString,int> m_uids; // This keeps track of uids we already covered. Especially useful for + // recurring events. + QWidget* mWindow; +}; + +} + +#endif + diff --git a/libkpimexchange/core/exchangemonitor.cpp b/libkpimexchange/core/exchangemonitor.cpp new file mode 100644 index 000000000..c659efb15 --- /dev/null +++ b/libkpimexchange/core/exchangemonitor.cpp @@ -0,0 +1,386 @@ +/* + 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 <qstring.h> +#include <qregexp.h> +#include <qsocketdevice.h> +#include <qsocketnotifier.h> +#include <qtextstream.h> + +#include <kurl.h> +#include <kdebug.h> +#include <krfcdate.h> +#include <kextsock.h> + +#include <kio/job.h> +#include <kio/slave.h> +#include <kio/scheduler.h> +#include <kio/slavebase.h> +#include <kio/davjob.h> +#include <kio/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; + +QString makeIDString( const ExchangeMonitor::IDList& IDs ) +{ + QString result; + ExchangeMonitor::IDList::ConstIterator it; + for ( it = IDs.begin(); it != IDs.end(); ++it ) { + if ( it == IDs.begin() ) + result += QString::number( (*it) ); + else + result += "," + QString::number( (*it) ); + } + return result; +} + +ExchangeMonitor::IDList makeIDList( const QString& input ) +{ + ExchangeMonitor::IDList IDs; + QStringList numbers = QStringList::split( ",", input ); + QStringList::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 QHostAddress& 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 QSocketDevice( QSocketDevice::Datagram ); + if ( ! mSocket->bind( ownInterface, 0 ) ) + kdDebug() << "bind() returned false" << endl; + mSocket->setBlocking( false ); + mNotifier = new QSocketNotifier( mSocket->socket(), QSocketNotifier::Read ); + connect( mNotifier, SIGNAL(activated( int )), this, 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: " << mSocket->address().toString() << endl; + // mStream = new QTextStream( mSocket ); + } + + if ( mPollMode == Poll ) { + mPollTimer = new QTimer( this, "mPollTimer" ); + connect( mPollTimer, SIGNAL(timeout()), this, SLOT(slotPollTimer()) ); + mPollTimer->start( 60000 ); // 1 minute timer + } + + mRenewTimer = new QTimer( this, "mRenewTimer" ); + connect( mRenewTimer, SIGNAL(timeout()), this, 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() ) { + QString 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, QString::null, false ); + job->addMetaData( "customHTTPHeader", headers ); + // Can't do, this is a destructor! + // job->addMetaData( "PropagateHttpHeader", "true" ); + // connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotUnsubscribeResult(KIO::Job *))); + } + kdDebug() << "Finished ExchangeMonitor destructor" << endl; + +} + +void ExchangeMonitor::addWatch( const KURL &url, int mode, int depth ) +{ + QString 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: " + QString::number( depth ); + + if (mPollMode == CallBack ) + headers += "\r\nCall-Back: httpu://" + mSocket->address().toString() + ":" + QString::number(mSocket->port()); + + kdDebug() << "Headers: " << headers << endl; + + KURL myURL = toDAV( url ); + KIO::DavJob *job = new KIO::DavJob( myURL, (int) KIO::DAV_SUBSCRIBE, QString::null, false ); + job->addMetaData( "customHTTPHeader", headers ); + job->addMetaData( "PropagateHttpHeader", "true" ); + connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotSubscribeResult(KIO::Job *))); +} + +void ExchangeMonitor::removeWatch( const KURL &url ) +{ + KURL myURL = toDAV( url ); + QMap<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 ) +{ + KIO::DavJob *job = new KIO::DavJob( mAccount->calendarURL(), (int) KIO::DAV_UNSUBSCRIBE, QString::null, false ); + job->addMetaData( "customHTTPHeader", "Subscription-id: " + QString::number( id )); + job->addMetaData( "PropagateHttpHeader", "true" ); + connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotUnsubscribeResult(KIO::Job *))); +} + +void ExchangeMonitor::slotSubscribeResult( KIO::Job * job ) +{ + if ( job->error() ) { + job->showErrorDialog( 0L ); + emit error( ExchangeClient::CommunicationError, "IO Error: " + QString::number(job->error()) + ":" + job->errorString() ); + return; + } + + ID id; + KURL url; + bool gotID = false; + bool gotURL = false; + + QStringList headers = QStringList::split( "\n", job->queryMetaData( "HTTP-Headers" ) ); + for ( QStringList::Iterator it = headers.begin(); it != headers.end(); ++it ) { + int colon = (*it).find( ": " ); + if ( colon<0 ) continue; + QString tag = (*it).left( colon ).stripWhiteSpace().lower(); + QString 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: " + QString::number(job->error()) + ":" + job->errorString() ); + return; + } + + QDomDocument& response = static_cast<KIO::DavJob *>( job )->response(); + kdDebug() << "UNSUBSCRIBE result: " << endl << response.toString() << endl; + + QDomElement status = response.documentElement().namedItem( "response" ).namedItem( "status" ).toElement(); + QDomElement 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; + + QCString response( maxLen+2 ); + Q_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; + + QString s(response); + IDList IDs; + + QStringList lines = QStringList::split( "\n", s ); + QStringList::iterator it; + for ( it = lines.begin(); it != lines.end(); ++it ) { + QString line = (*it).stripWhiteSpace().lower(); + if ( line.startsWith( "subscription-id: " ) ) + IDs = makeIDList( 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, QString::null, false ); + job->addMetaData( "customHTTPHeader", "Subscription-ID: " + makeIDString( IDs ) ); + connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotPollResult(KIO::Job *))); +} + +void ExchangeMonitor::slotPollResult( KIO::Job * job ) +{ + if ( job->error() ) { + job->showErrorDialog( 0L ); + emit error( ExchangeClient::CommunicationError, "IO Error: " + QString::number(job->error()) + ":" + job->errorString() ); + return; + } + QDomDocument& response = static_cast<KIO::DavJob *>( job )->response(); + kdDebug() << "POLL result: " << endl << response.toString() << endl; + + // Multiple results! + QDomNodeList 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++ ) { + QDomElement item = responses.item( i ).toElement(); + QDomElement status = item.namedItem( "status" ).toElement(); + QDomElement subscriptionID = item.namedItem( "subscriptionID" ).toElement(); + if ( status.text().contains( "200" ) ) { + kdDebug() << "subscriptionID: " << subscriptionID.text() << endl; + IDList IDs = makeIDList( subscriptionID.text() ); + QValueList<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; + + KIO::DavJob *job = new KIO::DavJob( mAccount->calendarURL(), (int) KIO::DAV_SUBSCRIBE, QString::null, false ); + job->addMetaData( "customHTTPHeader", "Subscription-id: " + makeIDString( mSubscriptionMap.keys() ) ); + connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotRenewResult(KIO::Job *))); +} + +void ExchangeMonitor::slotRenewResult( KIO::Job* job ) +{ + if ( job->error() ) { + job->showErrorDialog( 0L ); + emit error( ExchangeClient::CommunicationError, "IO Error: " + QString::number(job->error()) + ":" + job->errorString() ); + return; + } + kdDebug() << "ExchangeMonitor::slotRenewResult()" << endl; + + // FIXME: check for new subscription lifetime +} + +#include "exchangemonitor.moc" diff --git a/libkpimexchange/core/exchangemonitor.h b/libkpimexchange/core/exchangemonitor.h new file mode 100644 index 000000000..f1429ba4e --- /dev/null +++ b/libkpimexchange/core/exchangemonitor.h @@ -0,0 +1,106 @@ +/* + 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. +*/ +#ifndef KDEPIM_EXCHANGE_MONITOR_H +#define KDEPIM_EXCHANGE_MONITOR_H + +#include <qstring.h> +#include <qmap.h> +//#include <qwidget.h> +#include <qhostaddress.h> + +#include <kurl.h> +#include <kio/job.h> + +#include <libkcal/calendar.h> +#include <libkcal/event.h> + +class QSocketDevice; +class QSocketNotifier; +class QTextStream; + +namespace KPIM { + +class ExchangeAccount; + +class ExchangeMonitor : public QObject { + Q_OBJECT + public: + typedef long ID; + typedef QValueList<ID> IDList; + + enum { CallBack, Poll }; + enum { Delete, /** Any: 0 The message or folder subscribed to was deleted. + Folder 1 A message or folder was deleted from the folder. */ + Move, /** Any: 0 The message or folder was moved. + Folder 1 A message or folder was moved from or to the folder. */ + Newmail, /** Mailbox or Folder Any Special new mail update. + Message Any Not valid - return 400 (Bad Request). */ + Update, /** Message 0 The message was modified (either properties or body). + Folder 0 Properties of the folder were modified. + Folder 1 A message or sub-folder was created in the folder, copied to + the folder, moved to or from the folder, deleted from the folder, + modified in the folder, or the folder properties were modified. */ + UpdateNewMember, /** Any 0 Not valid - return 400 (Bad Request). + Existing Folder 1 A message or sub-folder was created in the folder, copied to + the folder, or moved to the folder. */ + Any /** Message 1 Treat as depth = 0. */ + }; + + ExchangeMonitor( ExchangeAccount* account, int pollMode, const QHostAddress& ownInterface ); + ~ExchangeMonitor(); + void addWatch( const KURL &url, int mode, int depth ); + void removeWatch( const KURL &url ); + void removeWatch( ID id ); + + signals: + void notify( const QValueList<long>& IDs, const QValueList<KURL>& urls ); +// void added( ID id, const KURL& url ); +// void removed( ID id, const KURL& url ); + + void error( int result, const QString& moreInfo ); + + private slots: + void slotSubscribeResult( KIO::Job * ); + void slotUnsubscribeResult( KIO::Job * ); + void slotPollTimer(); + void poll( const IDList& IDs ); + void slotPollResult( KIO::Job * ); + void slotRenewTimer(); + void slotRenewResult( KIO::Job * ); + void slotActivated(int socket); + + private: +// void init(); + + QMap<ID,KURL> mSubscriptionMap; + QSocketDevice *mSocket; + QSocketNotifier* mNotifier; + QTextStream *mStream; + ExchangeAccount* mAccount; + int mSubscriptionLifetime; + // QString mSubscriptionId; + QTimer* mPollTimer; + QTimer* mRenewTimer; + int mPollMode; +}; + +} + +#endif diff --git a/libkpimexchange/core/exchangeprogress.cpp b/libkpimexchange/core/exchangeprogress.cpp new file mode 100644 index 000000000..203a1a0b7 --- /dev/null +++ b/libkpimexchange/core/exchangeprogress.cpp @@ -0,0 +1,72 @@ +/* + 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 <qlayout.h> +#include <qlabel.h> +#include <qcombobox.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kapplication.h> +#include <kglobal.h> +#include <kconfig.h> +#include <kstandarddirs.h> +#include <ksimpleconfig.h> + +#include "exchangeprogress.h" +using namespace KPIM; + +ExchangeProgress::ExchangeProgress(QWidget *parent) + : KProgressDialog(parent, "", i18n("Exchange Download Progress"), i18n("Exchange Plugin"), "text" ) +{ + m_finished = 0; + m_total = 0; + setAutoClose( false ); + setLabel( i18n( "Listing appointments" ) ); +} + +ExchangeProgress::~ExchangeProgress() +{ +} + +void ExchangeProgress::slotTransferStarted() +{ + m_total++; + progressBar()->setTotalSteps( m_total ); + updateLabel(); +} + +void ExchangeProgress::slotTransferFinished() +{ + m_finished++; + updateLabel(); + if ( m_finished == m_total ) { + emit complete( this ); + } +} + +void ExchangeProgress::updateLabel() +{ + progressBar()->setValue( m_finished ); + QString str = i18n( "Downloading, %1 of %2" ).arg( m_finished ).arg( m_total ); + setLabel( str ); +} + +#include "exchangeprogress.moc" diff --git a/libkpimexchange/core/exchangeprogress.h b/libkpimexchange/core/exchangeprogress.h new file mode 100644 index 000000000..c37467db1 --- /dev/null +++ b/libkpimexchange/core/exchangeprogress.h @@ -0,0 +1,54 @@ +/* + 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. +*/ +#ifndef EXCHANGEPROGRESS_H +#define EXCHANGEPROGRESS_H + +#include <kprogress.h> + +class QComboBox; + +namespace KPIM { + +class ExchangeProgress : public KProgressDialog +{ + Q_OBJECT + + public: + ExchangeProgress(QWidget *parent=0); + virtual ~ExchangeProgress(); + + public slots: + void slotTransferStarted(); + void slotTransferFinished(); + + signals: + void complete( ExchangeProgress* ); + + private: + int m_total; + int m_finished; + + private: + void updateLabel(); +}; + +} + +#endif diff --git a/libkpimexchange/core/exchangeupload.cpp b/libkpimexchange/core/exchangeupload.cpp new file mode 100644 index 000000000..8332979a0 --- /dev/null +++ b/libkpimexchange/core/exchangeupload.cpp @@ -0,0 +1,371 @@ +/* + 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 <qstring.h> +#include <qregexp.h> + +#include <kurl.h> +#include <kdebug.h> +#include <krfcdate.h> +#include <kio/job.h> + +#include <kio/slave.h> +#include <kio/scheduler.h> +#include <kio/slavebase.h> +#include <kio/davjob.h> +#include <kio/http.h> + +extern "C" { + #include <ical.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 "exchangeclient.h" +#include "exchangeprogress.h" +#include "exchangeupload.h" +#include "exchangeaccount.h" +#include "utils.h" + +using namespace KPIM; + +ExchangeUpload::ExchangeUpload( KCal::Event *event, ExchangeAccount *account, + const QString &timeZoneId, QWidget *window ) + : mTimeZoneId( timeZoneId ), mWindow( window ) +{ + kdDebug() << "Called ExchangeUpload" << endl; + + mAccount = account; + m_currentUpload = event; + m_currentUploadNumber = 0; + +// kdDebug() << "Trying to add appointment " << m_currentUpload->summary() << endl; + + // TODO: For exisiting events the URL for the uid should already be known. + // Store it after downloading and keep the mapping + + findUid( m_currentUpload->uid() ); +} + +ExchangeUpload::~ExchangeUpload() +{ + kdDebug() << "Entering ExchangeUpload destructor" << endl; + kdDebug() << "Finished ExchangeUpload destructor" << endl; +} + +void ExchangeUpload::findUid( QString const &uid ) +{ + QString query = + "SELECT \"DAV:href\", \"urn:schemas:calendar:uid\"\r\n" + "FROM Scope('shallow traversal of \"\"')\r\n" + "WHERE \"urn:schemas:calendar:uid\" = '" + uid + "'\r\n"; + + kdDebug() << "Find uid query: " << endl << query << endl; + kdDebug() << "Looking for uid " << uid << endl; + + KIO::DavJob* job = KIO::davSearch( mAccount->calendarURL(), "DAV:", "sql", + query, false ); + job->setWindow( mWindow ); + connect( job, SIGNAL( result( KIO::Job * ) ), + SLOT( slotFindUidResult( KIO::Job * ) ) ); +} + +void ExchangeUpload::slotFindUidResult( KIO::Job * job ) +{ + kdDebug() << "slotFindUidResult()" << endl; + + if ( job->error() ) { + kdDebug() << "Error: " << job->error() << endl; + job->showErrorDialog( 0 ); + emit finished( this, ExchangeClient::CommunicationError, + "IO Error: " + QString::number(job->error()) + ":" + + job->errorString() ); + return; + } + QDomDocument &response = static_cast<KIO::DavJob *>( job )->response(); + + kdDebug() << "Search uid result: " << endl << response.toString() << endl; + + QDomElement item = response.documentElement().firstChild().toElement(); + QDomElement hrefElement = item.namedItem( "href" ).toElement(); + if ( item.isNull() || hrefElement.isNull() ) { + // No appointment with this UID in exchange database + // Create a new filename for this appointment and store it there + tryExist(); + return; + } + // The appointment is already in the exchange database + // Overwrite it with the new data + QString href = hrefElement.text(); + KURL url( href ); + kdDebug() << "Found URL with identical uid: " << url.prettyURL() + << ", overwriting that one" << endl; + + startUpload( toDAV( url ) ); +} + +void ExchangeUpload::tryExist() +{ + // FIXME: we should first check if current's uid is already in the Exchange database + // Maybe use locking? + KURL url = mAccount->calendarURL(); + if ( m_currentUploadNumber == 0 ) + url.addPath( m_currentUpload->summary() + ".EML" ); + else + url.addPath( m_currentUpload->summary() + "-" + QString::number( m_currentUploadNumber ) + ".EML" ); + + kdDebug() << "Trying to see whether " << url.prettyURL() << " exists" << endl; + + QDomDocument doc; + QDomElement root = addElement( doc, doc, "DAV:", "propfind" ); + QDomElement prop = addElement( doc, root, "DAV:", "prop" ); + addElement( doc, prop, "DAV:", "displayname" ); + addElement( doc, prop, "urn:schemas:calendar", "uid" ); + + KIO::DavJob *job = KIO::davPropFind( url, doc, "0", false ); + job->setWindow( mWindow ); + job->addMetaData( "errorPage", "false" ); + connect( job, SIGNAL( result( KIO::Job * ) ), + SLOT( slotPropFindResult( KIO::Job * ) ) ); +} + +void ExchangeUpload::slotPropFindResult( KIO::Job *job ) +{ + kdDebug() << "slotPropFindResult()" << endl; + int error = job->error(); + kdDebug() << "PROPFIND error: " << error << ":" << job->errorString() << endl; + if ( error && error != KIO::ERR_DOES_NOT_EXIST ) { + job->showErrorDialog( 0 ); + emit finished( this, ExchangeClient::CommunicationError, + "IO Error: " + QString::number(error) + ":" + + job->errorString() ); + return; + } + + if ( !error ) { + // File exist, try another one + m_currentUploadNumber++; + tryExist(); + return; + } + + // We got a 404 error, resource doesn't exist yet, create it + // FIXME: race condition possible if resource is created under + // our nose. + + KURL url = mAccount->calendarURL(); + if ( m_currentUploadNumber == 0 ) + url.addPath( m_currentUpload->summary() + ".EML" ); + else + url.addPath( m_currentUpload->summary() + "-" + + QString::number( m_currentUploadNumber ) + ".EML" ); + + startUpload( url ); +} + +QString timezoneid( int offset ) +{ + switch ( offset ) { + case 0: return "0"; + case -60: return "3"; + case -120: return "5"; + case -180: return "51"; + case -210: return "25"; + case -240: return "24"; // Abu Dhabi + case -270: return "48"; // Kabul + case -300: return "47"; // Islamabad + case -330: return "23"; // Bombay + case -360: return "46"; // Dhaka + case -420: return "22"; // Bangkok + case -480: return "45"; // Beijing + case -540: return "20"; // Tokyo + case -570: return "44"; // Darwin + case -600: return "18"; // Brisbane + case -660: return "41"; // Solomon Islands + case -720: return "17"; // Auckland + case 60: return "29"; // Azores + case 120: return "30"; // Mid Atlantic + case 180: return "8"; // Brasilia + case 210: return "28"; // Newfoundland + case 240: return "9"; // Atlantic time Canada + case 300: return "10"; // Eastern + case 360: return "11"; // Central time + case 420: return "12"; // Mountain time + case 480: return "13"; // Pacific time + case 540: return "14"; // Alaska time + case 600: return "15"; // Hawaii + case 660: return "16"; // Midway Island + case 720: return "39"; // Eniwetok + default: return "52"; // Invalid time zone + } +} + + +void ExchangeUpload::startUpload( const KURL &url ) +{ + KCal::Event *event = static_cast<KCal::Event *>( m_currentUpload ); + if ( ! event ) { + kdDebug() << "ERROR: trying to upload a non-Event Incidence" << endl; + emit finished( this, ExchangeClient::NonEventError, "The incidence that is to be uploaded to the exchange server is not of type KCal::Event" ); + return; + } + + QDomDocument doc; + QDomElement root = addElement( doc, doc, "DAV:", "propertyupdate" ); + QDomElement set = addElement( doc, root, "DAV:", "set" ); + QDomElement prop = addElement( doc, set, "DAV:", "prop" ); + addElement( doc, prop, "DAV:", "contentclass", "urn:content-classes:appointment" ); +// addElement( doc, prop, "http://schemas.microsoft.com/exchange/", "outlookmessageclass", "IPM.appointment" ); + addElement( doc, prop, "http://schemas.microsoft.com/exchange/", + "outlookmessageclass", "IPM.Appointment" ); + // addElement( doc, prop, "urn:schemas:calendar:", "method", "Add" ); + addElement( doc, prop, "urn:schemas:calendar:", "alldayevent", + event->doesFloat() ? "1" : "0" ); + addElement( doc, prop, "urn:schemas:calendar:", "busystatus", + event->transparency() ? "Free" : "Busy" ); + // KLUDGE: somehow we need to take the opposite of the + // value that localUTCOffset() supplies... + // FIXME: What do we need that offset for anyway??? + int tzOffset = - KRFCDate::localUTCOffset(); + QString offsetString; + if ( tzOffset == 0 ) + offsetString = "Z"; + else if ( tzOffset > 0 ) + offsetString = QString( "+%1:%2" ).arg(tzOffset/60, 2).arg( tzOffset%60, 2 ); + else + offsetString = QString( "-%1:%2" ).arg((-tzOffset)/60, 2).arg( (-tzOffset)%60, 2 ); + offsetString = offsetString.replace( QRegExp(" "), "0" ); + + kdDebug() << "Timezone offset: " << tzOffset << " : " << offsetString << endl; + kdDebug() << "ExchangeUpload::mTimeZoneId=" << mTimeZoneId << endl; + + addElement( doc, prop, "urn:schemas:calendar:", "dtstart", + zoneAsUtc( event->dtStart(), mTimeZoneId ).toString( Qt::ISODate ) + "Z" ); + // event->dtStart().toString( "yyyy-MM-ddThh:mm:ss.zzzZ" ) ); + // 2002-06-04T08:00:00.000Z" ); + addElement( doc, prop, "urn:schemas:calendar:", "dtend", + zoneAsUtc( event->dtEnd(), mTimeZoneId ).toString( Qt::ISODate ) + "Z" ); +#if 0 + addElement( doc, prop, "urn:schemas:calendar:", "dtstart", + event->dtStart().toString( "yyyy-MM-ddThh:mm:ss.zzz" )+ offsetString ); + // event->dtStart().toString( "yyyy-MM-ddThh:mm:ss.zzzZ" ) ); + // 2002-06-04T08:00:00.000Z" ); + addElement( doc, prop, "urn:schemas:calendar:", "dtend", + event->dtEnd().toString( "yyyy-MM-ddThh:mm:ss.zzz" ) + offsetString ); +#endif + addElement( doc, prop, "urn:schemas:calendar:", "lastmodified", zoneAsUtc( event->lastModified(), mTimeZoneId ).toString( Qt::ISODate )+"Z" ); + +// addElement( doc, prop, "urn:schemas:calendar:", "meetingstatus", "confirmed" ); + addElement( doc, prop, "urn:schemas:httpmail:", "textdescription", event->description() ); + addElement( doc, prop, "urn:schemas:httpmail:", "subject", event->summary() ); + addElement( doc, prop, "urn:schemas:calendar:", "location", event->location() ); + // addElement( doc, prop, "urn:schemas:mailheader:", "subject", event->summary() ); + addElement( doc, prop, "urn:schemas:calendar:", "uid", event->uid() ); +// addElement( doc, prop, "urn:schemas:calendar:", "organizer", event->organizer() ); + + KCal::Recurrence *recurrence = event->recurrence(); + kdDebug() << "Recurrence->doesRecur(): " << recurrence->doesRecur() << endl; + if ( recurrence->recurrenceType() != KCal::Recurrence::rNone ) { + addElement( doc, prop, "urn:schemas:calendar:", "instancetype", "1" ); + KCal::ICalFormat *format = new KCal::ICalFormat(); + QString recurstr = format->toString( recurrence->defaultRRule() ); + // Strip leading "RRULE\n :" and whitespace + recurstr = recurstr.replace( QRegExp("^[A-Z]*[\\s]*:"), "").stripWhiteSpace(); + kdDebug() << "Recurrence rule after replace: \"" << recurstr << "\"" << endl; + delete format; + QDomElement rrule = addElement( doc, prop, "urn:schemas:calendar:", "rrule" ); + addElement( doc, rrule, "xml:", "v", recurstr ); + addElement( doc, prop, "urn:schemas:calendar:", "timezoneid", timezoneid( tzOffset ) ); + } else { + addElement( doc, prop, "urn:schemas:calendar:", "instancetype", "0" ); + } + + KCal::DateList exdates = recurrence->exDates(); + if ( !exdates.isEmpty() ) { + QDomElement exdate = addElement( doc, prop, "urn:schemas:calendar:", "exdate" ); + KCal::DateList::iterator it; + for ( it = exdates.begin(); it != exdates.end(); ++it ) { + QString date = (*it).toString( "yyyy-MM-ddT00:00:00.000" )+ offsetString; +// QString date = zoneAsUtc( (*it), mTimeZoneId ).toString( Qt::ISODate ); + addElement( doc, exdate, "xml:", "v", date ); + } + } + + KCal::Alarm::List alarms = event->alarms(); + if ( alarms.count() > 0 ) { + KCal::Alarm* alarm = alarms.first(); + // TODO: handle multiple alarms + // TODO: handle end offsets and general alarm times + // TODO: handle alarm types + if ( alarm->hasStartOffset() ) { + int offset = - alarm->startOffset().asSeconds(); + addElement( doc, prop, "urn:schemas:calendar:", "reminderoffset", QString::number( offset ) ); + } + } + + kdDebug() << "Uploading event: " << endl; + kdDebug() << doc.toString() << endl; + + kdDebug() << "Upload url: " << url << endl; + + KIO::DavJob *job = KIO::davPropPatch( url, doc, false ); + job->setWindow( mWindow ); + connect( job, SIGNAL( result( KIO::Job * ) ), + SLOT( slotPatchResult( KIO::Job * ) ) ); +} + +void ExchangeUpload::slotPatchResult( KIO::Job *job ) +{ + kdDebug() << "slotPropPatchResult()" << endl; + if ( job->error() ) { + job->showErrorDialog( 0 ); + kdDebug() << "Error: " << job->error() << endl; + emit finished( this, ExchangeClient::CommunicationError, + "IO Error: " + QString::number(job->error()) + ":" + + job->errorString() ); + return; + } + QDomDocument response = static_cast<KIO::DavJob *>( job )->response(); + kdDebug() << "Patch result: " << response.toString() << endl; + + // Either we have a "201 Created" (if a new event has been created) or + // we have a "200 OK" (if an existing event has been altered), + // or else an error has occurred ;) + QDomElement status = response.documentElement().namedItem( "response" ) + .namedItem( "status" ).toElement(); + QDomElement propstat = response.documentElement().namedItem( "response" ) + .namedItem( "propstat" ).namedItem( "status" ) + .toElement(); + kdDebug() << "status: " << status.text() << endl; + kdDebug() << "propstat: " << propstat.text() << endl; + if ( ! ( status.text().contains( "201" ) || + propstat.text().contains( "200" ) ) ) + emit finished( this, ExchangeClient::EventWriteError, + "Upload error response: \n" + response.toString() ); + else + emit finished( this, ExchangeClient::ResultOK, QString::null ); +} + +#include "exchangeupload.moc" diff --git a/libkpimexchange/core/exchangeupload.h b/libkpimexchange/core/exchangeupload.h new file mode 100644 index 000000000..5ed269297 --- /dev/null +++ b/libkpimexchange/core/exchangeupload.h @@ -0,0 +1,66 @@ +/* + 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. +*/ +#ifndef KDEPIM_EXCHANGE_UPLOAD_H +#define KDEPIM_EXCHANGE_UPLOAD_H + +#include <qstring.h> +#include <qwidget.h> +#include <kio/job.h> + +#include <kdepimmacros.h> + +#include <libkcal/calendar.h> +#include <libkcal/event.h> + +namespace KPIM { + +class ExchangeAccount; + +class KDE_EXPORT ExchangeUpload : public QObject { + Q_OBJECT + public: + ExchangeUpload( KCal::Event* event, ExchangeAccount* account, const QString& timeZoneId, QWidget* window=0 ); + ~ExchangeUpload(); + + private slots: + void slotPatchResult( KIO::Job * ); + void slotPropFindResult( KIO::Job * ); + void slotFindUidResult( KIO::Job * ); + + signals: + void startDownload(); + void finishDownload(); + void finished( ExchangeUpload* worker, int result, const QString& moreInfo ); + + private: + void tryExist(); + void startUpload( const KURL& url ); + void findUid( QString const& uid ); + + ExchangeAccount* mAccount; + KCal::Event* m_currentUpload; + int m_currentUploadNumber; + QString mTimeZoneId; + QWidget* mWindow; +}; + +} + +#endif diff --git a/libkpimexchange/core/utils.cpp b/libkpimexchange/core/utils.cpp new file mode 100644 index 000000000..1d90cfcca --- /dev/null +++ b/libkpimexchange/core/utils.cpp @@ -0,0 +1,100 @@ +/* + 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 <qdatetime.h> + +extern "C" { + #include <ical.h> +} + +#include "utils.h" + +/** In a document doc with node node, add an element with name ns and tagname tag. Return the new element + */ +QDomElement addElement( QDomDocument& doc, QDomNode& node, const QString& ns, const QString& tag ) +{ + QDomElement el = doc.createElementNS( ns, tag ); + node.appendChild( el ); + return el; +} + +/** + In a document doc with node node, add an element with namespace ns and tagname tag. Add a textnode in + the element with text contents text. Return the new element. + */ +QDomElement addElement( QDomDocument& doc, QDomNode& node, const QString& ns, const QString& tag, const QString& text ) +{ + QDomElement el = doc.createElementNS( ns, tag ); + QDomText textnode = doc.createTextNode( text ); + el.appendChild( textnode ); + node.appendChild( el ); + return el; +} + +//TODO: should not call libical functions directly -- better to make +// a new libkcal abstraction method. +QDateTime utcAsZone( const QDateTime& utc, const QString& timeZoneId ) +{ + int daylight; + QDateTime epoch; + epoch.setTime_t( 0 ); + time_t v = epoch.secsTo( utc ); + struct icaltimetype tt = icaltime_from_timet( v, 0 ); // 0: is_date=false + int offset = icaltimezone_get_utc_offset( + icaltimezone_get_builtin_timezone( timeZoneId.latin1() ), + &tt, &daylight ); + + return utc.addSecs( offset ); +} + +//TODO: should not call libical functions directly -- better to make +// a new libkcal abstraction method. +QDateTime zoneAsUtc( const QDateTime& zone, const QString& timeZoneId ) +{ + int daylight; + QDateTime epoch; + epoch.setTime_t( 0 ); + time_t v = epoch.secsTo( zone ); + struct icaltimetype tt = icaltime_from_timet( v, 0 ); // 0: is_date=false + int offset = icaltimezone_get_utc_offset( + icaltimezone_get_builtin_timezone( timeZoneId.latin1() ), + &tt, &daylight ); + + return zone.addSecs( - offset ); +} + +KURL toDAV( const KURL& url ) { + KURL result( url ); + if ( result.protocol() == "http" ) + result.setProtocol( "webdav" ); + else if ( result.protocol() == "https" ) + result.setProtocol( "webdavs" ); + return result; +} + +KURL* toDAV( const KURL* url ) { + KURL* result = new KURL( *url ); + if ( result->protocol() == "http" ) + result->setProtocol( "webdav" ); + else if ( result->protocol() == "https" ) + result->setProtocol( "webdavs" ); + return result; +} + diff --git a/libkpimexchange/core/utils.h b/libkpimexchange/core/utils.h new file mode 100644 index 000000000..20b681416 --- /dev/null +++ b/libkpimexchange/core/utils.h @@ -0,0 +1,56 @@ +/* + 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. +*/ + +#ifndef KDEPIM_EXCHANGE_UTILS_H +#define KDEPIM_EXCHANGE_UTILS_H + +#include <qstring.h> +#include <qdom.h> + +#include <kurl.h> + +/** In a document doc with node node, add an element with name ns and tagname tag. Return the new element + */ +QDomElement addElement( QDomDocument& doc, QDomNode& node, const QString& ns, const QString& tag ); + +/** + In a document doc with node node, add an element with namespace ns and tagname tag. Add a textnode in + the element with text contents text. Return the new element. + */ +QDomElement addElement( QDomDocument& doc, QDomNode& node, const QString& ns, const QString& tag, const QString& text ); + +/** + * Return the representation of utc time in the time zone indicated by timeZoneId + */ +QDateTime utcAsZone( const QDateTime& utc, const QString& timeZoneId ); + +/** + * Return the UTC representation of local time in the time zone indicated by timeZoneId + */ +QDateTime zoneAsUtc( const QDateTime& zone, const QString& timeZoneId ); + +/** + * Convert http:// url to webdav:// and https:// to webdavs:// + */ +KURL toDAV( const KURL& url ); +KURL* toDAV( const KURL* url ); + +#endif + |