| 1 | /* |
| 2 | This file is part of the KDE libraries |
| 3 | SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org> |
| 4 | SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org> |
| 5 | |
| 6 | SPDX-License-Identifier: LGPL-2.0-or-later |
| 7 | */ |
| 8 | |
| 9 | #include "filecopyjob.h" |
| 10 | #include "askuseractioninterface.h" |
| 11 | #include "job_p.h" |
| 12 | #include "kprotocolmanager.h" |
| 13 | #include "scheduler.h" |
| 14 | #include "worker_p.h" |
| 15 | #include <kio/jobuidelegatefactory.h> |
| 16 | |
| 17 | #include <KLocalizedString> |
| 18 | |
| 19 | #include <QFile> |
| 20 | #include <QTimer> |
| 21 | |
| 22 | using namespace KIO; |
| 23 | |
| 24 | static inline Worker *jobWorker(SimpleJob *job) |
| 25 | { |
| 26 | return SimpleJobPrivate::get(job)->m_worker; |
| 27 | } |
| 28 | |
| 29 | class KIO::FileCopyJobPrivate : public KIO::JobPrivate |
| 30 | { |
| 31 | public: |
| 32 | FileCopyJobPrivate(const QUrl &src, const QUrl &dest, int permissions, bool move, JobFlags flags) |
| 33 | : m_sourceSize(filesize_t(-1)) |
| 34 | , m_src(src) |
| 35 | , m_dest(dest) |
| 36 | , m_moveJob(nullptr) |
| 37 | , m_copyJob(nullptr) |
| 38 | , m_delJob(nullptr) |
| 39 | , m_chmodJob(nullptr) |
| 40 | , m_getJob(nullptr) |
| 41 | , m_putJob(nullptr) |
| 42 | , m_permissions(permissions) |
| 43 | , m_move(move) |
| 44 | , m_mustChmod(0) |
| 45 | , m_bFileCopyInProgress(false) |
| 46 | , m_flags(flags) |
| 47 | { |
| 48 | } |
| 49 | KIO::filesize_t m_sourceSize; |
| 50 | QDateTime m_modificationTime; |
| 51 | QUrl m_src; |
| 52 | QUrl m_dest; |
| 53 | QByteArray m_buffer; |
| 54 | SimpleJob *m_moveJob; |
| 55 | SimpleJob *m_copyJob; |
| 56 | SimpleJob *m_delJob; |
| 57 | SimpleJob *m_chmodJob; |
| 58 | TransferJob *m_getJob; |
| 59 | TransferJob *m_putJob; |
| 60 | int m_permissions; |
| 61 | bool m_move : 1; |
| 62 | bool m_canResume : 1; |
| 63 | bool m_resumeAnswerSent : 1; |
| 64 | bool m_mustChmod : 1; |
| 65 | bool m_bFileCopyInProgress : 1; |
| 66 | JobFlags m_flags; |
| 67 | |
| 68 | void startBestCopyMethod(); |
| 69 | void startCopyJob(); |
| 70 | void startCopyJob(const QUrl &workerUrl); |
| 71 | void startRenameJob(const QUrl &workerUrl); |
| 72 | void startDataPump(); |
| 73 | void connectSubjob(SimpleJob *job); |
| 74 | |
| 75 | void slotStart(); |
| 76 | void slotData(KIO::Job *, const QByteArray &data); |
| 77 | void slotDataReq(KIO::Job *, QByteArray &data); |
| 78 | void slotMimetype(KIO::Job *, const QString &type); |
| 79 | /* |
| 80 | * Forward signal from subjob |
| 81 | * job the job that emitted this signal |
| 82 | * offset the offset to resume from |
| 83 | */ |
| 84 | void slotCanResume(KIO::Job *job, KIO::filesize_t offset); |
| 85 | void processCanResumeResult(KIO::Job *job, RenameDialog_Result result, KIO::filesize_t offset); |
| 86 | |
| 87 | Q_DECLARE_PUBLIC(FileCopyJob) |
| 88 | |
| 89 | static inline FileCopyJob *newJob(const QUrl &src, const QUrl &dest, int permissions, bool move, JobFlags flags) |
| 90 | { |
| 91 | // qDebug() << src << "->" << dest; |
| 92 | FileCopyJob *job = new FileCopyJob(*new FileCopyJobPrivate(src, dest, permissions, move, flags)); |
| 93 | job->setProperty(name: "destUrl" , value: dest.toString()); |
| 94 | job->setUiDelegate(KIO::createDefaultJobUiDelegate()); |
| 95 | if (!(flags & HideProgressInfo)) { |
| 96 | KIO::getJobTracker()->registerJob(job); |
| 97 | } |
| 98 | if (!(flags & NoPrivilegeExecution)) { |
| 99 | job->d_func()->m_privilegeExecutionEnabled = true; |
| 100 | job->d_func()->m_operationType = move ? Move : Copy; |
| 101 | } |
| 102 | return job; |
| 103 | } |
| 104 | }; |
| 105 | |
| 106 | static bool isSrcDestSameWorkerProcess(const QUrl &src, const QUrl &dest) |
| 107 | { |
| 108 | /* clang-format off */ |
| 109 | return src.scheme() == dest.scheme() |
| 110 | && src.host() == dest.host() |
| 111 | && src.port() == dest.port() |
| 112 | && src.userName() == dest.userName() |
| 113 | && src.password() == dest.password(); |
| 114 | /* clang-format on */ |
| 115 | } |
| 116 | |
| 117 | /* |
| 118 | * The FileCopyJob works according to the famous Bavarian |
| 119 | * 'Alternating Bitburger Protocol': we either drink a beer or we |
| 120 | * we order a beer, but never both at the same time. |
| 121 | * Translated to KIO workers: We alternate between receiving a block of data |
| 122 | * and sending it away. |
| 123 | */ |
| 124 | FileCopyJob::FileCopyJob(FileCopyJobPrivate &dd) |
| 125 | : Job(dd) |
| 126 | { |
| 127 | Q_D(FileCopyJob); |
| 128 | QTimer::singleShot(interval: 0, receiver: this, slot: [d]() { |
| 129 | d->slotStart(); |
| 130 | }); |
| 131 | } |
| 132 | |
| 133 | void FileCopyJobPrivate::slotStart() |
| 134 | { |
| 135 | Q_Q(FileCopyJob); |
| 136 | if (!m_move) { |
| 137 | JobPrivate::emitCopying(q, src: m_src, dest: m_dest); |
| 138 | } else { |
| 139 | JobPrivate::emitMoving(q, src: m_src, dest: m_dest); |
| 140 | } |
| 141 | |
| 142 | if (m_move) { |
| 143 | // The if() below must be the same as the one in startBestCopyMethod |
| 144 | if (isSrcDestSameWorkerProcess(src: m_src, dest: m_dest)) { |
| 145 | startRenameJob(workerUrl: m_src); |
| 146 | return; |
| 147 | } else if (m_src.isLocalFile() && KProtocolManager::canRenameFromFile(url: m_dest)) { |
| 148 | startRenameJob(workerUrl: m_dest); |
| 149 | return; |
| 150 | } else if (m_dest.isLocalFile() && KProtocolManager::canRenameToFile(url: m_src)) { |
| 151 | startRenameJob(workerUrl: m_src); |
| 152 | return; |
| 153 | } |
| 154 | // No fast-move available, use copy + del. |
| 155 | } |
| 156 | startBestCopyMethod(); |
| 157 | } |
| 158 | |
| 159 | void FileCopyJobPrivate::startBestCopyMethod() |
| 160 | { |
| 161 | if (isSrcDestSameWorkerProcess(src: m_src, dest: m_dest)) { |
| 162 | startCopyJob(); |
| 163 | } else if (m_src.isLocalFile() && KProtocolManager::canCopyFromFile(url: m_dest)) { |
| 164 | startCopyJob(workerUrl: m_dest); |
| 165 | } else if (m_dest.isLocalFile() && KProtocolManager::canCopyToFile(url: m_src) && !KIO::Scheduler::isWorkerOnHoldFor(url: m_src)) { |
| 166 | startCopyJob(workerUrl: m_src); |
| 167 | } else { |
| 168 | startDataPump(); |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | FileCopyJob::~FileCopyJob() |
| 173 | { |
| 174 | } |
| 175 | |
| 176 | void FileCopyJob::setSourceSize(KIO::filesize_t size) |
| 177 | { |
| 178 | Q_D(FileCopyJob); |
| 179 | d->m_sourceSize = size; |
| 180 | if (size != (KIO::filesize_t)-1) { |
| 181 | setTotalAmount(unit: KJob::Bytes, amount: size); |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | void FileCopyJob::setModificationTime(const QDateTime &mtime) |
| 186 | { |
| 187 | Q_D(FileCopyJob); |
| 188 | d->m_modificationTime = mtime; |
| 189 | } |
| 190 | |
| 191 | QUrl FileCopyJob::srcUrl() const |
| 192 | { |
| 193 | return d_func()->m_src; |
| 194 | } |
| 195 | |
| 196 | QUrl FileCopyJob::destUrl() const |
| 197 | { |
| 198 | return d_func()->m_dest; |
| 199 | } |
| 200 | |
| 201 | void FileCopyJobPrivate::startCopyJob() |
| 202 | { |
| 203 | startCopyJob(workerUrl: m_src); |
| 204 | } |
| 205 | |
| 206 | void FileCopyJobPrivate::startCopyJob(const QUrl &workerUrl) |
| 207 | { |
| 208 | Q_Q(FileCopyJob); |
| 209 | // qDebug(); |
| 210 | KIO_ARGS << m_src << m_dest << m_permissions << (qint8)(m_flags & Overwrite); |
| 211 | auto job = new DirectCopyJob(workerUrl, packedArgs); |
| 212 | m_copyJob = job; |
| 213 | m_copyJob->setParentJob(q); |
| 214 | if (m_modificationTime.isValid()) { |
| 215 | m_copyJob->addMetaData(QStringLiteral("modified" ), value: m_modificationTime.toString(format: Qt::ISODate)); // #55804 |
| 216 | } |
| 217 | q->addSubjob(job: m_copyJob); |
| 218 | connectSubjob(job: m_copyJob); |
| 219 | q->connect(sender: job, signal: &DirectCopyJob::canResume, context: q, slot: [this](KIO::Job *job, KIO::filesize_t offset) { |
| 220 | slotCanResume(job, offset); |
| 221 | }); |
| 222 | } |
| 223 | |
| 224 | void FileCopyJobPrivate::startRenameJob(const QUrl &workerUrl) |
| 225 | { |
| 226 | Q_Q(FileCopyJob); |
| 227 | m_mustChmod = true; // CMD_RENAME by itself doesn't change permissions |
| 228 | KIO_ARGS << m_src << m_dest << (qint8)(m_flags & Overwrite); |
| 229 | m_moveJob = SimpleJobPrivate::newJobNoUi(url: workerUrl, command: CMD_RENAME, packedArgs); |
| 230 | m_moveJob->setParentJob(q); |
| 231 | if (m_modificationTime.isValid()) { |
| 232 | m_moveJob->addMetaData(QStringLiteral("modified" ), value: m_modificationTime.toString(format: Qt::ISODate)); // #55804 |
| 233 | } |
| 234 | q->addSubjob(job: m_moveJob); |
| 235 | connectSubjob(job: m_moveJob); |
| 236 | } |
| 237 | |
| 238 | void FileCopyJobPrivate::connectSubjob(SimpleJob *job) |
| 239 | { |
| 240 | Q_Q(FileCopyJob); |
| 241 | q->connect(sender: job, signal: &KJob::totalSize, context: q, slot: [q](KJob *job, qulonglong totalSize) { |
| 242 | Q_UNUSED(job); |
| 243 | if (totalSize != q->totalAmount(unit: KJob::Bytes)) { |
| 244 | q->setTotalAmount(unit: KJob::Bytes, amount: totalSize); |
| 245 | } |
| 246 | }); |
| 247 | |
| 248 | q->connect(sender: job, signal: &KJob::processedSize, context: q, slot: [q, this](const KJob *job, qulonglong processedSize) { |
| 249 | if (job == m_copyJob) { |
| 250 | m_bFileCopyInProgress = processedSize > 0; |
| 251 | } |
| 252 | q->setProcessedAmount(unit: KJob::Bytes, amount: processedSize); |
| 253 | }); |
| 254 | |
| 255 | q->connect(sender: job, signal: &KJob::percentChanged, context: q, slot: [q](KJob *, ulong percent) { |
| 256 | if (percent > q->percent()) { |
| 257 | q->setPercent(percent); |
| 258 | } |
| 259 | }); |
| 260 | |
| 261 | if (q->isSuspended()) { |
| 262 | job->suspend(); |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | bool FileCopyJob::doSuspend() |
| 267 | { |
| 268 | Q_D(FileCopyJob); |
| 269 | if (d->m_moveJob) { |
| 270 | d->m_moveJob->suspend(); |
| 271 | } |
| 272 | |
| 273 | if (d->m_copyJob) { |
| 274 | d->m_copyJob->suspend(); |
| 275 | } |
| 276 | |
| 277 | if (d->m_getJob) { |
| 278 | d->m_getJob->suspend(); |
| 279 | } |
| 280 | |
| 281 | if (d->m_putJob) { |
| 282 | d->m_putJob->suspend(); |
| 283 | } |
| 284 | |
| 285 | Job::doSuspend(); |
| 286 | return true; |
| 287 | } |
| 288 | |
| 289 | bool FileCopyJob::doResume() |
| 290 | { |
| 291 | Q_D(FileCopyJob); |
| 292 | if (d->m_moveJob) { |
| 293 | d->m_moveJob->resume(); |
| 294 | } |
| 295 | |
| 296 | if (d->m_copyJob) { |
| 297 | d->m_copyJob->resume(); |
| 298 | } |
| 299 | |
| 300 | if (d->m_getJob) { |
| 301 | d->m_getJob->resume(); |
| 302 | } |
| 303 | |
| 304 | if (d->m_putJob) { |
| 305 | d->m_putJob->resume(); |
| 306 | } |
| 307 | |
| 308 | Job::doResume(); |
| 309 | return true; |
| 310 | } |
| 311 | |
| 312 | void FileCopyJobPrivate::startDataPump() |
| 313 | { |
| 314 | Q_Q(FileCopyJob); |
| 315 | // qDebug(); |
| 316 | |
| 317 | m_canResume = false; |
| 318 | m_resumeAnswerSent = false; |
| 319 | m_getJob = nullptr; // for now |
| 320 | m_putJob = put(url: m_dest, permissions: m_permissions, flags: (m_flags | HideProgressInfo) /* no GUI */); |
| 321 | m_putJob->setParentJob(q); |
| 322 | // qDebug() << "m_putJob=" << m_putJob << "m_dest=" << m_dest; |
| 323 | if (m_modificationTime.isValid()) { |
| 324 | m_putJob->setModificationTime(m_modificationTime); |
| 325 | } |
| 326 | |
| 327 | // The first thing the put job will tell us is whether we can |
| 328 | // resume or not (this is always emitted) |
| 329 | q->connect(sender: m_putJob, signal: &KIO::TransferJob::canResume, context: q, slot: [this](KIO::Job *job, KIO::filesize_t offset) { |
| 330 | slotCanResume(job, offset); |
| 331 | }); |
| 332 | q->connect(sender: m_putJob, signal: &KIO::TransferJob::dataReq, context: q, slot: [this](KIO::Job *job, QByteArray &data) { |
| 333 | slotDataReq(job, data); |
| 334 | }); |
| 335 | q->addSubjob(job: m_putJob); |
| 336 | } |
| 337 | |
| 338 | void FileCopyJobPrivate::slotCanResume(KIO::Job *job, KIO::filesize_t offset) |
| 339 | { |
| 340 | Q_Q(FileCopyJob); |
| 341 | |
| 342 | if (job == m_getJob) { |
| 343 | // Cool, the get job said ok, we can resume |
| 344 | m_canResume = true; |
| 345 | // qDebug() << "'can resume' from the GET job -> we can resume"; |
| 346 | |
| 347 | jobWorker(job: m_getJob)->setOffset(jobWorker(job: m_putJob)->offset()); |
| 348 | return; |
| 349 | } |
| 350 | |
| 351 | if (job == m_putJob || job == m_copyJob) { |
| 352 | // qDebug() << "'can resume' from PUT job. offset=" << KIO::number(offset); |
| 353 | if (offset == 0) { |
| 354 | m_resumeAnswerSent = true; // No need for an answer |
| 355 | } else { |
| 356 | KIO::Job *kioJob = q->parentJob() ? q->parentJob() : q; |
| 357 | auto *askUserActionInterface = KIO::delegateExtension<KIO::AskUserActionInterface *>(job: kioJob); |
| 358 | if (!KProtocolManager::autoResume() && !(m_flags & Overwrite) && askUserActionInterface) { |
| 359 | auto renameSignal = &AskUserActionInterface::askUserRenameResult; |
| 360 | |
| 361 | q->connect(sender: askUserActionInterface, signal: renameSignal, context: q, slot: [=, this](KIO::RenameDialog_Result result, const QUrl &, const KJob *askJob) { |
| 362 | Q_ASSERT(kioJob == askJob); |
| 363 | |
| 364 | // Only receive askUserRenameResult once per rename dialog |
| 365 | QObject::disconnect(sender: askUserActionInterface, signal: renameSignal, receiver: q, zero: nullptr); |
| 366 | |
| 367 | processCanResumeResult(job, result, offset); |
| 368 | }); |
| 369 | |
| 370 | // Ask confirmation about resuming previous transfer |
| 371 | askUserActionInterface->askUserRename(job: kioJob, |
| 372 | i18n("File Already Exists" ), |
| 373 | src: m_src, |
| 374 | dest: m_dest, |
| 375 | options: RenameDialog_Options(RenameDialog_Overwrite | RenameDialog_Resume | RenameDialog_NoRename), |
| 376 | sizeSrc: m_sourceSize, |
| 377 | sizeDest: offset); |
| 378 | return; |
| 379 | } |
| 380 | } |
| 381 | |
| 382 | processCanResumeResult(job, // |
| 383 | result: Result_Resume, // The default is to resume |
| 384 | offset); |
| 385 | |
| 386 | return; |
| 387 | } |
| 388 | |
| 389 | qCWarning(KIO_CORE) << "unknown job=" << job << "m_getJob=" << m_getJob << "m_putJob=" << m_putJob; |
| 390 | } |
| 391 | |
| 392 | void FileCopyJobPrivate::processCanResumeResult(KIO::Job *job, RenameDialog_Result result, KIO::filesize_t offset) |
| 393 | { |
| 394 | Q_Q(FileCopyJob); |
| 395 | if (result == Result_Overwrite || (m_flags & Overwrite)) { |
| 396 | offset = 0; |
| 397 | } else if (result == Result_Cancel) { |
| 398 | if (job == m_putJob) { |
| 399 | m_putJob->kill(verbosity: FileCopyJob::Quietly); |
| 400 | q->removeSubjob(job: m_putJob); |
| 401 | m_putJob = nullptr; |
| 402 | } else { |
| 403 | m_copyJob->kill(verbosity: FileCopyJob::Quietly); |
| 404 | q->removeSubjob(job: m_copyJob); |
| 405 | m_copyJob = nullptr; |
| 406 | } |
| 407 | q->setError(ERR_USER_CANCELED); |
| 408 | q->emitResult(); |
| 409 | return; |
| 410 | } |
| 411 | |
| 412 | if (job == m_copyJob) { |
| 413 | jobWorker(job: m_copyJob)->sendResumeAnswer(resume: offset != 0); |
| 414 | return; |
| 415 | } |
| 416 | |
| 417 | if (job == m_putJob) { |
| 418 | m_getJob = KIO::get(url: m_src, reload: NoReload, flags: HideProgressInfo /* no GUI */); |
| 419 | m_getJob->setParentJob(q); |
| 420 | // qDebug() << "m_getJob=" << m_getJob << m_src; |
| 421 | m_getJob->addMetaData(QStringLiteral("AllowCompressedPage" ), QStringLiteral("false" )); |
| 422 | // Set size in subjob. This helps if the worker doesn't emit totalSize. |
| 423 | if (m_sourceSize != (KIO::filesize_t)-1) { |
| 424 | m_getJob->setTotalAmount(unit: KJob::Bytes, amount: m_sourceSize); |
| 425 | } |
| 426 | |
| 427 | if (offset) { |
| 428 | // qDebug() << "Setting metadata for resume to" << (unsigned long) offset; |
| 429 | m_getJob->addMetaData(QStringLiteral("range-start" ), value: KIO::number(size: offset)); |
| 430 | |
| 431 | // Might or might not get emitted |
| 432 | q->connect(sender: m_getJob, signal: &KIO::TransferJob::canResume, context: q, slot: [this](KIO::Job *job, KIO::filesize_t offset) { |
| 433 | slotCanResume(job, offset); |
| 434 | }); |
| 435 | } |
| 436 | jobWorker(job: m_putJob)->setOffset(offset); |
| 437 | |
| 438 | m_putJob->d_func()->internalSuspend(); |
| 439 | q->addSubjob(job: m_getJob); |
| 440 | connectSubjob(job: m_getJob); // Progress info depends on get |
| 441 | m_getJob->d_func()->internalResume(); // Order a beer |
| 442 | |
| 443 | q->connect(sender: m_getJob, signal: &KIO::TransferJob::data, context: q, slot: [this](KIO::Job *job, const QByteArray &data) { |
| 444 | slotData(job, data); |
| 445 | }); |
| 446 | q->connect(sender: m_getJob, signal: &KIO::TransferJob::mimeTypeFound, context: q, slot: [this](KIO::Job *job, const QString &type) { |
| 447 | slotMimetype(job, type); |
| 448 | }); |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | void FileCopyJobPrivate::slotData(KIO::Job *, const QByteArray &data) |
| 453 | { |
| 454 | // qDebug() << "data size:" << data.size(); |
| 455 | Q_ASSERT(m_putJob); |
| 456 | if (!m_putJob) { |
| 457 | return; // Don't crash |
| 458 | } |
| 459 | m_getJob->d_func()->internalSuspend(); |
| 460 | m_putJob->d_func()->internalResume(); // Drink the beer |
| 461 | m_buffer += data; |
| 462 | |
| 463 | // On the first set of data incoming, we tell the "put" worker about our |
| 464 | // decision about resuming |
| 465 | if (!m_resumeAnswerSent) { |
| 466 | m_resumeAnswerSent = true; |
| 467 | // qDebug() << "(first time) -> send resume answer " << m_canResume; |
| 468 | jobWorker(job: m_putJob)->sendResumeAnswer(resume: m_canResume); |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | void FileCopyJobPrivate::slotDataReq(KIO::Job *, QByteArray &data) |
| 473 | { |
| 474 | Q_Q(FileCopyJob); |
| 475 | // qDebug(); |
| 476 | if (!m_resumeAnswerSent && !m_getJob) { |
| 477 | // This can't happen |
| 478 | q->setError(ERR_INTERNAL); |
| 479 | q->setErrorText(QStringLiteral("'Put' job did not send canResume or 'Get' job did not send data!" )); |
| 480 | m_putJob->kill(verbosity: FileCopyJob::Quietly); |
| 481 | q->removeSubjob(job: m_putJob); |
| 482 | m_putJob = nullptr; |
| 483 | q->emitResult(); |
| 484 | return; |
| 485 | } |
| 486 | if (m_getJob) { |
| 487 | m_getJob->d_func()->internalResume(); // Order more beer |
| 488 | m_putJob->d_func()->internalSuspend(); |
| 489 | } |
| 490 | data = m_buffer; |
| 491 | m_buffer = QByteArray(); |
| 492 | } |
| 493 | |
| 494 | void FileCopyJobPrivate::slotMimetype(KIO::Job *, const QString &type) |
| 495 | { |
| 496 | Q_Q(FileCopyJob); |
| 497 | Q_EMIT q->mimeTypeFound(job: q, mimeType: type); |
| 498 | } |
| 499 | |
| 500 | void FileCopyJob::slotResult(KJob *job) |
| 501 | { |
| 502 | Q_D(FileCopyJob); |
| 503 | // qDebug() << "this=" << this << "job=" << job; |
| 504 | removeSubjob(job); |
| 505 | |
| 506 | // If result comes from copyjob then we are not writing anymore. |
| 507 | if (job == d->m_copyJob) { |
| 508 | d->m_bFileCopyInProgress = false; |
| 509 | } |
| 510 | |
| 511 | // Did job have an error ? |
| 512 | if (job->error()) { |
| 513 | if ((job == d->m_moveJob) && (job->error() == ERR_UNSUPPORTED_ACTION)) { |
| 514 | d->m_moveJob = nullptr; |
| 515 | d->startBestCopyMethod(); |
| 516 | return; |
| 517 | } else if ((job == d->m_copyJob) && (job->error() == ERR_UNSUPPORTED_ACTION)) { |
| 518 | d->m_copyJob = nullptr; |
| 519 | d->startDataPump(); |
| 520 | return; |
| 521 | } else if (job == d->m_getJob) { |
| 522 | d->m_getJob = nullptr; |
| 523 | if (d->m_putJob) { |
| 524 | d->m_putJob->kill(verbosity: Quietly); |
| 525 | removeSubjob(job: d->m_putJob); |
| 526 | } |
| 527 | } else if (job == d->m_putJob) { |
| 528 | d->m_putJob = nullptr; |
| 529 | if (d->m_getJob) { |
| 530 | d->m_getJob->kill(verbosity: Quietly); |
| 531 | removeSubjob(job: d->m_getJob); |
| 532 | } |
| 533 | } else if (job == d->m_chmodJob) { |
| 534 | d->m_chmodJob = nullptr; |
| 535 | if (d->m_delJob) { |
| 536 | d->m_delJob->kill(verbosity: Quietly); |
| 537 | removeSubjob(job: d->m_delJob); |
| 538 | } |
| 539 | } else if (job == d->m_delJob) { |
| 540 | d->m_delJob = nullptr; |
| 541 | if (d->m_chmodJob) { |
| 542 | d->m_chmodJob->kill(verbosity: Quietly); |
| 543 | removeSubjob(job: d->m_chmodJob); |
| 544 | } |
| 545 | } |
| 546 | setError(job->error()); |
| 547 | setErrorText(job->errorText()); |
| 548 | emitResult(); |
| 549 | return; |
| 550 | } |
| 551 | |
| 552 | if (d->m_mustChmod) { |
| 553 | // If d->m_permissions == -1, keep the default permissions |
| 554 | if (d->m_permissions != -1) { |
| 555 | d->m_chmodJob = chmod(url: d->m_dest, permissions: d->m_permissions); |
| 556 | addSubjob(job: d->m_chmodJob); |
| 557 | } |
| 558 | d->m_mustChmod = false; |
| 559 | } |
| 560 | |
| 561 | if (job == d->m_moveJob) { |
| 562 | d->m_moveJob = nullptr; // Finished |
| 563 | } |
| 564 | |
| 565 | if (job == d->m_copyJob) { |
| 566 | d->m_copyJob = nullptr; |
| 567 | if (d->m_move) { |
| 568 | d->m_delJob = file_delete(src: d->m_src, flags: HideProgressInfo /*no GUI*/); // Delete source |
| 569 | addSubjob(job: d->m_delJob); |
| 570 | } |
| 571 | } |
| 572 | |
| 573 | if (job == d->m_getJob) { |
| 574 | // qDebug() << "m_getJob finished"; |
| 575 | d->m_getJob = nullptr; // No action required |
| 576 | if (d->m_putJob) { |
| 577 | d->m_putJob->d_func()->internalResume(); |
| 578 | } |
| 579 | } |
| 580 | |
| 581 | if (job == d->m_putJob) { |
| 582 | // qDebug() << "m_putJob finished"; |
| 583 | d->m_putJob = nullptr; |
| 584 | if (d->m_getJob) { |
| 585 | // The get job is still running, probably after emitting data(QByteArray()) |
| 586 | // and before we receive its finished(). |
| 587 | d->m_getJob->d_func()->internalResume(); |
| 588 | } |
| 589 | if (d->m_move) { |
| 590 | d->m_delJob = file_delete(src: d->m_src, flags: HideProgressInfo /*no GUI*/); // Delete source |
| 591 | addSubjob(job: d->m_delJob); |
| 592 | } |
| 593 | } |
| 594 | |
| 595 | if (job == d->m_delJob) { |
| 596 | d->m_delJob = nullptr; // Finished |
| 597 | } |
| 598 | |
| 599 | if (job == d->m_chmodJob) { |
| 600 | d->m_chmodJob = nullptr; // Finished |
| 601 | } |
| 602 | |
| 603 | if (!hasSubjobs()) { |
| 604 | emitResult(); |
| 605 | } |
| 606 | } |
| 607 | |
| 608 | bool FileCopyJob::doKill() |
| 609 | { |
| 610 | #ifdef Q_OS_WIN |
| 611 | // TODO Use SetConsoleCtrlHandler on Windows or similar behaviour. |
| 612 | // https://stackoverflow.com/questions/2007516/is-there-a-posix-sigterm-alternative-on-windows-a-gentle-kill-for-console-ap |
| 613 | // https://danielkaes.wordpress.com/2009/06/04/how-to-catch-kill-events-with-python/ |
| 614 | // https://phabricator.kde.org/D25117#566107 |
| 615 | |
| 616 | Q_D(FileCopyJob); |
| 617 | |
| 618 | // If we are interrupted in the middle of file copying, |
| 619 | // we may end up with corrupted file at the destination. |
| 620 | // It is better to clean up this file. If a copy is being |
| 621 | // made as part of move operation then delete the dest only if |
| 622 | // source file is intact (m_delJob == NULL). |
| 623 | if (d->m_bFileCopyInProgress && d->m_copyJob && d->m_dest.isLocalFile()) { |
| 624 | if (d->m_flags & Overwrite) { |
| 625 | QFile::remove(d->m_dest.toLocalFile() + QStringLiteral(".part" )); |
| 626 | } else { |
| 627 | QFile::remove(d->m_dest.toLocalFile()); |
| 628 | } |
| 629 | } |
| 630 | #endif |
| 631 | return Job::doKill(); |
| 632 | } |
| 633 | |
| 634 | FileCopyJob *KIO::file_copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) |
| 635 | { |
| 636 | return FileCopyJobPrivate::newJob(src, dest, permissions, move: false, flags); |
| 637 | } |
| 638 | |
| 639 | FileCopyJob *KIO::file_move(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) |
| 640 | { |
| 641 | FileCopyJob *job = FileCopyJobPrivate::newJob(src, dest, permissions, move: true, flags); |
| 642 | if (job->uiDelegateExtension()) { |
| 643 | job->uiDelegateExtension()->createClipboardUpdater(job, mode: JobUiDelegateExtension::UpdateContent); |
| 644 | } |
| 645 | return job; |
| 646 | } |
| 647 | |
| 648 | #include "moc_filecopyjob.cpp" |
| 649 | |