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

KDE's Doxygen guidelines are available online.