1 | /* |
2 | This file is part of the KDE project |
3 | SPDX-FileCopyrightText: 2004 Kevin Ottens <ervin@ipsquad.net> |
4 | SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> |
5 | |
6 | SPDX-License-Identifier: LGPL-2.0-or-later |
7 | */ |
8 | |
9 | #include "forwardingworkerbase.h" |
10 | #include "../utils_p.h" |
11 | |
12 | #include "deletejob.h" |
13 | #include "filecopyjob.h" |
14 | #include "kiocoredebug.h" |
15 | #include "listjob.h" |
16 | #include "mimetypejob.h" |
17 | #include "mkdirjob.h" |
18 | #include "statjob.h" |
19 | #include "transferjob.h" |
20 | |
21 | #include <QEventLoop> |
22 | #include <QMimeDatabase> |
23 | |
24 | namespace KIO |
25 | { |
26 | class ForwardingWorkerBasePrivate |
27 | { |
28 | public: |
29 | ForwardingWorkerBasePrivate(const QByteArray &protocol, QObject *eventLoopParent, ForwardingWorkerBase *qq) |
30 | : q(qq) |
31 | , m_protocol(QString::fromUtf8(ba: protocol)) |
32 | , eventLoop(eventLoopParent) |
33 | { |
34 | } |
35 | ForwardingWorkerBase *const q; |
36 | |
37 | const QString m_protocol; |
38 | QUrl m_processedURL; |
39 | QUrl m_requestedURL; |
40 | |
41 | bool internalRewriteUrl(const QUrl &url, QUrl &newURL); |
42 | |
43 | void connectJob(Job *job); |
44 | void connectSimpleJob(SimpleJob *job); |
45 | void connectListJob(ListJob *job); |
46 | void connectTransferJob(TransferJob *job); |
47 | |
48 | void _k_slotResult(KJob *job); |
49 | void _k_slotWarning(KJob *job, const QString &msg) const; |
50 | void _k_slotInfoMessage(KJob *job, const QString &msg) const; |
51 | void _k_slotTotalSize(KJob *job, qulonglong size) const; |
52 | void _k_slotProcessedSize(KJob *job, qulonglong size) const; |
53 | void _k_slotSpeed(KJob *job, unsigned long bytesPerSecond) const; |
54 | |
55 | // KIO::SimpleJob subclasses |
56 | void _k_slotRedirection(KIO::Job *job, const QUrl &url); |
57 | |
58 | // KIO::ListJob |
59 | void _k_slotEntries(KIO::Job *job, const KIO::UDSEntryList &entries) const; |
60 | |
61 | // KIO::TransferJob |
62 | void _k_slotData(KIO::Job *job, const QByteArray &data) const; |
63 | void _k_slotDataReq(KIO::Job *job, QByteArray &data) const; |
64 | void _k_slotMimetype(KIO::Job *job, const QString &type) const; |
65 | void _k_slotCanResume(KIO::Job *job, KIO::filesize_t offset) const; |
66 | |
67 | [[nodiscard]] WorkerResult loopResult() |
68 | { |
69 | eventLoop.exec(); |
70 | return m_pendingResult; |
71 | } |
72 | |
73 | private: |
74 | // These are intentionally private to force us to go through [[nodiscard]] helper functions, lest we forget to retrieve the result. |
75 | QEventLoop eventLoop; |
76 | WorkerResult m_pendingResult = WorkerResult::pass(); |
77 | }; |
78 | |
79 | ForwardingWorkerBase::ForwardingWorkerBase(const QByteArray &protocol, const QByteArray &poolSocket, const QByteArray &appSocket) |
80 | : WorkerBase(protocol, poolSocket, appSocket) |
81 | , d(new ForwardingWorkerBasePrivate(protocol, this, this)) |
82 | { |
83 | } |
84 | |
85 | ForwardingWorkerBase::~ForwardingWorkerBase() = default; |
86 | |
87 | bool ForwardingWorkerBasePrivate::internalRewriteUrl(const QUrl &url, QUrl &newURL) |
88 | { |
89 | bool result = true; |
90 | |
91 | if (url.scheme() == m_protocol) { |
92 | result = q->rewriteUrl(url, newURL); |
93 | } else { |
94 | newURL = url; |
95 | } |
96 | |
97 | m_processedURL = newURL; |
98 | m_requestedURL = url; |
99 | return result; |
100 | } |
101 | |
102 | void ForwardingWorkerBase::adjustUDSEntry(KIO::UDSEntry &entry, UDSEntryCreationMode creationMode) const |
103 | { |
104 | const bool listing = (creationMode == UDSEntryCreationInListDir); |
105 | // qDebug() << "listing==" << listing; |
106 | |
107 | const QString name = entry.stringValue(field: KIO::UDSEntry::UDS_NAME); |
108 | QString mimetype = entry.stringValue(field: KIO::UDSEntry::UDS_MIME_TYPE); |
109 | QUrl url; |
110 | const QString urlStr = entry.stringValue(field: KIO::UDSEntry::UDS_URL); |
111 | const bool url_found = !urlStr.isEmpty(); |
112 | if (url_found) { |
113 | url = QUrl(urlStr); |
114 | QUrl new_url(d->m_requestedURL); |
115 | if (listing) { |
116 | new_url.setPath(path: Utils::concatPaths(path1: new_url.path(), path2: url.fileName())); |
117 | } |
118 | // ## Didn't find a way to use an iterator instead of re-doing a key lookup |
119 | entry.replace(field: KIO::UDSEntry::UDS_URL, value: new_url.toString()); |
120 | // qDebug() << "URL =" << url; |
121 | // qDebug() << "New URL =" << new_url; |
122 | } |
123 | |
124 | if (mimetype.isEmpty()) { |
125 | QUrl new_url(d->m_processedURL); |
126 | if (url_found && listing) { |
127 | new_url.setPath(path: Utils::concatPaths(path1: new_url.path(), path2: url.fileName())); |
128 | } else if (listing) { |
129 | new_url.setPath(path: Utils::concatPaths(path1: new_url.path(), path2: name)); |
130 | } |
131 | |
132 | QMimeDatabase db; |
133 | mimetype = db.mimeTypeForUrl(url: new_url).name(); |
134 | |
135 | entry.replace(field: KIO::UDSEntry::UDS_MIME_TYPE, value: mimetype); |
136 | |
137 | // qDebug() << "New MIME type = " << mimetype; |
138 | } |
139 | |
140 | if (d->m_processedURL.isLocalFile()) { |
141 | QUrl new_url(d->m_processedURL); |
142 | if (listing) { |
143 | new_url.setPath(path: Utils::concatPaths(path1: new_url.path(), path2: name)); |
144 | } |
145 | |
146 | entry.replace(field: KIO::UDSEntry::UDS_LOCAL_PATH, value: new_url.toLocalFile()); |
147 | } |
148 | } |
149 | |
150 | QUrl ForwardingWorkerBase::processedUrl() const |
151 | { |
152 | return d->m_processedURL; |
153 | } |
154 | |
155 | QUrl ForwardingWorkerBase::requestedUrl() const |
156 | { |
157 | return d->m_requestedURL; |
158 | } |
159 | |
160 | WorkerResult ForwardingWorkerBase::get(const QUrl &url) |
161 | { |
162 | QUrl new_url; |
163 | if (d->internalRewriteUrl(url, newURL&: new_url)) { |
164 | KIO::TransferJob *job = KIO::get(url: new_url, reload: NoReload, flags: HideProgressInfo); |
165 | d->connectTransferJob(job); |
166 | |
167 | return d->loopResult(); |
168 | } |
169 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: url.toDisplayString()); |
170 | } |
171 | |
172 | WorkerResult ForwardingWorkerBase::put(const QUrl &url, int permissions, JobFlags flags) |
173 | { |
174 | QUrl new_url; |
175 | if (d->internalRewriteUrl(url, newURL&: new_url)) { |
176 | KIO::TransferJob *job = KIO::put(url: new_url, permissions, flags: flags | HideProgressInfo); |
177 | d->connectTransferJob(job); |
178 | |
179 | return d->loopResult(); |
180 | } |
181 | return WorkerResult::fail(error: KIO::ERR_MALFORMED_URL, errorString: url.toDisplayString()); |
182 | } |
183 | |
184 | WorkerResult ForwardingWorkerBase::stat(const QUrl &url) |
185 | { |
186 | QUrl new_url; |
187 | if (d->internalRewriteUrl(url, newURL&: new_url)) { |
188 | KIO::SimpleJob *job = KIO::stat(url: new_url, flags: KIO::HideProgressInfo); |
189 | d->connectSimpleJob(job); |
190 | |
191 | return d->loopResult(); |
192 | } |
193 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: url.toDisplayString()); |
194 | } |
195 | |
196 | WorkerResult ForwardingWorkerBase::mimetype(const QUrl &url) |
197 | { |
198 | QUrl new_url; |
199 | if (d->internalRewriteUrl(url, newURL&: new_url)) { |
200 | KIO::TransferJob *job = KIO::mimetype(url: new_url, flags: KIO::HideProgressInfo); |
201 | d->connectTransferJob(job); |
202 | |
203 | return d->loopResult(); |
204 | } |
205 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: url.toDisplayString()); |
206 | } |
207 | |
208 | WorkerResult ForwardingWorkerBase::listDir(const QUrl &url) |
209 | { |
210 | QUrl new_url; |
211 | if (d->internalRewriteUrl(url, newURL&: new_url)) { |
212 | KIO::ListJob *job = KIO::listDir(url: new_url, flags: KIO::HideProgressInfo); |
213 | d->connectListJob(job); |
214 | |
215 | return d->loopResult(); |
216 | } |
217 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: url.toDisplayString()); |
218 | } |
219 | |
220 | WorkerResult ForwardingWorkerBase::mkdir(const QUrl &url, int permissions) |
221 | { |
222 | QUrl new_url; |
223 | if (d->internalRewriteUrl(url, newURL&: new_url)) { |
224 | KIO::SimpleJob *job = KIO::mkdir(url: new_url, permissions); |
225 | d->connectSimpleJob(job); |
226 | |
227 | return d->loopResult(); |
228 | } |
229 | return WorkerResult::fail(error: KIO::ERR_MALFORMED_URL, errorString: url.toDisplayString()); |
230 | } |
231 | |
232 | WorkerResult ForwardingWorkerBase::rename(const QUrl &src, const QUrl &dest, JobFlags flags) |
233 | { |
234 | qCDebug(KIO_CORE) << "rename" << src << dest; |
235 | |
236 | QUrl new_src; |
237 | QUrl new_dest; |
238 | if (!d->internalRewriteUrl(url: src, newURL&: new_src)) { |
239 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: src.toDisplayString()); |
240 | } |
241 | if (d->internalRewriteUrl(url: dest, newURL&: new_dest)) { |
242 | KIO::Job *job = KIO::rename(src: new_src, dest: new_dest, flags); |
243 | d->connectJob(job); |
244 | |
245 | return d->loopResult(); |
246 | } |
247 | return WorkerResult::fail(error: KIO::ERR_MALFORMED_URL, errorString: dest.toDisplayString()); |
248 | } |
249 | |
250 | WorkerResult ForwardingWorkerBase::symlink(const QString &target, const QUrl &dest, JobFlags flags) |
251 | { |
252 | qCDebug(KIO_CORE) << "symlink" << target << dest; |
253 | |
254 | QUrl new_dest; |
255 | if (d->internalRewriteUrl(url: dest, newURL&: new_dest)) { |
256 | KIO::SimpleJob *job = KIO::symlink(target, dest: new_dest, flags: flags | HideProgressInfo); |
257 | d->connectSimpleJob(job); |
258 | |
259 | return d->loopResult(); |
260 | } |
261 | return WorkerResult::fail(error: KIO::ERR_MALFORMED_URL, errorString: dest.toDisplayString()); |
262 | } |
263 | |
264 | WorkerResult ForwardingWorkerBase::chmod(const QUrl &url, int permissions) |
265 | { |
266 | QUrl new_url; |
267 | if (d->internalRewriteUrl(url, newURL&: new_url)) { |
268 | KIO::SimpleJob *job = KIO::chmod(url: new_url, permissions); |
269 | d->connectSimpleJob(job); |
270 | |
271 | return d->loopResult(); |
272 | } |
273 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: url.toDisplayString()); |
274 | } |
275 | |
276 | WorkerResult ForwardingWorkerBase::setModificationTime(const QUrl &url, const QDateTime &mtime) |
277 | { |
278 | QUrl new_url; |
279 | if (d->internalRewriteUrl(url, newURL&: new_url)) { |
280 | KIO::SimpleJob *job = KIO::setModificationTime(url: new_url, mtime); |
281 | d->connectSimpleJob(job); |
282 | |
283 | return d->loopResult(); |
284 | } |
285 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: url.toDisplayString()); |
286 | } |
287 | |
288 | WorkerResult ForwardingWorkerBase::copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) |
289 | { |
290 | qCDebug(KIO_CORE) << "copy" << src << dest; |
291 | |
292 | QUrl new_src; |
293 | QUrl new_dest; |
294 | if (!d->internalRewriteUrl(url: src, newURL&: new_src)) { |
295 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: src.toDisplayString()); |
296 | } |
297 | if (d->internalRewriteUrl(url: dest, newURL&: new_dest)) { |
298 | KIO::Job *job = KIO::file_copy(src: new_src, dest: new_dest, permissions, flags: flags | HideProgressInfo); |
299 | d->connectJob(job); |
300 | |
301 | return d->loopResult(); |
302 | } |
303 | return WorkerResult::fail(error: KIO::ERR_MALFORMED_URL, errorString: dest.toDisplayString()); |
304 | } |
305 | |
306 | WorkerResult ForwardingWorkerBase::del(const QUrl &url, bool isfile) |
307 | { |
308 | QUrl new_url; |
309 | if (d->internalRewriteUrl(url, newURL&: new_url)) { |
310 | if (isfile) { |
311 | KIO::DeleteJob *job = KIO::del(src: new_url, flags: HideProgressInfo); |
312 | d->connectJob(job); |
313 | } else { |
314 | KIO::SimpleJob *job = KIO::rmdir(url: new_url); |
315 | d->connectSimpleJob(job); |
316 | } |
317 | |
318 | return d->loopResult(); |
319 | } |
320 | return WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: url.toDisplayString()); |
321 | } |
322 | |
323 | ////////////////////////////////////////////////////////////////////////////// |
324 | |
325 | void ForwardingWorkerBasePrivate::connectJob(KIO::Job *job) |
326 | { |
327 | // We will forward the warning message, no need to let the job |
328 | // display it itself |
329 | job->setUiDelegate(nullptr); |
330 | |
331 | // Forward metadata (e.g. modification time for put()) |
332 | job->setMetaData(q->allMetaData()); |
333 | |
334 | q->connect(sender: job, signal: &KJob::result, context: q, slot: [this](KJob *job) { |
335 | _k_slotResult(job); |
336 | }); |
337 | q->connect(sender: job, signal: &KJob::warning, context: q, slot: [this](KJob *job, const QString &text) { |
338 | _k_slotWarning(job, msg: text); |
339 | }); |
340 | q->connect(sender: job, signal: &KJob::infoMessage, context: q, slot: [this](KJob *job, const QString &info) { |
341 | _k_slotInfoMessage(job, msg: info); |
342 | }); |
343 | q->connect(sender: job, signal: &KJob::totalSize, context: q, slot: [this](KJob *job, qulonglong size) { |
344 | _k_slotTotalSize(job, size); |
345 | }); |
346 | q->connect(sender: job, signal: &KJob::processedSize, context: q, slot: [this](KJob *job, qulonglong size) { |
347 | _k_slotProcessedSize(job, size); |
348 | }); |
349 | q->connect(sender: job, signal: &KJob::speed, context: q, slot: [this](KJob *job, ulong speed) { |
350 | _k_slotSpeed(job, bytesPerSecond: speed); |
351 | }); |
352 | } |
353 | |
354 | void ForwardingWorkerBasePrivate::connectSimpleJob(KIO::SimpleJob *job) |
355 | { |
356 | connectJob(job); |
357 | if (job->metaObject()->indexOfSignal(signal: "redirection(KIO::Job*,QUrl)" ) > -1) { |
358 | q->connect(asender: job, SIGNAL(redirection(KIO::Job *, QUrl)), SLOT(_k_slotRedirection(KIO::Job *, QUrl))); |
359 | } |
360 | } |
361 | |
362 | void ForwardingWorkerBasePrivate::connectListJob(KIO::ListJob *job) |
363 | { |
364 | connectSimpleJob(job); |
365 | q->connect(sender: job, signal: &KIO::ListJob::entries, context: q, slot: [this](KIO::Job *job, const KIO::UDSEntryList &entries) { |
366 | _k_slotEntries(job, entries); |
367 | }); |
368 | } |
369 | |
370 | void ForwardingWorkerBasePrivate::connectTransferJob(KIO::TransferJob *job) |
371 | { |
372 | connectSimpleJob(job); |
373 | q->connect(sender: job, signal: &KIO::TransferJob::data, context: q, slot: [this](KIO::Job *job, const QByteArray &data) { |
374 | _k_slotData(job, data); |
375 | }); |
376 | q->connect(sender: job, signal: &KIO::TransferJob::dataReq, context: q, slot: [this](KIO::Job *job, QByteArray &data) { |
377 | _k_slotDataReq(job, data); |
378 | }); |
379 | q->connect(sender: job, signal: &KIO::TransferJob::mimeTypeFound, context: q, slot: [this](KIO::Job *job, const QString &mimeType) { |
380 | _k_slotMimetype(job, type: mimeType); |
381 | }); |
382 | q->connect(sender: job, signal: &KIO::TransferJob::canResume, context: q, slot: [this](KIO::Job *job, KIO::filesize_t offset) { |
383 | _k_slotCanResume(job, offset); |
384 | }); |
385 | } |
386 | |
387 | ////////////////////////////////////////////////////////////////////////////// |
388 | |
389 | void ForwardingWorkerBasePrivate::_k_slotResult(KJob *job) |
390 | { |
391 | if (job->error() != 0) { |
392 | m_pendingResult = WorkerResult::fail(error: job->error(), errorString: job->errorText()); |
393 | } else { |
394 | if (auto stat_job = qobject_cast<KIO::StatJob *>(object: job)) { |
395 | KIO::UDSEntry entry = stat_job->statResult(); |
396 | q->adjustUDSEntry(entry, creationMode: ForwardingWorkerBase::UDSEntryCreationInStat); |
397 | q->statEntry(entry: entry); |
398 | } |
399 | m_pendingResult = WorkerResult::pass(); |
400 | } |
401 | |
402 | eventLoop.exit(); |
403 | } |
404 | |
405 | void ForwardingWorkerBasePrivate::_k_slotWarning(KJob * /*job*/, const QString &msg) const |
406 | { |
407 | q->warning(msg); |
408 | } |
409 | |
410 | void ForwardingWorkerBasePrivate::_k_slotInfoMessage(KJob * /*job*/, const QString &msg) const |
411 | { |
412 | q->infoMessage(msg); |
413 | } |
414 | |
415 | void ForwardingWorkerBasePrivate::_k_slotTotalSize(KJob * /*job*/, qulonglong size) const |
416 | { |
417 | q->totalSize(bytes: size); |
418 | } |
419 | |
420 | void ForwardingWorkerBasePrivate::_k_slotProcessedSize(KJob * /*job*/, qulonglong size) const |
421 | { |
422 | q->processedSize(bytes: size); |
423 | } |
424 | |
425 | void ForwardingWorkerBasePrivate::_k_slotSpeed(KJob * /*job*/, unsigned long bytesPerSecond) const |
426 | { |
427 | q->speed(bytes_per_second: bytesPerSecond); |
428 | } |
429 | |
430 | void ForwardingWorkerBasePrivate::_k_slotRedirection(KIO::Job *job, const QUrl &url) |
431 | { |
432 | q->redirection(url: url); |
433 | |
434 | // We've been redirected stop everything. |
435 | job->kill(verbosity: KJob::Quietly); |
436 | m_pendingResult = WorkerResult::pass(); |
437 | |
438 | eventLoop.exit(); |
439 | } |
440 | |
441 | void ForwardingWorkerBasePrivate::_k_slotEntries(KIO::Job * /*job*/, const KIO::UDSEntryList &entries) const |
442 | { |
443 | KIO::UDSEntryList final_entries = entries; |
444 | |
445 | for (auto &entry : final_entries) { |
446 | q->adjustUDSEntry(entry, creationMode: ForwardingWorkerBase::UDSEntryCreationInListDir); |
447 | } |
448 | |
449 | q->listEntries(entry: final_entries); |
450 | } |
451 | |
452 | void ForwardingWorkerBasePrivate::_k_slotData(KIO::Job * /*job*/, const QByteArray &_data) const |
453 | { |
454 | q->data(data: _data); |
455 | } |
456 | |
457 | void ForwardingWorkerBasePrivate::_k_slotDataReq(KIO::Job * /*job*/, QByteArray &data) const |
458 | { |
459 | q->dataReq(); |
460 | q->readData(buffer&: data); |
461 | } |
462 | |
463 | void ForwardingWorkerBasePrivate::_k_slotMimetype(KIO::Job * /*job*/, const QString &type) const |
464 | { |
465 | q->mimeType(type: type); |
466 | } |
467 | |
468 | void ForwardingWorkerBasePrivate::_k_slotCanResume(KIO::Job * /*job*/, KIO::filesize_t offset) const |
469 | { |
470 | q->canResume(offset); |
471 | } |
472 | |
473 | } // namespace KIO |
474 | |
475 | #include "moc_forwardingworkerbase.cpp" |
476 | |