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 | |