diff --git a/src/common/davitemslistjob.cpp b/src/common/davitemslistjob.cpp index b99660f..9ddb583 100644 --- a/src/common/davitemslistjob.cpp +++ b/src/common/davitemslistjob.cpp @@ -1,266 +1,268 @@ /* Copyright (c) 2010 Tobias Koenig This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "davitemslistjob.h" #include "davjobbase_p.h" #include "daverror.h" #include "davmanager.h" #include "davmanager_p.h" #include "davprotocolbase_p.h" #include "davurl.h" #include "utils.h" #include "etagcache.h" #include #include +#include + using namespace KDAV; namespace KDAV { class DavItemsListJobPrivate : public DavJobBasePrivate { public: DavUrl mUrl; std::shared_ptr mEtagCache; QStringList mMimeTypes; QString mRangeStart; QString mRangeEnd; DavItem::List mItems; QSet mSeenUrls; // to prevent events duplication with some servers DavItem::List mChangedItems; QStringList mDeletedItems; uint mSubJobCount = 0; }; } DavItemsListJob::DavItemsListJob(const DavUrl &url, const std::shared_ptr &cache, QObject *parent) : DavJobBase(new DavItemsListJobPrivate, parent) { Q_D(DavItemsListJob); d->mUrl = url; d->mEtagCache = cache; } DavItemsListJob::~DavItemsListJob() = default; void DavItemsListJob::setContentMimeTypes(const QStringList &types) { Q_D(DavItemsListJob); d->mMimeTypes = types; } void DavItemsListJob::setTimeRange(const QString &start, const QString &end) { Q_D(DavItemsListJob); d->mRangeStart = start; d->mRangeEnd = end; } void DavItemsListJob::start() { Q_D(DavItemsListJob); const DavProtocolBase *protocol = DavManagerPrivate::davProtocol(d->mUrl.protocol()); Q_ASSERT(protocol); QVectorIterator it(protocol->itemsQueries()); while (it.hasNext()) { XMLQueryBuilder::Ptr builder = it.next(); if (!d->mRangeStart.isEmpty()) { builder->setParameter(QStringLiteral("start"), d->mRangeStart); } if (!d->mRangeEnd.isEmpty()) { builder->setParameter(QStringLiteral("end"), d->mRangeEnd); } const QDomDocument props = builder->buildQuery(); const QString mimeType = builder->mimeType(); if (d->mMimeTypes.isEmpty() || d->mMimeTypes.contains(mimeType)) { ++d->mSubJobCount; if (protocol->useReport()) { KIO::DavJob *job = DavManager::self()->createReportJob(d->mUrl.url(), props); job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true")); job->setProperty("davType", QStringLiteral("report")); job->setProperty("itemsMimeType", mimeType); connect(job, &KIO::DavJob::result, this, &DavItemsListJob::davJobFinished); } else { KIO::DavJob *job = DavManager::self()->createPropFindJob(d->mUrl.url(), props); job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true")); job->setProperty("davType", QStringLiteral("propFind")); job->setProperty("itemsMimeType", mimeType); connect(job, &KIO::DavJob::result, this, &DavItemsListJob::davJobFinished); } } } if (d->mSubJobCount == 0) { setError(ERR_ITEMLIST_NOMIMETYPE); setErrorTextFromDavError(); emitResult(); } } DavItem::List DavItemsListJob::items() const { Q_D(const DavItemsListJob); return d->mItems; } DavItem::List DavItemsListJob::changedItems() const { Q_D(const DavItemsListJob); return d->mChangedItems; } QStringList DavItemsListJob::deletedItems() const { Q_D(const DavItemsListJob); return d->mDeletedItems; } void DavItemsListJob::davJobFinished(KJob *job) { Q_D(DavItemsListJob); KIO::DavJob *davJob = qobject_cast(job); const int responseCode = davJob->queryMetaData(QStringLiteral("responsecode")).isEmpty() ? 0 : davJob->queryMetaData(QStringLiteral("responsecode")).toInt(); // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx if (davJob->error() || (responseCode >= 400 && responseCode < 600)) { setLatestResponseCode(responseCode); setError(ERR_PROBLEM_WITH_REQUEST); setJobErrorText(davJob->errorText()); setJobError(davJob->error()); setErrorTextFromDavError(); } else { /* * Extract data from a document like the following: * * * * /caldav.php/test1.user/home/KOrganizer-166749289.780.ics * * * "b4bbea0278f4f63854c4167a7656024a" * * HTTP/1.1 200 OK * * * * /caldav.php/test1.user/home/KOrganizer-399416366.464.ics * * * "52eb129018398a7da4f435b2bc4c6cd5" * * HTTP/1.1 200 OK * * * */ const QString itemsMimeType = job->property("itemsMimeType").toString(); const QDomDocument document = davJob->response(); const QDomElement documentElement = document.documentElement(); QDomElement responseElement = Utils::firstChildElementNS(documentElement, QStringLiteral("DAV:"), QStringLiteral("response")); while (!responseElement.isNull()) { QDomElement propstatElement; // check for the valid propstat, without giving up on first error { const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat")); for (int i = 0; i < propstats.length(); ++i) { const QDomElement propstatCandidate = propstats.item(i).toElement(); const QDomElement statusElement = Utils::firstChildElementNS(propstatCandidate, QStringLiteral("DAV:"), QStringLiteral("status")); if (statusElement.text().contains(QLatin1String("200"))) { propstatElement = propstatCandidate; } } } if (propstatElement.isNull()) { responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); continue; } const QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop")); // check whether it is a dav collection ... const QDomElement resourcetypeElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("resourcetype")); if (!responseElement.isNull()) { const QDomElement collectionElement = Utils::firstChildElementNS(resourcetypeElement, QStringLiteral("DAV:"), QStringLiteral("collection")); if (!collectionElement.isNull()) { responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); continue; } } // ... if not it is an item DavItem item; item.setContentType(itemsMimeType); // extract path const QDomElement hrefElement = Utils::firstChildElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("href")); const QString href = hrefElement.text(); QUrl url = davJob->url(); url.setUserInfo(QString()); if (href.startsWith(QLatin1Char('/'))) { // href is only a path, use request url to complete url.setPath(href, QUrl::TolerantMode); } else { // href is a complete url url = QUrl::fromUserInput(href); } QString itemUrl = url.toDisplayString(); if (d->mSeenUrls.contains(itemUrl)) { responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); continue; } d->mSeenUrls << itemUrl; auto _url = url; _url.setUserInfo(d->mUrl.url().userInfo()); item.setUrl(DavUrl(_url, d->mUrl.protocol())); // extract etag const QDomElement getetagElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("getetag")); item.setEtag(getetagElement.text()); d->mItems << item; if (d->mEtagCache->etagChanged(itemUrl, item.etag())) { d->mChangedItems << item; } responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response")); } } QSet removed = d->mEtagCache->urls().toSet(); removed.subtract(d->mSeenUrls); d->mDeletedItems = removed.toList(); if (--d->mSubJobCount == 0) { emitResult(); } } diff --git a/src/common/etagcache.cpp b/src/common/etagcache.cpp index f88f218..711f6ca 100644 --- a/src/common/etagcache.cpp +++ b/src/common/etagcache.cpp @@ -1,79 +1,94 @@ /* Copyright (c) 2010 Grégory Oestreicher This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "etagcache.h" +#include +#include + using namespace KDAV; +namespace KDAV { +class EtagCachePrivate +{ +public: + QMap mCache; + QSet mChangedRemoteIds; +}; +} + EtagCache::EtagCache(QObject *parent) : QObject(parent) + , d(new EtagCachePrivate) { } +EtagCache::~EtagCache() = default; + void EtagCache::setEtag(const QString &remoteId, const QString &etag) { setEtagInternal(remoteId, etag); - if (mChangedRemoteIds.contains(remoteId)) { - mChangedRemoteIds.remove(remoteId); + if (d->mChangedRemoteIds.contains(remoteId)) { + d->mChangedRemoteIds.remove(remoteId); } } void EtagCache::setEtagInternal(const QString &remoteId, const QString &etag) { - mCache[ remoteId ] = etag; + d->mCache[ remoteId ] = etag; } bool EtagCache::contains(const QString &remoteId) const { - return mCache.contains(remoteId); + return d->mCache.contains(remoteId); } bool EtagCache::etagChanged(const QString &remoteId, const QString &refEtag) const { if (!contains(remoteId)) { return true; } - return mCache.value(remoteId) != refEtag; + return d->mCache.value(remoteId) != refEtag; } void EtagCache::markAsChanged(const QString &remoteId) { - mChangedRemoteIds.insert(remoteId); + d->mChangedRemoteIds.insert(remoteId); } bool EtagCache::isOutOfDate(const QString &remoteId) const { - return mChangedRemoteIds.contains(remoteId); + return d->mChangedRemoteIds.contains(remoteId); } void EtagCache::removeEtag(const QString &remoteId) { - mChangedRemoteIds.remove(remoteId); - mCache.remove(remoteId); + d->mChangedRemoteIds.remove(remoteId); + d->mCache.remove(remoteId); } QStringList EtagCache::urls() const { - return mCache.keys(); + return d->mCache.keys(); } QStringList EtagCache::changedRemoteIds() const { - return mChangedRemoteIds.toList(); + return d->mChangedRemoteIds.toList(); } diff --git a/src/common/etagcache.h b/src/common/etagcache.h index 7e9a26c..1639319 100644 --- a/src/common/etagcache.h +++ b/src/common/etagcache.h @@ -1,105 +1,107 @@ /* Copyright (c) 2010 Grégory Oestreicher This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDAV_ETAGCACHE_H #define KDAV_ETAGCACHE_H #include "kpimkdav_export.h" -#include #include -#include #include +#include + namespace KDAV { +class EtagCachePrivate; + /** * @short A helper class to cache etags. * * The EtagCache caches the remote ids and etags of all items * in a given collection. This cache is needed to find * out which items have been changed in the backend and have to * be refetched on the next call of ResourceBase::retrieveItems() */ class KPIMKDAV_EXPORT EtagCache : public QObject { Q_OBJECT public: /** * Creates a new etag cache and populates it with the ETags * of items found in @p collection. */ explicit EtagCache(QObject *parent = nullptr); + ~EtagCache(); /** * Sets the ETag for the remote ID. If the remote ID is marked as * changed (is contained in the return of changedRemoteIds), remove * it from the changed list. */ void setEtag(const QString &remoteId, const QString &etag); /** * Checks if the given item is in the cache */ Q_REQUIRED_RESULT bool contains(const QString &remoteId) const; /** * Check if the known ETag for the remote ID is equal to @p refEtag. */ Q_REQUIRED_RESULT bool etagChanged(const QString &remoteId, const QString &refEtag) const; /** * Mark an item as changed in the backend. */ void markAsChanged(const QString &remoteId); /** * Returns true if the remote ID is marked as changed (is contained in the * return of changedRemoteIds) */ Q_REQUIRED_RESULT bool isOutOfDate(const QString &remoteId) const; /** * Removes the entry for item with remote ID @p remoteId. */ void removeEtag(const QString &remoteId); /** * Returns the list of all items URLs. */ Q_REQUIRED_RESULT QStringList urls() const; /** * Returns the list of remote ids of items that have been changed * in the backend. */ Q_REQUIRED_RESULT QStringList changedRemoteIds() const; protected: /** * Sets the ETag for the remote ID. */ void setEtagInternal(const QString &remoteId, const QString &etag); private: - QMap mCache; - QSet mChangedRemoteIds; + const std::unique_ptr d; }; } #endif