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
24namespace KIO
25{
26class ForwardingWorkerBasePrivate
27{
28public:
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
73private:
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
79ForwardingWorkerBase::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
85ForwardingWorkerBase::~ForwardingWorkerBase() = default;
86
87bool 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
102void 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
150QUrl ForwardingWorkerBase::processedUrl() const
151{
152 return d->m_processedURL;
153}
154
155QUrl ForwardingWorkerBase::requestedUrl() const
156{
157 return d->m_requestedURL;
158}
159
160WorkerResult 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
172WorkerResult 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
184WorkerResult 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
196WorkerResult 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
208WorkerResult 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
220WorkerResult 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
232WorkerResult 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
250WorkerResult 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
264WorkerResult 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
276WorkerResult 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
288WorkerResult 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
306WorkerResult 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
325void 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
354void 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
362void 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
370void 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
389void 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
405void ForwardingWorkerBasePrivate::_k_slotWarning(KJob * /*job*/, const QString &msg) const
406{
407 q->warning(msg);
408}
409
410void ForwardingWorkerBasePrivate::_k_slotInfoMessage(KJob * /*job*/, const QString &msg) const
411{
412 q->infoMessage(msg);
413}
414
415void ForwardingWorkerBasePrivate::_k_slotTotalSize(KJob * /*job*/, qulonglong size) const
416{
417 q->totalSize(bytes: size);
418}
419
420void ForwardingWorkerBasePrivate::_k_slotProcessedSize(KJob * /*job*/, qulonglong size) const
421{
422 q->processedSize(bytes: size);
423}
424
425void ForwardingWorkerBasePrivate::_k_slotSpeed(KJob * /*job*/, unsigned long bytesPerSecond) const
426{
427 q->speed(bytes_per_second: bytesPerSecond);
428}
429
430void 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
441void 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
452void ForwardingWorkerBasePrivate::_k_slotData(KIO::Job * /*job*/, const QByteArray &_data) const
453{
454 q->data(data: _data);
455}
456
457void ForwardingWorkerBasePrivate::_k_slotDataReq(KIO::Job * /*job*/, QByteArray &data) const
458{
459 q->dataReq();
460 q->readData(buffer&: data);
461}
462
463void ForwardingWorkerBasePrivate::_k_slotMimetype(KIO::Job * /*job*/, const QString &type) const
464{
465 q->mimeType(type: type);
466}
467
468void 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

source code of kio/src/core/forwardingworkerbase.cpp