• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.11.3 API Reference
  • KDE Home
  • Contact Us
 

akonadi

  • akonadi
  • calendar
freebusymanager.cpp
1 /*
2  Copyright (c) 2011 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
3  Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
4 
5  This library is free software; you can redistribute it and/or modify it
6  under the terms of the GNU Library General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or (at your
8  option) any later version.
9 
10  This library is distributed in the hope that it will be useful, but WITHOUT
11  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13  License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to the
17  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  02110-1301, USA.
19 */
20 
21 #include "freebusymanager.h"
22 #include "freebusymanager_p.h"
23 #include "freebusydownloadjob_p.h"
24 #include "mailscheduler_p.h"
25 #include "publishdialog.h"
26 #include "calendarsettings.h"
27 #include "utils_p.h"
28 
29 #include <akonadi/agentinstance.h>
30 #include <akonadi/agentmanager.h>
31 #include <akonadi/contact/contactsearchjob.h>
32 
33 #include <kcalcore/event.h>
34 #include <kcalcore/freebusy.h>
35 #include <kcalcore/person.h>
36 
37 #include <KDebug>
38 #include <KMessageBox>
39 #include <KStandardDirs>
40 #include <KTemporaryFile>
41 #include <KUrl>
42 #include <KIO/Job>
43 #include <KIO/JobUiDelegate>
44 #include <KIO/NetAccess>
45 #include <KLocalizedString>
46 
47 #include <QDir>
48 #include <QFile>
49 #include <QRegExp>
50 #include <QTextStream>
51 #include <QTimer>
52 #include <QTimerEvent>
53 
54 using namespace Akonadi;
55 using namespace KCalCore;
56 
58 
59 KUrl replaceVariablesUrl( const KUrl &url, const QString &email )
60 {
61  QString emailName;
62  QString emailHost;
63 
64  const int atPos = email.indexOf( '@' );
65  if ( atPos >= 0 ) {
66  emailName = email.left( atPos );
67  emailHost = email.mid( atPos + 1 );
68  }
69 
70  QString saveStr = url.path();
71  saveStr.replace( QRegExp( "%[Ee][Mm][Aa][Ii][Ll]%" ), email );
72  saveStr.replace( QRegExp( "%[Nn][Aa][Mm][Ee]%" ), emailName );
73  saveStr.replace( QRegExp( "%[Ss][Ee][Rr][Vv][Ee][Rr]%" ), emailHost );
74 
75  KUrl retUrl( url );
76  retUrl.setPath( saveStr );
77  return retUrl;
78 }
79 
80 // We need this function because using KIO::NetAccess::exists()
81 // is useless for the http and https protocols. And getting back
82 // arbitrary data is also useless because a server can respond back
83 // with a "no such document" page. So we need smart checking.
84 FbCheckerJob::FbCheckerJob( const QList<KUrl> &urlsToCheck, QObject *parent )
85  : KJob( parent ),
86  mUrlsToCheck(urlsToCheck)
87 {
88 }
89 
90 void FbCheckerJob::start()
91 {
92  checkNextUrl();
93 }
94 
95 void FbCheckerJob::checkNextUrl()
96 {
97  if ( mUrlsToCheck.isEmpty() ) {
98  kDebug() << "No fb file found";
99  setError( KJob::UserDefinedError );
100  emitResult();
101  return;
102  }
103  const KUrl url = mUrlsToCheck.takeFirst();
104 
105  mData.clear();
106  KIO::TransferJob *job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
107  connect( job, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(dataReceived(KIO::Job*, QByteArray)) );
108  connect( job, SIGNAL(result(KJob*)), this, SLOT(onGetJobFinished(KJob*)) );
109 }
110 
111 void FbCheckerJob::dataReceived( KIO::Job*, const QByteArray &data )
112 {
113  mData.append( data );
114 }
115 
116 void FbCheckerJob::onGetJobFinished( KJob *job )
117 {
118  KIO::TransferJob *transferJob = static_cast<KIO::TransferJob*>( job );
119  if ( mData.contains( "BEGIN:VCALENDAR" ) ) {
120  kDebug() << "found freebusy";
121  mValidUrl = transferJob->url();
122  emitResult();
123  } else {
124  checkNextUrl();
125  }
126 }
127 
128 KUrl FbCheckerJob::validUrl() const
129 {
130  return mValidUrl;
131 }
132 
133 
135 
136 FreeBusyManagerPrivate::FreeBusyProviderRequest::FreeBusyProviderRequest( const QString &provider )
137  : mRequestStatus( NotStarted ), mInterface( 0 )
138 {
139  mInterface =
140  QSharedPointer<QDBusInterface>(
141  new QDBusInterface( "org.freedesktop.Akonadi.Resource." + provider,
142  "/FreeBusyProvider",
143  "org.freedesktop.Akonadi.Resource.FreeBusyProvider" ) );
144 }
145 
147 
148 FreeBusyManagerPrivate::FreeBusyProvidersRequestsQueue::FreeBusyProvidersRequestsQueue(
149  const QString &start, const QString &end )
150  : mHandlersCount( 0 ), mResultingFreeBusy( 0 )
151 {
152  KDateTime startDate, endDate;
153 
154  if ( !start.isEmpty() ) {
155  mStartTime = start;
156  startDate = KDateTime::fromString( start );
157  } else {
158  // Set the start of the period to today 00:00:00
159  startDate = KDateTime( KDateTime::currentLocalDate() );
160  mStartTime = startDate.toString();
161  }
162 
163  if ( !end.isEmpty() ) {
164  mEndTime = end;
165  endDate = KDateTime::fromString( end );
166  } else {
167  // Set the end of the period to today + 14 days.
168  endDate = KDateTime( KDateTime::currentLocalDate() ).addDays( 14 );
169  mEndTime = endDate.toString();
170  }
171 
172  mResultingFreeBusy = KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( startDate, endDate ) );
173 }
174 
175 FreeBusyManagerPrivate::FreeBusyProvidersRequestsQueue::FreeBusyProvidersRequestsQueue(
176  const KDateTime &start, const KDateTime &end )
177  : mHandlersCount( 0 ), mResultingFreeBusy( 0 )
178  {
179  mStartTime = start.toString();
180  mEndTime = end.toString();
181  mResultingFreeBusy = KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( start, end ) );
182  }
183 
185 
186 FreeBusyManagerPrivate::FreeBusyManagerPrivate( FreeBusyManager *q )
187  : QObject(),
188  q_ptr( q ),
189  mTimerID( 0 ),
190  mUploadingFreeBusy( false ),
191  mBrokenUrl( false ),
192  mParentWidgetForRetrieval( 0 )
193 {
194  connect( this, SIGNAL(freeBusyUrlRetrieved(QString,KUrl)),
195  SLOT(finishProcessRetrieveQueue(QString,KUrl)) );
196 }
197 
198 QString FreeBusyManagerPrivate::freeBusyDir() const
199 {
200  return KStandardDirs::locateLocal( "data", QLatin1String( "korganizer/freebusy" ) );
201 }
202 
203 void FreeBusyManagerPrivate::checkFreeBusyUrl()
204 {
205  KUrl targetURL( CalendarSettings::self()->freeBusyPublishUrl() );
206  mBrokenUrl = targetURL.isEmpty() || !targetURL.isValid();
207 }
208 
209 static QString configFile()
210 {
211  static QString file = KStandardDirs::locateLocal( "data", QLatin1String( "korganizer/freebusyurls" ) );
212  return file;
213 }
214 
215 void FreeBusyManagerPrivate::fetchFreeBusyUrl( const QString &email )
216 {
217  // First check if there is a specific FB url for this email
218  KConfig cfg( configFile() );
219  KConfigGroup group = cfg.group( email );
220  QString url = group.readEntry( QLatin1String( "url" ) );
221  if ( !url.isEmpty() ) {
222  kDebug() << "Found cached url:" << url;
223  KUrl cachedUrl( url );
224  if ( Akonadi::CalendarUtils::thatIsMe( email ) ) {
225  cachedUrl.setUser( CalendarSettings::self()->freeBusyRetrieveUser() );
226  cachedUrl.setPass( CalendarSettings::self()->freeBusyRetrievePassword() );
227  }
228  emit freeBusyUrlRetrieved( email, replaceVariablesUrl( cachedUrl, email ) );
229  return;
230  }
231  // Try with the url configured by preferred email in kcontactmanager
232  Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob();
233  job->setQuery( Akonadi::ContactSearchJob::Email, email );
234  job->setProperty( "contactEmail", QVariant::fromValue( email ) );
235  connect( job, SIGNAL(result(KJob*)), this, SLOT(contactSearchJobFinished(KJob*)) );
236  job->start();
237 }
238 
239 void FreeBusyManagerPrivate::contactSearchJobFinished( KJob *_job )
240 {
241  const QString email = _job->property( "contactEmail" ).toString();
242 
243  if ( _job->error() ) {
244  kError() << "Error while searching for contact: "
245  << _job->errorString() << ", email = " << email;
246  emit freeBusyUrlRetrieved( email, KUrl() );
247  return;
248  }
249 
250  Akonadi::ContactSearchJob *job = qobject_cast<Akonadi::ContactSearchJob*>( _job );
251  KConfig cfg( configFile() );
252  KConfigGroup group = cfg.group( email );
253  QString url = group.readEntry( QLatin1String( "url" ) );
254 
255  const KABC::Addressee::List contacts = job->contacts();
256  foreach ( const KABC::Addressee &contact, contacts ) {
257  const QString pref = contact.preferredEmail();
258  if ( !pref.isEmpty() && pref != email ) {
259  group = cfg.group( pref );
260  url = group.readEntry ( "url" );
261  kDebug() << "Preferred email of" << email << "is" << pref;
262  if ( !url.isEmpty() ) {
263  kDebug() << "Taken url from preferred email:" << url;
264  emit freeBusyUrlRetrieved( email, replaceVariablesUrl( KUrl( url ), email ) );
265  return;
266  }
267  }
268  }
269  // None found. Check if we do automatic FB retrieving then
270  if ( !CalendarSettings::self()->freeBusyRetrieveAuto() ) {
271  // No, so no FB list here
272  kDebug() << "No automatic retrieving";
273  emit freeBusyUrlRetrieved( email, KUrl() );
274  return;
275  }
276 
277  // Sanity check: Don't download if it's not a correct email
278  // address (this also avoids downloading for "(empty email)").
279  int emailpos = email.indexOf( QLatin1Char( '@' ) );
280  if( emailpos == -1 ) {
281  kWarning() << "No '@' found in" << email;
282  emit freeBusyUrlRetrieved( email, KUrl() );
283  return;
284  }
285 
286  const QString emailHost = email.mid( emailpos + 1 );
287 
288  // Build the URL
289  if ( CalendarSettings::self()->freeBusyCheckHostname() ) {
290  // Don't try to fetch free/busy data for users not on the specified servers
291  // This tests if the hostnames match, or one is a subset of the other
292  const QString hostDomain = KUrl( CalendarSettings::self()->freeBusyRetrieveUrl() ).host();
293  if ( hostDomain != emailHost &&
294  !hostDomain.endsWith( QLatin1Char( '.' ) + emailHost ) &&
295  !emailHost.endsWith( QLatin1Char( '.' ) + hostDomain ) ) {
296  // Host names do not match
297  kDebug() << "Host '" << hostDomain << "' doesn't match email '" << email << '\'';
298  emit freeBusyUrlRetrieved( email, KUrl() );
299  return;
300  }
301  }
302 
303  if ( CalendarSettings::self()->freeBusyRetrieveUrl().contains( QRegExp( "\\.[xiv]fb$" ) ) ) {
304  // user specified a fullpath
305  // do variable string replacements to the URL (MS Outlook style)
306  const KUrl sourceUrl( CalendarSettings::self()->freeBusyRetrieveUrl() );
307  KUrl fullpathURL = replaceVariablesUrl( sourceUrl, email );
308 
309  // set the User and Password part of the URL
310  fullpathURL.setUser( CalendarSettings::self()->freeBusyRetrieveUser() );
311  fullpathURL.setPass( CalendarSettings::self()->freeBusyRetrievePassword() );
312 
313  // no need to cache this URL as this is pretty fast to get from the config value.
314  // return the fullpath URL
315  kDebug() << "Found url. email=" << email << "; url=" << fullpathURL;
316  emit freeBusyUrlRetrieved( email, fullpathURL );
317  return;
318  }
319 
320  // else we search for a fb file in the specified URL with known possible extensions
321  const QStringList extensions = QStringList() << "xfb" << "ifb" << "vfb";
322  QStringList::ConstIterator ext;
323  QList<KUrl> urlsToCheck;
324  for ( ext = extensions.constBegin(); ext != extensions.constEnd(); ++ext ) {
325  // build a url for this extension
326  const KUrl sourceUrl = CalendarSettings::self()->freeBusyRetrieveUrl();
327  KUrl dirURL = replaceVariablesUrl( sourceUrl, email );
328  if ( CalendarSettings::self()->freeBusyFullDomainRetrieval() ) {
329  dirURL.addPath( email + '.' + (*ext) );
330  } else {
331  // Cut off everything left of the @ sign to get the user name.
332  const QString emailName = email.left( emailpos );
333  dirURL.addPath( emailName + '.' + (*ext ) );
334  }
335  dirURL.setUser( CalendarSettings::self()->freeBusyRetrieveUser() );
336  dirURL.setPass( CalendarSettings::self()->freeBusyRetrievePassword() );
337  urlsToCheck << dirURL;
338  }
339  KJob *checkerJob = new FbCheckerJob( urlsToCheck, this );
340  checkerJob->setProperty( "email", email );
341  connect( checkerJob, SIGNAL(result(KJob*)), this, SLOT(fbCheckerJobFinished(KJob*)) );
342  checkerJob->start();
343 }
344 
345 void FreeBusyManagerPrivate::fbCheckerJobFinished( KJob *job )
346 {
347  const QString email = job->property( "email" ).toString();
348  if ( !job->error() ) {
349  FbCheckerJob *checkerJob = static_cast<FbCheckerJob*>( job );
350  KUrl dirURL = checkerJob->validUrl();
351  // write the URL to the cache
352  KConfig cfg( configFile() );
353  KConfigGroup group = cfg.group( email );
354  group.writeEntry( "url", dirURL.prettyUrl() ); // prettyURL() does not write user nor password
355  kDebug() << "Found url email=" << email << "; url=" << dirURL;
356  emit freeBusyUrlRetrieved( email, dirURL );
357  } else {
358  kDebug() << "Returning invalid url";
359  emit freeBusyUrlRetrieved( email, KUrl() );
360  }
361 }
362 
363 QString FreeBusyManagerPrivate::freeBusyToIcal( const KCalCore::FreeBusy::Ptr &freebusy )
364 {
365  return mFormat.createScheduleMessage( freebusy, KCalCore::iTIPPublish );
366 }
367 
368 KCalCore::FreeBusy::Ptr FreeBusyManagerPrivate::iCalToFreeBusy( const QByteArray &freeBusyData )
369 {
370  const QString freeBusyVCal( QString::fromUtf8( freeBusyData ) );
371  KCalCore::FreeBusy::Ptr fb = mFormat.parseFreeBusy( freeBusyVCal );
372 
373  if ( !fb ) {
374  kDebug() << "Error parsing free/busy";
375  kDebug() << freeBusyVCal;
376  }
377 
378  return fb;
379 }
380 
381 KCalCore::FreeBusy::Ptr FreeBusyManagerPrivate::ownerFreeBusy()
382 {
383  KDateTime start = KDateTime::currentUtcDateTime();
384  KDateTime end = start.addDays( CalendarSettings::self()->freeBusyPublishDays() );
385 
386  KCalCore::Event::List events = mCalendar ? mCalendar->rawEvents( start.date(), end.date() ) : KCalCore::Event::List();
387  KCalCore::FreeBusy::Ptr freebusy ( new KCalCore::FreeBusy( events, start, end ) );
388  freebusy->setOrganizer( KCalCore::Person::Ptr(
389  new KCalCore::Person( Akonadi::CalendarUtils::fullName(),
390  Akonadi::CalendarUtils::email() ) ) );
391  return freebusy;
392 }
393 
394 QString FreeBusyManagerPrivate::ownerFreeBusyAsString()
395 {
396  return freeBusyToIcal( ownerFreeBusy() );
397 }
398 
399 void FreeBusyManagerPrivate::processFreeBusyDownloadResult( KJob *_job )
400 {
401  Q_Q( FreeBusyManager );
402 
403  FreeBusyDownloadJob *job = qobject_cast<FreeBusyDownloadJob *>( _job );
404  Q_ASSERT( job );
405  if ( job->error() ) {
406  kError() << "Error downloading freebusy" << _job->errorString();
407  KMessageBox::sorry(
408  mParentWidgetForRetrieval,
409  i18n( "Failed to download free/busy data from: %1\nReason: %2",
410  job->url().prettyUrl(), job->errorText() ),
411  i18n( "Free/busy retrieval error" ) );
412 
413  // TODO: Ask for a retry? (i.e. queue the email again when the user wants it).
414 
415  // Make sure we don't fill up the map with unneeded data on failures.
416  mFreeBusyUrlEmailMap.take( job->url() );
417  } else {
418  KCalCore::FreeBusy::Ptr fb = iCalToFreeBusy( job->rawFreeBusyData() );
419 
420  Q_ASSERT( mFreeBusyUrlEmailMap.contains( job->url() ) );
421  const QString email = mFreeBusyUrlEmailMap.take( job->url() );
422 
423  if ( fb ) {
424  KCalCore::Person::Ptr p = fb->organizer();
425  p->setEmail( email );
426  q->saveFreeBusy( fb, p );
427  kDebug() << "Freebusy retrieved for " << email;
428  emit q->freeBusyRetrieved( fb, email );
429  } else {
430  kError() << "Error downloading freebusy, invalid fb.";
431  KMessageBox::sorry(
432  mParentWidgetForRetrieval,
433  i18n( "Failed to parse free/busy information that was retrieved from: %1",
434  job->url().prettyUrl() ),
435  i18n( "Free/busy retrieval error" ) );
436  }
437  }
438 
439  // When downloading failed or finished, start a job for the next one in the
440  // queue if needed.
441  processRetrieveQueue();
442 }
443 
444 void FreeBusyManagerPrivate::processFreeBusyUploadResult( KJob *_job )
445 {
446  KIO::FileCopyJob *job = static_cast<KIO::FileCopyJob *>( _job );
447  if ( job->error() ) {
448  KMessageBox::sorry(
449  job->ui()->window(),
450  i18n( "<qt><p>The software could not upload your free/busy list to "
451  "the URL '%1'. There might be a problem with the access "
452  "rights, or you specified an incorrect URL. The system said: "
453  "<em>%2</em>.</p>"
454  "<p>Please check the URL or contact your system administrator."
455  "</p></qt>", job->destUrl().prettyUrl(),
456  job->errorString() ) );
457  }
458  // Delete temp file
459  KUrl src = job->srcUrl();
460  Q_ASSERT( src.isLocalFile() );
461  if ( src.isLocalFile() ) {
462  QFile::remove( src.toLocalFile() );
463  }
464  mUploadingFreeBusy = false;
465 }
466 
467 void FreeBusyManagerPrivate::processRetrieveQueue()
468 {
469  if ( mRetrieveQueue.isEmpty() ) {
470  return;
471  }
472 
473  QString email = mRetrieveQueue.takeFirst();
474 
475  // First, try to find all agents that are free-busy providers
476  QStringList providers = getFreeBusyProviders();
477  kDebug() << "Got the following FreeBusy providers: " << providers;
478 
479  // If some free-busy providers were found let's query them first and ask them
480  // if they manage the free-busy information for the email address we have.
481  if ( !providers.isEmpty() ) {
482  queryFreeBusyProviders( providers, email );
483  } else {
484  fetchFreeBusyUrl( email );
485  }
486 
487  return;
488 }
489 
490 void FreeBusyManagerPrivate::finishProcessRetrieveQueue( const QString &email,
491  const KUrl &freeBusyUrlForEmail )
492 {
493  Q_Q( FreeBusyManager );
494 
495  if ( !freeBusyUrlForEmail.isValid() ) {
496  kDebug() << "Invalid FreeBusy URL" << freeBusyUrlForEmail.prettyUrl() << email;
497  return;
498  }
499 
500  if ( mFreeBusyUrlEmailMap.contains( freeBusyUrlForEmail ) ) {
501  kDebug() << "Download already in progress for " << freeBusyUrlForEmail;
502  return;
503  }
504 
505  mFreeBusyUrlEmailMap.insert( freeBusyUrlForEmail, email );
506 
507  FreeBusyDownloadJob *job = new FreeBusyDownloadJob( freeBusyUrlForEmail, mParentWidgetForRetrieval );
508  q->connect( job, SIGNAL(result(KJob*)), SLOT(processFreeBusyDownloadResult(KJob*)) );
509  job->start();
510 }
511 
512 void FreeBusyManagerPrivate::uploadFreeBusy()
513 {
514  Q_Q( FreeBusyManager );
515 
516  // user has automatic uploading disabled, bail out
517  if ( !CalendarSettings::self()->freeBusyPublishAuto() ||
518  CalendarSettings::self()->freeBusyPublishUrl().isEmpty() ) {
519  return;
520  }
521 
522  if( mTimerID != 0 ) {
523  // A timer is already running, so we don't need to do anything
524  return;
525  }
526 
527  int now = static_cast<int>( QDateTime::currentDateTime().toTime_t() );
528  int eta = static_cast<int>( mNextUploadTime.toTime_t() ) - now;
529 
530  if ( !mUploadingFreeBusy ) {
531  // Not currently uploading
532  if ( mNextUploadTime.isNull() ||
533  QDateTime::currentDateTime() > mNextUploadTime ) {
534  // No uploading have been done in this session, or delay time is over
535  q->publishFreeBusy();
536  return;
537  }
538 
539  // We're in the delay time and no timer is running. Start one
540  if ( eta <= 0 ) {
541  // Sanity check failed - better do the upload
542  q->publishFreeBusy();
543  return;
544  }
545  } else {
546  // We are currently uploading the FB list. Start the timer
547  if ( eta <= 0 ) {
548  kDebug() << "This shouldn't happen! eta <= 0";
549  eta = 10; // whatever
550  }
551  }
552 
553  // Start the timer
554  mTimerID = q->startTimer( eta * 1000 );
555 
556  if ( mTimerID == 0 ) {
557  // startTimer failed - better do the upload
558  q->publishFreeBusy();
559  }
560 }
561 
562 QStringList FreeBusyManagerPrivate::getFreeBusyProviders() const
563 {
564  QStringList providers;
565  Akonadi::AgentInstance::List agents = Akonadi::AgentManager::self()->instances();
566  foreach ( const Akonadi::AgentInstance &agent, agents ) {
567  if ( agent.type().capabilities().contains( QLatin1String( "FreeBusyProvider" ) ) ) {
568  providers << agent.identifier();
569  }
570  }
571  return providers;
572 }
573 
574 void FreeBusyManagerPrivate::queryFreeBusyProviders( const QStringList &providers,
575  const QString &email )
576 {
577  if ( !mProvidersRequestsByEmail.contains( email ) ) {
578  mProvidersRequestsByEmail[email] = FreeBusyProvidersRequestsQueue();
579  }
580 
581  foreach ( const QString &provider, providers ) {
582  FreeBusyProviderRequest request( provider );
583 
584  connect( request.mInterface.data(), SIGNAL(handlesFreeBusy(QString,bool)),
585  this, SLOT(onHandlesFreeBusy(QString,bool)) );
586 
587  request.mInterface->call( "canHandleFreeBusy", email );
588  request.mRequestStatus = FreeBusyProviderRequest::HandlingRequested;
589  mProvidersRequestsByEmail[email].mRequests << request;
590  }
591 }
592 
593 void FreeBusyManagerPrivate::queryFreeBusyProviders( const QStringList &providers,
594  const QString &email,
595  const KDateTime &start,
596  const KDateTime &end )
597 {
598  if ( !mProvidersRequestsByEmail.contains( email ) ) {
599  mProvidersRequestsByEmail[email] = FreeBusyProvidersRequestsQueue( start, end );
600  }
601 
602  queryFreeBusyProviders( providers, email );
603 }
604 
605 void FreeBusyManagerPrivate::onHandlesFreeBusy( const QString &email, bool handles )
606 {
607  if ( !mProvidersRequestsByEmail.contains( email ) ) {
608  return;
609  }
610 
611  QDBusInterface *iface = dynamic_cast<QDBusInterface*>( sender() );
612  if ( !iface ) {
613  return;
614  }
615 
616  FreeBusyProvidersRequestsQueue *queue = &mProvidersRequestsByEmail[email];
617  QString respondingService = iface->service();
618  kDebug() << respondingService << "responded to our FreeBusy request:" << handles;
619  int requestIndex = -1;
620 
621  for ( int i = 0; i < queue->mRequests.size(); ++i ) {
622  if ( queue->mRequests.at( i ).mInterface->service() == respondingService ) {
623  requestIndex = i;
624  }
625  }
626 
627  if ( requestIndex == -1 ) {
628  return;
629  }
630 
631  disconnect( iface, SIGNAL(handlesFreeBusy(QString,bool)),
632  this, SLOT(onHandlesFreeBusy(QString,bool)) );
633 
634  if ( !handles ) {
635  queue->mRequests.removeAt( requestIndex );
636  // If no more requests are left and no handler responded
637  // then fall back to the URL mechanism
638  if ( queue->mRequests.isEmpty() && queue->mHandlersCount == 0 ) {
639  mProvidersRequestsByEmail.remove( email );
640  fetchFreeBusyUrl( email );
641  }
642  } else {
643  ++queue->mHandlersCount;
644  connect( iface, SIGNAL(freeBusyRetrieved(QString,QString,bool,QString)),
645  this, SLOT(onFreeBusyRetrieved(QString,QString,bool,QString)) );
646  iface->call( "retrieveFreeBusy", email, queue->mStartTime, queue->mEndTime );
647  queue->mRequests[requestIndex].mRequestStatus = FreeBusyProviderRequest::FreeBusyRequested;
648  }
649 }
650 
651 void FreeBusyManagerPrivate::processMailSchedulerResult( Akonadi::Scheduler::Result result,
652  const QString &errorMsg )
653 {
654  if ( result == Scheduler::ResultSuccess ) {
655  KMessageBox::information(
656  mParentWidgetForMailling,
657  i18n( "The free/busy information was successfully sent." ),
658  i18n( "Sending Free/Busy" ),
659  "FreeBusyPublishSuccess" );
660  } else {
661  KMessageBox::error( mParentWidgetForMailling,
662  i18n( "Unable to publish the free/busy data: %1", errorMsg ) );
663  }
664 
665  sender()->deleteLater();
666 }
667 
668 void FreeBusyManagerPrivate::onFreeBusyRetrieved( const QString &email,
669  const QString &freeBusy,
670  bool success,
671  const QString &errorText )
672 {
673  Q_Q( FreeBusyManager );
674  Q_UNUSED( errorText );
675 
676  if ( !mProvidersRequestsByEmail.contains( email ) ) {
677  return;
678  }
679 
680  QDBusInterface *iface = dynamic_cast<QDBusInterface*>( sender() );
681  if ( !iface ) {
682  return;
683  }
684 
685  FreeBusyProvidersRequestsQueue *queue = &mProvidersRequestsByEmail[email];
686  QString respondingService = iface->service();
687  int requestIndex = -1;
688 
689  for ( int i = 0; i < queue->mRequests.size(); ++i ) {
690  if ( queue->mRequests.at( i ).mInterface->service() == respondingService ) {
691  requestIndex = i;
692  }
693  }
694 
695  if ( requestIndex == -1 ) {
696  return;
697  }
698 
699  disconnect( iface, SIGNAL(freeBusyRetrieved(QString,QString,bool,QString)),
700  this, SLOT(onFreeBusyRetrieved(QString,QString,bool,QString)) );
701 
702  queue->mRequests.removeAt( requestIndex );
703 
704  if ( success ) {
705  KCalCore::FreeBusy::Ptr fb = iCalToFreeBusy( freeBusy.toUtf8() );
706  if ( !fb ) {
707  --queue->mHandlersCount;
708  } else {
709  queue->mResultingFreeBusy->merge( fb );
710  }
711  }
712 
713  if ( queue->mRequests.isEmpty() ) {
714  if ( queue->mHandlersCount == 0 ) {
715  fetchFreeBusyUrl( email );
716  } else {
717  emit q->freeBusyRetrieved( queue->mResultingFreeBusy, email );
718  }
719  mProvidersRequestsByEmail.remove( email );
720  }
721 }
722 
724 
725 namespace Akonadi {
726 
727 struct FreeBusyManagerStatic
728 {
729  FreeBusyManager instance;
730 };
731 
732 }
733 
734 K_GLOBAL_STATIC( FreeBusyManagerStatic, sManagerInstance )
735 
736 FreeBusyManager::FreeBusyManager() : d_ptr( new FreeBusyManagerPrivate( this ) )
737 {
738  setObjectName( QLatin1String( "FreeBusyManager" ) );
739  connect( CalendarSettings::self(), SIGNAL(configChanged()), SLOT(checkFreeBusyUrl()) );
740 }
741 
742 FreeBusyManager::~FreeBusyManager()
743 {
744  delete d_ptr;
745 }
746 
747 FreeBusyManager *FreeBusyManager::self()
748 {
749  return &sManagerInstance->instance;
750 }
751 
752 void FreeBusyManager::setCalendar( const Akonadi::ETMCalendar::Ptr &c )
753 {
754  Q_D( FreeBusyManager );
755 
756  if ( d->mCalendar ) {
757  disconnect( d->mCalendar.data(), SIGNAL(calendarChanged()) );
758  }
759 
760  d->mCalendar = c;
761  if ( d->mCalendar ) {
762  d->mFormat.setTimeSpec( d->mCalendar->timeSpec() );
763  connect( d->mCalendar.data(), SIGNAL(calendarChanged()), SLOT(uploadFreeBusy()) );
764  }
765 
766  // Lets see if we need to update our published
767  QTimer::singleShot( 0, this, SLOT(uploadFreeBusy()) );
768 }
769 
774 void FreeBusyManager::publishFreeBusy( QWidget *parentWidget )
775 {
776  Q_D( FreeBusyManager );
777  // Already uploading? Skip this one then.
778  if ( d->mUploadingFreeBusy ) {
779  return;
780  }
781 
782  // No calendar set yet? Don't upload to prevent losing published information that
783  // might still be valid.
784  if ( !d->mCalendar ) {
785  return;
786  }
787 
788  KUrl targetURL( CalendarSettings::self()->freeBusyPublishUrl() );
789  if ( targetURL.isEmpty() ) {
790  KMessageBox::sorry(
791  parentWidget,
792  i18n( "<qt><p>No URL configured for uploading your free/busy list. "
793  "Please set it in KOrganizer's configuration dialog, on the "
794  "\"Free/Busy\" page.</p>"
795  "<p>Contact your system administrator for the exact URL and the "
796  "account details.</p></qt>" ),
797  i18n( "No Free/Busy Upload URL" ) );
798  return;
799  }
800 
801  if ( d->mBrokenUrl ) {
802  // Url is invalid, don't try again
803  return;
804  }
805  if ( !targetURL.isValid() ) {
806  KMessageBox::sorry(
807  parentWidget,
808  i18n( "<qt>The target URL '%1' provided is invalid.</qt>", targetURL.prettyUrl() ),
809  i18n( "Invalid URL" ) );
810  d->mBrokenUrl = true;
811  return;
812  }
813  targetURL.setUser( CalendarSettings::self()->freeBusyPublishUser() );
814  targetURL.setPass( CalendarSettings::self()->freeBusyPublishPassword() );
815 
816  d->mUploadingFreeBusy = true;
817 
818  // If we have a timer running, it should be stopped now
819  if ( d->mTimerID != 0 ) {
820  killTimer( d->mTimerID );
821  d->mTimerID = 0;
822  }
823 
824  // Save the time of the next free/busy uploading
825  d->mNextUploadTime = QDateTime::currentDateTime();
826  if ( CalendarSettings::self()->freeBusyPublishDelay() > 0 ) {
827  d->mNextUploadTime =
828  d->mNextUploadTime.addSecs( CalendarSettings::self()->freeBusyPublishDelay() * 60 );
829  }
830 
831  QString messageText = d->ownerFreeBusyAsString();
832 
833  // We need to massage the list a bit so that Outlook understands
834  // it.
835  messageText = messageText.replace( QRegExp( QLatin1String( "ORGANIZER\\s*:MAILTO:" ) ),
836  QLatin1String( "ORGANIZER:" ) );
837 
838  // Create a local temp file and save the message to it
839  KTemporaryFile tempFile;
840  tempFile.setAutoRemove( false );
841  if ( tempFile.open() ) {
842  QTextStream textStream ( &tempFile );
843  textStream << messageText;
844  textStream.flush();
845 
846 #if 0
847  QString defaultEmail = KOCore()::self()->email();
848  QString emailHost = defaultEmail.mid( defaultEmail.indexOf( '@' ) + 1 );
849 
850  // Put target string together
851  KUrl targetURL;
852  if( CalendarSettings::self()->publishKolab() ) {
853  // we use Kolab
854  QString server;
855  if ( CalendarSettings::self()->publishKolabServer() == QLatin1String( "%SERVER%" ) ||
856  CalendarSettings::self()->publishKolabServer().isEmpty() ) {
857  server = emailHost;
858  } else {
859  server = CalendarSettings::self()->publishKolabServer();
860  }
861 
862  targetURL.setProtocol( "webdavs" );
863  targetURL.setHost( server );
864 
865  QString fbname = CalendarSettings::self()->publishUserName();
866  int at = fbname.indexOf( '@' );
867  if ( at > 1 && fbname.length() > (uint)at ) {
868  fbname = fbname.left(at);
869  }
870  targetURL.setPath( "/freebusy/" + fbname + ".ifb" );
871  targetURL.setUser( CalendarSettings::self()->publishUserName() );
872  targetURL.setPass( CalendarSettings::self()->publishPassword() );
873  } else {
874  // we use something else
875  targetURL = CalendarSettings::self()->+publishAnyURL().replace( "%SERVER%", emailHost );
876  targetURL.setUser( CalendarSettings::self()->publishUserName() );
877  targetURL.setPass( CalendarSettings::self()->publishPassword() );
878  }
879 #endif
880 
881  KUrl src;
882  src.setPath( tempFile.fileName() );
883 
884  kDebug() << targetURL;
885 
886  KIO::Job *job = KIO::file_copy( src, targetURL, -1, KIO::Overwrite | KIO::HideProgressInfo );
887 
888  job->ui()->setWindow( parentWidget );
889 
890  connect( job, SIGNAL(result(KJob*)), SLOT(slotUploadFreeBusyResult(KJob*)) );
891  }
892 }
893 
894 void FreeBusyManager::mailFreeBusy( int daysToPublish, QWidget *parentWidget )
895 {
896  Q_D( FreeBusyManager );
897  // No calendar set yet?
898  if ( !d->mCalendar ) {
899  return;
900  }
901 
902  KDateTime start = KDateTime::currentUtcDateTime().toTimeSpec( d->mCalendar->timeSpec() );
903  KDateTime end = start.addDays( daysToPublish );
904 
905  KCalCore::Event::List events = d->mCalendar->rawEvents( start.date(), end.date() );
906 
907  FreeBusy::Ptr freebusy( new FreeBusy( events, start, end ) );
908  freebusy->setOrganizer( Person::Ptr(
909  new Person( Akonadi::CalendarUtils::fullName(),
910  Akonadi::CalendarUtils::email() ) ) );
911 
912  QPointer<PublishDialog> publishdlg = new PublishDialog();
913  if ( publishdlg->exec() == QDialog::Accepted ) {
914  // Send the mail
915  MailScheduler *scheduler = new MailScheduler();
916  connect( scheduler, SIGNAL(transactionFinished(Akonadi::Scheduler::Result,QString))
917  , d, SLOT(processMailSchedulerResult(Akonadi::Scheduler::Result,QString)) );
918  d->mParentWidgetForMailling = parentWidget;
919 
920  scheduler->publish( freebusy, publishdlg->addresses() );
921  }
922  delete publishdlg;
923 }
924 
925 bool FreeBusyManager::retrieveFreeBusy( const QString &email, bool forceDownload,
926  QWidget *parentWidget )
927 {
928  Q_D( FreeBusyManager );
929 
930  kDebug() << email;
931  if ( email.isEmpty() ) {
932  kDebug() << "Email is empty";
933  return false;
934  }
935 
936  d->mParentWidgetForRetrieval = parentWidget;
937 
938  if ( Akonadi::CalendarUtils::thatIsMe( email ) ) {
939  // Don't download our own free-busy list from the net
940  kDebug() << "freebusy of owner, not downloading";
941  emit freeBusyRetrieved( d->ownerFreeBusy(), email );
942  return true;
943  }
944 
945  // Check for cached copy of free/busy list
946  KCalCore::FreeBusy::Ptr fb = loadFreeBusy( email );
947  if ( fb ) {
948  kDebug() << "Found a cached copy for " << email;
949  emit freeBusyRetrieved( fb, email );
950  return true;
951  }
952 
953  // Don't download free/busy if the user does not want it.
954  if ( !CalendarSettings::self()->freeBusyRetrieveAuto() && !forceDownload ) {
955  kDebug() << "Not downloading freebusy";
956  return false;
957  }
958 
959  d->mRetrieveQueue.append( email );
960 
961  if ( d->mRetrieveQueue.count() > 1 ) {
962  // TODO: true should always emit
963  kWarning() << "Returning true without emit, is this correct?";
964  return true;
965  }
966 
967  // queued, because "true" means the download was initiated. So lets
968  // return before starting stuff
969  QMetaObject::invokeMethod( d, "processRetrieveQueue", Qt::QueuedConnection );
970  return true;
971 }
972 
973 void FreeBusyManager::cancelRetrieval()
974 {
975  Q_D( FreeBusyManager );
976  d->mRetrieveQueue.clear();
977 }
978 
979 KCalCore::FreeBusy::Ptr FreeBusyManager::loadFreeBusy( const QString &email )
980 {
981  Q_D( FreeBusyManager );
982  const QString fbd = d->freeBusyDir();
983 
984  QFile f( fbd + QLatin1Char( '/' ) + email + QLatin1String( ".ifb" ) );
985  if ( !f.exists() ) {
986  kDebug() << f.fileName() << "doesn't exist.";
987  return KCalCore::FreeBusy::Ptr();
988  }
989 
990  if ( !f.open( QIODevice::ReadOnly ) ) {
991  kDebug() << "Unable to open file" << f.fileName();
992  return KCalCore::FreeBusy::Ptr();
993  }
994 
995  QTextStream ts( &f );
996  QString str = ts.readAll();
997 
998  return d->iCalToFreeBusy( str.toUtf8() );
999 }
1000 
1001 bool FreeBusyManager::saveFreeBusy( const KCalCore::FreeBusy::Ptr &freebusy,
1002  const KCalCore::Person::Ptr &person )
1003 {
1004  Q_D( FreeBusyManager );
1005  Q_ASSERT( person );
1006  kDebug() << person->fullName();
1007 
1008  QString fbd = d->freeBusyDir();
1009 
1010  QDir freeBusyDirectory( fbd );
1011  if ( !freeBusyDirectory.exists() ) {
1012  kDebug() << "Directory" << fbd <<" does not exist!";
1013  kDebug() << "Creating directory:" << fbd;
1014 
1015  if( !freeBusyDirectory.mkpath( fbd ) ) {
1016  kDebug() << "Could not create directory:" << fbd;
1017  return false;
1018  }
1019  }
1020 
1021  QString filename( fbd );
1022  filename += QLatin1Char( '/' );
1023  filename += person->email();
1024  filename += QLatin1String( ".ifb" );
1025  QFile f( filename );
1026 
1027  kDebug() << "filename:" << filename;
1028 
1029  freebusy->clearAttendees();
1030  freebusy->setOrganizer( person );
1031 
1032  QString messageText = d->mFormat.createScheduleMessage( freebusy, KCalCore::iTIPPublish );
1033 
1034  if ( !f.open( QIODevice::ReadWrite ) ) {
1035  kDebug() << "acceptFreeBusy: Can't open:" << filename << "for writing";
1036  return false;
1037  }
1038  QTextStream t( &f );
1039  t << messageText;
1040  f.close();
1041 
1042  return true;
1043 }
1044 
1045 void FreeBusyManager::timerEvent( QTimerEvent * )
1046 {
1047  publishFreeBusy();
1048 }
1049 
1050 #include "freebusymanager.moc"
1051 #include "freebusymanager_p.moc"
Akonadi::AgentInstance::List
QList< AgentInstance > List
Describes a list of agent instances.
Definition: agentinstance.h:71
Akonadi::AgentManager::instances
AgentInstance::List instances() const
Returns the list of all available agent instances.
Definition: agentmanager.cpp:398
Akonadi::AgentInstance::type
AgentType type() const
Returns the agent type of this instance.
Definition: agentinstance.cpp:51
Akonadi::AgentInstance::identifier
QString identifier() const
Returns the unique identifier of the agent instance.
Definition: agentinstance.cpp:56
Akonadi::Job::start
void start()
Jobs are started automatically once entering the event loop again, no need to explicitly call this...
Definition: job.cpp:276
Akonadi::FreeBusyManagerPrivate::FreeBusyProviderRequest::FreeBusyProviderRequest
FreeBusyProviderRequest(const QString &provider)
FreeBusyManagerPrivate::FreeBusyProviderRequest.
Definition: freebusymanager.cpp:136
Akonadi::AgentType::capabilities
QStringList capabilities() const
Returns the list of supported capabilities of the agent type.
Definition: agenttype.cpp:76
Akonadi::ContactSearchJob
Job that searches for contacts in the Akonadi storage.
Definition: contactsearchjob.h:79
Akonadi::ContactSearchJob::contacts
KABC::Addressee::List contacts() const
Returns the contacts that matched the search criteria.
Definition: contactsearchjob.cpp:593
Akonadi::ContactSearchJob::Email
The email address of the contact.
Definition: contactsearchjob.h:101
Akonadi::ContactSearchJob::setQuery
void setQuery(Criterion criterion, const QString &value)
Sets the criterion and value for the search.
Definition: contactsearchjob.cpp:62
Akonadi::AgentManager::self
static AgentManager * self()
Returns the global instance of the agent manager.
Definition: agentmanager.cpp:379
Akonadi::AgentInstance
A representation of an agent instance.
Definition: agentinstance.h:62
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Tue Nov 26 2013 09:03:17 by doxygen 1.8.5 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

akonadi

Skip menu "akonadi"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Modules
  • Related Pages

kdepimlibs-4.11.3 API Reference

Skip menu "kdepimlibs-4.11.3 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal