• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.14.10 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
54using namespace Akonadi;
55using namespace KCalCore;
56
58
59KUrl 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.
84FbCheckerJob::FbCheckerJob(const QList<KUrl> &urlsToCheck, QObject *parent)
85 : KJob(parent),
86 mUrlsToCheck(urlsToCheck)
87{
88}
89
90void FbCheckerJob::start()
91{
92 checkNextUrl();
93}
94
95void 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
111void FbCheckerJob::dataReceived(KIO::Job*, const QByteArray &data)
112{
113 mData.append(data);
114}
115
116void 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
128KUrl FbCheckerJob::validUrl() const
129{
130 return mValidUrl;
131}
132
133
135
136FreeBusyManagerPrivate::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
148FreeBusyManagerPrivate::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
175FreeBusyManagerPrivate::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
186FreeBusyManagerPrivate::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
198QString FreeBusyManagerPrivate::freeBusyDir() const
199{
200 return KStandardDirs::locateLocal("data", QLatin1String("korganizer/freebusy"));
201}
202
203void FreeBusyManagerPrivate::checkFreeBusyUrl()
204{
205 KUrl targetURL(CalendarSettings::self()->freeBusyPublishUrl());
206 mBrokenUrl = targetURL.isEmpty() || !targetURL.isValid();
207}
208
209static QString configFile()
210{
211 static QString file = KStandardDirs::locateLocal("data", QLatin1String("korganizer/freebusyurls"));
212 return file;
213}
214
215void 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
239void 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
345void 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
363QString FreeBusyManagerPrivate::freeBusyToIcal(const KCalCore::FreeBusy::Ptr &freebusy)
364{
365 return mFormat.createScheduleMessage(freebusy, KCalCore::iTIPPublish);
366}
367
368KCalCore::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
381KCalCore::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
394QString FreeBusyManagerPrivate::ownerFreeBusyAsString()
395{
396 return freeBusyToIcal(ownerFreeBusy());
397}
398
399void 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
444void 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
467void 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
490void 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
512void 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
562QStringList 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
574void 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
593void 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
605void 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
651void 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
668void 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
725namespace Akonadi {
726
727class FreeBusyManagerStatic
728{
729public:
730 FreeBusyManager instance;
731};
732
733}
734
735K_GLOBAL_STATIC(FreeBusyManagerStatic, sManagerInstance)
736
737FreeBusyManager::FreeBusyManager() : d_ptr(new FreeBusyManagerPrivate(this))
738{
739 setObjectName(QLatin1String("FreeBusyManager"));
740 connect(CalendarSettings::self(), SIGNAL(configChanged()), SLOT(checkFreeBusyUrl()));
741}
742
743FreeBusyManager::~FreeBusyManager()
744{
745 delete d_ptr;
746}
747
748FreeBusyManager *FreeBusyManager::self()
749{
750 return &sManagerInstance->instance;
751}
752
753void 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
775void 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 //FIXME slot doesn't exist
892 //connect(job, SIGNAL(result(KJob*)), SLOT(slotUploadFreeBusyResult(KJob*)));
893 }
894}
895
896void FreeBusyManager::mailFreeBusy(int daysToPublish, QWidget *parentWidget)
897{
898 Q_D(FreeBusyManager);
899 // No calendar set yet?
900 if (!d->mCalendar) {
901 return;
902 }
903
904 KDateTime start = KDateTime::currentUtcDateTime().toTimeSpec(d->mCalendar->timeSpec());
905 KDateTime end = start.addDays(daysToPublish);
906
907 KCalCore::Event::List events = d->mCalendar->rawEvents(start.date(), end.date());
908
909 FreeBusy::Ptr freebusy(new FreeBusy(events, start, end));
910 freebusy->setOrganizer(Person::Ptr(
911 new Person(Akonadi::CalendarUtils::fullName(),
912 Akonadi::CalendarUtils::email())));
913
914 QPointer<PublishDialog> publishdlg = new PublishDialog();
915 if (publishdlg->exec() == QDialog::Accepted) {
916 // Send the mail
917 MailScheduler *scheduler = new MailScheduler();
918 connect(scheduler, SIGNAL(transactionFinished(Akonadi::Scheduler::Result,QString))
919 , d, SLOT(processMailSchedulerResult(Akonadi::Scheduler::Result,QString)));
920 d->mParentWidgetForMailling = parentWidget;
921
922 scheduler->publish(freebusy, publishdlg->addresses());
923 }
924 delete publishdlg;
925}
926
927bool FreeBusyManager::retrieveFreeBusy(const QString &email, bool forceDownload,
928 QWidget *parentWidget)
929{
930 Q_D(FreeBusyManager);
931
932 kDebug() << email;
933 if (email.isEmpty()) {
934 kDebug() << "Email is empty";
935 return false;
936 }
937
938 d->mParentWidgetForRetrieval = parentWidget;
939
940 if (Akonadi::CalendarUtils::thatIsMe(email)) {
941 // Don't download our own free-busy list from the net
942 kDebug() << "freebusy of owner, not downloading";
943 emit freeBusyRetrieved(d->ownerFreeBusy(), email);
944 return true;
945 }
946
947 // Check for cached copy of free/busy list
948 KCalCore::FreeBusy::Ptr fb = loadFreeBusy(email);
949 if (fb) {
950 kDebug() << "Found a cached copy for " << email;
951 emit freeBusyRetrieved(fb, email);
952 return true;
953 }
954
955 // Don't download free/busy if the user does not want it.
956 if (!CalendarSettings::self()->freeBusyRetrieveAuto() && !forceDownload) {
957 kDebug() << "Not downloading freebusy";
958 return false;
959 }
960
961 d->mRetrieveQueue.append(email);
962
963 if (d->mRetrieveQueue.count() > 1) {
964 // TODO: true should always emit
965 kWarning() << "Returning true without emit, is this correct?";
966 return true;
967 }
968
969 // queued, because "true" means the download was initiated. So lets
970 // return before starting stuff
971 QMetaObject::invokeMethod(d, "processRetrieveQueue", Qt::QueuedConnection);
972 return true;
973}
974
975void FreeBusyManager::cancelRetrieval()
976{
977 Q_D(FreeBusyManager);
978 d->mRetrieveQueue.clear();
979}
980
981KCalCore::FreeBusy::Ptr FreeBusyManager::loadFreeBusy(const QString &email)
982{
983 Q_D(FreeBusyManager);
984 const QString fbd = d->freeBusyDir();
985
986 QFile f(fbd + QLatin1Char('/') + email + QLatin1String(".ifb"));
987 if (!f.exists()) {
988 kDebug() << f.fileName() << "doesn't exist.";
989 return KCalCore::FreeBusy::Ptr();
990 }
991
992 if (!f.open(QIODevice::ReadOnly)) {
993 kDebug() << "Unable to open file" << f.fileName();
994 return KCalCore::FreeBusy::Ptr();
995 }
996
997 QTextStream ts(&f);
998 QString str = ts.readAll();
999
1000 return d->iCalToFreeBusy(str.toUtf8());
1001}
1002
1003bool FreeBusyManager::saveFreeBusy(const KCalCore::FreeBusy::Ptr &freebusy,
1004 const KCalCore::Person::Ptr &person)
1005{
1006 Q_D(FreeBusyManager);
1007 Q_ASSERT(person);
1008 kDebug() << person->fullName();
1009
1010 QString fbd = d->freeBusyDir();
1011
1012 QDir freeBusyDirectory(fbd);
1013 if (!freeBusyDirectory.exists()) {
1014 kDebug() << "Directory" << fbd <<" does not exist!";
1015 kDebug() << "Creating directory:" << fbd;
1016
1017 if (!freeBusyDirectory.mkpath(fbd)) {
1018 kDebug() << "Could not create directory:" << fbd;
1019 return false;
1020 }
1021 }
1022
1023 QString filename(fbd);
1024 filename += QLatin1Char('/');
1025 filename += person->email();
1026 filename += QLatin1String(".ifb");
1027 QFile f(filename);
1028
1029 kDebug() << "filename:" << filename;
1030
1031 freebusy->clearAttendees();
1032 freebusy->setOrganizer(person);
1033
1034 QString messageText = d->mFormat.createScheduleMessage(freebusy, KCalCore::iTIPPublish);
1035
1036 if (!f.open(QIODevice::ReadWrite)) {
1037 kDebug() << "acceptFreeBusy: Can't open:" << filename << "for writing";
1038 return false;
1039 }
1040 QTextStream t(&f);
1041 t << messageText;
1042 f.close();
1043
1044 return true;
1045}
1046
1047void FreeBusyManager::timerEvent(QTimerEvent *)
1048{
1049 publishFreeBusy();
1050}
1051
1052#include "moc_freebusymanager.cpp"
1053#include "moc_freebusymanager_p.cpp"
Akonadi::AgentInstance
A representation of an agent instance.
Definition: agentinstance.h:63
Akonadi::AgentInstance::identifier
QString identifier() const
Returns the unique identifier of the agent instance.
Definition: agentinstance.cpp:55
Akonadi::AgentInstance::type
AgentType type() const
Returns the agent type of this instance.
Definition: agentinstance.cpp:50
Akonadi::AgentInstance::List
QList< AgentInstance > List
Describes a list of agent instances.
Definition: agentinstance.h:71
Akonadi::AgentManager::self
static AgentManager * self()
Returns the global instance of the agent manager.
Definition: agentmanager.cpp:377
Akonadi::AgentManager::instances
AgentInstance::List instances() const
Returns the list of all available agent instances.
Definition: agentmanager.cpp:396
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:80
Akonadi::ContactSearchJob::setQuery
void setQuery(Criterion criterion, const QString &value)
Sets the criterion and value for the search.
Definition: contactsearchjob.cpp:54
Akonadi::ContactSearchJob::Email
@ Email
The email address of the contact.
Definition: contactsearchjob.h:101
Akonadi::ContactSearchJob::contacts
KABC::Addressee::List contacts() const
Returns the contacts that matched the search criteria.
Definition: contactsearchjob.cpp:99
Akonadi::Job::start
void start()
Jobs are started automatically once entering the event loop again, no need to explicitly call this.
Definition: job.cpp:286
Akonadi
FreeBusyManager::Singleton.
Definition: actionstatemanager_p.h:28
Akonadi::FreeBusyManagerPrivate::FreeBusyProviderRequest::FreeBusyProviderRequest
FreeBusyProviderRequest(const QString &provider)
FreeBusyManagerPrivate::FreeBusyProviderRequest.
Definition: freebusymanager.cpp:136
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jul 21 2022 00:00:00 by doxygen 1.9.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.14.10 API Reference

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