1 | /* |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org> |
4 | SPDX-FileCopyrightText: 2000-2013 David Faure <faure@kde.org> |
5 | |
6 | SPDX-License-Identifier: LGPL-2.0-or-later |
7 | */ |
8 | |
9 | #include "simplejob.h" |
10 | #include "job_p.h" |
11 | #include "kprotocolinfo.h" |
12 | #include "scheduler.h" |
13 | #include "worker_p.h" |
14 | #include <QDebug> |
15 | #include <QTimer> |
16 | #include <kdirnotify.h> |
17 | |
18 | using namespace KIO; |
19 | |
20 | SimpleJob::SimpleJob(SimpleJobPrivate &dd) |
21 | : Job(dd) |
22 | { |
23 | d_func()->simpleJobInit(); |
24 | } |
25 | |
26 | void SimpleJobPrivate::simpleJobInit() |
27 | { |
28 | Q_Q(SimpleJob); |
29 | if (!m_url.isValid() || m_url.scheme().isEmpty()) { |
30 | qCWarning(KIO_CORE) << "Invalid URL:" << m_url; |
31 | q->setError(ERR_MALFORMED_URL); |
32 | q->setErrorText(m_url.toString()); |
33 | QTimer::singleShot(interval: 0, receiver: q, slot: &SimpleJob::slotFinished); |
34 | return; |
35 | } |
36 | |
37 | Scheduler::doJob(job: q); |
38 | } |
39 | |
40 | bool SimpleJob::doKill() |
41 | { |
42 | Q_D(SimpleJob); |
43 | if ((d->m_extraFlags & JobPrivate::EF_KillCalled) == 0) { |
44 | d->m_extraFlags |= JobPrivate::EF_KillCalled; |
45 | Scheduler::cancelJob(job: this); // deletes the worker if not 0 |
46 | } else { |
47 | qCWarning(KIO_CORE) << this << "killed twice, this is overkill" ; |
48 | } |
49 | return Job::doKill(); |
50 | } |
51 | |
52 | bool SimpleJob::doSuspend() |
53 | { |
54 | Q_D(SimpleJob); |
55 | if (d->m_worker) { |
56 | d->m_worker->suspend(); |
57 | } |
58 | return Job::doSuspend(); |
59 | } |
60 | |
61 | bool SimpleJob::doResume() |
62 | { |
63 | Q_D(SimpleJob); |
64 | if (d->m_worker) { |
65 | d->m_worker->resume(); |
66 | } |
67 | return Job::doResume(); |
68 | } |
69 | |
70 | const QUrl &SimpleJob::url() const |
71 | { |
72 | return d_func()->m_url; |
73 | } |
74 | |
75 | void SimpleJob::putOnHold() |
76 | { |
77 | Q_D(SimpleJob); |
78 | Q_ASSERT(d->m_worker); |
79 | if (d->m_worker) { |
80 | Scheduler::putWorkerOnHold(job: this, url: d->m_url); |
81 | } |
82 | // we should now be disassociated from the worker |
83 | Q_ASSERT(!d->m_worker); |
84 | kill(verbosity: Quietly); |
85 | } |
86 | |
87 | void SimpleJob::removeOnHold() |
88 | { |
89 | Scheduler::removeWorkerOnHold(); |
90 | } |
91 | |
92 | bool SimpleJob::isRedirectionHandlingEnabled() const |
93 | { |
94 | return d_func()->m_redirectionHandlingEnabled; |
95 | } |
96 | |
97 | void SimpleJob::setRedirectionHandlingEnabled(bool handle) |
98 | { |
99 | Q_D(SimpleJob); |
100 | d->m_redirectionHandlingEnabled = handle; |
101 | } |
102 | |
103 | SimpleJob::~SimpleJob() |
104 | { |
105 | Q_D(SimpleJob); |
106 | // last chance to remove this job from the scheduler! |
107 | if (d->m_schedSerial) { |
108 | // qDebug() << "Killing job" << this << "in destructor!"/* << qBacktrace()*/; |
109 | Scheduler::cancelJob(job: this); |
110 | } |
111 | } |
112 | |
113 | void SimpleJobPrivate::start(Worker *worker) |
114 | { |
115 | Q_Q(SimpleJob); |
116 | m_worker = worker; |
117 | |
118 | // Worker::setJob can send us SSL metadata if there is a persistent connection |
119 | QObject::connect(sender: worker, signal: &Worker::metaData, context: q, slot: &SimpleJob::slotMetaData); |
120 | |
121 | worker->setJob(q); |
122 | |
123 | QObject::connect(sender: worker, signal: &Worker::error, context: q, slot: &SimpleJob::slotError); |
124 | |
125 | QObject::connect(sender: worker, signal: &Worker::warning, context: q, slot: &SimpleJob::slotWarning); |
126 | |
127 | QObject::connect(sender: worker, signal: &Worker::finished, context: q, slot: &SimpleJob::slotFinished); |
128 | |
129 | QObject::connect(sender: worker, signal: &Worker::infoMessage, context: q, slot: [this](const QString &message) { |
130 | _k_slotWorkerInfoMessage(s: message); |
131 | }); |
132 | |
133 | QObject::connect(sender: worker, signal: &Worker::connected, context: q, slot: [this]() { |
134 | slotConnected(); |
135 | }); |
136 | |
137 | QObject::connect(sender: worker, signal: &Worker::privilegeOperationRequested, context: q, slot: [this]() { |
138 | slotPrivilegeOperationRequested(); |
139 | }); |
140 | |
141 | if ((m_extraFlags & EF_TransferJobDataSent) == 0) { // this is a "get" job |
142 | QObject::connect(sender: worker, signal: &Worker::totalSize, context: q, slot: [this](KIO::filesize_t size) { |
143 | slotTotalSize(data_size: size); |
144 | }); |
145 | |
146 | QObject::connect(sender: worker, signal: &Worker::processedSize, context: q, slot: [this](KIO::filesize_t size) { |
147 | slotProcessedSize(data_size: size); |
148 | }); |
149 | |
150 | QObject::connect(sender: worker, signal: &Worker::speed, context: q, slot: [this](ulong speed) { |
151 | slotSpeed(speed); |
152 | }); |
153 | } |
154 | |
155 | const QVariant windowIdProp = q->property(name: "window-id" ); // see KJobWidgets::setWindow |
156 | if (windowIdProp.isValid()) { |
157 | m_outgoingMetaData.insert(QStringLiteral("window-id" ), value: QString::number(windowIdProp.toULongLong())); |
158 | } |
159 | |
160 | const QVariant userTimestampProp = q->property(name: "userTimestamp" ); // see KJobWidgets::updateUserTimestamp |
161 | if (userTimestampProp.isValid()) { |
162 | m_outgoingMetaData.insert(QStringLiteral("user-timestamp" ), value: QString::number(userTimestampProp.toULongLong())); |
163 | } |
164 | |
165 | if (q->uiDelegate() == nullptr) { // not interactive |
166 | m_outgoingMetaData.insert(QStringLiteral("no-auth-prompt" ), QStringLiteral("true" )); |
167 | } |
168 | |
169 | if (!m_outgoingMetaData.isEmpty()) { |
170 | KIO_ARGS << m_outgoingMetaData; |
171 | worker->send(cmd: CMD_META_DATA, arr: packedArgs); |
172 | } |
173 | |
174 | worker->send(cmd: m_command, arr: m_packedArgs); |
175 | if (q->isSuspended()) { |
176 | worker->suspend(); |
177 | } |
178 | } |
179 | |
180 | void SimpleJobPrivate::workerDone() |
181 | { |
182 | Q_Q(SimpleJob); |
183 | if (m_worker) { |
184 | if (m_command == CMD_OPEN) { |
185 | m_worker->send(cmd: CMD_CLOSE); |
186 | } |
187 | q->disconnect(receiver: m_worker); // Remove all signals between worker and job |
188 | } |
189 | // only finish a job once; Scheduler::jobFinished() resets schedSerial to zero. |
190 | if (m_schedSerial) { |
191 | Scheduler::jobFinished(job: q, worker: m_worker); |
192 | } |
193 | } |
194 | |
195 | void SimpleJob::slotFinished() |
196 | { |
197 | Q_D(SimpleJob); |
198 | // Return worker to the scheduler |
199 | d->workerDone(); |
200 | |
201 | if (!hasSubjobs()) { |
202 | if (!error() && (d->m_command == CMD_MKDIR || d->m_command == CMD_RENAME)) { |
203 | if (d->m_command == CMD_MKDIR) { |
204 | const QUrl urlDir = url().adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash); |
205 | #ifndef KIO_ANDROID_STUB |
206 | org::kde::KDirNotify::emitFilesAdded(directory: urlDir); |
207 | #endif |
208 | } else { /*if ( m_command == CMD_RENAME )*/ |
209 | QUrl src; |
210 | QUrl dst; |
211 | QDataStream str(d->m_packedArgs); |
212 | str >> src >> dst; |
213 | if (src.adjusted(options: QUrl::RemoveFilename) == dst.adjusted(options: QUrl::RemoveFilename) // For the user, moving isn't |
214 | // renaming. Only renaming is. |
215 | ) { |
216 | #ifndef KIO_ANDROID_STUB |
217 | org::kde::KDirNotify::emitFileRenamed(src, dst); |
218 | #endif |
219 | } |
220 | |
221 | #ifndef KIO_ANDROID_STUB |
222 | org::kde::KDirNotify::emitFileMoved(src, dst); |
223 | #endif |
224 | if (d->m_uiDelegateExtension) { |
225 | d->m_uiDelegateExtension->updateUrlInClipboard(src, dest: dst); |
226 | } |
227 | } |
228 | } |
229 | emitResult(); |
230 | } |
231 | } |
232 | |
233 | void SimpleJob::slotError(int err, const QString &errorText) |
234 | { |
235 | Q_D(SimpleJob); |
236 | setError(err); |
237 | setErrorText(errorText); |
238 | if ((error() == ERR_UNKNOWN_HOST) && d->m_url.host().isEmpty()) { |
239 | setErrorText(QString()); |
240 | } |
241 | // error terminates the job |
242 | slotFinished(); |
243 | } |
244 | |
245 | void SimpleJob::slotWarning(const QString &errorText) |
246 | { |
247 | Q_EMIT warning(job: this, message: errorText); |
248 | } |
249 | |
250 | void SimpleJobPrivate::_k_slotWorkerInfoMessage(const QString &msg) |
251 | { |
252 | Q_EMIT q_func()->infoMessage(job: q_func(), message: msg); |
253 | } |
254 | |
255 | void SimpleJobPrivate::slotConnected() |
256 | { |
257 | Q_EMIT q_func()->connected(job: q_func()); |
258 | } |
259 | |
260 | void SimpleJobPrivate::slotTotalSize(KIO::filesize_t size) |
261 | { |
262 | Q_Q(SimpleJob); |
263 | if (size != q->totalAmount(unit: KJob::Bytes)) { |
264 | q->setTotalAmount(unit: KJob::Bytes, amount: size); |
265 | } |
266 | } |
267 | |
268 | void SimpleJobPrivate::slotProcessedSize(KIO::filesize_t size) |
269 | { |
270 | Q_Q(SimpleJob); |
271 | // qDebug() << KIO::number(size); |
272 | q->setProcessedAmount(unit: KJob::Bytes, amount: size); |
273 | } |
274 | |
275 | void SimpleJobPrivate::slotSpeed(unsigned long speed) |
276 | { |
277 | // qDebug() << speed; |
278 | q_func()->emitSpeed(speed); |
279 | } |
280 | |
281 | void SimpleJobPrivate::restartAfterRedirection(QUrl *redirectionUrl) |
282 | { |
283 | Q_Q(SimpleJob); |
284 | // Return worker to the scheduler while we still have the old URL in place; the scheduler |
285 | // requires a job URL to stay invariant while the job is running. |
286 | workerDone(); |
287 | |
288 | m_url = *redirectionUrl; |
289 | redirectionUrl->clear(); |
290 | if ((m_extraFlags & EF_KillCalled) == 0) { |
291 | Scheduler::doJob(job: q); |
292 | } |
293 | } |
294 | |
295 | void SimpleJob::slotMetaData(const KIO::MetaData &_metaData) |
296 | { |
297 | Q_D(SimpleJob); |
298 | QMapIterator<QString, QString> it(_metaData); |
299 | while (it.hasNext()) { |
300 | it.next(); |
301 | if (it.key().startsWith(s: QLatin1String("{internal~" ), cs: Qt::CaseInsensitive)) { |
302 | d->m_internalMetaData.insert(key: it.key(), value: it.value()); |
303 | } else { |
304 | d->m_incomingMetaData.insert(key: it.key(), value: it.value()); |
305 | } |
306 | } |
307 | |
308 | // Update the internal meta-data values as soon as possible. Waiting until |
309 | // the KIO worker is finished has unintended consequences if the client starts |
310 | // a new connection without waiting for the KIO worker to finish. |
311 | if (!d->m_internalMetaData.isEmpty()) { |
312 | Scheduler::updateInternalMetaData(job: this); |
313 | } |
314 | } |
315 | |
316 | void SimpleJobPrivate::slotPrivilegeOperationRequested() |
317 | { |
318 | m_worker->send(cmd: MSG_PRIVILEGE_EXEC, arr: privilegeOperationData()); |
319 | } |
320 | |
321 | ////////// |
322 | SimpleJob *KIO::rmdir(const QUrl &url) |
323 | { |
324 | // qDebug() << "rmdir " << url; |
325 | KIO_ARGS << url << qint8(false); // isFile is false |
326 | return SimpleJobPrivate::newJob(url, command: CMD_DEL, packedArgs); |
327 | } |
328 | |
329 | SimpleJob *KIO::chmod(const QUrl &url, int permissions) |
330 | { |
331 | // qDebug() << "chmod " << url; |
332 | KIO_ARGS << url << permissions; |
333 | return SimpleJobPrivate::newJob(url, command: CMD_CHMOD, packedArgs); |
334 | } |
335 | |
336 | SimpleJob *KIO::chown(const QUrl &url, const QString &owner, const QString &group) |
337 | { |
338 | KIO_ARGS << url << owner << group; |
339 | return SimpleJobPrivate::newJob(url, command: CMD_CHOWN, packedArgs); |
340 | } |
341 | |
342 | SimpleJob *KIO::setModificationTime(const QUrl &url, const QDateTime &mtime) |
343 | { |
344 | // qDebug() << "setModificationTime " << url << " " << mtime; |
345 | KIO_ARGS << url << mtime; |
346 | return SimpleJobPrivate::newJobNoUi(url, command: CMD_SETMODIFICATIONTIME, packedArgs); |
347 | } |
348 | |
349 | SimpleJob *KIO::rename(const QUrl &src, const QUrl &dest, JobFlags flags) |
350 | { |
351 | // qDebug() << "rename " << src << " " << dest; |
352 | KIO_ARGS << src << dest << (qint8)(flags & Overwrite); |
353 | return SimpleJobPrivate::newJob(url: src, command: CMD_RENAME, packedArgs, flags); |
354 | } |
355 | |
356 | SimpleJob *KIO::symlink(const QString &target, const QUrl &dest, JobFlags flags) |
357 | { |
358 | // qDebug() << "symlink target=" << target << " " << dest; |
359 | KIO_ARGS << target << dest << (qint8)(flags & Overwrite); |
360 | return SimpleJobPrivate::newJob(url: dest, command: CMD_SYMLINK, packedArgs, flags); |
361 | } |
362 | |
363 | SimpleJob *KIO::special(const QUrl &url, const QByteArray &data, JobFlags flags) |
364 | { |
365 | // qDebug() << "special " << url; |
366 | return SimpleJobPrivate::newJob(url, command: CMD_SPECIAL, packedArgs: data, flags); |
367 | } |
368 | |
369 | SimpleJob *KIO::mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags) |
370 | { |
371 | KIO_ARGS << int(1) << qint8(ro ? 1 : 0) << QString::fromLatin1(ba: fstype) << dev << point; |
372 | SimpleJob *job = special(url: QUrl(QStringLiteral("file:///" )), data: packedArgs, flags); |
373 | if (!(flags & HideProgressInfo)) { |
374 | KIO::JobPrivate::emitMounting(job, dev, point); |
375 | } |
376 | return job; |
377 | } |
378 | |
379 | SimpleJob *KIO::unmount(const QString &point, JobFlags flags) |
380 | { |
381 | KIO_ARGS << int(2) << point; |
382 | SimpleJob *job = special(url: QUrl(QStringLiteral("file:///" )), data: packedArgs, flags); |
383 | if (!(flags & HideProgressInfo)) { |
384 | KIO::JobPrivate::emitUnmounting(job, point); |
385 | } |
386 | return job; |
387 | } |
388 | |
389 | ////////// |
390 | |
391 | SimpleJob *KIO::http_update_cache(const QUrl &url, bool no_cache, const QDateTime &expireDate) |
392 | { |
393 | Q_ASSERT(url.scheme() == QLatin1String("http" ) || url.scheme() == QLatin1String("https" )); |
394 | // Send http update_cache command (2) |
395 | KIO_ARGS << (int)2 << url << no_cache << qlonglong(expireDate.toMSecsSinceEpoch() / 1000); |
396 | return SimpleJobPrivate::newJob(url, command: CMD_SPECIAL, packedArgs); |
397 | } |
398 | |
399 | #include "moc_simplejob.cpp" |
400 | |