00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
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
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
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
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
00149
00150 handleAppointments( response, false );
00151
00152 decreaseDownloads();
00153 }
00154
00155 void ExchangeDownload::handleAppointments( const QDomDocument& response, bool recurrence ) {
00156
00157 int successCount = 0;
00158
00159 if ( response.documentElement().firstChild().toElement().isNull() ) {
00160
00161
00162 return;
00163 }
00164
00165 for( QDomElement item = response.documentElement().firstChild().toElement();
00166 !item.isNull();
00167 item = item.nextSibling().toElement() )
00168 {
00169
00170 QDomNodeList propstats = item.elementsByTagNameNS( "DAV:", "propstat" );
00171
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
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
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
00232
00233
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
00275
00276
00277
00278
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
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
00303
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
00317
00318 QString timezoneid = prop.namedItem( "timezoneid" ).toElement().text();
00319
00320
00321 QString timezone = prop.namedItem( "timezone" ).toElement().text();
00322
00323
00324
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
00331
00332 QString organizer = QString::fromUtf8( prop.namedItem( "organizer" ).toElement().text() );
00333 event->setOrganizer( organizer );
00334
00335
00336
00337 QString contact = QString::fromUtf8( prop.namedItem( "contact" ).toElement().text() );
00338
00339
00340
00341
00342 QString to = QString::fromUtf8( prop.namedItem( "to" ).toElement().text() );
00343
00344 QStringList attn = QStringList::split( ",", to );
00345 QStringList::iterator it;
00346 for ( it = attn.begin(); it != attn.end(); ++it ) {
00347
00348 QString name = "";
00349
00350
00351
00352 }
00353
00354 QString readonly = prop.namedItem( "isreadonly" ).toElement().text();
00355 event->setReadOnly( readonly != "0" );
00356
00357
00358 QString created = prop.namedItem( "created" ).toElement().text();
00359 dt = utcAsZone( QDateTime::fromString( created, Qt::ISODate ), mCalendar->timeZoneId() );
00360 event->setCreated( dt );
00361
00362
00363 QString dtstart = prop.namedItem( "dtstart" ).toElement().text();
00364 dt = utcAsZone( QDateTime::fromString( dtstart, Qt::ISODate ), mCalendar->timeZoneId() );
00365 event->setDtStart( dt );
00366
00367
00368 QString alldayevent = prop.namedItem( "alldayevent" ).toElement().text();
00369 bool floats = alldayevent.toInt() != 0;
00370 event->setFloats( floats );
00371
00372
00373 QString dtend = prop.namedItem( "dtend" ).toElement().text();
00374 dt = utcAsZone( QDateTime::fromString( dtend, Qt::ISODate ), mCalendar->timeZoneId() );
00375
00376 if ( floats ) dt = dt.addDays( -1 );
00377 event->setDtEnd( dt );
00378
00379
00380 QString transparent = prop.namedItem( "transparent" ).toElement().text();
00381 event->setTransparency( transparent.toInt() );
00382
00383
00384 QString description = QString::fromUtf8( prop.namedItem( "textdescription" ).toElement().text() );
00385 event->setDescription( description );
00386
00387
00388 QString subject = QString::fromUtf8( prop.namedItem( "subject" ).toElement().text() );
00389 event->setSummary( subject );
00390
00391
00392 QString location = QString::fromUtf8( prop.namedItem( "location" ).toElement().text() );
00393 event->setLocation( location );
00394
00395
00396 QString rrule = prop.namedItem( "rrule" ).toElement().text();
00397
00398 if ( ! rrule.isNull() ) {
00399
00400
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
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
00425 }
00426 event->setExDates( exdates );
00427
00428
00429
00430
00431
00432
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
00443
00444
00445 QString reminder = prop.namedItem( "reminderoffset" ).toElement().text();
00446
00447 if ( ! reminder.isNull() ) {
00448
00449 KCal::Duration offset( - reminder.toInt() );
00450 KCal::Alarm* alarm = event->newAlarm();
00451 alarm->setOffset( offset );
00452 alarm->setEnabled( true );
00453
00454 }
00456
00458
00459
00461
00463
00464
00466
00467
00469
00470
00472
00473
00479
00480
00481
00482
00483
00485
00486
00487
00488
00489
00490 KCal::Event* oldEvent = mCalendar->event( event->uid() );
00491 if ( oldEvent ) {
00492 kdWarning() << "Already got his event, keeping old version..." << endl;
00493
00494
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
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
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"