/* This file is part of libkpimexchange Copyright (c) 2002 Jan-Pascal van Best This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include } #include #include #include #include #include #include #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 TQString &timeZoneId, TQWidget *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( TQString const &uid ) { TQString 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; TDEIO::DavJob* job = TDEIO::davSearch( mAccount->calendarURL(), "DAV:", "sql", query, false ); job->setWindow( mWindow ); connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ), TQT_SLOT( slotFindUidResult( TDEIO::Job * ) ) ); } void ExchangeUpload::slotFindUidResult( TDEIO::Job * job ) { kdDebug() << "slotFindUidResult()" << endl; if ( job->error() ) { kdDebug() << "Error: " << job->error() << endl; job->showErrorDialog( 0 ); emit finished( this, ExchangeClient::CommunicationError, "IO Error: " + TQString::number(job->error()) + ":" + job->errorString() ); return; } TQDomDocument &response = static_cast( job )->response(); kdDebug() << "Search uid result: " << endl << response.toString() << endl; TQDomElement item = response.documentElement().firstChild().toElement(); TQDomElement 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 TQString 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() + "-" + TQString::number( m_currentUploadNumber ) + ".EML" ); kdDebug() << "Trying to see whether " << url.prettyURL() << " exists" << endl; TQDomDocument doc; TQDomElement root = addElement( doc, doc, "DAV:", "propfind" ); TQDomElement prop = addElement( doc, root, "DAV:", "prop" ); addElement( doc, prop, "DAV:", "displayname" ); addElement( doc, prop, "urn:schemas:calendar", "uid" ); TDEIO::DavJob *job = TDEIO::davPropFind( url, doc, "0", false ); job->setWindow( mWindow ); job->addMetaData( "errorPage", "false" ); connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ), TQT_SLOT( slotPropFindResult( TDEIO::Job * ) ) ); } void ExchangeUpload::slotPropFindResult( TDEIO::Job *job ) { kdDebug() << "slotPropFindResult()" << endl; int error = job->error(); kdDebug() << "PROPFIND error: " << error << ":" << job->errorString() << endl; if ( error && error != TDEIO::ERR_DOES_NOT_EXIST ) { job->showErrorDialog( 0 ); emit finished( this, ExchangeClient::CommunicationError, "IO Error: " + TQString::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() + "-" + TQString::number( m_currentUploadNumber ) + ".EML" ); startUpload( url ); } TQString 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( 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; } TQDomDocument doc; TQDomElement root = addElement( doc, doc, "DAV:", "propertyupdate" ); TQDomElement set = addElement( doc, root, "DAV:", "set" ); TQDomElement 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(); TQString offsetString; if ( tzOffset == 0 ) offsetString = "Z"; else if ( tzOffset > 0 ) offsetString = TQString( "+%1:%2" ).arg(tzOffset/60, 2).arg( tzOffset%60, 2 ); else offsetString = TQString( "-%1:%2" ).arg((-tzOffset)/60, 2).arg( (-tzOffset)%60, 2 ); offsetString = offsetString.replace( TQRegExp(" "), "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(); TQString recurstr = format->toString( recurrence->defaultRRule() ); // Strip leading "RRULE\n :" and whitespace recurstr = recurstr.replace( TQRegExp("^[A-Z]*[\\s]*:"), "").stripWhiteSpace(); kdDebug() << "Recurrence rule after replace: \"" << recurstr << "\"" << endl; delete format; TQDomElement 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() ) { TQDomElement exdate = addElement( doc, prop, "urn:schemas:calendar:", "exdate" ); KCal::DateList::iterator it; for ( it = exdates.begin(); it != exdates.end(); ++it ) { TQString date = (*it).toString( "yyyy-MM-ddT00:00:00.000" )+ offsetString; // TQString 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", TQString::number( offset ) ); } } kdDebug() << "Uploading event: " << endl; kdDebug() << doc.toString() << endl; kdDebug() << "Upload url: " << url << endl; TDEIO::DavJob *job = TDEIO::davPropPatch( url, doc, false ); job->setWindow( mWindow ); connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ), TQT_SLOT( slotPatchResult( TDEIO::Job * ) ) ); } void ExchangeUpload::slotPatchResult( TDEIO::Job *job ) { kdDebug() << "slotPropPatchResult()" << endl; if ( job->error() ) { job->showErrorDialog( 0 ); kdDebug() << "Error: " << job->error() << endl; emit finished( this, ExchangeClient::CommunicationError, "IO Error: " + TQString::number(job->error()) + ":" + job->errorString() ); return; } TQDomDocument response = static_cast( 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 ;) TQDomElement status = response.documentElement().namedItem( "response" ) .namedItem( "status" ).toElement(); TQDomElement 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, TQString() ); } #include "exchangeupload.moc"