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