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