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

Skip menu "kdepimlibs-4.10.5 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