| 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 | #ifdef WITH_QTDBUS |
| 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 | #ifdef WITH_QTDBUS |
| 217 | org::kde::KDirNotify::emitFileRenamed(src, dst); |
| 218 | #endif |
| 219 | } |
| 220 | |
| 221 | #ifdef WITH_QTDBUS |
| 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 | #if KIOCORE_BUILD_DEPRECATED_SINCE(6, 9) |
| 392 | SimpleJob *KIO::http_update_cache(const QUrl &url, bool no_cache, const QDateTime &expireDate) |
| 393 | { |
| 394 | Q_ASSERT(url.scheme() == QLatin1String("http" ) || url.scheme() == QLatin1String("https" )); |
| 395 | // Send http update_cache command (2) |
| 396 | KIO_ARGS << (int)2 << url << no_cache << qlonglong(expireDate.toMSecsSinceEpoch() / 1000); |
| 397 | return SimpleJobPrivate::newJob(url, command: CMD_SPECIAL, packedArgs); |
| 398 | } |
| 399 | #endif |
| 400 | |
| 401 | #include "moc_simplejob.cpp" |
| 402 | |