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

akonadi

  • sources
  • kde-4.12
  • kdepimlibs
  • 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 class FreeBusyManagerStatic
728 {
729 public:
730  FreeBusyManager instance;
731 };
732 
733 }
734 
735 K_GLOBAL_STATIC(FreeBusyManagerStatic, sManagerInstance)
736 
737 FreeBusyManager::FreeBusyManager() : d_ptr(new FreeBusyManagerPrivate(this))
738 {
739  setObjectName(QLatin1String("FreeBusyManager"));
740  connect(CalendarSettings::self(), SIGNAL(configChanged()), SLOT(checkFreeBusyUrl()));
741 }
742 
743 FreeBusyManager::~FreeBusyManager()
744 {
745  delete d_ptr;
746 }
747 
748 FreeBusyManager *FreeBusyManager::self()
749 {
750  return &sManagerInstance->instance;
751 }
752 
753 void FreeBusyManager::setCalendar(const Akonadi::ETMCalendar::Ptr &c)
754 {
755  Q_D(FreeBusyManager);
756 
757  if (d->mCalendar) {
758  disconnect(d->mCalendar.data(), SIGNAL(calendarChanged()));
759  }
760 
761  d->mCalendar = c;
762  if (d->mCalendar) {
763  d->mFormat.setTimeSpec(d->mCalendar->timeSpec());
764  connect(d->mCalendar.data(), SIGNAL(calendarChanged()), SLOT(uploadFreeBusy()));
765  }
766 
767  // Lets see if we need to update our published
768  QTimer::singleShot(0, this, SLOT(uploadFreeBusy()));
769 }
770 
775 void FreeBusyManager::publishFreeBusy(QWidget *parentWidget)
776 {
777  Q_D(FreeBusyManager);
778  // Already uploading? Skip this one then.
779  if (d->mUploadingFreeBusy) {
780  return;
781  }
782 
783  // No calendar set yet? Don't upload to prevent losing published information that
784  // might still be valid.
785  if (!d->mCalendar) {
786  return;
787  }
788 
789  KUrl targetURL(CalendarSettings::self()->freeBusyPublishUrl());
790  if (targetURL.isEmpty()) {
791  KMessageBox::sorry(
792  parentWidget,
793  i18n("<qt><p>No URL configured for uploading your free/busy list. "
794  "Please set it in KOrganizer's configuration dialog, on the "
795  "\"Free/Busy\" page.</p>"
796  "<p>Contact your system administrator for the exact URL and the "
797  "account details.</p></qt>"),
798  i18n("No Free/Busy Upload URL"));
799  return;
800  }
801 
802  if (d->mBrokenUrl) {
803  // Url is invalid, don't try again
804  return;
805  }
806  if (!targetURL.isValid()) {
807  KMessageBox::sorry(
808  parentWidget,
809  i18n("<qt>The target URL '%1' provided is invalid.</qt>", targetURL.prettyUrl()),
810  i18n("Invalid URL"));
811  d->mBrokenUrl = true;
812  return;
813  }
814  targetURL.setUser(CalendarSettings::self()->freeBusyPublishUser());
815  targetURL.setPass(CalendarSettings::self()->freeBusyPublishPassword());
816 
817  d->mUploadingFreeBusy = true;
818 
819  // If we have a timer running, it should be stopped now
820  if (d->mTimerID != 0) {
821  killTimer(d->mTimerID);
822  d->mTimerID = 0;
823  }
824 
825  // Save the time of the next free/busy uploading
826  d->mNextUploadTime = QDateTime::currentDateTime();
827  if (CalendarSettings::self()->freeBusyPublishDelay() > 0) {
828  d->mNextUploadTime =
829  d->mNextUploadTime.addSecs(CalendarSettings::self()->freeBusyPublishDelay() * 60);
830  }
831 
832  QString messageText = d->ownerFreeBusyAsString();
833 
834  // We need to massage the list a bit so that Outlook understands
835  // it.
836  messageText = messageText.replace(QRegExp(QLatin1String("ORGANIZER\\s*:MAILTO:")),
837  QLatin1String("ORGANIZER:"));
838 
839  // Create a local temp file and save the message to it
840  KTemporaryFile tempFile;
841  tempFile.setAutoRemove(false);
842  if (tempFile.open()) {
843  QTextStream textStream(&tempFile);
844  textStream << messageText;
845  textStream.flush();
846 
847 #if 0
848  QString defaultEmail = KOCore()::self()->email();
849  QString emailHost = defaultEmail.mid(defaultEmail.indexOf('@') + 1);
850 
851  // Put target string together
852  KUrl targetURL;
853  if (CalendarSettings::self()->publishKolab()) {
854  // we use Kolab
855  QString server;
856  if (CalendarSettings::self()->publishKolabServer() == QLatin1String("%SERVER%") ||
857  CalendarSettings::self()->publishKolabServer().isEmpty()) {
858  server = emailHost;
859  } else {
860  server = CalendarSettings::self()->publishKolabServer();
861  }
862 
863  targetURL.setProtocol("webdavs");
864  targetURL.setHost(server);
865 
866  QString fbname = CalendarSettings::self()->publishUserName();
867  int at = fbname.indexOf('@');
868  if (at > 1 && fbname.length() > (uint)at) {
869  fbname = fbname.left(at);
870  }
871  targetURL.setPath("/freebusy/" + fbname + ".ifb");
872  targetURL.setUser(CalendarSettings::self()->publishUserName());
873  targetURL.setPass(CalendarSettings::self()->publishPassword());
874  } else {
875  // we use something else
876  targetURL = CalendarSettings::self()->+publishAnyURL().replace("%SERVER%", emailHost);
877  targetURL.setUser(CalendarSettings::self()->publishUserName());
878  targetURL.setPass(CalendarSettings::self()->publishPassword());
879  }
880 #endif
881 
882  KUrl src;
883  src.setPath(tempFile.fileName());
884 
885  kDebug() << targetURL;
886 
887  KIO::Job *job = KIO::file_copy(src, targetURL, -1, KIO::Overwrite | KIO::HideProgressInfo);
888 
889  job->ui()->setWindow(parentWidget);
890 
891  connect(job, SIGNAL(result(KJob*)), SLOT(slotUploadFreeBusyResult(KJob*)));
892  }
893 }
894 
895 void FreeBusyManager::mailFreeBusy(int daysToPublish, QWidget *parentWidget)
896 {
897  Q_D(FreeBusyManager);
898  // No calendar set yet?
899  if (!d->mCalendar) {
900  return;
901  }
902 
903  KDateTime start = KDateTime::currentUtcDateTime().toTimeSpec(d->mCalendar->timeSpec());
904  KDateTime end = start.addDays(daysToPublish);
905 
906  KCalCore::Event::List events = d->mCalendar->rawEvents(start.date(), end.date());
907 
908  FreeBusy::Ptr freebusy(new FreeBusy(events, start, end));
909  freebusy->setOrganizer(Person::Ptr(
910  new Person(Akonadi::CalendarUtils::fullName(),
911  Akonadi::CalendarUtils::email())));
912 
913  QPointer<PublishDialog> publishdlg = new PublishDialog();
914  if (publishdlg->exec() == QDialog::Accepted) {
915  // Send the mail
916  MailScheduler *scheduler = new MailScheduler();
917  connect(scheduler, SIGNAL(transactionFinished(Akonadi::Scheduler::Result,QString))
918  , d, SLOT(processMailSchedulerResult(Akonadi::Scheduler::Result,QString)));
919  d->mParentWidgetForMailling = parentWidget;
920 
921  scheduler->publish(freebusy, publishdlg->addresses());
922  }
923  delete publishdlg;
924 }
925 
926 bool FreeBusyManager::retrieveFreeBusy(const QString &email, bool forceDownload,
927  QWidget *parentWidget)
928 {
929  Q_D(FreeBusyManager);
930 
931  kDebug() << email;
932  if (email.isEmpty()) {
933  kDebug() << "Email is empty";
934  return false;
935  }
936 
937  d->mParentWidgetForRetrieval = parentWidget;
938 
939  if (Akonadi::CalendarUtils::thatIsMe(email)) {
940  // Don't download our own free-busy list from the net
941  kDebug() << "freebusy of owner, not downloading";
942  emit freeBusyRetrieved(d->ownerFreeBusy(), email);
943  return true;
944  }
945 
946  // Check for cached copy of free/busy list
947  KCalCore::FreeBusy::Ptr fb = loadFreeBusy(email);
948  if (fb) {
949  kDebug() << "Found a cached copy for " << email;
950  emit freeBusyRetrieved(fb, email);
951  return true;
952  }
953 
954  // Don't download free/busy if the user does not want it.
955  if (!CalendarSettings::self()->freeBusyRetrieveAuto() && !forceDownload) {
956  kDebug() << "Not downloading freebusy";
957  return false;
958  }
959 
960  d->mRetrieveQueue.append(email);
961 
962  if (d->mRetrieveQueue.count() > 1) {
963  // TODO: true should always emit
964  kWarning() << "Returning true without emit, is this correct?";
965  return true;
966  }
967 
968  // queued, because "true" means the download was initiated. So lets
969  // return before starting stuff
970  QMetaObject::invokeMethod(d, "processRetrieveQueue", Qt::QueuedConnection);
971  return true;
972 }
973 
974 void FreeBusyManager::cancelRetrieval()
975 {
976  Q_D(FreeBusyManager);
977  d->mRetrieveQueue.clear();
978 }
979 
980 KCalCore::FreeBusy::Ptr FreeBusyManager::loadFreeBusy(const QString &email)
981 {
982  Q_D(FreeBusyManager);
983  const QString fbd = d->freeBusyDir();
984 
985  QFile f(fbd + QLatin1Char('/') + email + QLatin1String(".ifb"));
986  if (!f.exists()) {
987  kDebug() << f.fileName() << "doesn't exist.";
988  return KCalCore::FreeBusy::Ptr();
989  }
990 
991  if (!f.open(QIODevice::ReadOnly)) {
992  kDebug() << "Unable to open file" << f.fileName();
993  return KCalCore::FreeBusy::Ptr();
994  }
995 
996  QTextStream ts(&f);
997  QString str = ts.readAll();
998 
999  return d->iCalToFreeBusy(str.toUtf8());
1000 }
1001 
1002 bool FreeBusyManager::saveFreeBusy(const KCalCore::FreeBusy::Ptr &freebusy,
1003  const KCalCore::Person::Ptr &person)
1004 {
1005  Q_D(FreeBusyManager);
1006  Q_ASSERT(person);
1007  kDebug() << person->fullName();
1008 
1009  QString fbd = d->freeBusyDir();
1010 
1011  QDir freeBusyDirectory(fbd);
1012  if (!freeBusyDirectory.exists()) {
1013  kDebug() << "Directory" << fbd <<" does not exist!";
1014  kDebug() << "Creating directory:" << fbd;
1015 
1016  if (!freeBusyDirectory.mkpath(fbd)) {
1017  kDebug() << "Could not create directory:" << fbd;
1018  return false;
1019  }
1020  }
1021 
1022  QString filename(fbd);
1023  filename += QLatin1Char('/');
1024  filename += person->email();
1025  filename += QLatin1String(".ifb");
1026  QFile f(filename);
1027 
1028  kDebug() << "filename:" << filename;
1029 
1030  freebusy->clearAttendees();
1031  freebusy->setOrganizer(person);
1032 
1033  QString messageText = d->mFormat.createScheduleMessage(freebusy, KCalCore::iTIPPublish);
1034 
1035  if (!f.open(QIODevice::ReadWrite)) {
1036  kDebug() << "acceptFreeBusy: Can't open:" << filename << "for writing";
1037  return false;
1038  }
1039  QTextStream t(&f);
1040  t << messageText;
1041  f.close();
1042 
1043  return true;
1044 }
1045 
1046 void FreeBusyManager::timerEvent(QTimerEvent *)
1047 {
1048  publishFreeBusy();
1049 }
1050 
1051 #include "moc_freebusymanager.cpp"
1052 #include "moc_freebusymanager_p.cpp"
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:399
Akonadi::AgentInstance::type
AgentType type() const
Returns the agent type of this instance.
Definition: agentinstance.cpp:50
Akonadi::AgentInstance::identifier
QString identifier() const
Returns the unique identifier of the agent instance.
Definition: agentinstance.cpp:55
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:380
Akonadi::AgentInstance
A representation of an agent instance.
Definition: agentinstance.h:62
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 23:00:27 by doxygen 1.8.7 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 API Reference

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kldap
  • kmbox
  • kmime
  • kpimidentities
  • kpimtextedit
  • kresources
  • ktnef
  • kxmlrpcclient
  • microblog

Search



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