libkpimexchange Library API Documentation

exchangedownload.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 <qfile.h>
00022 #include <qinputdialog.h>
00023 #include <qtextstream.h>
00024 #include <qdatastream.h>
00025 #include <qcstring.h>
00026 #include <qregexp.h>
00027 
00028 #include <kapplication.h>
00029 #include <kconfig.h>
00030 #include <kstandarddirs.h>
00031 #include <kmessagebox.h>
00032 #include <klocale.h>
00033 #include <kaction.h>
00034 #include <kurl.h>
00035 #include <kdebug.h>
00036 #include <krfcdate.h>
00037 
00038 #include <kio/slave.h>
00039 #include <kio/scheduler.h>
00040 #include <kio/slavebase.h>
00041 #include <kio/davjob.h>
00042 #include <kio/http.h>
00043 #include <kio/job.h>
00044 
00045 #include <libkcal/incidence.h>
00046 #include <libkcal/event.h>
00047 #include <libkcal/recurrence.h>
00048 #include <libkcal/icalformat.h>
00049 #include <libkcal/icalformatimpl.h>
00050 #include <libkcal/calendarlocal.h>
00051 
00052 extern "C" {
00053   #include <ical.h>
00054 }
00055 
00056 #include "exchangeclient.h"
00057 #include "exchangeaccount.h"
00058 #include "exchangeprogress.h"
00059 #include "utils.h"
00060 
00061 #include "exchangedownload.h"
00062 
00063 using namespace KPIM;
00064 
00065 ExchangeDownload::ExchangeDownload( ExchangeAccount* account, QWidget* window ) :
00066   mWindow( window )
00067 {
00068   mAccount = account;
00069   mDownloadsBusy = 0;
00070   mProgress = 0L;
00071   mCalendar = 0L;
00072   mFormat = new KCal::ICalFormat();
00073 }
00074 
00075 ExchangeDownload::~ExchangeDownload()
00076 {
00077   kdDebug() << "ExchangeDownload destructor" << endl;
00078   delete mFormat;
00079 }
00080 
00081 void ExchangeDownload::download(KCal::Calendar* calendar, const QDate& start, const QDate& end, bool showProgress)
00082 {
00083   mCalendar = calendar;
00084 
00085   if( showProgress ) {
00086     //kdDebug() << "Creating progress dialog" << endl;
00087     mProgress = new ExchangeProgress();
00088     mProgress->show();
00089   
00090     connect( this, SIGNAL(startDownload()), mProgress, SLOT(slotTransferStarted()) );
00091     connect( this, SIGNAL(finishDownload()), mProgress, SLOT(slotTransferFinished()) );
00092   }
00093 
00094   QString sql = dateSelectQuery( start, end.addDays( 1 ) );
00095  
00096   // kdDebug() << "Exchange download query: " << endl << sql << endl;
00097 
00098   increaseDownloads();
00099 
00100   KIO::DavJob *job = KIO::davSearch( mAccount->calendarURL(), "DAV:", "sql", sql, false );
00101   KIO::Scheduler::scheduleJob(job);
00102   job->setWindow( mWindow );
00103   connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotSearchResult(KIO::Job *)));
00104 }
00105 
00106 QString ExchangeDownload::dateSelectQuery( const QDate& start, const QDate& end )
00107 {
00108   QString startString;
00109   startString.sprintf("%04i/%02i/%02i",start.year(),start.month(),start.day());
00110   QString endString;
00111   endString.sprintf("%04i/%02i/%02i",end.year(),end.month(),end.day());
00112   QString sql = 
00113         "SELECT \"DAV:href\", \"urn:schemas:calendar:instancetype\", \"urn:schemas:calendar:uid\"\r\n"
00114         "FROM Scope('shallow traversal of \"\"')\r\n"
00115         "WHERE \"urn:schemas:calendar:dtend\" > '" + startString + "'\r\n"
00116         "AND \"urn:schemas:calendar:dtstart\" < '" + endString + "'";
00117   return sql;
00118 }
00119 
00120 
00121 void ExchangeDownload::slotSearchResult( KIO::Job *job )
00122 {
00123   if ( job->error() ) {
00124     kdError() << "Error result for search: " << job->error() << endl;
00125     job->showErrorDialog( 0L );
00126     finishUp( ExchangeClient::CommunicationError, job );
00127     return;
00128   }
00129   QDomDocument& response = static_cast<KIO::DavJob *>( job )->response();
00130 
00131   // kdDebug() << "Search result: " << endl << response.toString() << endl;
00132 
00133   handleAppointments( response, true );
00134   
00135   decreaseDownloads();
00136 }
00137 
00138 void ExchangeDownload::slotMasterResult( KIO::Job *job )
00139 {
00140   if ( job->error() ) {
00141     kdError() << "Error result for Master search: " << job->error() << endl;
00142     job->showErrorDialog( 0L );
00143     finishUp( ExchangeClient::CommunicationError, job );
00144     return;
00145   }
00146   QDomDocument& response = static_cast<KIO::DavJob *>( job )->response();
00147 
00148   // kdDebug() << "Search (master) result: " << endl << response.toString() << endl;
00149 
00150   handleAppointments( response, false );
00151   
00152   decreaseDownloads();
00153 }
00154 
00155 void ExchangeDownload::handleAppointments( const QDomDocument& response, bool recurrence ) {
00156   //kdDebug() << "Entering handleAppointments" << endl;
00157   int successCount = 0;
00158 
00159   if ( response.documentElement().firstChild().toElement().isNull() ) {
00160     // Got an empty response, but no error. This would mean there are
00161     // no appointments in this time period.
00162     return;
00163   }
00164 
00165   for( QDomElement item = response.documentElement().firstChild().toElement();
00166        !item.isNull();
00167        item = item.nextSibling().toElement() )
00168   {
00169     //kdDebug() << "Current item:" << item.tagName() << endl;
00170     QDomNodeList propstats = item.elementsByTagNameNS( "DAV:", "propstat" );
00171     // kdDebug() << "Item has " << propstats.count() << " propstat children" << endl; 
00172     for( uint i=0; i < propstats.count(); i++ )
00173     {
00174       QDomElement propstat = propstats.item(i).toElement();
00175       QDomElement prop = propstat.namedItem( "prop" ).toElement();
00176       if ( prop.isNull() )
00177       {
00178         kdError() << "Error: no <prop> in response" << endl;
00179         continue;
00180       }
00181 
00182       QDomElement instancetypeElement = prop.namedItem( "instancetype" ).toElement();
00183       if ( instancetypeElement.isNull() ) {
00184         kdError() << "Error: no instance type in Exchange server reply" << endl;
00185         continue;
00186       }
00187       int instanceType = instancetypeElement.text().toInt();
00188       //kdDebug() << "Instance type: " << instanceType << endl;
00189     
00190       if ( recurrence && instanceType > 0 ) {
00191         QDomElement uidElement = prop.namedItem( "uid" ).toElement();
00192         if ( uidElement.isNull() ) {
00193           kdError() << "Error: no uid in Exchange server reply" << endl;
00194           continue;
00195         }
00196         QString uid = uidElement.text();
00197         if ( ! m_uids.contains( uid ) ) {
00198           m_uids[uid] = 1;
00199           handleRecurrence(uid);
00200           successCount++;
00201         }
00202         continue;
00203       }
00204 
00205       QDomElement hrefElement = prop.namedItem( "href" ).toElement();
00206       if ( hrefElement.isNull() ) {
00207         kdError() << "Error: no href in Exchange server reply" << endl;
00208         continue;
00209       }
00210       QString href = hrefElement.text();
00211       KURL url(href);
00212       
00213       kdDebug() << "Getting appointment from url: " << url.prettyURL() << endl;
00214       
00215       readAppointment( toDAV( url ) );
00216       successCount++;
00217     }
00218   }
00219   if ( !successCount ) {
00220     finishUp( ExchangeClient::ServerResponseError, "WebDAV server response:\n" + response.toString() );
00221   }
00222 }  
00223 
00224 void ExchangeDownload::handleRecurrence(QString uid) {
00225   // kdDebug() << "Handling recurrence info for uid=" << uid << endl;
00226   QString query = 
00227         "SELECT \"DAV:href\", \"urn:schemas:calendar:instancetype\"\r\n"
00228         "FROM Scope('shallow traversal of \"\"')\r\n"
00229         "WHERE \"urn:schemas:calendar:uid\" = '" + uid + "'\r\n"
00230         " AND (\"urn:schemas:calendar:instancetype\" = 1)\r\n";
00231 //      "      OR \"urn:schemas:calendar:instancetype\" = 3)\r\n" // FIXME: exception are not handled
00232 
00233   // kdDebug() << "Exchange master query: " << endl << query << endl;
00234 
00235   increaseDownloads();
00236  
00237   KIO::DavJob* job = KIO::davSearch( mAccount->calendarURL(), "DAV:", "sql", query, false );
00238   KIO::Scheduler::scheduleJob(job);
00239   job->setWindow( mWindow );
00240   connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotMasterResult(KIO::Job *)));
00241 }
00242 
00243 void ExchangeDownload::readAppointment( const KURL& url )
00244 {
00245   QDomDocument doc;
00246   QDomElement root = addElement( doc, doc, "DAV:", "propfind" );
00247   QDomElement prop = addElement( doc, root, "DAV:", "prop" );
00248   addElement( doc, prop, "urn:schemas:calendar:", "uid" );
00249   addElement( doc, prop, "urn:schemas:calendar:", "timezoneid" );
00250   addElement( doc, prop, "urn:schemas:calendar:", "timezone" );
00251   addElement( doc, prop, "urn:schemas:calendar:", "lastmodified" );
00252   addElement( doc, prop, "urn:schemas:calendar:", "organizer" );
00253   addElement( doc, prop, "urn:schemas:calendar:", "contact" );
00254   addElement( doc, prop, "urn:schemas:httpmail:", "to" );
00255   addElement( doc, prop, "urn:schemas:calendar:", "attendeestatus" );
00256   addElement( doc, prop, "urn:schemas:calendar:", "attendeerole" );
00257   addElement( doc, prop, "DAV:", "isreadonly" );
00258   addElement( doc, prop, "urn:schemas:calendar:", "instancetype" );
00259   addElement( doc, prop, "urn:schemas:calendar:", "created" );
00260   addElement( doc, prop, "urn:schemas:calendar:", "dtstart" );
00261   addElement( doc, prop, "urn:schemas:calendar:", "dtend" );
00262   addElement( doc, prop, "urn:schemas:calendar:", "alldayevent" );
00263   addElement( doc, prop, "urn:schemas:calendar:", "transparent" );
00264   addElement( doc, prop, "urn:schemas:httpmail:", "textdescription" );
00265   addElement( doc, prop, "urn:schemas:httpmail:", "subject" );
00266   addElement( doc, prop, "urn:schemas:calendar:", "location" );
00267   addElement( doc, prop, "urn:schemas:calendar:", "rrule" );
00268   addElement( doc, prop, "urn:schemas:calendar:", "exdate" );
00269   addElement( doc, prop, "urn:schemas:mailheader:", "sensitivity" );
00270   addElement( doc, prop, "urn:schemas:calendar:", "reminderoffset" );
00271   
00272   addElement( doc, prop, "urn:schemas-microsoft-com:office:office", "Keywords" );
00273 
00274 //  addElement( doc, prop, "", "" );
00275 //  addElement( doc, prop, "DAV:", "" );
00276 //  addElement( doc, prop, "urn:schemas:calendar:", "" );
00277 //  addElement( doc, prop, "urn:content-classes:appointment", "" );
00278 //  addElement( doc, prop, "urn:schemas:httpmail:", "" );
00279 
00280   increaseDownloads();
00281 
00282   KIO::DavJob* job = KIO::davPropFind( url, doc, "0", false );
00283   KIO::Scheduler::scheduleJob(job);
00284   job->setWindow( mWindow );
00285   job->addMetaData( "errorPage", "false" );
00286   connect( job, SIGNAL( result( KIO::Job * ) ), this, SLOT( slotPropFindResult( KIO::Job * ) ) );
00287 }
00288 
00289 void ExchangeDownload::slotPropFindResult( KIO::Job * job )
00290 {
00291   // kdDebug() << "slotPropFindResult" << endl;
00292 
00293   int error = job->error(); 
00294   if ( error )
00295   {
00296     job->showErrorDialog( 0L );
00297     finishUp( ExchangeClient::CommunicationError, job );
00298     return;
00299   }
00300 
00301   QDomDocument response = static_cast<KIO::DavJob *>( job )->response();
00302 //  kdDebug() << "Response: " << endl;
00303 //  kdDebug() << response.toString() << endl;
00304 
00305   QDomElement prop = response.documentElement().namedItem( "response" ).namedItem( "propstat" ).namedItem( "prop" ).toElement();
00306  
00307   KCal::Event* event = new KCal::Event();
00308 
00309   QDomElement uidElement = prop.namedItem( "uid" ).toElement();
00310   if ( uidElement.isNull() ) {
00311     kdError() << "Error: no uid in Exchange server reply" << endl;
00312     finishUp( ExchangeClient::IllegalAppointmentError, "WebDAV server response:\n" + response.toString() );
00313     return;
00314   }
00315   event->setUid( QString::fromUtf8( uidElement.text() ) );
00316   // kdDebug() << "Got UID: " << uidElement.text() << endl;
00317 
00318   QString timezoneid = prop.namedItem( "timezoneid" ).toElement().text();
00319   // kdDebug() << "DEBUG: timezoneid = " << timezoneid << endl;
00320 
00321   QString timezone = prop.namedItem( "timezone" ).toElement().text();
00322   // kdDebug() << "DEBUG: timezone = " << timezone << endl;
00323 
00324   // mFormat is used for parsing recurrence rules.
00325   mFormat->setTimeZone( mCalendar->timeZoneId(), !mCalendar->isLocalTime() );
00326 
00327   QString lastModified = prop.namedItem( "lastmodified" ).toElement().text();
00328   QDateTime dt = utcAsZone( QDateTime::fromString( lastModified, Qt::ISODate ), mCalendar->timeZoneId() );
00329   event->setLastModified( dt );
00330   // kdDebug() << "Got lastModified:" << lastModified << ", " << dt.toString() << endl;
00331 
00332   QString organizer = QString::fromUtf8( prop.namedItem( "organizer" ).toElement().text() );
00333   event->setOrganizer( organizer );
00334   // kdDebug() << "Got organizer: " << organizer << endl;
00335 
00336   // Trying to find attendees, not working yet
00337   QString contact = QString::fromUtf8( prop.namedItem( "contact" ).toElement().text() );
00338 //  event->setOrganizer( organizer );
00339   // kdDebug() << "DEBUG: Got contact: " << contact << endl;
00340 
00341   // This looks promising for finding attendees
00342   QString to = QString::fromUtf8( prop.namedItem( "to" ).toElement().text() );
00343   // kdDebug() << "DEBUG: Got to: " << to << endl;
00344   QStringList attn = QStringList::split( ",", to ); // This doesn't work: there can be commas between ""
00345   QStringList::iterator it;
00346   for ( it = attn.begin(); it != attn.end(); ++it ) {
00347     // kdDebug() << "    attendee: " << (*it) << endl;
00348     QString name = "";
00349     // KCal::Attendee* a = new KCal::Attendee( name, email );
00350 
00351     // event->addAttendee( a );
00352   }
00353 
00354   QString readonly = prop.namedItem( "isreadonly" ).toElement().text();
00355   event->setReadOnly( readonly != "0" );
00356   // kdDebug() << "Got readonly: " << readonly << ":" << (readonly != "0") << endl;
00357 
00358   QString created = prop.namedItem( "created" ).toElement().text();
00359   dt = utcAsZone( QDateTime::fromString( created, Qt::ISODate ), mCalendar->timeZoneId() );
00360   event->setCreated( dt );
00361   // kdDebug() << "got created: " << dt.toString() << endl;
00362 
00363   QString dtstart = prop.namedItem( "dtstart" ).toElement().text();
00364   dt = utcAsZone( QDateTime::fromString( dtstart, Qt::ISODate ), mCalendar->timeZoneId() );
00365   event->setDtStart( dt );
00366   // kdDebug() << "got dtstart: " << dtstart << " becomes in timezone " << dt.toString() << endl;
00367 
00368   QString alldayevent = prop.namedItem( "alldayevent" ).toElement().text();
00369   bool floats = alldayevent.toInt() != 0;
00370   event->setFloats( floats );
00371   // kdDebug() << "Got alldayevent: \"" << alldayevent << "\":" << floats << endl;
00372 
00373   QString dtend = prop.namedItem( "dtend" ).toElement().text();
00374   dt = utcAsZone( QDateTime::fromString( dtend, Qt::ISODate ), mCalendar->timeZoneId() );
00375   // Outlook thinks differently about floating event timing than libkcal
00376   if ( floats ) dt = dt.addDays( -1 );
00377   event->setDtEnd( dt );
00378   // kdDebug() << "got dtstart: " << dtend << " becomes in timezone " << dt.toString() << endl;
00379 
00380   QString transparent = prop.namedItem( "transparent" ).toElement().text();
00381   event->setTransparency( transparent.toInt() );
00382  //  kdDebug() << "Got transparent: " << transparent << endl;
00383 
00384   QString description = QString::fromUtf8( prop.namedItem( "textdescription" ).toElement().text() );
00385   event->setDescription( description );
00386   // kdDebug() << "Got description: " << description << endl;
00387 
00388   QString subject = QString::fromUtf8( prop.namedItem( "subject" ).toElement().text() );
00389   event->setSummary( subject );
00390   // kdDebug() << "Got summary: " << subject << endl;
00391 
00392   QString location = QString::fromUtf8( prop.namedItem( "location" ).toElement().text() );
00393   event->setLocation( location );
00394   // kdDebug() << "Got location: " << location << endl;
00395 
00396   QString rrule = prop.namedItem( "rrule" ).toElement().text();
00397   // kdDebug() << "Got rrule: " << rrule << endl;
00398   if ( ! rrule.isNull() ) {
00399     // Timezone should be handled automatically 
00400     // because we used mFormat->setTimeZone() earlier
00401     if ( ! mFormat->fromString( event->recurrence(), rrule ) ) {
00402       kdError() << "ERROR parsing rrule " << rrule << endl;
00403     }
00404   }
00405 
00406   QDomElement keywords = prop.namedItem( "Keywords" ).toElement();
00407   QStringList categories;
00408   QDomNodeList list = keywords.elementsByTagNameNS( "xml:", "v" );
00409   for( uint i=0; i < list.count(); i++ ) {
00410     QDomElement item = list.item(i).toElement();
00411     categories.append( QString::fromUtf8( item.text() ) );
00412   }
00413   event->setCategories( categories );
00414   // kdDebug() << "Got categories: " << categories.join( ", " ) << endl;
00415 
00416 
00417   QDomElement exdate = prop.namedItem( "exdate" ).toElement();
00418   KCal::DateList exdates;
00419   list = exdate.elementsByTagNameNS( "xml:", "v" );
00420   for( uint i=0; i < list.count(); i++ ) {
00421     QDomElement item = list.item(i).toElement();
00422     QDate date = utcAsZone( QDateTime::fromString( item.text(), Qt::ISODate ), mCalendar->timeZoneId() ).date();
00423     exdates.append( date );
00424     // kdDebug() << "Got exdate: " << date.toString() << endl;
00425   }
00426   event->setExDates( exdates );
00427 
00428   // Exchange sentitivity values:
00429   // 0 None
00430   // 1 Personal
00431   // 2 Private
00432   // 3 Company Confidential
00433   QString sensitivity = prop.namedItem( "sensitivity" ).toElement().text();
00434   if ( ! sensitivity.isNull() ) 
00435   switch( sensitivity.toInt() ) {
00436     case 0: event->setSecrecy( KCal::Incidence::SecrecyPublic ); break;
00437     case 1: event->setSecrecy( KCal::Incidence::SecrecyPrivate ); break;
00438     case 2: event->setSecrecy( KCal::Incidence::SecrecyPrivate ); break;
00439     case 3: event->setSecrecy( KCal::Incidence::SecrecyConfidential ); break;
00440     default: kdWarning() << "Unknown sensitivity: " << sensitivity << endl;
00441   }
00442   // kdDebug() << "Got sensitivity: " << sensitivity << endl;
00443 
00444 
00445   QString reminder = prop.namedItem( "reminderoffset" ).toElement().text();
00446   // kdDebug() << "Reminder offset: " << reminder << endl;
00447   if ( ! reminder.isNull() ) {
00448     // Duration before event in seconds
00449     KCal::Duration offset( - reminder.toInt() );
00450     KCal::Alarm* alarm = event->newAlarm();
00451     alarm->setOffset( offset );
00452     alarm->setEnabled( true );
00453     // TODO: multiple alarms; alarm->setType( KCal::Alarm::xxxx );
00454   }
00456     //Alarm* newAlarm();
00458     //void addAlarm(Alarm*);
00459 
00461     //void setRelatedTo(Incidence *relatedTo);
00463     //void addRelation(Incidence *);
00464 
00466     //void setAttachments(const QStringList &attachments);
00467  
00469     //void setResources(const QStringList &resources);
00470 
00472     //void setPriority(int priority);
00473 
00479     //void addAttendee(Attendee *a, bool doupdate=true );
00480 
00481   // THE FOLLOWING EVENT PROPERTIES ARE NOT READ
00482 
00483   // Revision ID in webdav is a String, not an int
00485     //void setRevision(int rev);
00486 
00487   // Problem: When you sync Outlook to a Palm, the conduit splits up
00488   // multi-day events into single-day events WITH ALL THE SAME UID
00489   // Grrrrrrr.
00490   KCal::Event* oldEvent = mCalendar->event( event->uid() );
00491   if ( oldEvent ) {
00492     kdWarning() << "Already got his event, keeping old version..." << endl;
00493     // This doesn't work
00494     // *oldEvent = *event;
00495   } else {
00496     mCalendar->addEvent( event );
00497   }
00498 
00499   decreaseDownloads();
00500 }
00501 
00502 void ExchangeDownload::increaseDownloads()
00503 {
00504   mDownloadsBusy++;
00505   emit startDownload();
00506 }
00507 
00508 void ExchangeDownload::decreaseDownloads()
00509 {
00510   mDownloadsBusy--;
00511   // kdDebug() << "Download finished, waiting for " << mDownloadsBusy << " more" << endl;
00512   emit finishDownload();
00513   if ( mDownloadsBusy == 0 ) {
00514     kdDebug() << "All downloads finished" << endl;
00515     finishUp( ExchangeClient::ResultOK );
00516   }
00517 }
00518 
00519 void ExchangeDownload::finishUp( int result, const QString& moreInfo )
00520 {
00521   mCalendar->setModified( true );
00522   // Disconnect from progress bar
00523   if ( mProgress ) {
00524     disconnect( this, 0, mProgress, 0 );
00525     disconnect( mProgress, 0, this, 0 );
00526     mProgress->delayedDestruct();
00527   }
00528 
00529   emit finished( this, result, moreInfo );
00530 }
00531 
00532 void ExchangeDownload::finishUp( int result, KIO::Job* job )
00533 {
00534   finishUp( result, QString("WebDAV job error code = ") + QString::number( job->error() ) + ";\n" + "\"" + job->errorString() + "\"" );
00535 }
00536 
00537 #include "exchangedownload.moc"
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.4.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Sat Oct 18 02:47:24 2003 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001