KDAV

davitemslistjob.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "davitemslistjob.h"
8#include "davjobbase_p.h"
9
10#include "daverror.h"
11#include "davmanager_p.h"
12#include "davprotocolbase_p.h"
13#include "davurl.h"
14#include "etagcache.h"
15#include "libkdav_debug.h"
16#include "utils_p.h"
17
18#include <KIO/DavJob>
19#include <KIO/Job>
20
21#include <set>
22
23using namespace KDAV;
24
25namespace KDAV
26{
27class DavItemsListJobPrivate : public DavJobBasePrivate
28{
29public:
30 void davJobFinished(KJob *job);
31
32 DavUrl mUrl;
33 std::shared_ptr<EtagCache> mEtagCache;
34 QStringList mMimeTypes;
35 QString mRangeStart;
36 QString mRangeEnd;
37 DavItem::List mItems;
38 std::set<QString> mSeenUrls; // to prevent events duplication with some servers
39 DavItem::List mChangedItems;
40 QStringList mDeletedItems;
41 uint mSubJobCount = 0;
42};
43}
44
45DavItemsListJob::DavItemsListJob(const DavUrl &url, const std::shared_ptr<EtagCache> &cache, QObject *parent)
46 : DavJobBase(new DavItemsListJobPrivate, parent)
47{
49 d->mUrl = url;
50 d->mEtagCache = cache;
51}
52
53DavItemsListJob::~DavItemsListJob() = default;
54
56{
58 d->mMimeTypes = types;
59}
60
62{
64 d->mRangeStart = start;
65 d->mRangeEnd = end;
66}
67
69{
71 const DavProtocolBase *protocol = DavManager::davProtocol(d->mUrl.protocol());
72 Q_ASSERT(protocol);
73
74 const auto queries = protocol->itemsQueries();
75 for (XMLQueryBuilder::Ptr builder : queries) {
76 if (!d->mRangeStart.isEmpty()) {
77 builder->setParameter(QStringLiteral("start"), d->mRangeStart);
78 }
79 if (!d->mRangeEnd.isEmpty()) {
80 builder->setParameter(QStringLiteral("end"), d->mRangeEnd);
81 }
82
83 const QDomDocument props = builder->buildQuery();
84 const QString mimeType = builder->mimeType();
85
86 if (d->mMimeTypes.isEmpty() || d->mMimeTypes.contains(mimeType)) {
87 ++d->mSubJobCount;
88 if (protocol->useReport()) {
89 KIO::DavJob *job = DavManager::self()->createReportJob(d->mUrl.url(), props.toString());
90 job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true"));
91 job->setProperty("davType", QStringLiteral("report"));
92 job->setProperty("itemsMimeType", mimeType);
93 connect(job, &KIO::DavJob::result, this, [d](KJob *job) {
94 d->davJobFinished(job);
95 });
96 } else {
97 KIO::DavJob *job = DavManager::self()->createPropFindJob(d->mUrl.url(), props.toString());
98 job->addMetaData(QStringLiteral("PropagateHttpHeader"), QStringLiteral("true"));
99 job->setProperty("davType", QStringLiteral("propFind"));
100 job->setProperty("itemsMimeType", mimeType);
101 connect(job, &KIO::DavJob::result, this, [d](KJob *job) {
102 d->davJobFinished(job);
103 });
104 }
105 }
106 }
107
108 if (d->mSubJobCount == 0) {
109 setError(ERR_ITEMLIST_NOMIMETYPE);
110 d->setErrorTextFromDavError();
111 emitResult();
112 }
113}
114
116{
117 Q_D(const DavItemsListJob);
118 return d->mItems;
119}
120
122{
123 Q_D(const DavItemsListJob);
124 return d->mChangedItems;
125}
126
128{
129 Q_D(const DavItemsListJob);
130 return d->mDeletedItems;
131}
132
133void DavItemsListJobPrivate::davJobFinished(KJob *job)
134{
135 KIO::DavJob *davJob = qobject_cast<KIO::DavJob *>(job);
136 const int responseCode = davJob->queryMetaData(QStringLiteral("responsecode")).isEmpty() //
137 ? 0
138 : davJob->queryMetaData(QStringLiteral("responsecode")).toInt();
139
140 // KIO::DavJob does not set error() even if the HTTP status code is a 4xx or a 5xx
141 if (davJob->error() || (responseCode >= 400 && responseCode < 600)) {
142 setLatestResponseCode(responseCode);
143 setError(ERR_PROBLEM_WITH_REQUEST);
144 setJobErrorText(davJob->errorText());
145 setJobError(davJob->error());
146 setErrorTextFromDavError();
147 } else {
148 /*
149 * Extract data from a document like the following:
150 *
151 * <multistatus xmlns="DAV:">
152 * <response xmlns="DAV:">
153 * <href xmlns="DAV:">/caldav.php/test1.user/home/KOrganizer-166749289.780.ics</href>
154 * <propstat xmlns="DAV:">
155 * <prop xmlns="DAV:">
156 * <getetag xmlns="DAV:">"b4bbea0278f4f63854c4167a7656024a"</getetag>
157 * </prop>
158 * <status xmlns="DAV:">HTTP/1.1 200 OK</status>
159 * </propstat>
160 * </response>
161 * <response xmlns="DAV:">
162 * <href xmlns="DAV:">/caldav.php/test1.user/home/KOrganizer-399416366.464.ics</href>
163 * <propstat xmlns="DAV:">
164 * <prop xmlns="DAV:">
165 * <getetag xmlns="DAV:">"52eb129018398a7da4f435b2bc4c6cd5"</getetag>
166 * </prop>
167 * <status xmlns="DAV:">HTTP/1.1 200 OK</status>
168 * </propstat>
169 * </response>
170 * </multistatus>
171 */
172
173 const QString itemsMimeType = job->property("itemsMimeType").toString();
174 QDomDocument document;
175 document.setContent(davJob->responseData(), QDomDocument::ParseOption::UseNamespaceProcessing);
176 const QDomElement documentElement = document.documentElement();
177
178 QDomElement responseElement = Utils::firstChildElementNS(documentElement, QStringLiteral("DAV:"), QStringLiteral("response"));
179 while (!responseElement.isNull()) {
180 QDomElement propstatElement;
181
182 // check for the valid propstat, without giving up on first error
183 {
184 const QDomNodeList propstats = responseElement.elementsByTagNameNS(QStringLiteral("DAV:"), QStringLiteral("propstat"));
185 for (int i = 0; i < propstats.length(); ++i) {
186 const QDomElement propstatCandidate = propstats.item(i).toElement();
187 const QDomElement statusElement = Utils::firstChildElementNS(propstatCandidate, QStringLiteral("DAV:"), QStringLiteral("status"));
188 if (statusElement.text().contains(QLatin1String("200"))) {
189 propstatElement = propstatCandidate;
190 }
191 }
192 }
193
194 if (propstatElement.isNull()) {
195 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
196 continue;
197 }
198
199 const QDomElement propElement = Utils::firstChildElementNS(propstatElement, QStringLiteral("DAV:"), QStringLiteral("prop"));
200
201 // check whether it is a DAV collection ...
202 const QDomElement resourcetypeElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("resourcetype"));
203 if (!resourcetypeElement.isNull()) {
204 const QDomElement collectionElement = Utils::firstChildElementNS(resourcetypeElement, QStringLiteral("DAV:"), QStringLiteral("collection"));
205 if (!collectionElement.isNull()) {
206 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
207 continue;
208 }
209 }
210
211 // ... if not it is an item
212 DavItem item;
213 item.setContentType(itemsMimeType);
214
215 // extract path
216 const QDomElement hrefElement = Utils::firstChildElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("href"));
217 const QString href = hrefElement.text();
218
219 QUrl url = davJob->url();
220 url.setUserInfo(QString());
221 if (href.startsWith(QLatin1Char('/'))) {
222 // href is only a path, use request url to complete
223 url.setPath(href, QUrl::TolerantMode);
224 } else {
225 // href is a complete url
226 url = QUrl::fromUserInput(href);
227 }
228
229 const QString itemUrl = url.toDisplayString();
230 const auto [it, isInserted] = mSeenUrls.insert(itemUrl);
231 if (!isInserted) {
232 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
233 continue;
234 }
235
236 qCDebug(KDAV_LOG) << href << "->" << itemUrl;
237 auto _url = url;
238 _url.setUserInfo(mUrl.url().userInfo());
239 item.setUrl(DavUrl(_url, mUrl.protocol()));
240
241 // extract ETag
242 const QDomElement getetagElement = Utils::firstChildElementNS(propElement, QStringLiteral("DAV:"), QStringLiteral("getetag"));
243
244 item.setEtag(getetagElement.text());
245
246 mItems << item;
247
248 if (mEtagCache->etagChanged(itemUrl, item.etag())) {
249 mChangedItems << item;
250 }
251
252 responseElement = Utils::nextSiblingElementNS(responseElement, QStringLiteral("DAV:"), QStringLiteral("response"));
253 }
254 }
255
256 mDeletedItems.clear();
257
258 const auto map = mEtagCache->etagCacheMap();
259 for (auto it = map.cbegin(); it != map.cend(); ++it) {
260 const QString remoteId = it.key();
261 if (mSeenUrls.find(remoteId) == mSeenUrls.cend()) {
262 mDeletedItems.append(remoteId);
263 }
264 }
265
266 if (--mSubJobCount == 0) {
267 emitResult();
268 }
269}
270
271#include "moc_davitemslistjob.cpp"
void setEtag(const QString &etag)
Sets the etag of the item.
Definition davitem.cpp:72
void setUrl(const DavUrl &url)
Sets the url that identifies the item.
Definition davitem.cpp:42
QList< DavItem > List
Defines a list of DAV item objects.
Definition davitem.h:44
void setContentType(const QString &type)
Sets the content type of the item.
Definition davitem.cpp:52
void setTimeRange(const QString &start, const QString &end)
Sets the start and end time to list items for.
void setContentMimeTypes(const QStringList &types)
Limits the mime types of the items requested.
DavItem::List items() const
Returns the list of items seen including identifier URL and ETag information.
QStringList deletedItems() const
Returns the list of items URLs that were not seen in the backend.
DavItemsListJob(const DavUrl &url, const std::shared_ptr< EtagCache > &cache, QObject *parent=nullptr)
Creates a new DAV items list job.
DavItem::List changedItems() const
Returns the list of items that were changed on the server.
void start() override
Starts the job.
A helper class to combine URL and protocol of a DAV URL.
Definition davurl.h:27
QByteArray responseData() const
void addMetaData(const QMap< QString, QString > &values)
QString queryMetaData(const QString &key)
const QUrl & url() const
void emitResult()
void setError(int errorCode)
KJob(QObject *parent=nullptr)
The KDAV namespace.
QDomElement documentElement() const const
ParseResult setContent(QAnyStringView text, ParseOptions options)
QString toString(int indent) const const
QDomNodeList elementsByTagNameNS(const QString &nsURI, const QString &localName) const const
QString text() const const
bool isNull() const const
QDomElement toElement() const const
QDomNode item(int index) const const
int length() const const
QObject(QObject *parent)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QVariant property(const char *name) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
TolerantMode
QUrl fromUserInput(const QString &userInput, const QString &workingDirectory, UserInputResolutionOptions options)
void setPath(const QString &path, ParsingMode mode)
void setUserInfo(const QString &userInfo, ParsingMode mode)
QString toDisplayString(FormattingOptions options) const const
QString toString() const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 28 2025 11:51:48 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.