File indexing completed on 2024-09-29 06:32:11
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org> 0004 SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "filecopyjob.h" 0010 #include "askuseractioninterface.h" 0011 #include "job_p.h" 0012 #include "kprotocolmanager.h" 0013 #include "scheduler.h" 0014 #include "worker_p.h" 0015 #include <kio/jobuidelegatefactory.h> 0016 0017 #include <KLocalizedString> 0018 0019 #include <QFile> 0020 #include <QTimer> 0021 0022 using namespace KIO; 0023 0024 static inline Worker *jobWorker(SimpleJob *job) 0025 { 0026 return SimpleJobPrivate::get(job)->m_worker; 0027 } 0028 0029 /** @internal */ 0030 class KIO::FileCopyJobPrivate : public KIO::JobPrivate 0031 { 0032 public: 0033 FileCopyJobPrivate(const QUrl &src, const QUrl &dest, int permissions, bool move, JobFlags flags) 0034 : m_sourceSize(filesize_t(-1)) 0035 , m_src(src) 0036 , m_dest(dest) 0037 , m_moveJob(nullptr) 0038 , m_copyJob(nullptr) 0039 , m_delJob(nullptr) 0040 , m_chmodJob(nullptr) 0041 , m_getJob(nullptr) 0042 , m_putJob(nullptr) 0043 , m_permissions(permissions) 0044 , m_move(move) 0045 , m_mustChmod(0) 0046 , m_bFileCopyInProgress(false) 0047 , m_flags(flags) 0048 { 0049 } 0050 KIO::filesize_t m_sourceSize; 0051 QDateTime m_modificationTime; 0052 QUrl m_src; 0053 QUrl m_dest; 0054 QByteArray m_buffer; 0055 SimpleJob *m_moveJob; 0056 SimpleJob *m_copyJob; 0057 SimpleJob *m_delJob; 0058 SimpleJob *m_chmodJob; 0059 TransferJob *m_getJob; 0060 TransferJob *m_putJob; 0061 int m_permissions; 0062 bool m_move : 1; 0063 bool m_canResume : 1; 0064 bool m_resumeAnswerSent : 1; 0065 bool m_mustChmod : 1; 0066 bool m_bFileCopyInProgress : 1; 0067 JobFlags m_flags; 0068 0069 void startBestCopyMethod(); 0070 void startCopyJob(); 0071 void startCopyJob(const QUrl &workerUrl); 0072 void startRenameJob(const QUrl &workerUrl); 0073 void startDataPump(); 0074 void connectSubjob(SimpleJob *job); 0075 0076 void slotStart(); 0077 void slotData(KIO::Job *, const QByteArray &data); 0078 void slotDataReq(KIO::Job *, QByteArray &data); 0079 void slotMimetype(KIO::Job *, const QString &type); 0080 /** 0081 * Forward signal from subjob 0082 * @param job the job that emitted this signal 0083 * @param offset the offset to resume from 0084 */ 0085 void slotCanResume(KIO::Job *job, KIO::filesize_t offset); 0086 void processCanResumeResult(KIO::Job *job, RenameDialog_Result result, KIO::filesize_t offset); 0087 0088 Q_DECLARE_PUBLIC(FileCopyJob) 0089 0090 static inline FileCopyJob *newJob(const QUrl &src, const QUrl &dest, int permissions, bool move, JobFlags flags) 0091 { 0092 // qDebug() << src << "->" << dest; 0093 FileCopyJob *job = new FileCopyJob(*new FileCopyJobPrivate(src, dest, permissions, move, flags)); 0094 job->setProperty("destUrl", dest.toString()); 0095 job->setUiDelegate(KIO::createDefaultJobUiDelegate()); 0096 if (!(flags & HideProgressInfo)) { 0097 KIO::getJobTracker()->registerJob(job); 0098 } 0099 if (!(flags & NoPrivilegeExecution)) { 0100 job->d_func()->m_privilegeExecutionEnabled = true; 0101 job->d_func()->m_operationType = move ? Move : Copy; 0102 } 0103 return job; 0104 } 0105 }; 0106 0107 static bool isSrcDestSameWorkerProcess(const QUrl &src, const QUrl &dest) 0108 { 0109 /* clang-format off */ 0110 return src.scheme() == dest.scheme() 0111 && src.host() == dest.host() 0112 && src.port() == dest.port() 0113 && src.userName() == dest.userName() 0114 && src.password() == dest.password(); 0115 /* clang-format on */ 0116 } 0117 0118 /* 0119 * The FileCopyJob works according to the famous Bavarian 0120 * 'Alternating Bitburger Protocol': we either drink a beer or we 0121 * we order a beer, but never both at the same time. 0122 * Translated to KIO workers: We alternate between receiving a block of data 0123 * and sending it away. 0124 */ 0125 FileCopyJob::FileCopyJob(FileCopyJobPrivate &dd) 0126 : Job(dd) 0127 { 0128 Q_D(FileCopyJob); 0129 QTimer::singleShot(0, this, [d]() { 0130 d->slotStart(); 0131 }); 0132 } 0133 0134 void FileCopyJobPrivate::slotStart() 0135 { 0136 Q_Q(FileCopyJob); 0137 if (!m_move) { 0138 JobPrivate::emitCopying(q, m_src, m_dest); 0139 } else { 0140 JobPrivate::emitMoving(q, m_src, m_dest); 0141 } 0142 0143 if (m_move) { 0144 // The if() below must be the same as the one in startBestCopyMethod 0145 if (isSrcDestSameWorkerProcess(m_src, m_dest)) { 0146 startRenameJob(m_src); 0147 return; 0148 } else if (m_src.isLocalFile() && KProtocolManager::canRenameFromFile(m_dest)) { 0149 startRenameJob(m_dest); 0150 return; 0151 } else if (m_dest.isLocalFile() && KProtocolManager::canRenameToFile(m_src)) { 0152 startRenameJob(m_src); 0153 return; 0154 } 0155 // No fast-move available, use copy + del. 0156 } 0157 startBestCopyMethod(); 0158 } 0159 0160 void FileCopyJobPrivate::startBestCopyMethod() 0161 { 0162 if (isSrcDestSameWorkerProcess(m_src, m_dest)) { 0163 startCopyJob(); 0164 } else if (m_src.isLocalFile() && KProtocolManager::canCopyFromFile(m_dest)) { 0165 startCopyJob(m_dest); 0166 } else if (m_dest.isLocalFile() && KProtocolManager::canCopyToFile(m_src) && !KIO::Scheduler::isWorkerOnHoldFor(m_src)) { 0167 startCopyJob(m_src); 0168 } else { 0169 startDataPump(); 0170 } 0171 } 0172 0173 FileCopyJob::~FileCopyJob() 0174 { 0175 } 0176 0177 void FileCopyJob::setSourceSize(KIO::filesize_t size) 0178 { 0179 Q_D(FileCopyJob); 0180 d->m_sourceSize = size; 0181 if (size != (KIO::filesize_t)-1) { 0182 setTotalAmount(KJob::Bytes, size); 0183 } 0184 } 0185 0186 void FileCopyJob::setModificationTime(const QDateTime &mtime) 0187 { 0188 Q_D(FileCopyJob); 0189 d->m_modificationTime = mtime; 0190 } 0191 0192 QUrl FileCopyJob::srcUrl() const 0193 { 0194 return d_func()->m_src; 0195 } 0196 0197 QUrl FileCopyJob::destUrl() const 0198 { 0199 return d_func()->m_dest; 0200 } 0201 0202 void FileCopyJobPrivate::startCopyJob() 0203 { 0204 startCopyJob(m_src); 0205 } 0206 0207 void FileCopyJobPrivate::startCopyJob(const QUrl &workerUrl) 0208 { 0209 Q_Q(FileCopyJob); 0210 // qDebug(); 0211 KIO_ARGS << m_src << m_dest << m_permissions << (qint8)(m_flags & Overwrite); 0212 auto job = new DirectCopyJob(workerUrl, packedArgs); 0213 m_copyJob = job; 0214 m_copyJob->setParentJob(q); 0215 if (m_modificationTime.isValid()) { 0216 m_copyJob->addMetaData(QStringLiteral("modified"), m_modificationTime.toString(Qt::ISODate)); // #55804 0217 } 0218 q->addSubjob(m_copyJob); 0219 connectSubjob(m_copyJob); 0220 q->connect(job, &DirectCopyJob::canResume, q, [this](KIO::Job *job, KIO::filesize_t offset) { 0221 slotCanResume(job, offset); 0222 }); 0223 } 0224 0225 void FileCopyJobPrivate::startRenameJob(const QUrl &workerUrl) 0226 { 0227 Q_Q(FileCopyJob); 0228 m_mustChmod = true; // CMD_RENAME by itself doesn't change permissions 0229 KIO_ARGS << m_src << m_dest << (qint8)(m_flags & Overwrite); 0230 m_moveJob = SimpleJobPrivate::newJobNoUi(workerUrl, CMD_RENAME, packedArgs); 0231 m_moveJob->setParentJob(q); 0232 if (m_modificationTime.isValid()) { 0233 m_moveJob->addMetaData(QStringLiteral("modified"), m_modificationTime.toString(Qt::ISODate)); // #55804 0234 } 0235 q->addSubjob(m_moveJob); 0236 connectSubjob(m_moveJob); 0237 } 0238 0239 void FileCopyJobPrivate::connectSubjob(SimpleJob *job) 0240 { 0241 Q_Q(FileCopyJob); 0242 q->connect(job, &KJob::totalSize, q, [q](KJob *job, qulonglong totalSize) { 0243 Q_UNUSED(job); 0244 if (totalSize != q->totalAmount(KJob::Bytes)) { 0245 q->setTotalAmount(KJob::Bytes, totalSize); 0246 } 0247 }); 0248 0249 q->connect(job, &KJob::processedSize, q, [q, this](const KJob *job, qulonglong processedSize) { 0250 if (job == m_copyJob) { 0251 m_bFileCopyInProgress = processedSize > 0; 0252 } 0253 q->setProcessedAmount(KJob::Bytes, processedSize); 0254 }); 0255 0256 q->connect(job, &KJob::percentChanged, q, [q](KJob *, ulong percent) { 0257 if (percent > q->percent()) { 0258 q->setPercent(percent); 0259 } 0260 }); 0261 0262 if (q->isSuspended()) { 0263 job->suspend(); 0264 } 0265 } 0266 0267 bool FileCopyJob::doSuspend() 0268 { 0269 Q_D(FileCopyJob); 0270 if (d->m_moveJob) { 0271 d->m_moveJob->suspend(); 0272 } 0273 0274 if (d->m_copyJob) { 0275 d->m_copyJob->suspend(); 0276 } 0277 0278 if (d->m_getJob) { 0279 d->m_getJob->suspend(); 0280 } 0281 0282 if (d->m_putJob) { 0283 d->m_putJob->suspend(); 0284 } 0285 0286 Job::doSuspend(); 0287 return true; 0288 } 0289 0290 bool FileCopyJob::doResume() 0291 { 0292 Q_D(FileCopyJob); 0293 if (d->m_moveJob) { 0294 d->m_moveJob->resume(); 0295 } 0296 0297 if (d->m_copyJob) { 0298 d->m_copyJob->resume(); 0299 } 0300 0301 if (d->m_getJob) { 0302 d->m_getJob->resume(); 0303 } 0304 0305 if (d->m_putJob) { 0306 d->m_putJob->resume(); 0307 } 0308 0309 Job::doResume(); 0310 return true; 0311 } 0312 0313 void FileCopyJobPrivate::startDataPump() 0314 { 0315 Q_Q(FileCopyJob); 0316 // qDebug(); 0317 0318 m_canResume = false; 0319 m_resumeAnswerSent = false; 0320 m_getJob = nullptr; // for now 0321 m_putJob = put(m_dest, m_permissions, (m_flags | HideProgressInfo) /* no GUI */); 0322 m_putJob->setParentJob(q); 0323 // qDebug() << "m_putJob=" << m_putJob << "m_dest=" << m_dest; 0324 if (m_modificationTime.isValid()) { 0325 m_putJob->setModificationTime(m_modificationTime); 0326 } 0327 0328 // The first thing the put job will tell us is whether we can 0329 // resume or not (this is always emitted) 0330 q->connect(m_putJob, &KIO::TransferJob::canResume, q, [this](KIO::Job *job, KIO::filesize_t offset) { 0331 slotCanResume(job, offset); 0332 }); 0333 q->connect(m_putJob, &KIO::TransferJob::dataReq, q, [this](KIO::Job *job, QByteArray &data) { 0334 slotDataReq(job, data); 0335 }); 0336 q->addSubjob(m_putJob); 0337 } 0338 0339 void FileCopyJobPrivate::slotCanResume(KIO::Job *job, KIO::filesize_t offset) 0340 { 0341 Q_Q(FileCopyJob); 0342 0343 if (job == m_getJob) { 0344 // Cool, the get job said ok, we can resume 0345 m_canResume = true; 0346 // qDebug() << "'can resume' from the GET job -> we can resume"; 0347 0348 jobWorker(m_getJob)->setOffset(jobWorker(m_putJob)->offset()); 0349 return; 0350 } 0351 0352 if (job == m_putJob || job == m_copyJob) { 0353 // qDebug() << "'can resume' from PUT job. offset=" << KIO::number(offset); 0354 if (offset == 0) { 0355 m_resumeAnswerSent = true; // No need for an answer 0356 } else { 0357 KIO::Job *kioJob = q->parentJob() ? q->parentJob() : q; 0358 auto *askUserActionInterface = KIO::delegateExtension<KIO::AskUserActionInterface *>(kioJob); 0359 if (!KProtocolManager::autoResume() && !(m_flags & Overwrite) && askUserActionInterface) { 0360 auto renameSignal = &AskUserActionInterface::askUserRenameResult; 0361 0362 q->connect(askUserActionInterface, renameSignal, q, [=](KIO::RenameDialog_Result result, const QUrl &, const KJob *askJob) { 0363 Q_ASSERT(kioJob == askJob); 0364 0365 // Only receive askUserRenameResult once per rename dialog 0366 QObject::disconnect(askUserActionInterface, renameSignal, q, nullptr); 0367 0368 processCanResumeResult(job, result, offset); 0369 }); 0370 0371 // Ask confirmation about resuming previous transfer 0372 askUserActionInterface->askUserRename(kioJob, 0373 i18n("File Already Exists"), 0374 m_src, 0375 m_dest, 0376 RenameDialog_Options(RenameDialog_Overwrite | RenameDialog_Resume | RenameDialog_NoRename), 0377 m_sourceSize, 0378 offset); 0379 return; 0380 } 0381 } 0382 0383 processCanResumeResult(job, // 0384 Result_Resume, // The default is to resume 0385 offset); 0386 0387 return; 0388 } 0389 0390 qCWarning(KIO_CORE) << "unknown job=" << job << "m_getJob=" << m_getJob << "m_putJob=" << m_putJob; 0391 } 0392 0393 void FileCopyJobPrivate::processCanResumeResult(KIO::Job *job, RenameDialog_Result result, KIO::filesize_t offset) 0394 { 0395 Q_Q(FileCopyJob); 0396 if (result == Result_Overwrite || (m_flags & Overwrite)) { 0397 offset = 0; 0398 } else if (result == Result_Cancel) { 0399 if (job == m_putJob) { 0400 m_putJob->kill(FileCopyJob::Quietly); 0401 q->removeSubjob(m_putJob); 0402 m_putJob = nullptr; 0403 } else { 0404 m_copyJob->kill(FileCopyJob::Quietly); 0405 q->removeSubjob(m_copyJob); 0406 m_copyJob = nullptr; 0407 } 0408 q->setError(ERR_USER_CANCELED); 0409 q->emitResult(); 0410 return; 0411 } 0412 0413 if (job == m_copyJob) { 0414 jobWorker(m_copyJob)->sendResumeAnswer(offset != 0); 0415 return; 0416 } 0417 0418 if (job == m_putJob) { 0419 m_getJob = KIO::get(m_src, NoReload, HideProgressInfo /* no GUI */); 0420 m_getJob->setParentJob(q); 0421 // qDebug() << "m_getJob=" << m_getJob << m_src; 0422 m_getJob->addMetaData(QStringLiteral("errorPage"), QStringLiteral("false")); 0423 m_getJob->addMetaData(QStringLiteral("AllowCompressedPage"), QStringLiteral("false")); 0424 // Set size in subjob. This helps if the worker doesn't emit totalSize. 0425 if (m_sourceSize != (KIO::filesize_t)-1) { 0426 m_getJob->setTotalAmount(KJob::Bytes, m_sourceSize); 0427 } 0428 0429 if (offset) { 0430 // qDebug() << "Setting metadata for resume to" << (unsigned long) offset; 0431 m_getJob->addMetaData(QStringLiteral("range-start"), KIO::number(offset)); 0432 0433 // Might or might not get emitted 0434 q->connect(m_getJob, &KIO::TransferJob::canResume, q, [this](KIO::Job *job, KIO::filesize_t offset) { 0435 slotCanResume(job, offset); 0436 }); 0437 } 0438 jobWorker(m_putJob)->setOffset(offset); 0439 0440 m_putJob->d_func()->internalSuspend(); 0441 q->addSubjob(m_getJob); 0442 connectSubjob(m_getJob); // Progress info depends on get 0443 m_getJob->d_func()->internalResume(); // Order a beer 0444 0445 q->connect(m_getJob, &KIO::TransferJob::data, q, [this](KIO::Job *job, const QByteArray &data) { 0446 slotData(job, data); 0447 }); 0448 q->connect(m_getJob, &KIO::TransferJob::mimeTypeFound, q, [this](KIO::Job *job, const QString &type) { 0449 slotMimetype(job, type); 0450 }); 0451 } 0452 } 0453 0454 void FileCopyJobPrivate::slotData(KIO::Job *, const QByteArray &data) 0455 { 0456 // qDebug() << "data size:" << data.size(); 0457 Q_ASSERT(m_putJob); 0458 if (!m_putJob) { 0459 return; // Don't crash 0460 } 0461 m_getJob->d_func()->internalSuspend(); 0462 m_putJob->d_func()->internalResume(); // Drink the beer 0463 m_buffer += data; 0464 0465 // On the first set of data incoming, we tell the "put" worker about our 0466 // decision about resuming 0467 if (!m_resumeAnswerSent) { 0468 m_resumeAnswerSent = true; 0469 // qDebug() << "(first time) -> send resume answer " << m_canResume; 0470 jobWorker(m_putJob)->sendResumeAnswer(m_canResume); 0471 } 0472 } 0473 0474 void FileCopyJobPrivate::slotDataReq(KIO::Job *, QByteArray &data) 0475 { 0476 Q_Q(FileCopyJob); 0477 // qDebug(); 0478 if (!m_resumeAnswerSent && !m_getJob) { 0479 // This can't happen 0480 q->setError(ERR_INTERNAL); 0481 q->setErrorText(QStringLiteral("'Put' job did not send canResume or 'Get' job did not send data!")); 0482 m_putJob->kill(FileCopyJob::Quietly); 0483 q->removeSubjob(m_putJob); 0484 m_putJob = nullptr; 0485 q->emitResult(); 0486 return; 0487 } 0488 if (m_getJob) { 0489 m_getJob->d_func()->internalResume(); // Order more beer 0490 m_putJob->d_func()->internalSuspend(); 0491 } 0492 data = m_buffer; 0493 m_buffer = QByteArray(); 0494 } 0495 0496 void FileCopyJobPrivate::slotMimetype(KIO::Job *, const QString &type) 0497 { 0498 Q_Q(FileCopyJob); 0499 Q_EMIT q->mimeTypeFound(q, type); 0500 } 0501 0502 void FileCopyJob::slotResult(KJob *job) 0503 { 0504 Q_D(FileCopyJob); 0505 // qDebug() << "this=" << this << "job=" << job; 0506 removeSubjob(job); 0507 0508 // If result comes from copyjob then we are not writing anymore. 0509 if (job == d->m_copyJob) { 0510 d->m_bFileCopyInProgress = false; 0511 } 0512 0513 // Did job have an error ? 0514 if (job->error()) { 0515 if ((job == d->m_moveJob) && (job->error() == ERR_UNSUPPORTED_ACTION)) { 0516 d->m_moveJob = nullptr; 0517 d->startBestCopyMethod(); 0518 return; 0519 } else if ((job == d->m_copyJob) && (job->error() == ERR_UNSUPPORTED_ACTION)) { 0520 d->m_copyJob = nullptr; 0521 d->startDataPump(); 0522 return; 0523 } else if (job == d->m_getJob) { 0524 d->m_getJob = nullptr; 0525 if (d->m_putJob) { 0526 d->m_putJob->kill(Quietly); 0527 removeSubjob(d->m_putJob); 0528 } 0529 } else if (job == d->m_putJob) { 0530 d->m_putJob = nullptr; 0531 if (d->m_getJob) { 0532 d->m_getJob->kill(Quietly); 0533 removeSubjob(d->m_getJob); 0534 } 0535 } else if (job == d->m_chmodJob) { 0536 d->m_chmodJob = nullptr; 0537 if (d->m_delJob) { 0538 d->m_delJob->kill(Quietly); 0539 removeSubjob(d->m_delJob); 0540 } 0541 } else if (job == d->m_delJob) { 0542 d->m_delJob = nullptr; 0543 if (d->m_chmodJob) { 0544 d->m_chmodJob->kill(Quietly); 0545 removeSubjob(d->m_chmodJob); 0546 } 0547 } 0548 setError(job->error()); 0549 setErrorText(job->errorText()); 0550 emitResult(); 0551 return; 0552 } 0553 0554 if (d->m_mustChmod) { 0555 // If d->m_permissions == -1, keep the default permissions 0556 if (d->m_permissions != -1) { 0557 d->m_chmodJob = chmod(d->m_dest, d->m_permissions); 0558 addSubjob(d->m_chmodJob); 0559 } 0560 d->m_mustChmod = false; 0561 } 0562 0563 if (job == d->m_moveJob) { 0564 d->m_moveJob = nullptr; // Finished 0565 } 0566 0567 if (job == d->m_copyJob) { 0568 d->m_copyJob = nullptr; 0569 if (d->m_move) { 0570 d->m_delJob = file_delete(d->m_src, HideProgressInfo /*no GUI*/); // Delete source 0571 addSubjob(d->m_delJob); 0572 } 0573 } 0574 0575 if (job == d->m_getJob) { 0576 // qDebug() << "m_getJob finished"; 0577 d->m_getJob = nullptr; // No action required 0578 if (d->m_putJob) { 0579 d->m_putJob->d_func()->internalResume(); 0580 } 0581 } 0582 0583 if (job == d->m_putJob) { 0584 // qDebug() << "m_putJob finished"; 0585 d->m_putJob = nullptr; 0586 if (d->m_getJob) { 0587 // The get job is still running, probably after emitting data(QByteArray()) 0588 // and before we receive its finished(). 0589 d->m_getJob->d_func()->internalResume(); 0590 } 0591 if (d->m_move) { 0592 d->m_delJob = file_delete(d->m_src, HideProgressInfo /*no GUI*/); // Delete source 0593 addSubjob(d->m_delJob); 0594 } 0595 } 0596 0597 if (job == d->m_delJob) { 0598 d->m_delJob = nullptr; // Finished 0599 } 0600 0601 if (job == d->m_chmodJob) { 0602 d->m_chmodJob = nullptr; // Finished 0603 } 0604 0605 if (!hasSubjobs()) { 0606 emitResult(); 0607 } 0608 } 0609 0610 bool FileCopyJob::doKill() 0611 { 0612 #ifdef Q_OS_WIN 0613 // TODO Use SetConsoleCtrlHandler on Windows or similar behaviour. 0614 // https://stackoverflow.com/questions/2007516/is-there-a-posix-sigterm-alternative-on-windows-a-gentle-kill-for-console-ap 0615 // https://danielkaes.wordpress.com/2009/06/04/how-to-catch-kill-events-with-python/ 0616 // https://phabricator.kde.org/D25117#566107 0617 0618 Q_D(FileCopyJob); 0619 0620 // If we are interrupted in the middle of file copying, 0621 // we may end up with corrupted file at the destination. 0622 // It is better to clean up this file. If a copy is being 0623 // made as part of move operation then delete the dest only if 0624 // source file is intact (m_delJob == NULL). 0625 if (d->m_bFileCopyInProgress && d->m_copyJob && d->m_dest.isLocalFile()) { 0626 if (d->m_flags & Overwrite) { 0627 QFile::remove(d->m_dest.toLocalFile() + QStringLiteral(".part")); 0628 } else { 0629 QFile::remove(d->m_dest.toLocalFile()); 0630 } 0631 } 0632 #endif 0633 return Job::doKill(); 0634 } 0635 0636 FileCopyJob *KIO::file_copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) 0637 { 0638 return FileCopyJobPrivate::newJob(src, dest, permissions, false, flags); 0639 } 0640 0641 FileCopyJob *KIO::file_move(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) 0642 { 0643 FileCopyJob *job = FileCopyJobPrivate::newJob(src, dest, permissions, true, flags); 0644 if (job->uiDelegateExtension()) { 0645 job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent); 0646 } 0647 return job; 0648 } 0649 0650 #include "moc_filecopyjob.cpp"