Commit 5e1f283c authored by Damien Caliste's avatar Damien Caliste

[buteo-sync-plugin-caldav] Add initial listing of calendars from server.

This is an preliminary implementation. The path to run the
listing is obtain with the existing calendar paths and removing
one level. This may be improved later on.
parent ad965464
......@@ -22,6 +22,7 @@
*/
#include "caldavclient.h"
#include "propfind.h"
#include "notebooksyncagent.h"
#include <sailfishkeyprovider_iniparser.h>
......@@ -263,6 +264,40 @@ void CalDavClient::connectivityStateChanged(Sync::ConnectivityType aType, bool a
}
}
static Accounts::Account* getAccountForCalendars(Accounts::Manager *manager,
int accountId,
Accounts::Service *service)
{
Accounts::Account *account = manager->account(accountId);
if (!account) {
LOG_WARNING("cannot find account" << accountId);
return NULL;
}
if (!account->enabled()) {
LOG_WARNING("Account" << accountId << "is disabled!");
return NULL;
}
Q_FOREACH (const Accounts::Service &currService, account->services()) {
account->selectService(currService);
if (!account->value("calendars").toStringList().isEmpty()) {
*service = currService;
break;
}
}
if (!service->isValid()) {
LOG_WARNING("cannot find a service for account" << accountId << "with a valid calendar list");
return NULL;
}
account->selectService(*service);
if (!account->enabled()) {
LOG_WARNING("Account" << accountId << "service:" << service->name() << "is disabled!");
return NULL;
}
return account;
}
QList<Settings::CalendarInfo> CalDavClient::loadCalendars(Accounts::Account *account, Accounts::Service srv) const
{
if (!account || !srv.isValid()) {
......@@ -298,6 +333,54 @@ QList<Settings::CalendarInfo> CalDavClient::loadCalendars(Accounts::Account *acc
return allCalendarInfo;
}
void CalDavClient::mergeCalendars(const QList<Settings::CalendarInfo> &calendars)
{
Accounts::Service srv;
Accounts::Account *account = getAccountForCalendars(mManager, mAccountId, &srv);
if (!account) {
return;
}
QStringList calendarPaths = account->value("calendars").toStringList();
QStringList enabledCalendars = account->value("enabled_calendars").toStringList();
QStringList displayNames = account->value("calendar_display_names").toStringList();
QStringList colors = account->value("calendar_colors").toStringList();
account->selectService(Accounts::Service());
bool modified = false;
for (QList<Settings::CalendarInfo>::ConstIterator it = calendars.constBegin();
it != calendars.constEnd(); ++it) {
if (!calendarPaths.contains(it->remotePath)) {
LOG_DEBUG("Found a new upstream calendar:" << it->remotePath << it->displayName);
calendarPaths.append(it->remotePath);
enabledCalendars.append(it->remotePath);
displayNames.append(it->displayName);
colors.append(it->color);
modified = true;
} else {
int i = calendarPaths.indexOf(it->remotePath);
LOG_DEBUG("Already existing calendar:" << it->remotePath << displayNames[i] << colors[i]);
if (displayNames[i] != it->displayName
|| colors[i] != it->color) {
LOG_DEBUG("Updating display name and color:" << it->displayName << it->color);
displayNames[i] = it->displayName;
colors[i] = it->color;
modified = true;
}
}
}
if (modified) {
account->selectService(srv);
account->setValue("calendars", calendarPaths);
account->setValue("enabled_calendars", enabledCalendars);
account->setValue("calendar_display_names", displayNames);
account->setValue("calendar_colors", colors);
account->selectService(Accounts::Service());
account->syncAndBlock();
mSettings.setCalendars(loadCalendars(account, srv));
}
}
bool CalDavClient::initConfig()
{
FUNCTION_CALL_TRACE;
......@@ -315,33 +398,13 @@ bool CalDavClient::initConfig()
return false;
}
mAccountId = accountId;
Accounts::Account *account = mManager->account(accountId);
if (!account) {
LOG_WARNING("cannot find account" << accountId);
return false;
}
if (!account->enabled()) {
LOG_WARNING("Account" << accountId << "is disabled!");
return false;
}
Accounts::Service srv;
Q_FOREACH (const Accounts::Service &currService, account->services()) {
account->selectService(currService);
if (!account->value("calendars").toStringList().isEmpty()) {
srv = currService;
break;
}
}
if (!srv.isValid()) {
LOG_WARNING("cannot find a service for account" << accountId << "with a valid calendar list");
Accounts::Account *account = getAccountForCalendars(mManager, accountId, &srv);
if (!account) {
return false;
}
account->selectService(srv);
if (!account->enabled()) {
LOG_WARNING("Account" << accountId << "service:" << srv.name() << "is disabled!");
return false;
}
mSettings.setServerAddress(account->value("server_address").toString());
if (mSettings.serverAddress().isEmpty()) {
LOG_WARNING("remote_address not found in service settings");
......@@ -452,6 +515,30 @@ void CalDavClient::start()
}
mSettings.setAuthToken(mAuth->token());
QList<Settings::CalendarInfo> allCalendarInfo = mSettings.calendars();
if (allCalendarInfo.isEmpty()) {
syncFinished(Buteo::SyncResults::NO_ERROR, "No calendars for this account");
return;
}
PropFind *calendarRequest = new PropFind(mNAManager, &mSettings, this);
connect(calendarRequest, &Request::finished, this, &CalDavClient::syncCalendars);
// Hacky here, try to guess the root for calendars from known
// calendar paths, by removing one level.
int lastIndex = allCalendarInfo[0].remotePath.lastIndexOf('/', -2);
calendarRequest->listCalendars(allCalendarInfo[0].remotePath.left(lastIndex + 1));
}
void CalDavClient::syncCalendars()
{
PropFind *request = qobject_cast<PropFind*>(sender());
request->deleteLater();
if (request->errorCode() == Buteo::SyncResults::NO_ERROR) {
mergeCalendars(request->calendars());
} else {
LOG_WARNING("Unable to list calendars from server.");
}
QList<Settings::CalendarInfo> allCalendarInfo = mSettings.calendars();
if (allCalendarInfo.isEmpty()) {
syncFinished(Buteo::SyncResults::NO_ERROR, "No calendars for this account");
......
......@@ -139,6 +139,7 @@ public Q_SLOTS:
private Q_SLOTS:
void start();
void authenticationError();
void syncCalendars();
void notebookSyncFinished(int errorCode, const QString &errorString);
private:
......@@ -152,6 +153,7 @@ private:
bool cleanSyncRequired(int accountId);
void getSyncDateRange(const QDateTime &sourceDate, QDateTime *fromDateTime, QDateTime *toDateTime);
QList<Settings::CalendarInfo> loadCalendars(Accounts::Account *account, Accounts::Service srv) const;
void mergeCalendars(const QList<Settings::CalendarInfo> &calendars);
Buteo::SyncProfile::SyncDirection syncDirection();
Buteo::SyncProfile::ConflictResolutionPolicy conflictResolutionPolicy();
......
......@@ -232,6 +232,8 @@ bool NotebookSyncAgent::setNotebookFromInfo(const QString &notebookName,
if (!mStorage->loadNotebookIncidences(notebook->uid()))
return false;
mNotebook = notebook;
mNotebook->setColor(color);
mNotebook->setName(notebookName);
return true;
}
}
......
/*
* This file is part of buteo-sync-plugin-caldav package
*
* Copyright (C) 2019 Jolla Ltd. and/or its subsidiary(-ies).
*
* Contributors: Damien Caliste <dcaliste@free.fr>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#include "propfind.h"
#include "settings.h"
#include <QNetworkAccessManager>
#include <QBuffer>
#include <QXmlStreamReader>
#include <LogMacros.h>
static bool readResourceType(QXmlStreamReader *reader, bool *isCalendar)
{
for (; !reader->atEnd(); reader->readNext()) {
if (reader->name() == "calendar") {
*isCalendar = true;
}
if (reader->name() == "resourcetype" && reader->isEndElement()) {
return true;
}
}
return false;
}
static bool readProp(QXmlStreamReader *reader, bool *isCalendar, QString *label, QString *color)
{
QString displayName;
QString displayColor;
*isCalendar = false;
for (; !reader->atEnd(); reader->readNext()) {
if (reader->name() == "displayname" && reader->isStartElement()) {
displayName = reader->readElementText();
}
if (reader->name() == "calendar-color" && reader->isStartElement()) {
displayColor = reader->readElementText();
if (displayColor.startsWith("#") && displayColor.length() == 9) {
displayColor = displayColor.left(7);
}
}
if (reader->name() == "resourcetype") {
if (!readResourceType(reader, isCalendar)) {
return false;
}
}
if (reader->name() == "prop" && reader->isEndElement()) {
if (*isCalendar) {
*label = displayName.isEmpty() ? QStringLiteral("Calendar") : displayName;
*color = displayColor;
}
return true;
}
}
return false;
}
static bool readPropStat(QXmlStreamReader *reader, bool *isCalendar, QString *label, QString *color)
{
for (; !reader->atEnd(); reader->readNext()) {
if (reader->name() == "prop") {
if (!readProp(reader, isCalendar, label, color)) {
return false;
}
}
if (reader->name() == "propstat" && reader->isEndElement()) {
return true;
}
}
return false;
}
static bool readResponse(QXmlStreamReader *reader, QList<Settings::CalendarInfo> *calendars)
{
bool isCalendar = false;
QString href;
QString label;
QString color;
for (; !reader->atEnd(); reader->readNext()) {
if (reader->name() == "href" && reader->isStartElement()) {
href = reader->readElementText();
}
if (reader->name() == "propstat") {
if (!readPropStat(reader, &isCalendar, &label, &color)) {
return false;
}
}
if (reader->name() == "response" && reader->isEndElement()) {
if (!isCalendar) {
return true;
}
if (href.isEmpty()) {
return false;
}
calendars->append(Settings::CalendarInfo{href, label, color});
return true;
}
}
return false;
}
PropFind::PropFind(QNetworkAccessManager *manager, Settings *settings, QObject *parent)
: Request(manager, settings, "PROPFIND", parent)
{
FUNCTION_CALL_TRACE;
}
void PropFind::listCalendars(const QString &calendarsPath)
{
QByteArray requestData("<d:propfind xmlns:d=\"DAV:\" xmlns:a=\"http://apple.com/ns/ical/\">" \
" <d:prop>" \
" <d:resourcetype />" \
" <d:current-user-principal />" \
" <d:displayname />" \
" <a:calendar-color />" \
" </d:prop>" \
"</d:propfind>");
mCalendars.clear();
sendRequest(calendarsPath, requestData);
}
void PropFind::sendRequest(const QString &remotePath, const QByteArray &requestData)
{
FUNCTION_CALL_TRACE;
QNetworkRequest request;
prepareRequest(&request, remotePath);
request.setRawHeader("Depth", "1");
request.setRawHeader("Prefer", "return-minimal");
request.setHeader(QNetworkRequest::ContentLengthHeader, requestData.length());
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml; charset=utf-8");
QBuffer *buffer = new QBuffer(this);
buffer->setData(requestData);
QNetworkReply *reply = mNAManager->sendCustomRequest(request, REQUEST_TYPE.toLatin1(), buffer);
debugRequest(request, buffer->buffer());
connect(reply, SIGNAL(finished()), this, SLOT(processResponse()));
connect(reply, SIGNAL(sslErrors(QList<QSslError>)),
this, SLOT(slotSslErrors(QList<QSslError>)));
}
void PropFind::processResponse()
{
FUNCTION_CALL_TRACE;
LOG_DEBUG("Process PROPFIND response.");
if (wasDeleted()) {
LOG_DEBUG("PROPFIND request was aborted");
return;
}
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (!reply) {
finishedWithInternalError();
return;
}
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
finishedWithReplyResult(reply->error());
return;
}
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (statusCode.isValid()) {
int status = statusCode.toInt();
if (status > 299) {
finishedWithError(Buteo::SyncResults::INTERNAL_ERROR,
QString("Got error status response for PROPFIND: %1").arg(status));
return;
}
}
QByteArray data = reply->readAll();
debugReply(*reply, data);
if (data.isNull() || data.isEmpty()) {
finishedWithError(Buteo::SyncResults::INTERNAL_ERROR,
QString("Empty response body for PROPFIND"));
return;
}
QXmlStreamReader reader(data);
reader.setNamespaceProcessing(true);
for (; !reader.atEnd(); reader.readNext()) {
if (reader.name() == "response") {
if (!readResponse(&reader, &mCalendars)) {
finishedWithError(Buteo::SyncResults::INTERNAL_ERROR,
QString("Cannot parse response body for PROPFIND"));
return;
}
}
}
finishedWithSuccess();
}
const QList<Settings::CalendarInfo>& PropFind::calendars() const
{
return mCalendars;
}
/*
* This file is part of buteo-sync-plugin-caldav package
*
* Copyright (C) 2019 Jolla Ltd. and/or its subsidiary(-ies).
*
* Contributors: Damien Caliste <dcaliste@free.fr>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#ifndef PROPFIND_H
#define PROPFIND_H
#include "request.h"
class QNetworkAccessManager;
class Settings;
class PropFind : public Request
{
Q_OBJECT
public:
explicit PropFind(QNetworkAccessManager *manager, Settings *settings, QObject *parent = 0);
void listCalendars(const QString &calendarsPath);
const QList<Settings::CalendarInfo>& calendars() const;
private Q_SLOTS:
void processResponse();
private:
void sendRequest(const QString &remotePath, const QByteArray &requestData);
QList<Settings::CalendarInfo> mCalendars;
};
#endif
......@@ -13,6 +13,7 @@ SOURCES += \
$$PWD/report.cpp \
$$PWD/put.cpp \
$$PWD/delete.cpp \
$$PWD/propfind.cpp \
$$PWD/reader.cpp \
$$PWD/settings.cpp \
$$PWD/request.cpp \
......@@ -26,6 +27,7 @@ HEADERS += \
$$PWD/report.h \
$$PWD/put.h \
$$PWD/delete.h \
$$PWD/propfind.h \
$$PWD/reader.h \
$$PWD/settings.h \
$$PWD/request.h \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment