File indexing completed on 2024-04-14 03:52:52

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"