libkpimexchange Library API Documentation

exchangeupload.cpp

00001 /*
00002     This file is part of libkpimexchange
00003     Copyright (c) 2002 Jan-Pascal van Best <janpascal@vanbest.org>
00004 
00005     This library is free software; you can redistribute it and/or
00006     modify it under the terms of the GNU Library General Public
00007     License as published by the Free Software Foundation; either
00008     version 2 of the License, or (at your option) any later version.
00009 
00010     This library is distributed in the hope that it will be useful,
00011     but WITHOUT ANY WARRANTY; without even the implied warranty of
00012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013     Library General Public License for more details.
00014 
00015     You should have received a copy of the GNU Library General Public License
00016     along with this library; see the file COPYING.LIB.  If not, write to
00017     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018     Boston, MA 02111-1307, USA.
00019 */
00020 
00021 #include <qstring.h>
00022 #include <qregexp.h>
00023 
00024 #include <kurl.h>
00025 #include <kdebug.h>
00026 #include <krfcdate.h>
00027 #include <kio/job.h>
00028 
00029 #include <kio/slave.h>
00030 #include <kio/scheduler.h>
00031 #include <kio/slavebase.h>
00032 #include <kio/davjob.h>
00033 #include <kio/http.h>
00034 
00035 extern "C" {
00036   #include <ical.h>
00037 }
00038 
00039 #include <libkcal/event.h>
00040 #include <libkcal/icalformat.h>
00041 #include <libkcal/icalformatimpl.h>
00042 #include <libkcal/recurrence.h>
00043 #include <libkcal/incidence.h>
00044 #include <libkcal/event.h>
00045 
00046 #include "exchangeclient.h"
00047 #include "exchangeprogress.h"
00048 #include "exchangeupload.h"
00049 #include "exchangeaccount.h"
00050 #include "utils.h"
00051 
00052 using namespace KPIM;
00053 
00054 ExchangeUpload::ExchangeUpload( KCal::Event *event, ExchangeAccount *account,
00055                                 const QString &timeZoneId, QWidget *window )
00056   : mTimeZoneId( timeZoneId ), mWindow( window )
00057 {
00058   kdDebug() << "Called ExchangeUpload" << endl;
00059 
00060   mAccount = account;
00061   m_currentUpload = event;
00062   m_currentUploadNumber = 0;
00063 
00064 //  kdDebug() << "Trying to add appointment " << m_currentUpload->summary() << endl;
00065 
00066   // TODO: For exisiting events the URL for the uid should already be known.
00067   // Store it after downloading and keep the mapping
00068 
00069   findUid( m_currentUpload->uid() );
00070 }
00071 
00072 ExchangeUpload::~ExchangeUpload()
00073 {
00074   kdDebug() << "Entering ExchangeUpload destructor" << endl;
00075   kdDebug() << "Finished ExchangeUpload destructor" << endl;
00076 }
00077 
00078 void ExchangeUpload::findUid( QString const &uid )
00079 {
00080   QString query = 
00081         "SELECT \"DAV:href\", \"urn:schemas:calendar:uid\"\r\n"
00082         "FROM Scope('shallow traversal of \"\"')\r\n"
00083         "WHERE \"urn:schemas:calendar:uid\" = '" + uid + "'\r\n";
00084 
00085   kdDebug() << "Find uid query: " << endl << query << endl;
00086   kdDebug() << "Looking for uid " << uid << endl;
00087   
00088   KIO::DavJob* job = KIO::davSearch( mAccount->calendarURL(), "DAV:", "sql",
00089                                      query, false );
00090   job->setWindow( mWindow );
00091   connect( job, SIGNAL( result( KIO::Job * ) ),
00092            SLOT( slotFindUidResult( KIO::Job * ) ) );
00093 }
00094 
00095 void ExchangeUpload::slotFindUidResult( KIO::Job * job )
00096 {
00097   kdDebug() << "slotFindUidResult()" << endl;
00098 
00099   if ( job->error() ) {
00100     kdDebug() << "Error: " << job->error() << endl;
00101     job->showErrorDialog( 0 );
00102     emit finished( this, ExchangeClient::CommunicationError,
00103                    "IO Error: " + QString::number(job->error()) + ":" +
00104                    job->errorString() );
00105     return;
00106   }
00107   QDomDocument &response = static_cast<KIO::DavJob *>( job )->response();
00108 
00109   kdDebug() << "Search uid result: " << endl << response.toString() << endl;
00110 
00111   QDomElement item = response.documentElement().firstChild().toElement();
00112   QDomElement hrefElement = item.namedItem( "href" ).toElement();
00113   if ( item.isNull() || hrefElement.isNull() ) {
00114     // No appointment with this UID in exchange database
00115     // Create a new filename for this appointment and store it there
00116     tryExist();
00117     return;
00118   }
00119   // The appointment is already in the exchange database
00120   // Overwrite it with the new data
00121   QString href = hrefElement.text();
00122   KURL url( href );
00123   kdDebug() << "Found URL with identical uid: " << url.prettyURL()
00124             << ", overwriting that one" << endl;
00125 
00126   startUpload( toDAV( url ) );  
00127 }
00128 
00129 void ExchangeUpload::tryExist()
00130 {
00131   // FIXME: we should first check if current's uid is already in the Exchange database
00132   // Maybe use locking?
00133   KURL url = mAccount->calendarURL();
00134   if ( m_currentUploadNumber == 0 )
00135     url.addPath( m_currentUpload->summary() + ".EML" );
00136   else
00137     url.addPath( m_currentUpload->summary() + "-" + QString::number( m_currentUploadNumber ) + ".EML" );
00138 
00139   kdDebug() << "Trying to see whether " << url.prettyURL() << " exists" << endl;
00140  
00141   QDomDocument doc;
00142   QDomElement root = addElement( doc, doc, "DAV:", "propfind" );
00143   QDomElement prop = addElement( doc, root, "DAV:", "prop" );
00144   addElement( doc, prop, "DAV:", "displayname" );
00145   addElement( doc, prop, "urn:schemas:calendar", "uid" );
00146 
00147   KIO::DavJob *job = KIO::davPropFind( url, doc, "0", false );
00148   job->setWindow( mWindow );
00149   job->addMetaData( "errorPage", "false" );
00150   connect( job, SIGNAL( result( KIO::Job * ) ),
00151            SLOT( slotPropFindResult( KIO::Job * ) ) );
00152 }
00153 
00154 void ExchangeUpload::slotPropFindResult( KIO::Job *job )
00155 {
00156   kdDebug() << "slotPropFindResult()" << endl;
00157   int error = job->error(); 
00158   kdDebug() << "PROPFIND error: " << error << ":" << job->errorString() << endl;
00159   if ( error && error != KIO::ERR_DOES_NOT_EXIST ) {
00160     job->showErrorDialog( 0 );
00161     emit finished( this, ExchangeClient::CommunicationError,
00162                    "IO Error: " + QString::number(error) + ":" +
00163                    job->errorString() );
00164     return;
00165   }
00166 
00167   if ( !error ) {
00168     // File exist, try another one
00169     m_currentUploadNumber++;
00170     tryExist();
00171     return;
00172   }
00173 
00174   // We got a 404 error, resource doesn't exist yet, create it
00175   // FIXME: race condition possible if resource is created under
00176   // our nose.
00177 
00178   KURL url = mAccount->calendarURL();
00179   if ( m_currentUploadNumber == 0 )
00180     url.addPath( m_currentUpload->summary() + ".EML" );
00181   else
00182     url.addPath( m_currentUpload->summary() + "-" +
00183                  QString::number( m_currentUploadNumber ) + ".EML" );
00184 
00185   startUpload( url );
00186 }
00187 
00188 QString timezoneid( int offset )
00189 {
00190   switch ( offset ) {
00191     case 0: return "0";
00192     case -60: return "3";
00193     case -120: return "5";
00194     case -180: return "51";
00195     case -210: return "25";
00196     case -240: return "24"; // Abu Dhabi
00197     case -270: return "48"; // Kabul
00198     case -300: return "47"; // Islamabad
00199     case -330: return "23"; // Bombay
00200     case -360: return "46"; // Dhaka
00201     case -420: return "22"; // Bangkok
00202     case -480: return "45"; // Beijing
00203     case -540: return "20"; // Tokyo
00204     case -570: return "44"; // Darwin
00205     case -600: return "18"; // Brisbane
00206     case -660: return "41"; // Solomon Islands
00207     case -720: return "17"; // Auckland
00208     case 60: return "29"; // Azores
00209     case 120: return "30"; // Mid Atlantic
00210     case 180: return "8"; // Brasilia
00211     case 210: return "28";  // Newfoundland
00212     case 240: return "9"; // Atlantic time Canada
00213     case 300: return "10"; // Eastern
00214     case 360: return "11"; // Central time
00215     case 420: return "12"; // Mountain time
00216     case 480: return "13"; // Pacific time
00217     case 540: return "14"; // Alaska time
00218     case 600: return "15"; // Hawaii
00219     case 660: return "16"; // Midway Island
00220     case 720: return "39"; // Eniwetok
00221     default: return "52"; // Invalid time zone
00222   }
00223 }
00224 
00225 
00226 void ExchangeUpload::startUpload( const KURL &url )
00227 {
00228   KCal::Event *event = static_cast<KCal::Event *>( m_currentUpload );
00229   if ( ! event ) {
00230     kdDebug() << "ERROR: trying to upload a non-Event Incidence" << endl;
00231     emit finished( this, ExchangeClient::NonEventError, "The incidence that is to be uploaded to the exchange server is not of type KCal::Event" );
00232     return;
00233   }
00234 
00235   QDomDocument doc;
00236   QDomElement root = addElement( doc, doc, "DAV:", "propertyupdate" );
00237   QDomElement set = addElement( doc, root, "DAV:", "set" );
00238   QDomElement prop = addElement( doc, set, "DAV:", "prop" );
00239   addElement( doc, prop, "DAV:", "contentclass", "urn:content-classes:appointment" );
00240 //  addElement( doc, prop, "http://schemas.microsoft.com/exchange/", "outlookmessageclass", "IPM.appointment" );
00241   addElement( doc, prop, "http://schemas.microsoft.com/exchange/",
00242               "outlookmessageclass", "IPM.Appointment" );
00243  // addElement( doc, prop, "urn:schemas:calendar:", "method", "Add" );
00244   addElement( doc, prop, "urn:schemas:calendar:", "alldayevent", 
00245       event->doesFloat() ? "1" : "0" );
00246   addElement( doc, prop, "urn:schemas:calendar:", "busystatus", 
00247       event->transparency() ? "Free" : "Busy" );
00248   // KLUDGE: somehow we need to take the opposite of the
00249   // value that localUTCOffset() supplies...
00250   // FIXME: What do we need that offset for anyway???
00251   int tzOffset = - KRFCDate::localUTCOffset(); 
00252   QString offsetString;
00253   if ( tzOffset == 0 ) 
00254     offsetString = "Z";
00255   else if ( tzOffset > 0 ) 
00256     offsetString = QString( "+%1:%2" ).arg(tzOffset/60, 2).arg( tzOffset%60, 2 );
00257   else
00258     offsetString = QString( "-%1:%2" ).arg((-tzOffset)/60, 2).arg( (-tzOffset)%60, 2 );
00259   offsetString = offsetString.replace( QRegExp(" "), "0" );
00260 
00261   kdDebug() << "Timezone offset: " << tzOffset << " : " << offsetString << endl;
00262   kdDebug() << "ExchangeUpload::mTimeZoneId=" << mTimeZoneId << endl;
00263 
00264   addElement( doc, prop, "urn:schemas:calendar:", "dtstart", 
00265       zoneAsUtc( event->dtStart(), mTimeZoneId ).toString( Qt::ISODate ) + "Z" );
00266   //    event->dtStart().toString( "yyyy-MM-ddThh:mm:ss.zzzZ" ) );
00267   //    2002-06-04T08:00:00.000Z" );
00268   addElement( doc, prop, "urn:schemas:calendar:", "dtend", 
00269       zoneAsUtc( event->dtEnd(), mTimeZoneId ).toString( Qt::ISODate ) + "Z" );
00270 #if 0
00271   addElement( doc, prop, "urn:schemas:calendar:", "dtstart", 
00272       event->dtStart().toString( "yyyy-MM-ddThh:mm:ss.zzz" )+ offsetString );
00273   //    event->dtStart().toString( "yyyy-MM-ddThh:mm:ss.zzzZ" ) );
00274   //    2002-06-04T08:00:00.000Z" );
00275   addElement( doc, prop, "urn:schemas:calendar:", "dtend", 
00276       event->dtEnd().toString( "yyyy-MM-ddThh:mm:ss.zzz" ) + offsetString );
00277 #endif
00278   addElement( doc, prop, "urn:schemas:calendar:", "lastmodified", zoneAsUtc( event->lastModified(), mTimeZoneId ).toString( Qt::ISODate )+"Z" );
00279 
00280 //  addElement( doc, prop, "urn:schemas:calendar:", "meetingstatus", "confirmed" );
00281   addElement( doc, prop, "urn:schemas:httpmail:", "textdescription", event->description() );
00282   addElement( doc, prop, "urn:schemas:httpmail:", "subject", event->summary() );
00283   addElement( doc, prop, "urn:schemas:calendar:", "location", event->location() );
00284   // addElement( doc, prop, "urn:schemas:mailheader:", "subject", event->summary() );
00285   addElement( doc, prop, "urn:schemas:calendar:", "uid", event->uid() );
00286 //  addElement( doc, prop, "urn:schemas:calendar:", "organizer", event->organizer() );
00287 
00288   KCal::Recurrence *recurrence = event->recurrence();
00289   kdDebug() << "Recurrence->doesRecur(): " << recurrence->doesRecur() << endl;
00290   if ( recurrence->doesRecur() != KCal::Recurrence::rNone ) {
00291     addElement( doc, prop, "urn:schemas:calendar:", "instancetype", "1" );
00292     KCal::ICalFormat *format = new KCal::ICalFormat();
00293     QString recurstr = format->toString( recurrence );
00294     // Strip leading "RRULE\n :" and whitespace
00295     recurstr = recurstr.replace( QRegExp("^[A-Z]*[\\s]*:"), "").stripWhiteSpace();
00296     kdDebug() << "Recurrence rule after replace: \"" << recurstr << "\"" << endl;
00297     delete format;
00298     QDomElement rrule = addElement( doc, prop, "urn:schemas:calendar:", "rrule" );
00299     addElement( doc, rrule, "xml:", "v", recurstr );
00300     addElement( doc, prop, "urn:schemas:calendar:", "timezoneid", timezoneid( tzOffset ) );
00301   } else {
00302     addElement( doc, prop, "urn:schemas:calendar:", "instancetype", "0" );
00303   }
00304 
00305   KCal::DateList exdates = event->exDates();
00306   if ( !exdates.isEmpty() ) {
00307     QDomElement exdate = addElement( doc, prop, "urn:schemas:calendar:", "exdate" );
00308     KCal::DateList::iterator it;
00309     for ( it = exdates.begin(); it != exdates.end(); ++it ) {
00310       QString date = (*it).toString( "yyyy-MM-ddT00:00:00.000" )+ offsetString;
00311 //      QString date = zoneAsUtc( (*it), mTimeZoneId ).toString( Qt::ISODate );
00312       addElement( doc, exdate, "xml:", "v", date );
00313     }
00314   }
00315 
00316   KCal::Alarm::List alarms = event->alarms();
00317   if ( alarms.count() > 0 ) {
00318     KCal::Alarm* alarm = alarms.first();
00319     // TODO: handle multiple alarms
00320     // TODO: handle end offsets and general alarm times
00321     // TODO: handle alarm types
00322     if ( alarm->hasStartOffset() ) {
00323       int offset = - alarm->startOffset().asSeconds();
00324       addElement( doc, prop, "urn:schemas:calendar:", "reminderoffset", QString::number( offset ) );
00325     }
00326   }
00327 
00328   kdDebug() << "Uploading event: " << endl;
00329   kdDebug() << doc.toString() << endl;
00330 
00331   kdDebug() << "Upload url: " << url << endl;
00332 
00333   KIO::DavJob *job = KIO::davPropPatch( url, doc, false );
00334   job->setWindow( mWindow );
00335   connect( job, SIGNAL( result( KIO::Job * ) ),
00336            SLOT( slotPatchResult( KIO::Job * ) ) );
00337 }
00338 
00339 void ExchangeUpload::slotPatchResult( KIO::Job *job )
00340 {
00341   kdDebug() << "slotPropPatchResult()" << endl;
00342   if ( job->error() ) {
00343     job->showErrorDialog( 0 );
00344     kdDebug() << "Error: " << job->error() << endl;
00345     emit finished( this, ExchangeClient::CommunicationError,
00346                    "IO Error: " + QString::number(job->error()) + ":" +
00347                    job->errorString() );
00348     return;
00349   }
00350   QDomDocument response = static_cast<KIO::DavJob *>( job )->response();
00351   kdDebug() << "Patch result: " << response.toString() << endl;
00352 
00353   // Either we have a "201 Created" (if a new event has been created) or 
00354   // we have a "200 OK" (if an existing event has been altered),
00355   // or else an error has occurred ;)
00356   QDomElement status = response.documentElement().namedItem( "response" )
00357                        .namedItem( "status" ).toElement();
00358   QDomElement propstat = response.documentElement().namedItem( "response" )
00359                          .namedItem( "propstat" ).namedItem( "status" )
00360                          .toElement();
00361   kdDebug() << "status: " << status.text() << endl;
00362   kdDebug() << "propstat: " << propstat.text() << endl;
00363   if ( ! ( status.text().contains( "201" ) || 
00364            propstat.text().contains( "200" ) ) )
00365     emit finished( this, ExchangeClient::EventWriteError,
00366                    "Upload error response: \n" + response.toString() ); 
00367   else 
00368     emit finished( this, ExchangeClient::ResultOK, QString::null );
00369 }
00370 
00371 #include "exchangeupload.moc"
KDE Logo
This file is part of the documentation for libkpimexchange Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Aug 23 18:20:56 2007 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003