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 <qtextstream.h>
00023 #include <qdatastream.h>
00024 #include <qcstring.h>
00025 #include <qregexp.h>
00026
00027 #include <kapplication.h>
00028 #include <kconfig.h>
00029 #include <kstandarddirs.h>
00030 #include <kmessagebox.h>
00031 #include <klocale.h>
00032 #include <kaction.h>
00033 #include <kurl.h>
00034 #include <kdebug.h>
00035 #include <krfcdate.h>
00036
00037 #include <kio/slave.h>
00038 #include <kio/scheduler.h>
00039 #include <kio/slavebase.h>
00040 #include <kio/davjob.h>
00041 #include <kio/http.h>
00042 #include <kio/job.h>
00043
00044 #include <libkcal/incidence.h>
00045 #include <libkcal/event.h>
00046 #include <libkcal/recurrence.h>
00047 #include <libkcal/icalformat.h>
00048 #include <libkcal/icalformatimpl.h>
00049 #include <libkcal/calendarlocal.h>
00050
00051 extern "C" {
00052 #include <ical.h>
00053 }
00054
00055 #include "exchangeclient.h"
00056 #include "exchangeaccount.h"
00057 #include "exchangeprogress.h"
00058 #include "utils.h"
00059
00060 #include "exchangedownload.h"
00061
00062 using namespace KPIM;
00063
00064 ExchangeDownload::ExchangeDownload( ExchangeAccount* account, QWidget* window ) :
00065 mWindow( window )
00066 {
00067 mAccount = account;
00068 mDownloadsBusy = 0;
00069 mProgress = 0L;
00070 mCalendar = 0L;
00071 mFormat = new KCal::ICalFormat();
00072 }
00073
00074 ExchangeDownload::~ExchangeDownload()
00075 {
00076 kdDebug() << "ExchangeDownload destructor" << endl;
00077 delete mFormat;
00078 if ( mEvents ) delete mEvents;
00079 }
00080
00081 void ExchangeDownload::download(KCal::Calendar* calendar, const QDate& start, const QDate& end, bool showProgress)
00082 {
00083 mCalendar = calendar;
00084 mEvents = 0;
00085
00086 if( showProgress ) {
00087
00088 mProgress = new ExchangeProgress();
00089 mProgress->show();
00090
00091 connect( this, SIGNAL(startDownload()), mProgress, SLOT(slotTransferStarted()) );
00092 connect( this, SIGNAL(finishDownload()), mProgress, SLOT(slotTransferFinished()) );
00093 }
00094
00095 QString sql = dateSelectQuery( start, end.addDays( 1 ) );
00096
00097
00098
00099 increaseDownloads();
00100
00101 KIO::DavJob *job = KIO::davSearch( mAccount->calendarURL(), "DAV:", "sql", sql, false );
00102 KIO::Scheduler::scheduleJob(job);
00103 job->setWindow( mWindow );
00104 connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotSearchResult(KIO::Job *)));
00105 }
00106
00107 void ExchangeDownload::download( const QDate& start, const QDate& end, bool showProgress )
00108 {
00109 mCalendar = 0;
00110 mEvents = new QPtrList<KCal::Event>;
00111
00112 if( showProgress ) {
00113
00114 mProgress = new ExchangeProgress();
00115 mProgress->show();
00116
00117 connect( this, SIGNAL(startDownload()), mProgress, SLOT(slotTransferStarted()) );
00118 connect( this, SIGNAL(finishDownload()), mProgress, SLOT(slotTransferFinished()) );
00119 }
00120
00121 QString sql = dateSelectQuery( start, end.addDays( 1 ) );
00122
00123 increaseDownloads();
00124
00125 KIO::DavJob *job = KIO::davSearch( mAccount->calendarURL(), "DAV:", "sql", sql, false );
00126 KIO::Scheduler::scheduleJob(job);
00127 job->setWindow( mWindow );
00128 connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotSearchResult(KIO::Job *)));
00129 }
00130
00131 QString ExchangeDownload::dateSelectQuery( const QDate& start, const QDate& end )
00132 {
00133 QString startString;
00134 startString.sprintf("%04i/%02i/%02i",start.year(),start.month(),start.day());
00135 QString endString;
00136 endString.sprintf("%04i/%02i/%02i",end.year(),end.month(),end.day());
00137 QString sql =
00138 "SELECT \"DAV:href\", \"urn:schemas:calendar:instancetype\", \"urn:schemas:calendar:uid\"\r\n"
00139 "FROM Scope('shallow traversal of \"\"')\r\n"
00140 "WHERE \"urn:schemas:calendar:dtend\" > '" + startString + "'\r\n"
00141 "AND \"urn:schemas:calendar:dtstart\" < '" + endString + "'";
00142 return sql;
00143 }
00144
00145
00146 void ExchangeDownload::slotSearchResult( KIO::Job *job )
00147 {
00148 if ( job->error() ) {
00149 kdError() << "Error result for search: " << job->error() << endl;
00150 job->showErrorDialog( 0L );
00151 finishUp( ExchangeClient::CommunicationError, job );
00152 return;
00153 }
00154 QDomDocument& response = static_cast<KIO::DavJob *>( job )->response();
00155
00156
00157
00158 handleAppointments( response, true );
00159
00160 decreaseDownloads();
00161 }
00162
00163 void ExchangeDownload::slotMasterResult( KIO::Job *job )
00164 {
00165 if ( job->error() ) {
00166 kdError() << "Error result for Master search: " << job->error() << endl;
00167 job->showErrorDialog( 0L );
00168 finishUp( ExchangeClient::CommunicationError, job );
00169 return;
00170 }
00171 QDomDocument& response = static_cast<KIO::DavJob *>( job )->response();
00172
00173 kdDebug() << "Search (master) result: " << endl << response.toString() << endl;
00174
00175 handleAppointments( response, false );
00176
00177 decreaseDownloads();
00178 }
00179
00180 void ExchangeDownload::handleAppointments( const QDomDocument& response, bool recurrence ) {
00181
00182 int successCount = 0;
00183
00184 if ( response.documentElement().firstChild().toElement().isNull() ) {
00185
00186
00187 return;
00188 }
00189
00190 for( QDomElement item = response.documentElement().firstChild().toElement();
00191 !item.isNull();
00192 item = item.nextSibling().toElement() )
00193 {
00194
00195 QDomNodeList propstats = item.elementsByTagNameNS( "DAV:", "propstat" );
00196
00197 for( uint i=0; i < propstats.count(); i++ )
00198 {
00199 QDomElement propstat = propstats.item(i).toElement();
00200 QDomElement prop = propstat.namedItem( "prop" ).toElement();
00201 if ( prop.isNull() )
00202 {
00203 kdError() << "Error: no <prop> in response" << endl;
00204 continue;
00205 }
00206
00207 QDomElement instancetypeElement = prop.namedItem( "instancetype" ).toElement();
00208 if ( instancetypeElement.isNull() ) {
00209 kdError() << "Error: no instance type in Exchange server reply" << endl;
00210 continue;
00211 }
00212 int instanceType = instancetypeElement.text().toInt();
00213
00214
00215 if ( recurrence && instanceType > 0 ) {
00216 QDomElement uidElement = prop.namedItem( "uid" ).toElement();
00217 if ( uidElement.isNull() ) {
00218 kdError() << "Error: no uid in Exchange server reply" << endl;
00219 continue;
00220 }
00221 QString uid = uidElement.text();
00222 if ( ! m_uids.contains( uid ) ) {
00223 m_uids[uid] = 1;
00224 handleRecurrence(uid);
00225 successCount++;
00226 }
00227 continue;
00228 }
00229
00230 QDomElement hrefElement = prop.namedItem( "href" ).toElement();
00231 if ( hrefElement.isNull() ) {
00232 kdError() << "Error: no href in Exchange server reply" << endl;
00233 continue;
00234 }
00235 QString href = hrefElement.text();
00236 KURL url(href);
00237
00238 kdDebug() << "Getting appointment from url: " << url.prettyURL() << endl;
00239
00240 readAppointment( toDAV( url ) );
00241 successCount++;
00242 }
00243 }
00244 if ( !successCount ) {
00245 finishUp( ExchangeClient::ServerResponseError, "WebDAV SEARCH response:\n" + response.toString() );
00246 }
00247 }
00248
00249 void ExchangeDownload::handleRecurrence(QString uid) {
00250
00251 QString query =
00252 "SELECT \"DAV:href\", \"urn:schemas:calendar:instancetype\"\r\n"
00253 "FROM Scope('shallow traversal of \"\"')\r\n"
00254 "WHERE \"urn:schemas:calendar:uid\" = '" + uid + "'\r\n"
00255 " AND (\"urn:schemas:calendar:instancetype\" = 1)\r\n";
00256
00257
00258
00259
00260 increaseDownloads();
00261
00262 KIO::DavJob* job = KIO::davSearch( mAccount->calendarURL(), "DAV:", "sql", query, false );
00263 KIO::Scheduler::scheduleJob(job);
00264 job->setWindow( mWindow );
00265 connect(job, SIGNAL(result( KIO::Job * )), this, SLOT(slotMasterResult(KIO::Job *)));
00266 }
00267
00268 void ExchangeDownload::readAppointment( const KURL& url )
00269 {
00270 QDomDocument doc;
00271 QDomElement root = addElement( doc, doc, "DAV:", "propfind" );
00272 QDomElement prop = addElement( doc, root, "DAV:", "prop" );
00273 addElement( doc, prop, "urn:schemas:calendar:", "uid" );
00274 addElement( doc, prop, "urn:schemas:calendar:", "timezoneid" );
00275 addElement( doc, prop, "urn:schemas:calendar:", "timezone" );
00276 addElement( doc, prop, "urn:schemas:calendar:", "lastmodified" );
00277 addElement( doc, prop, "urn:schemas:calendar:", "organizer" );
00278 addElement( doc, prop, "urn:schemas:calendar:", "contact" );
00279 addElement( doc, prop, "urn:schemas:httpmail:", "to" );
00280 addElement( doc, prop, "urn:schemas:calendar:", "attendeestatus" );
00281 addElement( doc, prop, "urn:schemas:calendar:", "attendeerole" );
00282 addElement( doc, prop, "DAV:", "isreadonly" );
00283 addElement( doc, prop, "urn:schemas:calendar:", "instancetype" );
00284 addElement( doc, prop, "urn:schemas:calendar:", "created" );
00285 addElement( doc, prop, "urn:schemas:calendar:", "dtstart" );
00286 addElement( doc, prop, "urn:schemas:calendar:", "dtend" );
00287 addElement( doc, prop, "urn:schemas:calendar:", "alldayevent" );
00288 addElement( doc, prop, "urn:schemas:calendar:", "transparent" );
00289 addElement( doc, prop, "urn:schemas:httpmail:", "textdescription" );
00290 addElement( doc, prop, "urn:schemas:httpmail:", "subject" );
00291 addElement( doc, prop, "urn:schemas:calendar:", "location" );
00292 addElement( doc, prop, "urn:schemas:calendar:", "rrule" );
00293 addElement( doc, prop, "urn:schemas:calendar:", "exdate" );
00294 addElement( doc, prop, "urn:schemas:mailheader:", "sensitivity" );
00295 addElement( doc, prop, "urn:schemas:calendar:", "reminderoffset" );
00296
00297 addElement( doc, prop, "urn:schemas-microsoft-com:office:office", "Keywords" );
00298
00299
00300
00301
00302
00303
00304
00305 increaseDownloads();
00306
00307 KIO::DavJob* job = KIO::davPropFind( url, doc, "0", false );
00308 KIO::Scheduler::scheduleJob(job);
00309 job->setWindow( mWindow );
00310 job->addMetaData( "errorPage", "false" );
00311 connect( job, SIGNAL( result( KIO::Job * ) ), this, SLOT( slotPropFindResult( KIO::Job * ) ) );
00312 }
00313
00314 void ExchangeDownload::slotPropFindResult( KIO::Job * job )
00315 {
00316
00317
00318 int error = job->error();
00319 if ( error )
00320 {
00321 job->showErrorDialog( 0L );
00322 finishUp( ExchangeClient::CommunicationError, job );
00323 return;
00324 }
00325
00326 QDomDocument response = static_cast<KIO::DavJob *>( job )->response();
00327
00328
00329
00330 QDomElement prop = response.documentElement().namedItem( "response" ).namedItem( "propstat" ).namedItem( "prop" ).toElement();
00331
00332 KCal::Event* event = new KCal::Event();
00333
00334 QDomElement uidElement = prop.namedItem( "uid" ).toElement();
00335 if ( uidElement.isNull() ) {
00336 kdError() << "Error: no uid in Exchange server reply" << endl;
00337 finishUp( ExchangeClient::IllegalAppointmentError, "WebDAV server response:\n" + response.toString() );
00338 return;
00339 }
00340 event->setUid( uidElement.text() );
00341
00342
00343 QString timezoneid = prop.namedItem( "timezoneid" ).toElement().text();
00344
00345
00346 QString timezone = prop.namedItem( "timezone" ).toElement().text();
00347
00348
00349
00350 QString localTimeZoneId;
00351 if ( mCalendar ) {
00352 mFormat->setTimeZone( mCalendar->timeZoneId(), !mCalendar->isLocalTime() );
00353 localTimeZoneId = mCalendar->timeZoneId();
00354 } else {
00355 localTimeZoneId = "UTC";
00356
00357 }
00358
00359 QString lastModified = prop.namedItem( "lastmodified" ).toElement().text();
00360 QDateTime dt = utcAsZone( QDateTime::fromString( lastModified, Qt::ISODate ), localTimeZoneId );
00361 event->setLastModified( dt );
00362
00363
00364 QString organizer = prop.namedItem( "organizer" ).toElement().text();
00365 event->setOrganizer( organizer );
00366
00367
00368
00369 QString contact = prop.namedItem( "contact" ).toElement().text();
00370
00371
00372
00373
00374
00375 QString to = prop.namedItem( "to" ).toElement().text();
00376
00377 QStringList attn = QStringList::split( ",", to );
00378 QStringList::iterator it;
00379 for ( it = attn.begin(); it != attn.end(); ++it ) {
00380
00381 QString name = "";
00382
00383
00384
00385 }
00386
00387 QString readonly = prop.namedItem( "isreadonly" ).toElement().text();
00388 event->setReadOnly( readonly != "0" );
00389
00390
00391 QString created = prop.namedItem( "created" ).toElement().text();
00392 dt = utcAsZone( QDateTime::fromString( created, Qt::ISODate ), localTimeZoneId );
00393 event->setCreated( dt );
00394
00395
00396 QString dtstart = prop.namedItem( "dtstart" ).toElement().text();
00397 dt = utcAsZone( QDateTime::fromString( dtstart, Qt::ISODate ), localTimeZoneId );
00398 event->setDtStart( dt );
00399
00400
00401 QString alldayevent = prop.namedItem( "alldayevent" ).toElement().text();
00402 bool floats = alldayevent.toInt() != 0;
00403 event->setFloats( floats );
00404
00405
00406 QString dtend = prop.namedItem( "dtend" ).toElement().text();
00407 dt = utcAsZone( QDateTime::fromString( dtend, Qt::ISODate ), localTimeZoneId );
00408
00409 if ( floats ) dt = dt.addDays( -1 );
00410 event->setDtEnd( dt );
00411
00412
00413 QString transparent = prop.namedItem( "transparent" ).toElement().text();
00414 event->setTransparency( transparent.toInt() > 0 ? KCal::Event::Transparent
00415 : KCal::Event::Opaque );
00416
00417
00418 QString description = prop.namedItem( "textdescription" ).toElement().text();
00419 event->setDescription( description );
00420
00421
00422 QString subject = prop.namedItem( "subject" ).toElement().text();
00423 event->setSummary( subject );
00424
00425
00426 QString location = prop.namedItem( "location" ).toElement().text();
00427 event->setLocation( location );
00428
00429
00430 QString rrule = prop.namedItem( "rrule" ).toElement().text();
00431
00432 if ( ! rrule.isNull() ) {
00433
00434
00435 if ( ! mFormat->fromString( event->recurrence(), rrule ) ) {
00436 kdError() << "ERROR parsing rrule " << rrule << endl;
00437 }
00438 }
00439
00440 QDomElement keywords = prop.namedItem( "Keywords" ).toElement();
00441 QStringList categories;
00442 QDomNodeList list = keywords.elementsByTagNameNS( "xml:", "v" );
00443 for( uint i=0; i < list.count(); i++ ) {
00444 QDomElement item = list.item(i).toElement();
00445 categories.append( item.text() );
00446 }
00447 event->setCategories( categories );
00448
00449
00450
00451 QDomElement exdate = prop.namedItem( "exdate" ).toElement();
00452 KCal::DateList exdates;
00453 list = exdate.elementsByTagNameNS( "xml:", "v" );
00454 for( uint i=0; i < list.count(); i++ ) {
00455 QDomElement item = list.item(i).toElement();
00456 QDate date = utcAsZone( QDateTime::fromString( item.text(), Qt::ISODate ), localTimeZoneId ).date();
00457 exdates.append( date );
00458
00459 }
00460 event->setExDates( exdates );
00461
00462
00463
00464
00465
00466
00467 QString sensitivity = prop.namedItem( "sensitivity" ).toElement().text();
00468 if ( ! sensitivity.isNull() )
00469 switch( sensitivity.toInt() ) {
00470 case 0: event->setSecrecy( KCal::Incidence::SecrecyPublic ); break;
00471 case 1: event->setSecrecy( KCal::Incidence::SecrecyPrivate ); break;
00472 case 2: event->setSecrecy( KCal::Incidence::SecrecyPrivate ); break;
00473 case 3: event->setSecrecy( KCal::Incidence::SecrecyConfidential ); break;
00474 default: kdWarning() << "Unknown sensitivity: " << sensitivity << endl;
00475 }
00476
00477
00478
00479 QString reminder = prop.namedItem( "reminderoffset" ).toElement().text();
00480
00481 if ( ! reminder.isNull() ) {
00482
00483 KCal::Duration offset( - reminder.toInt() );
00484 KCal::Alarm* alarm = event->newAlarm();
00485 alarm->setStartOffset( offset );
00486 alarm->setEnabled( true );
00487
00488 }
00490
00492
00493
00495
00497
00498
00500
00501
00503
00504
00506
00507
00513
00514
00515
00516
00517
00519
00520
00521
00522
00523
00524 if ( mCalendar ) {
00525 KCal::Event* oldEvent = mCalendar->event( event->uid() );
00526 if ( oldEvent ) {
00527 kdWarning() << "Already got his event, keeping old version..." << endl;
00528
00529
00530 } else {
00531 mCalendar->addEvent( event );
00532 }
00533 } else {
00534 emit gotEvent( event, static_cast<KIO::DavJob *>( job )->url() );
00535
00536 }
00537
00538 decreaseDownloads();
00539 }
00540
00541 void ExchangeDownload::increaseDownloads()
00542 {
00543 mDownloadsBusy++;
00544 emit startDownload();
00545 }
00546
00547 void ExchangeDownload::decreaseDownloads()
00548 {
00549 mDownloadsBusy--;
00550
00551 emit finishDownload();
00552 if ( mDownloadsBusy == 0 ) {
00553 kdDebug() << "All downloads finished" << endl;
00554 finishUp( ExchangeClient::ResultOK );
00555 }
00556 }
00557
00558 void ExchangeDownload::finishUp( int result, const QString& moreInfo )
00559 {
00560 if ( mCalendar ) mCalendar->setModified( true );
00561
00562 if ( mProgress ) {
00563 disconnect( this, 0, mProgress, 0 );
00564 disconnect( mProgress, 0, this, 0 );
00565 mProgress->delayedDestruct();
00566 }
00567
00568
00569
00570
00571 emit finished( this, result, moreInfo );
00572
00573 }
00574
00575 void ExchangeDownload::finishUp( int result, KIO::Job* job )
00576 {
00577 finishUp( result, QString("WebDAV job error code = ") + QString::number( job->error() ) + ";\n" + "\"" + job->errorString() + "\"" );
00578 }
00579
00580 #include "exchangedownload.moc"