You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
372 lines
14 KiB
372 lines
14 KiB
15 years ago
|
/*
|
||
|
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"
|