kde-playground/kdepimlibs/akonadi/calendar/freebusymanager.cpp
2015-04-14 21:49:29 +00:00

1053 lines
34 KiB
C++

/*
Copyright (c) 2011 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "freebusymanager.h"
#include "freebusymanager_p.h"
#include "freebusydownloadjob_p.h"
#include "mailscheduler_p.h"
#include "publishdialog.h"
#include "calendarsettings.h"
#include "utils_p.h"
#include <akonadi/agentinstance.h>
#include <akonadi/agentmanager.h>
#include <akonadi/contact/contactsearchjob.h>
#include <kcalcore/event.h>
#include <kcalcore/freebusy.h>
#include <kcalcore/person.h>
#include <KDebug>
#include <KMessageBox>
#include <KStandardDirs>
#include <KTemporaryFile>
#include <KUrl>
#include <KIO/Job>
#include <KIO/JobUiDelegate>
#include <KIO/NetAccess>
#include <KLocalizedString>
#include <QDir>
#include <QFile>
#include <QRegExp>
#include <QTextStream>
#include <QTimer>
#include <QTimerEvent>
using namespace Akonadi;
using namespace KCalCore;
/// Free helper functions
KUrl replaceVariablesUrl(const KUrl &url, const QString &email)
{
QString emailName;
QString emailHost;
const int atPos = email.indexOf('@');
if (atPos >= 0) {
emailName = email.left(atPos);
emailHost = email.mid(atPos + 1);
}
QString saveStr = url.path();
saveStr.replace(QRegExp("%[Ee][Mm][Aa][Ii][Ll]%"), email);
saveStr.replace(QRegExp("%[Nn][Aa][Mm][Ee]%"), emailName);
saveStr.replace(QRegExp("%[Ss][Ee][Rr][Vv][Ee][Rr]%"), emailHost);
KUrl retUrl(url);
retUrl.setPath(saveStr);
return retUrl;
}
// We need this function because using KIO::NetAccess::exists()
// is useless for the http and https protocols. And getting back
// arbitrary data is also useless because a server can respond back
// with a "no such document" page. So we need smart checking.
FbCheckerJob::FbCheckerJob(const QList<KUrl> &urlsToCheck, QObject *parent)
: KJob(parent),
mUrlsToCheck(urlsToCheck)
{
}
void FbCheckerJob::start()
{
checkNextUrl();
}
void FbCheckerJob::checkNextUrl()
{
if (mUrlsToCheck.isEmpty()) {
kDebug() << "No fb file found";
setError(KJob::UserDefinedError);
emitResult();
return;
}
const KUrl url = mUrlsToCheck.takeFirst();
mData.clear();
KIO::TransferJob *job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
connect(job, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(dataReceived(KIO::Job*,QByteArray)));
connect(job, SIGNAL(result(KJob*)), this, SLOT(onGetJobFinished(KJob*)));
}
void FbCheckerJob::dataReceived(KIO::Job*, const QByteArray &data)
{
mData.append(data);
}
void FbCheckerJob::onGetJobFinished(KJob *job)
{
KIO::TransferJob *transferJob = static_cast<KIO::TransferJob*>(job);
if (mData.contains("BEGIN:VCALENDAR")) {
kDebug() << "found freebusy";
mValidUrl = transferJob->url();
emitResult();
} else {
checkNextUrl();
}
}
KUrl FbCheckerJob::validUrl() const
{
return mValidUrl;
}
/// FreeBusyManagerPrivate::FreeBusyProviderRequest
FreeBusyManagerPrivate::FreeBusyProviderRequest::FreeBusyProviderRequest(const QString &provider)
: mRequestStatus(NotStarted), mInterface(0)
{
mInterface =
QSharedPointer<QDBusInterface>(
new QDBusInterface("org.freedesktop.Akonadi.Resource." + provider,
"/FreeBusyProvider",
"org.freedesktop.Akonadi.Resource.FreeBusyProvider"));
}
/// FreeBusyManagerPrivate::FreeBusyProvidersRequestsQueue
FreeBusyManagerPrivate::FreeBusyProvidersRequestsQueue::FreeBusyProvidersRequestsQueue(
const QString &start, const QString &end)
: mHandlersCount(0), mResultingFreeBusy(0)
{
KDateTime startDate, endDate;
if (!start.isEmpty()) {
mStartTime = start;
startDate = KDateTime::fromString(start);
} else {
// Set the start of the period to today 00:00:00
startDate = KDateTime(KDateTime::currentLocalDate());
mStartTime = startDate.toString();
}
if (!end.isEmpty()) {
mEndTime = end;
endDate = KDateTime::fromString(end);
} else {
// Set the end of the period to today + 14 days.
endDate = KDateTime(KDateTime::currentLocalDate()).addDays(14);
mEndTime = endDate.toString();
}
mResultingFreeBusy = KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(startDate, endDate));
}
FreeBusyManagerPrivate::FreeBusyProvidersRequestsQueue::FreeBusyProvidersRequestsQueue(
const KDateTime &start, const KDateTime &end)
: mHandlersCount(0), mResultingFreeBusy(0)
{
mStartTime = start.toString();
mEndTime = end.toString();
mResultingFreeBusy = KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(start, end));
}
/// FreeBusyManagerPrivate
FreeBusyManagerPrivate::FreeBusyManagerPrivate(FreeBusyManager *q)
: QObject(),
q_ptr(q),
mTimerID(0),
mUploadingFreeBusy(false),
mBrokenUrl(false),
mParentWidgetForRetrieval(0)
{
connect(this, SIGNAL(freeBusyUrlRetrieved(QString,KUrl)),
SLOT(finishProcessRetrieveQueue(QString,KUrl)));
}
QString FreeBusyManagerPrivate::freeBusyDir() const
{
return KStandardDirs::locateLocal("data", QLatin1String("korganizer/freebusy"));
}
void FreeBusyManagerPrivate::checkFreeBusyUrl()
{
KUrl targetURL(CalendarSettings::self()->freeBusyPublishUrl());
mBrokenUrl = targetURL.isEmpty() || !targetURL.isValid();
}
static QString configFile()
{
static QString file = KStandardDirs::locateLocal("data", QLatin1String("korganizer/freebusyurls"));
return file;
}
void FreeBusyManagerPrivate::fetchFreeBusyUrl(const QString &email)
{
// First check if there is a specific FB url for this email
KConfig cfg(configFile());
KConfigGroup group = cfg.group(email);
QString url = group.readEntry(QLatin1String("url"));
if (!url.isEmpty()) {
kDebug() << "Found cached url:" << url;
KUrl cachedUrl(url);
if (Akonadi::CalendarUtils::thatIsMe(email)) {
cachedUrl.setUser(CalendarSettings::self()->freeBusyRetrieveUser());
cachedUrl.setPass(CalendarSettings::self()->freeBusyRetrievePassword());
}
emit freeBusyUrlRetrieved(email, replaceVariablesUrl(cachedUrl, email));
return;
}
// Try with the url configured by preferred email in kcontactmanager
Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob();
job->setQuery(Akonadi::ContactSearchJob::Email, email);
job->setProperty("contactEmail", QVariant::fromValue(email));
connect(job, SIGNAL(result(KJob*)), this, SLOT(contactSearchJobFinished(KJob*)));
job->start();
}
void FreeBusyManagerPrivate::contactSearchJobFinished(KJob *_job)
{
const QString email = _job->property("contactEmail").toString();
if (_job->error()) {
kError() << "Error while searching for contact: "
<< _job->errorString() << ", email = " << email;
emit freeBusyUrlRetrieved(email, KUrl());
return;
}
Akonadi::ContactSearchJob *job = qobject_cast<Akonadi::ContactSearchJob*>(_job);
KConfig cfg(configFile());
KConfigGroup group = cfg.group(email);
QString url = group.readEntry(QLatin1String("url"));
const KABC::Addressee::List contacts = job->contacts();
foreach(const KABC::Addressee &contact, contacts) {
const QString pref = contact.preferredEmail();
if (!pref.isEmpty() && pref != email) {
group = cfg.group(pref);
url = group.readEntry("url");
kDebug() << "Preferred email of" << email << "is" << pref;
if (!url.isEmpty()) {
kDebug() << "Taken url from preferred email:" << url;
emit freeBusyUrlRetrieved(email, replaceVariablesUrl(KUrl(url), email));
return;
}
}
}
// None found. Check if we do automatic FB retrieving then
if (!CalendarSettings::self()->freeBusyRetrieveAuto()) {
// No, so no FB list here
kDebug() << "No automatic retrieving";
emit freeBusyUrlRetrieved(email, KUrl());
return;
}
// Sanity check: Don't download if it's not a correct email
// address (this also avoids downloading for "(empty email)").
int emailpos = email.indexOf(QLatin1Char('@'));
if (emailpos == -1) {
kWarning() << "No '@' found in" << email;
emit freeBusyUrlRetrieved(email, KUrl());
return;
}
const QString emailHost = email.mid(emailpos + 1);
// Build the URL
if (CalendarSettings::self()->freeBusyCheckHostname()) {
// Don't try to fetch free/busy data for users not on the specified servers
// This tests if the hostnames match, or one is a subset of the other
const QString hostDomain = KUrl(CalendarSettings::self()->freeBusyRetrieveUrl()).host();
if (hostDomain != emailHost &&
!hostDomain.endsWith(QLatin1Char('.') + emailHost) &&
!emailHost.endsWith(QLatin1Char('.') + hostDomain)) {
// Host names do not match
kDebug() << "Host '" << hostDomain << "' doesn't match email '" << email << '\'';
emit freeBusyUrlRetrieved(email, KUrl());
return;
}
}
if (CalendarSettings::self()->freeBusyRetrieveUrl().contains(QRegExp("\\.[xiv]fb$"))) {
// user specified a fullpath
// do variable string replacements to the URL (MS Outlook style)
const KUrl sourceUrl(CalendarSettings::self()->freeBusyRetrieveUrl());
KUrl fullpathURL = replaceVariablesUrl(sourceUrl, email);
// set the User and Password part of the URL
fullpathURL.setUser(CalendarSettings::self()->freeBusyRetrieveUser());
fullpathURL.setPass(CalendarSettings::self()->freeBusyRetrievePassword());
// no need to cache this URL as this is pretty fast to get from the config value.
// return the fullpath URL
kDebug() << "Found url. email=" << email << "; url=" << fullpathURL;
emit freeBusyUrlRetrieved(email, fullpathURL);
return;
}
// else we search for a fb file in the specified URL with known possible extensions
const QStringList extensions = QStringList() << "xfb" << "ifb" << "vfb";
QStringList::ConstIterator ext;
QList<KUrl> urlsToCheck;
for (ext = extensions.constBegin(); ext != extensions.constEnd(); ++ext) {
// build a url for this extension
const KUrl sourceUrl = CalendarSettings::self()->freeBusyRetrieveUrl();
KUrl dirURL = replaceVariablesUrl(sourceUrl, email);
if (CalendarSettings::self()->freeBusyFullDomainRetrieval()) {
dirURL.addPath(email + '.' + (*ext));
} else {
// Cut off everything left of the @ sign to get the user name.
const QString emailName = email.left(emailpos);
dirURL.addPath(emailName + '.' + (*ext));
}
dirURL.setUser(CalendarSettings::self()->freeBusyRetrieveUser());
dirURL.setPass(CalendarSettings::self()->freeBusyRetrievePassword());
urlsToCheck << dirURL;
}
KJob *checkerJob = new FbCheckerJob(urlsToCheck, this);
checkerJob->setProperty("email", email);
connect(checkerJob, SIGNAL(result(KJob*)), this, SLOT(fbCheckerJobFinished(KJob*)));
checkerJob->start();
}
void FreeBusyManagerPrivate::fbCheckerJobFinished(KJob *job)
{
const QString email = job->property("email").toString();
if (!job->error()) {
FbCheckerJob *checkerJob = static_cast<FbCheckerJob*>(job);
KUrl dirURL = checkerJob->validUrl();
// write the URL to the cache
KConfig cfg(configFile());
KConfigGroup group = cfg.group(email);
group.writeEntry("url", dirURL.prettyUrl()); // prettyURL() does not write user nor password
kDebug() << "Found url email=" << email << "; url=" << dirURL;
emit freeBusyUrlRetrieved(email, dirURL);
} else {
kDebug() << "Returning invalid url";
emit freeBusyUrlRetrieved(email, KUrl());
}
}
QString FreeBusyManagerPrivate::freeBusyToIcal(const KCalCore::FreeBusy::Ptr &freebusy)
{
return mFormat.createScheduleMessage(freebusy, KCalCore::iTIPPublish);
}
KCalCore::FreeBusy::Ptr FreeBusyManagerPrivate::iCalToFreeBusy(const QByteArray &freeBusyData)
{
const QString freeBusyVCal(QString::fromUtf8(freeBusyData));
KCalCore::FreeBusy::Ptr fb = mFormat.parseFreeBusy(freeBusyVCal);
if (!fb) {
kDebug() << "Error parsing free/busy";
kDebug() << freeBusyVCal;
}
return fb;
}
KCalCore::FreeBusy::Ptr FreeBusyManagerPrivate::ownerFreeBusy()
{
KDateTime start = KDateTime::currentUtcDateTime();
KDateTime end = start.addDays(CalendarSettings::self()->freeBusyPublishDays());
KCalCore::Event::List events = mCalendar ? mCalendar->rawEvents(start.date(), end.date()) : KCalCore::Event::List();
KCalCore::FreeBusy::Ptr freebusy(new KCalCore::FreeBusy(events, start, end));
freebusy->setOrganizer(KCalCore::Person::Ptr(
new KCalCore::Person(Akonadi::CalendarUtils::fullName(),
Akonadi::CalendarUtils::email())));
return freebusy;
}
QString FreeBusyManagerPrivate::ownerFreeBusyAsString()
{
return freeBusyToIcal(ownerFreeBusy());
}
void FreeBusyManagerPrivate::processFreeBusyDownloadResult(KJob *_job)
{
Q_Q(FreeBusyManager);
FreeBusyDownloadJob *job = qobject_cast<FreeBusyDownloadJob *>(_job);
Q_ASSERT(job);
if (job->error()) {
kError() << "Error downloading freebusy" << _job->errorString();
KMessageBox::sorry(
mParentWidgetForRetrieval,
i18n("Failed to download free/busy data from: %1\nReason: %2",
job->url().prettyUrl(), job->errorText()),
i18n("Free/busy retrieval error"));
// TODO: Ask for a retry? (i.e. queue the email again when the user wants it).
// Make sure we don't fill up the map with unneeded data on failures.
mFreeBusyUrlEmailMap.take(job->url());
} else {
KCalCore::FreeBusy::Ptr fb = iCalToFreeBusy(job->rawFreeBusyData());
Q_ASSERT(mFreeBusyUrlEmailMap.contains(job->url()));
const QString email = mFreeBusyUrlEmailMap.take(job->url());
if (fb) {
KCalCore::Person::Ptr p = fb->organizer();
p->setEmail(email);
q->saveFreeBusy(fb, p);
kDebug() << "Freebusy retrieved for " << email;
emit q->freeBusyRetrieved(fb, email);
} else {
kError() << "Error downloading freebusy, invalid fb.";
KMessageBox::sorry(
mParentWidgetForRetrieval,
i18n("Failed to parse free/busy information that was retrieved from: %1",
job->url().prettyUrl()),
i18n("Free/busy retrieval error"));
}
}
// When downloading failed or finished, start a job for the next one in the
// queue if needed.
processRetrieveQueue();
}
void FreeBusyManagerPrivate::processFreeBusyUploadResult(KJob *_job)
{
KIO::FileCopyJob *job = static_cast<KIO::FileCopyJob *>(_job);
if (job->error()) {
KMessageBox::sorry(
job->ui()->window(),
i18n("<qt><p>The software could not upload your free/busy list to "
"the URL '%1'. There might be a problem with the access "
"rights, or you specified an incorrect URL. The system said: "
"<em>%2</em>.</p>"
"<p>Please check the URL or contact your system administrator."
"</p></qt>", job->destUrl().prettyUrl(),
job->errorString()));
}
// Delete temp file
KUrl src = job->srcUrl();
Q_ASSERT(src.isLocalFile());
if (src.isLocalFile()) {
QFile::remove(src.toLocalFile());
}
mUploadingFreeBusy = false;
}
void FreeBusyManagerPrivate::processRetrieveQueue()
{
if (mRetrieveQueue.isEmpty()) {
return;
}
QString email = mRetrieveQueue.takeFirst();
// First, try to find all agents that are free-busy providers
QStringList providers = getFreeBusyProviders();
kDebug() << "Got the following FreeBusy providers: " << providers;
// If some free-busy providers were found let's query them first and ask them
// if they manage the free-busy information for the email address we have.
if (!providers.isEmpty()) {
queryFreeBusyProviders(providers, email);
} else {
fetchFreeBusyUrl(email);
}
return;
}
void FreeBusyManagerPrivate::finishProcessRetrieveQueue(const QString &email,
const KUrl &freeBusyUrlForEmail)
{
Q_Q(FreeBusyManager);
if (!freeBusyUrlForEmail.isValid()) {
kDebug() << "Invalid FreeBusy URL" << freeBusyUrlForEmail.prettyUrl() << email;
return;
}
if (mFreeBusyUrlEmailMap.contains(freeBusyUrlForEmail)) {
kDebug() << "Download already in progress for " << freeBusyUrlForEmail;
return;
}
mFreeBusyUrlEmailMap.insert(freeBusyUrlForEmail, email);
FreeBusyDownloadJob *job = new FreeBusyDownloadJob(freeBusyUrlForEmail, mParentWidgetForRetrieval);
q->connect(job, SIGNAL(result(KJob*)), SLOT(processFreeBusyDownloadResult(KJob*)));
job->start();
}
void FreeBusyManagerPrivate::uploadFreeBusy()
{
Q_Q(FreeBusyManager);
// user has automatic uploading disabled, bail out
if (!CalendarSettings::self()->freeBusyPublishAuto() ||
CalendarSettings::self()->freeBusyPublishUrl().isEmpty()) {
return;
}
if (mTimerID != 0) {
// A timer is already running, so we don't need to do anything
return;
}
int now = static_cast<int>(QDateTime::currentDateTime().toTime_t());
int eta = static_cast<int>(mNextUploadTime.toTime_t()) - now;
if (!mUploadingFreeBusy) {
// Not currently uploading
if (mNextUploadTime.isNull() ||
QDateTime::currentDateTime() > mNextUploadTime) {
// No uploading have been done in this session, or delay time is over
q->publishFreeBusy();
return;
}
// We're in the delay time and no timer is running. Start one
if (eta <= 0) {
// Sanity check failed - better do the upload
q->publishFreeBusy();
return;
}
} else {
// We are currently uploading the FB list. Start the timer
if (eta <= 0) {
kDebug() << "This shouldn't happen! eta <= 0";
eta = 10; // whatever
}
}
// Start the timer
mTimerID = q->startTimer(eta * 1000);
if (mTimerID == 0) {
// startTimer failed - better do the upload
q->publishFreeBusy();
}
}
QStringList FreeBusyManagerPrivate::getFreeBusyProviders() const
{
QStringList providers;
Akonadi::AgentInstance::List agents = Akonadi::AgentManager::self()->instances();
foreach(const Akonadi::AgentInstance &agent, agents) {
if (agent.type().capabilities().contains(QLatin1String("FreeBusyProvider"))) {
providers << agent.identifier();
}
}
return providers;
}
void FreeBusyManagerPrivate::queryFreeBusyProviders(const QStringList &providers,
const QString &email)
{
if (!mProvidersRequestsByEmail.contains(email)) {
mProvidersRequestsByEmail[email] = FreeBusyProvidersRequestsQueue();
}
foreach(const QString &provider, providers) {
FreeBusyProviderRequest request(provider);
connect(request.mInterface.data(), SIGNAL(handlesFreeBusy(QString,bool)),
this, SLOT(onHandlesFreeBusy(QString,bool)));
request.mInterface->call("canHandleFreeBusy", email);
request.mRequestStatus = FreeBusyProviderRequest::HandlingRequested;
mProvidersRequestsByEmail[email].mRequests << request;
}
}
void FreeBusyManagerPrivate::queryFreeBusyProviders(const QStringList &providers,
const QString &email,
const KDateTime &start,
const KDateTime &end)
{
if (!mProvidersRequestsByEmail.contains(email)) {
mProvidersRequestsByEmail[email] = FreeBusyProvidersRequestsQueue(start, end);
}
queryFreeBusyProviders(providers, email);
}
void FreeBusyManagerPrivate::onHandlesFreeBusy(const QString &email, bool handles)
{
if (!mProvidersRequestsByEmail.contains(email)) {
return;
}
QDBusInterface *iface = dynamic_cast<QDBusInterface*>(sender());
if (!iface) {
return;
}
FreeBusyProvidersRequestsQueue *queue = &mProvidersRequestsByEmail[email];
QString respondingService = iface->service();
kDebug() << respondingService << "responded to our FreeBusy request:" << handles;
int requestIndex = -1;
for (int i = 0; i < queue->mRequests.size(); ++i) {
if (queue->mRequests.at(i).mInterface->service() == respondingService) {
requestIndex = i;
}
}
if (requestIndex == -1) {
return;
}
disconnect(iface, SIGNAL(handlesFreeBusy(QString,bool)),
this, SLOT(onHandlesFreeBusy(QString,bool)));
if (!handles) {
queue->mRequests.removeAt(requestIndex);
// If no more requests are left and no handler responded
// then fall back to the URL mechanism
if (queue->mRequests.isEmpty() && queue->mHandlersCount == 0) {
mProvidersRequestsByEmail.remove(email);
fetchFreeBusyUrl(email);
}
} else {
++queue->mHandlersCount;
connect(iface, SIGNAL(freeBusyRetrieved(QString,QString,bool,QString)),
this, SLOT(onFreeBusyRetrieved(QString,QString,bool,QString)));
iface->call("retrieveFreeBusy", email, queue->mStartTime, queue->mEndTime);
queue->mRequests[requestIndex].mRequestStatus = FreeBusyProviderRequest::FreeBusyRequested;
}
}
void FreeBusyManagerPrivate::processMailSchedulerResult(Akonadi::Scheduler::Result result,
const QString &errorMsg)
{
if (result == Scheduler::ResultSuccess) {
KMessageBox::information(
mParentWidgetForMailling,
i18n("The free/busy information was successfully sent."),
i18n("Sending Free/Busy"),
"FreeBusyPublishSuccess");
} else {
KMessageBox::error(mParentWidgetForMailling,
i18n("Unable to publish the free/busy data: %1", errorMsg));
}
sender()->deleteLater();
}
void FreeBusyManagerPrivate::onFreeBusyRetrieved(const QString &email,
const QString &freeBusy,
bool success,
const QString &errorText)
{
Q_Q(FreeBusyManager);
Q_UNUSED(errorText);
if (!mProvidersRequestsByEmail.contains(email)) {
return;
}
QDBusInterface *iface = dynamic_cast<QDBusInterface*>(sender());
if (!iface) {
return;
}
FreeBusyProvidersRequestsQueue *queue = &mProvidersRequestsByEmail[email];
QString respondingService = iface->service();
int requestIndex = -1;
for (int i = 0; i < queue->mRequests.size(); ++i) {
if (queue->mRequests.at(i).mInterface->service() == respondingService) {
requestIndex = i;
}
}
if (requestIndex == -1) {
return;
}
disconnect(iface, SIGNAL(freeBusyRetrieved(QString,QString,bool,QString)),
this, SLOT(onFreeBusyRetrieved(QString,QString,bool,QString)));
queue->mRequests.removeAt(requestIndex);
if (success) {
KCalCore::FreeBusy::Ptr fb = iCalToFreeBusy(freeBusy.toUtf8());
if (!fb) {
--queue->mHandlersCount;
} else {
queue->mResultingFreeBusy->merge(fb);
}
}
if (queue->mRequests.isEmpty()) {
if (queue->mHandlersCount == 0) {
fetchFreeBusyUrl(email);
} else {
emit q->freeBusyRetrieved(queue->mResultingFreeBusy, email);
}
mProvidersRequestsByEmail.remove(email);
}
}
/// FreeBusyManager::Singleton
namespace Akonadi {
class FreeBusyManagerStatic
{
public:
FreeBusyManager instance;
};
}
K_GLOBAL_STATIC(FreeBusyManagerStatic, sManagerInstance)
FreeBusyManager::FreeBusyManager() : d_ptr(new FreeBusyManagerPrivate(this))
{
setObjectName(QLatin1String("FreeBusyManager"));
connect(CalendarSettings::self(), SIGNAL(configChanged()), SLOT(checkFreeBusyUrl()));
}
FreeBusyManager::~FreeBusyManager()
{
delete d_ptr;
}
FreeBusyManager *FreeBusyManager::self()
{
return &sManagerInstance->instance;
}
void FreeBusyManager::setCalendar(const Akonadi::ETMCalendar::Ptr &c)
{
Q_D(FreeBusyManager);
if (d->mCalendar) {
disconnect(d->mCalendar.data(), SIGNAL(calendarChanged()));
}
d->mCalendar = c;
if (d->mCalendar) {
d->mFormat.setTimeSpec(d->mCalendar->timeSpec());
connect(d->mCalendar.data(), SIGNAL(calendarChanged()), SLOT(uploadFreeBusy()));
}
// Lets see if we need to update our published
QTimer::singleShot(0, this, SLOT(uploadFreeBusy()));
}
/*!
This method is called when the user has selected to publish its
free/busy list or when the delay have passed.
*/
void FreeBusyManager::publishFreeBusy(QWidget *parentWidget)
{
Q_D(FreeBusyManager);
// Already uploading? Skip this one then.
if (d->mUploadingFreeBusy) {
return;
}
// No calendar set yet? Don't upload to prevent losing published information that
// might still be valid.
if (!d->mCalendar) {
return;
}
KUrl targetURL(CalendarSettings::self()->freeBusyPublishUrl());
if (targetURL.isEmpty()) {
KMessageBox::sorry(
parentWidget,
i18n("<qt><p>No URL configured for uploading your free/busy list. "
"Please set it in KOrganizer's configuration dialog, on the "
"\"Free/Busy\" page.</p>"
"<p>Contact your system administrator for the exact URL and the "
"account details.</p></qt>"),
i18n("No Free/Busy Upload URL"));
return;
}
if (d->mBrokenUrl) {
// Url is invalid, don't try again
return;
}
if (!targetURL.isValid()) {
KMessageBox::sorry(
parentWidget,
i18n("<qt>The target URL '%1' provided is invalid.</qt>", targetURL.prettyUrl()),
i18n("Invalid URL"));
d->mBrokenUrl = true;
return;
}
targetURL.setUser(CalendarSettings::self()->freeBusyPublishUser());
targetURL.setPass(CalendarSettings::self()->freeBusyPublishPassword());
d->mUploadingFreeBusy = true;
// If we have a timer running, it should be stopped now
if (d->mTimerID != 0) {
killTimer(d->mTimerID);
d->mTimerID = 0;
}
// Save the time of the next free/busy uploading
d->mNextUploadTime = QDateTime::currentDateTime();
if (CalendarSettings::self()->freeBusyPublishDelay() > 0) {
d->mNextUploadTime =
d->mNextUploadTime.addSecs(CalendarSettings::self()->freeBusyPublishDelay() * 60);
}
QString messageText = d->ownerFreeBusyAsString();
// We need to massage the list a bit so that Outlook understands
// it.
messageText = messageText.replace(QRegExp(QLatin1String("ORGANIZER\\s*:MAILTO:")),
QLatin1String("ORGANIZER:"));
// Create a local temp file and save the message to it
KTemporaryFile tempFile;
tempFile.setAutoRemove(false);
if (tempFile.open()) {
QTextStream textStream(&tempFile);
textStream << messageText;
textStream.flush();
#if 0
QString defaultEmail = KOCore()::self()->email();
QString emailHost = defaultEmail.mid(defaultEmail.indexOf('@') + 1);
// Put target string together
KUrl targetURL;
if (CalendarSettings::self()->publishKolab()) {
// we use Kolab
QString server;
if (CalendarSettings::self()->publishKolabServer() == QLatin1String("%SERVER%") ||
CalendarSettings::self()->publishKolabServer().isEmpty()) {
server = emailHost;
} else {
server = CalendarSettings::self()->publishKolabServer();
}
targetURL.setProtocol("webdavs");
targetURL.setHost(server);
QString fbname = CalendarSettings::self()->publishUserName();
int at = fbname.indexOf('@');
if (at > 1 && fbname.length() > (uint)at) {
fbname = fbname.left(at);
}
targetURL.setPath("/freebusy/" + fbname + ".ifb");
targetURL.setUser(CalendarSettings::self()->publishUserName());
targetURL.setPass(CalendarSettings::self()->publishPassword());
} else {
// we use something else
targetURL = CalendarSettings::self()->+publishAnyURL().replace("%SERVER%", emailHost);
targetURL.setUser(CalendarSettings::self()->publishUserName());
targetURL.setPass(CalendarSettings::self()->publishPassword());
}
#endif
KUrl src;
src.setPath(tempFile.fileName());
kDebug() << targetURL;
KIO::Job *job = KIO::file_copy(src, targetURL, -1, KIO::Overwrite | KIO::HideProgressInfo);
job->ui()->setWindow(parentWidget);
//FIXME slot doesn't exist
//connect(job, SIGNAL(result(KJob*)), SLOT(slotUploadFreeBusyResult(KJob*)));
}
}
void FreeBusyManager::mailFreeBusy(int daysToPublish, QWidget *parentWidget)
{
Q_D(FreeBusyManager);
// No calendar set yet?
if (!d->mCalendar) {
return;
}
KDateTime start = KDateTime::currentUtcDateTime().toTimeSpec(d->mCalendar->timeSpec());
KDateTime end = start.addDays(daysToPublish);
KCalCore::Event::List events = d->mCalendar->rawEvents(start.date(), end.date());
FreeBusy::Ptr freebusy(new FreeBusy(events, start, end));
freebusy->setOrganizer(Person::Ptr(
new Person(Akonadi::CalendarUtils::fullName(),
Akonadi::CalendarUtils::email())));
QPointer<PublishDialog> publishdlg = new PublishDialog();
if (publishdlg->exec() == QDialog::Accepted) {
// Send the mail
MailScheduler *scheduler = new MailScheduler();
connect(scheduler, SIGNAL(transactionFinished(Akonadi::Scheduler::Result,QString))
, d, SLOT(processMailSchedulerResult(Akonadi::Scheduler::Result,QString)));
d->mParentWidgetForMailling = parentWidget;
scheduler->publish(freebusy, publishdlg->addresses());
}
delete publishdlg;
}
bool FreeBusyManager::retrieveFreeBusy(const QString &email, bool forceDownload,
QWidget *parentWidget)
{
Q_D(FreeBusyManager);
kDebug() << email;
if (email.isEmpty()) {
kDebug() << "Email is empty";
return false;
}
d->mParentWidgetForRetrieval = parentWidget;
if (Akonadi::CalendarUtils::thatIsMe(email)) {
// Don't download our own free-busy list from the net
kDebug() << "freebusy of owner, not downloading";
emit freeBusyRetrieved(d->ownerFreeBusy(), email);
return true;
}
// Check for cached copy of free/busy list
KCalCore::FreeBusy::Ptr fb = loadFreeBusy(email);
if (fb) {
kDebug() << "Found a cached copy for " << email;
emit freeBusyRetrieved(fb, email);
return true;
}
// Don't download free/busy if the user does not want it.
if (!CalendarSettings::self()->freeBusyRetrieveAuto() && !forceDownload) {
kDebug() << "Not downloading freebusy";
return false;
}
d->mRetrieveQueue.append(email);
if (d->mRetrieveQueue.count() > 1) {
// TODO: true should always emit
kWarning() << "Returning true without emit, is this correct?";
return true;
}
// queued, because "true" means the download was initiated. So lets
// return before starting stuff
QMetaObject::invokeMethod(d, "processRetrieveQueue", Qt::QueuedConnection);
return true;
}
void FreeBusyManager::cancelRetrieval()
{
Q_D(FreeBusyManager);
d->mRetrieveQueue.clear();
}
KCalCore::FreeBusy::Ptr FreeBusyManager::loadFreeBusy(const QString &email)
{
Q_D(FreeBusyManager);
const QString fbd = d->freeBusyDir();
QFile f(fbd + QLatin1Char('/') + email + QLatin1String(".ifb"));
if (!f.exists()) {
kDebug() << f.fileName() << "doesn't exist.";
return KCalCore::FreeBusy::Ptr();
}
if (!f.open(QIODevice::ReadOnly)) {
kDebug() << "Unable to open file" << f.fileName();
return KCalCore::FreeBusy::Ptr();
}
QTextStream ts(&f);
QString str = ts.readAll();
return d->iCalToFreeBusy(str.toUtf8());
}
bool FreeBusyManager::saveFreeBusy(const KCalCore::FreeBusy::Ptr &freebusy,
const KCalCore::Person::Ptr &person)
{
Q_D(FreeBusyManager);
Q_ASSERT(person);
kDebug() << person->fullName();
QString fbd = d->freeBusyDir();
QDir freeBusyDirectory(fbd);
if (!freeBusyDirectory.exists()) {
kDebug() << "Directory" << fbd <<" does not exist!";
kDebug() << "Creating directory:" << fbd;
if (!freeBusyDirectory.mkpath(fbd)) {
kDebug() << "Could not create directory:" << fbd;
return false;
}
}
QString filename(fbd);
filename += QLatin1Char('/');
filename += person->email();
filename += QLatin1String(".ifb");
QFile f(filename);
kDebug() << "filename:" << filename;
freebusy->clearAttendees();
freebusy->setOrganizer(person);
QString messageText = d->mFormat.createScheduleMessage(freebusy, KCalCore::iTIPPublish);
if (!f.open(QIODevice::ReadWrite)) {
kDebug() << "acceptFreeBusy: Can't open:" << filename << "for writing";
return false;
}
QTextStream t(&f);
t << messageText;
f.close();
return true;
}
void FreeBusyManager::timerEvent(QTimerEvent *)
{
publishFreeBusy();
}
#include "moc_freebusymanager.cpp"
#include "moc_freebusymanager_p.cpp"