KIO

copyjob.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
4 SPDX-FileCopyrightText: 2000-2006 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
6 SPDX-FileCopyrightText: 2021 Ahmad Samir <a.samirh78@gmail.com>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10
11#include "copyjob.h"
12#include "../utils_p.h"
13#include "deletejob.h"
14#include "filecopyjob.h"
15#include "global.h"
16#include "job.h" // buildErrorString
17#include "kcoredirlister.h"
18#include "kfileitem.h"
19#include "kiocoredebug.h"
20#include "kioglobal_p.h"
21#include "listjob.h"
22#include "mkdirjob.h"
23#include "statjob.h"
24#include <cerrno>
25
26#include <KConfigGroup>
27#include <KDesktopFile>
28#include <KLocalizedString>
29
30#include "kprotocolmanager.h"
31#include "worker_p.h"
32#include <KDirWatch>
33
34#include "askuseractioninterface.h"
35#include <jobuidelegateextension.h>
36#include <kio/jobuidelegatefactory.h>
37
38#include <kdirnotify.h>
39
40#ifdef Q_OS_UNIX
41#include <utime.h>
42#endif
43
44#include <QDateTime>
45#include <QFile>
46#include <QFileInfo>
47#include <QPointer>
48#include <QQueue>
49#include <QTemporaryFile>
50#include <QTimeZone>
51#include <QTimer>
52
53#include <sys/stat.h> // mode_t
54
55#include "job_p.h"
56#include <KFileSystemType>
57#include <KFileUtils>
58#include <KIO/FileSystemFreeSpaceJob>
59
60#include <list>
61#include <set>
62
63#include <QLoggingCategory>
64Q_DECLARE_LOGGING_CATEGORY(KIO_COPYJOB_DEBUG)
65Q_LOGGING_CATEGORY(KIO_COPYJOB_DEBUG, "kf.kio.core.copyjob", QtWarningMsg)
66
67using namespace KIO;
68
69// this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
70static constexpr int s_reportTimeout = 200;
71
72#if !defined(NAME_MAX)
73#if defined(_MAX_FNAME)
74static constexpr int NAME_MAX = _MAX_FNAME; // For Windows
75#else
76static constexpr NAME_MAX = 0;
77#endif
78#endif
79
80enum DestinationState {
81 DEST_NOT_STATED,
82 DEST_IS_DIR,
83 DEST_IS_FILE,
84 DEST_DOESNT_EXIST,
85};
86
87/**
88 * States:
89 * STATE_INITIAL the constructor was called
90 * STATE_STATING for the dest
91 * statCurrentSrc then does, for each src url:
92 * STATE_RENAMING if direct rename looks possible
93 * (on already exists, and user chooses rename, TODO: go to STATE_RENAMING again)
94 * STATE_STATING
95 * and then, if dir -> STATE_LISTING (filling 'd->dirs' and 'd->files')
96 * STATE_CREATING_DIRS (createNextDir, iterating over 'd->dirs')
97 * if conflict: STATE_CONFLICT_CREATING_DIRS
98 * STATE_COPYING_FILES (copyNextFile, iterating over 'd->files')
99 * if conflict: STATE_CONFLICT_COPYING_FILES
100 * STATE_DELETING_DIRS (deleteNextDir) (if moving)
101 * STATE_SETTING_DIR_ATTRIBUTES (setNextDirAttribute, iterating over d->m_directoriesCopied)
102 * done.
103 */
104enum CopyJobState {
105 STATE_INITIAL,
106 STATE_STATING,
107 STATE_RENAMING,
108 STATE_LISTING,
109 STATE_CREATING_DIRS,
110 STATE_CONFLICT_CREATING_DIRS,
111 STATE_COPYING_FILES,
112 STATE_CONFLICT_COPYING_FILES,
113 STATE_DELETING_DIRS,
114 STATE_SETTING_DIR_ATTRIBUTES,
115};
116
117static QUrl addPathToUrl(const QUrl &url, const QString &relPath)
118{
119 QUrl u(url);
120 u.setPath(Utils::concatPaths(url.path(), relPath));
121 return u;
122}
123
124static bool compareUrls(const QUrl &srcUrl, const QUrl &destUrl)
125{
126 /* clang-format off */
127 return srcUrl.scheme() == destUrl.scheme()
128 && srcUrl.host() == destUrl.host()
129 && srcUrl.port() == destUrl.port()
130 && srcUrl.userName() == destUrl.userName()
131 && srcUrl.password() == destUrl.password();
132 /* clang-format on */
133}
134
135// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
136static const char s_msdosInvalidChars[] = R"(<>:"/\|?*)";
137
138static bool hasInvalidChars(const QString &dest)
139{
140 return std::any_of(std::begin(s_msdosInvalidChars), std::end(s_msdosInvalidChars), [=](const char c) {
141 return dest.contains(QLatin1Char(c));
142 });
143}
144
145static void cleanMsdosDestName(QString &name)
146{
147 for (const char c : s_msdosInvalidChars) {
149 }
150}
151
152static bool isFatFs(KFileSystemType::Type fsType)
153{
154 return fsType == KFileSystemType::Fat || fsType == KFileSystemType::Exfat;
155}
156
157static bool isFatOrNtfs(KFileSystemType::Type fsType)
158{
159 return fsType == KFileSystemType::Ntfs || isFatFs(fsType);
160}
161
162static QString symlinkSupportMsg(const QString &path, const QString &fsName)
163{
164 const QString msg = i18nc(
165 "The first arg is the path to the symlink that couldn't be created, the second"
166 "arg is the filesystem type (e.g. vfat, exfat)",
167 "Could not create symlink \"%1\".\n"
168 "The destination filesystem (%2) doesn't support symlinks.",
169 path,
170 fsName);
171 return msg;
172}
173
174static QString invalidCharsSupportMsg(const QString &path, const QString &fsName, bool isDir = false)
175{
176 QString msg;
177 if (isDir) {
178 msg = i18n(
179 "Could not create \"%1\".\n"
180 "The destination filesystem (%2) disallows the following characters in folder names: %3\n"
181 "Selecting Replace will replace any invalid characters (in the destination folder name) with an underscore \"_\".",
182 path,
183 fsName,
184 QLatin1String(s_msdosInvalidChars));
185 } else {
186 msg = i18n(
187 "Could not create \"%1\".\n"
188 "The destination filesystem (%2) disallows the following characters in file names: %3\n"
189 "Selecting Replace will replace any invalid characters (in the destination file name) with an underscore \"_\".",
190 path,
191 fsName,
192 QLatin1String(s_msdosInvalidChars));
193 }
194
195 return msg;
196}
197
198/** @internal */
199struct CopyInfo {
200 QUrl uSource;
201 QUrl uDest;
202 QString linkDest; // for symlinks only
203 int permissions;
204 QDateTime ctime;
205 QDateTime mtime;
206 KIO::filesize_t size; // 0 for dirs
207};
208
209/** @internal */
210class KIO::CopyJobPrivate : public KIO::JobPrivate
211{
212public:
213 CopyJobPrivate(const QList<QUrl> &src, const QUrl &dest, CopyJob::CopyMode mode, bool asMethod)
214 : m_globalDest(dest)
215 , m_globalDestinationState(DEST_NOT_STATED)
216 , m_defaultPermissions(false)
217 , m_bURLDirty(false)
218 , m_mode(mode)
219 , m_asMethod(asMethod)
220 , destinationState(DEST_NOT_STATED)
221 , state(STATE_INITIAL)
222 , m_freeSpace(-1)
223 , m_totalSize(0)
224 , m_processedSize(0)
225 , m_fileProcessedSize(0)
226 , m_filesHandledByDirectRename(0)
227 , m_processedFiles(0)
228 , m_processedDirs(0)
229 , m_srcList(src)
230 , m_currentStatSrc(m_srcList.constBegin())
231 , m_bCurrentOperationIsLink(false)
232 , m_bSingleFileCopy(false)
233 , m_bOnlyRenames(mode == CopyJob::Move)
234 , m_dest(dest)
235 , m_bAutoRenameFiles(false)
236 , m_bAutoRenameDirs(false)
237 , m_bAutoSkipFiles(false)
238 , m_bAutoSkipDirs(false)
239 , m_bOverwriteAllFiles(false)
240 , m_bOverwriteAllDirs(false)
241 , m_bOverwriteWhenOlder(false)
242 , m_conflictError(0)
243 , m_reportTimer(nullptr)
244 {
245 }
246
247 // This is the dest URL that was initially given to CopyJob
248 // It is copied into m_dest, which can be changed for a given src URL
249 // (when using the RENAME dialog in slotResult),
250 // and which will be reset for the next src URL.
251 QUrl m_globalDest;
252 // The state info about that global dest
253 DestinationState m_globalDestinationState;
254 // See setDefaultPermissions
255 bool m_defaultPermissions;
256 // Whether URLs changed (and need to be emitted by the next slotReport call)
257 bool m_bURLDirty;
258 // Used after copying all the files into the dirs, to set mtime (TODO: and permissions?)
259 // after the copy is done
260 std::list<CopyInfo> m_directoriesCopied;
261 std::list<CopyInfo>::const_iterator m_directoriesCopiedIterator;
262
263 CopyJob::CopyMode m_mode;
264 bool m_asMethod; // See copyAs() method
265 DestinationState destinationState;
266 CopyJobState state;
267
268 KIO::filesize_t m_freeSpace;
269
270 KIO::filesize_t m_totalSize;
271 KIO::filesize_t m_processedSize;
272 KIO::filesize_t m_fileProcessedSize;
273 int m_filesHandledByDirectRename;
274 int m_processedFiles;
275 int m_processedDirs;
276 QList<CopyInfo> files;
277 QList<CopyInfo> dirs;
278 // List of dirs that will be copied then deleted when CopyMode is Move
279 QList<QUrl> dirsToRemove;
280 QList<QUrl> m_srcList;
281 QList<QUrl> m_successSrcList; // Entries in m_srcList that have successfully been moved
282 QList<QUrl>::const_iterator m_currentStatSrc;
283 bool m_bCurrentSrcIsDir;
284 bool m_bCurrentOperationIsLink;
285 bool m_bSingleFileCopy;
286 bool m_bOnlyRenames;
287 QUrl m_dest;
288 QUrl m_currentDest; // set during listing, used by slotEntries
289 //
290 QStringList m_skipList;
291 QSet<QString> m_overwriteList;
292 bool m_bAutoRenameFiles;
293 bool m_bAutoRenameDirs;
294 bool m_bAutoSkipFiles;
295 bool m_bAutoSkipDirs;
296 bool m_bOverwriteAllFiles;
297 bool m_bOverwriteAllDirs;
298 bool m_bOverwriteWhenOlder;
299
300 bool m_autoSkipDirsWithInvalidChars = false;
301 bool m_autoSkipFilesWithInvalidChars = false;
302 bool m_autoReplaceInvalidChars = false;
303
304 bool m_autoSkipFatSymlinks = false;
305
306 enum SkipType {
307 // No skip dialog is involved
308 NoSkipType = 0,
309 // SkipDialog is asking about invalid chars in destination file/dir names
310 SkipInvalidChars,
311 // SkipDialog is asking about how to handle symlinks why copying to a
312 // filesystem that doesn't support symlinks
313 SkipFatSymlinks,
314 };
315
316 int m_conflictError;
317
318 QTimer *m_reportTimer;
319 QElapsedTimer m_speedMeasurementTimer;
320
321 struct CopyProgressPoint {
322 qint64 elapsedTime;
323 KIO::filesize_t processedSize;
324 };
325 QQueue<CopyProgressPoint> m_speedMeasurementPoints;
326
327 // The current src url being stat'ed or copied
328 // During the stat phase, this is initially equal to *m_currentStatSrc but it can be resolved to a local file equivalent (#188903).
329 QUrl m_currentSrcURL;
330 QUrl m_currentDestURL;
331
332 std::set<QString> m_parentDirs;
333 bool m_ignoreSourcePermissions = false;
334
335 void statCurrentSrc();
336 void statNextSrc();
337
338 // Those aren't slots but submethods for slotResult.
339 void slotResultStating(KJob *job);
340 void startListing(const QUrl &src);
341
342 void slotResultCreatingDirs(KJob *job);
343 void slotResultConflictCreatingDirs(KJob *job);
344 void createNextDir();
345 void processCreateNextDir(const QList<CopyInfo>::Iterator &it, int result);
346
347 void slotResultCopyingFiles(KJob *job);
348 void slotResultErrorCopyingFiles(KJob *job);
349 void processFileRenameDialogResult(const QList<CopyInfo>::Iterator &it, RenameDialog_Result result, const QUrl &newUrl, const QDateTime &destmtime);
350
351 // KIO::Job* linkNextFile( const QUrl& uSource, const QUrl& uDest, bool overwrite );
352 KIO::Job *linkNextFile(const QUrl &uSource, const QUrl &uDest, JobFlags flags);
353 // MsDos filesystems don't allow certain characters in filenames, and VFAT and ExFAT
354 // don't support symlinks, this method detects those conditions and tries to handle it
355 bool handleMsdosFsQuirks(QList<CopyInfo>::Iterator it, KFileSystemType::Type fsType);
356 void copyNextFile();
357 void processCopyNextFile(const QList<CopyInfo>::Iterator &it, int result, SkipType skipType);
358
359 void slotResultDeletingDirs(KJob *job);
360 void deleteNextDir();
361 void sourceStated(const UDSEntry &entry, const QUrl &sourceUrl);
362 // Removes a dir from the "dirsToRemove" list
363 void skip(const QUrl &sourceURL, bool isDir);
364
365 void slotResultRenaming(KJob *job);
366 void directRenamingFailed(const QUrl &dest);
367 void processDirectRenamingConflictResult(RenameDialog_Result result,
368 bool srcIsDir,
369 bool destIsDir,
370 const QDateTime &mtimeSrc,
371 const QDateTime &mtimeDest,
372 const QUrl &dest,
373 const QUrl &newUrl);
374
375 void slotResultSettingDirAttributes(KJob *job);
376 void setNextDirAttribute();
377
378 void startRenameJob(const QUrl &workerUrl);
379 bool shouldOverwriteDir(const QString &path) const;
380 bool shouldOverwriteFile(const QString &path) const;
381 bool shouldSkip(const QString &path) const;
382 void skipSrc(bool isDir);
383 void renameDirectory(const QList<CopyInfo>::iterator &it, const QUrl &newUrl);
384 QUrl finalDestUrl(const QUrl &src, const QUrl &dest) const;
385
386 void slotStart();
387 void slotEntries(KIO::Job *, const KIO::UDSEntryList &list);
388 void slotSubError(KIO::ListJob *job, KIO::ListJob *subJob);
389 void addCopyInfoFromUDSEntry(const UDSEntry &entry, const QUrl &srcUrl, bool srcIsDir, const QUrl &currentDest);
390 /**
391 * Forward signal from subjob
392 */
393 void slotProcessedSize(KJob *, qulonglong data_size);
394 /**
395 * Forward signal from subjob
396 * @param size the total size
397 */
398 void slotTotalSize(KJob *, qulonglong size);
399
400 void slotReport();
401
402 Q_DECLARE_PUBLIC(CopyJob)
403
404 static inline CopyJob *newJob(const QList<QUrl> &src, const QUrl &dest, CopyJob::CopyMode mode, bool asMethod, JobFlags flags)
405 {
406 CopyJob *job = new CopyJob(*new CopyJobPrivate(src, dest, mode, asMethod));
408 if (!(flags & HideProgressInfo)) {
410 }
411 if (flags & KIO::Overwrite) {
412 job->d_func()->m_bOverwriteAllDirs = true;
413 job->d_func()->m_bOverwriteAllFiles = true;
414 }
415 if (!(flags & KIO::NoPrivilegeExecution)) {
416 job->d_func()->m_privilegeExecutionEnabled = true;
417 FileOperationType copyType;
418 switch (mode) {
419 case CopyJob::Copy:
420 copyType = Copy;
421 break;
422 case CopyJob::Move:
423 copyType = Move;
424 break;
425 case CopyJob::Link:
426 copyType = Symlink;
427 break;
428 default:
429 Q_UNREACHABLE();
430 }
431 job->d_func()->m_operationType = copyType;
432 }
433 return job;
434 }
435};
436
437CopyJob::CopyJob(CopyJobPrivate &dd)
438 : Job(dd)
439{
440 Q_D(CopyJob);
441 setProperty("destUrl", d_func()->m_dest.toString());
442 QTimer::singleShot(0, this, [d]() {
443 d->slotStart();
444 });
445 qRegisterMetaType<KIO::UDSEntry>();
446}
447
448CopyJob::~CopyJob()
449{
450}
451
453{
454 return d_func()->m_srcList;
455}
456
458{
459 return d_func()->m_dest;
460}
461
462void CopyJobPrivate::slotStart()
463{
464 Q_Q(CopyJob);
465 if (q->isSuspended()) {
466 return;
467 }
468
469 q->startElapsedTimer();
470
471 if (m_mode == CopyJob::CopyMode::Move) {
472 for (const QUrl &url : std::as_const(m_srcList)) {
473 if (m_dest.scheme() == url.scheme() && m_dest.host() == url.host()) {
474 const QString srcPath = Utils::slashAppended(url.path());
475 if (m_dest.path().startsWith(srcPath)) {
477 q->emitResult();
478 return;
479 }
480 }
481 }
482 }
483
484 if (m_mode == CopyJob::CopyMode::Link && m_globalDest.isLocalFile()) {
485 const QString destPath = m_globalDest.toLocalFile();
486 const auto destFs = KFileSystemType::fileSystemType(destPath);
487 if (isFatFs(destFs)) {
488 q->setError(ERR_SYMLINKS_NOT_SUPPORTED);
489 const QString errText = destPath + QLatin1String(" [") + KFileSystemType::fileSystemName(destFs) + QLatin1Char(']');
490 q->setErrorText(errText);
491 q->emitResult();
492 return;
493 }
494 }
495
496 /**
497 We call the functions directly instead of using signals.
498 Calling a function via a signal takes approx. 65 times the time
499 compared to calling it directly (at least on my machine). aleXXX
500 */
501 m_reportTimer = new QTimer(q);
502
503 q->connect(m_reportTimer, &QTimer::timeout, q, [this]() {
504 slotReport();
505 });
506 m_reportTimer->start(s_reportTimeout);
507
508 // Stat the dest
509 state = STATE_STATING;
510 const QUrl dest = m_asMethod ? m_dest.adjusted(QUrl::RemoveFilename) : m_dest;
511 // We need isDir() and UDS_LOCAL_PATH (for workers who set it). Let's assume the latter is part of StatBasic too.
512 KIO::Job *job = KIO::stat(dest, StatJob::DestinationSide, KIO::StatBasic | KIO::StatResolveSymlink, KIO::HideProgressInfo);
513 qCDebug(KIO_COPYJOB_DEBUG) << "CopyJob: stating the dest" << dest;
514 q->addSubjob(job);
515}
516
517// For unit test purposes
518KIOCORE_EXPORT bool kio_resolve_local_urls = true;
519
520void CopyJobPrivate::slotResultStating(KJob *job)
521{
522 Q_Q(CopyJob);
523 qCDebug(KIO_COPYJOB_DEBUG);
524 // Was there an error while stating the src ?
525 if (job->error() && destinationState != DEST_NOT_STATED) {
526 const QUrl srcurl = static_cast<SimpleJob *>(job)->url();
527 if (!srcurl.isLocalFile()) {
528 // Probably : src doesn't exist. Well, over some protocols (e.g. FTP)
529 // this info isn't really reliable (thanks to MS FTP servers).
530 // We'll assume a file, and try to download anyway.
531 qCDebug(KIO_COPYJOB_DEBUG) << "Error while stating source. Activating hack";
532 q->removeSubjob(job);
533 Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ...
534 struct CopyInfo info;
535 info.permissions = (mode_t)-1;
536 info.size = KIO::invalidFilesize;
537 info.uSource = srcurl;
538 info.uDest = m_dest;
539 // Append filename or dirname to destination URL, if allowed
540 if (destinationState == DEST_IS_DIR && !m_asMethod) {
541 const QString fileName = srcurl.scheme() == QLatin1String("data") ? QStringLiteral("data") : srcurl.fileName(); // #379093
542 info.uDest = addPathToUrl(info.uDest, fileName);
543 }
544
545 files.append(info);
546 statNextSrc();
547 return;
548 }
549 // Local file. If stat fails, the file definitely doesn't exist.
550 // yes, q->Job::, because we don't want to call our override
551 q->Job::slotResult(job); // will set the error and emit result(this)
552 return;
553 }
554
555 // Keep copy of the stat result
556 auto statJob = static_cast<StatJob *>(job);
557 const UDSEntry entry = statJob->statResult();
558
559 if (destinationState == DEST_NOT_STATED) {
560 const bool isGlobalDest = m_dest == m_globalDest;
561
562 // we were stating the dest
563 if (job->error()) {
564 destinationState = DEST_DOESNT_EXIST;
565 qCDebug(KIO_COPYJOB_DEBUG) << "dest does not exist";
566 } else {
567 const bool isDir = entry.isDir();
568
569 // Check for writability, before spending time stat'ing everything (#141564).
570 // This assumes all KIO workers set permissions correctly...
571 const int permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, -1);
572 const bool isWritable = (permissions != -1) && (permissions & S_IWUSR);
573 if (!m_privilegeExecutionEnabled && !isWritable) {
574 const QUrl dest = m_asMethod ? m_dest.adjusted(QUrl::RemoveFilename) : m_dest;
575 q->setError(ERR_WRITE_ACCESS_DENIED);
576 q->setErrorText(dest.toDisplayString(QUrl::PreferLocalFile));
577 q->emitResult();
578 return;
579 }
580
581 // Treat symlinks to dirs as dirs here, so no test on isLink
582 destinationState = isDir ? DEST_IS_DIR : DEST_IS_FILE;
583 qCDebug(KIO_COPYJOB_DEBUG) << "dest is dir:" << isDir;
584
585 if (isGlobalDest) {
586 m_globalDestinationState = destinationState;
587 }
588
589 const QString sLocalPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
590 if (!sLocalPath.isEmpty() && kio_resolve_local_urls && statJob->url().scheme() != QStringLiteral("trash")) {
591 const QString fileName = m_dest.fileName();
592 m_dest = QUrl::fromLocalFile(sLocalPath);
593 if (m_asMethod) {
594 m_dest = addPathToUrl(m_dest, fileName);
595 }
596 qCDebug(KIO_COPYJOB_DEBUG) << "Setting m_dest to the local path:" << sLocalPath;
597 if (isGlobalDest) {
598 m_globalDest = m_dest;
599 }
600 }
601 }
602
603 q->removeSubjob(job);
604 Q_ASSERT(!q->hasSubjobs());
605
606 // In copy-as mode, we want to check the directory to which we're
607 // copying. The target file or directory does not exist yet, which
608 // might confuse FileSystemFreeSpaceJob.
609 const QUrl existingDest = m_asMethod ? m_dest.adjusted(QUrl::RemoveFilename) : m_dest;
611 q->connect(spaceJob, &KJob::result, q, [this, existingDest, spaceJob]() {
612 if (!spaceJob->error()) {
613 m_freeSpace = spaceJob->availableSize();
614 } else {
615 qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't determine free space information for" << existingDest;
616 }
617 // After knowing what the dest is, we can start stat'ing the first src.
618 statCurrentSrc();
619 });
620 return;
621 } else {
622 sourceStated(entry, static_cast<SimpleJob *>(job)->url());
623 q->removeSubjob(job);
624 }
625}
626
627void CopyJobPrivate::sourceStated(const UDSEntry &entry, const QUrl &sourceUrl)
628{
629 const QString sLocalPath = sourceUrl.scheme() != QStringLiteral("trash") ? entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH) : QString();
630 const bool isDir = entry.isDir();
631
632 // We were stating the current source URL
633 // Is it a file or a dir ?
634
635 // There 6 cases, and all end up calling addCopyInfoFromUDSEntry first :
636 // 1 - src is a dir, destination is a directory,
637 // slotEntries will append the source-dir-name to the destination
638 // 2 - src is a dir, destination is a file -- will offer to overwrite, later on.
639 // 3 - src is a dir, destination doesn't exist, then it's the destination dirname,
640 // so slotEntries will use it as destination.
641
642 // 4 - src is a file, destination is a directory,
643 // slotEntries will append the filename to the destination.
644 // 5 - src is a file, destination is a file, m_dest is the exact destination name
645 // 6 - src is a file, destination doesn't exist, m_dest is the exact destination name
646
647 QUrl srcurl;
648 if (!sLocalPath.isEmpty() && destinationState != DEST_DOESNT_EXIST) {
649 qCDebug(KIO_COPYJOB_DEBUG) << "Using sLocalPath. destinationState=" << destinationState;
650 // Prefer the local path -- but only if we were able to stat() the dest.
651 // Otherwise, renaming a desktop:/ url would copy from src=file to dest=desktop (#218719)
652 srcurl = QUrl::fromLocalFile(sLocalPath);
653 } else {
654 srcurl = sourceUrl;
655 }
656 addCopyInfoFromUDSEntry(entry, srcurl, false, m_dest);
657
658 m_currentDest = m_dest;
659 m_bCurrentSrcIsDir = false;
660
661 if (isDir //
662 && !entry.isLink() // treat symlinks as files (no recursion)
663 && m_mode != CopyJob::Link) { // No recursion in Link mode either.
664 qCDebug(KIO_COPYJOB_DEBUG) << "Source is a directory";
665
666 if (srcurl.isLocalFile()) {
667 const QString parentDir = srcurl.adjusted(QUrl::StripTrailingSlash).toLocalFile();
668 m_parentDirs.insert(parentDir);
669 }
670
671 m_bCurrentSrcIsDir = true; // used by slotEntries
672 if (destinationState == DEST_IS_DIR) { // (case 1)
673 if (!m_asMethod) {
674 // Use <desturl>/<directory_copied> as destination, from now on
675 QString directory = srcurl.fileName();
676 const QString sName = entry.stringValue(KIO::UDSEntry::UDS_NAME);
677 KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(srcurl);
678 if (fnu == KProtocolInfo::Name) {
679 if (!sName.isEmpty()) {
680 directory = sName;
681 }
682 } else if (fnu == KProtocolInfo::DisplayName) {
684 if (!dispName.isEmpty()) {
685 directory = dispName;
686 } else if (!sName.isEmpty()) {
687 directory = sName;
688 }
689 }
690 m_currentDest = addPathToUrl(m_currentDest, directory);
691 }
692 } else { // (case 3)
693 // otherwise dest is new name for toplevel dir
694 // so the destination exists, in fact, from now on.
695 // (This even works with other src urls in the list, since the
696 // dir has effectively been created)
697 destinationState = DEST_IS_DIR;
698 if (m_dest == m_globalDest) {
699 m_globalDestinationState = destinationState;
700 }
701 }
702
703 startListing(srcurl);
704 } else {
705 qCDebug(KIO_COPYJOB_DEBUG) << "Source is a file (or a symlink), or we are linking -> no recursive listing";
706
707 if (srcurl.isLocalFile()) {
709 m_parentDirs.insert(parentDir);
710 }
711
712 statNextSrc();
713 }
714}
715
717{
718 Q_D(CopyJob);
719 d->slotReport();
720 return Job::doSuspend();
721}
722
724{
725 Q_D(CopyJob);
726 switch (d->state) {
727 case STATE_INITIAL:
728 QTimer::singleShot(0, this, [d]() {
729 d->slotStart();
730 });
731 break;
732 default:
733 // not implemented
734 break;
735 }
736 return Job::doResume();
737}
738
739void CopyJobPrivate::slotReport()
740{
741 Q_Q(CopyJob);
742 if (q->isSuspended()) {
743 return;
744 }
745
746 // If showProgressInfo was set, progressId() is > 0.
747 switch (state) {
748 case STATE_RENAMING:
749 if (m_bURLDirty) {
750 m_bURLDirty = false;
751 Q_ASSERT(m_mode == CopyJob::Move);
752 emitMoving(q, m_currentSrcURL, m_currentDestURL);
753 Q_EMIT q->moving(q, m_currentSrcURL, m_currentDestURL);
754 }
755 // "N" files renamed shouldn't include skipped files
756 q->setProcessedAmount(KJob::Files, m_processedFiles);
757 // % value should include skipped files
758 q->emitPercent(m_filesHandledByDirectRename, q->totalAmount(KJob::Files));
759 break;
760
761 case STATE_COPYING_FILES:
762 q->setProcessedAmount(KJob::Files, m_processedFiles);
763 q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize);
764 if (m_bURLDirty) {
765 // Only emit urls when they changed. This saves time, and fixes #66281
766 m_bURLDirty = false;
767 if (m_mode == CopyJob::Move) {
768 emitMoving(q, m_currentSrcURL, m_currentDestURL);
769 Q_EMIT q->moving(q, m_currentSrcURL, m_currentDestURL);
770 } else if (m_mode == CopyJob::Link) {
771 emitCopying(q, m_currentSrcURL, m_currentDestURL); // we don't have a delegate->linking
772 Q_EMIT q->linking(q, m_currentSrcURL.path(), m_currentDestURL);
773 } else {
774 emitCopying(q, m_currentSrcURL, m_currentDestURL);
775 Q_EMIT q->copying(q, m_currentSrcURL, m_currentDestURL);
776 }
777 }
778 break;
779
780 case STATE_CREATING_DIRS:
781 q->setProcessedAmount(KJob::Directories, m_processedDirs);
782 if (m_bURLDirty) {
783 m_bURLDirty = false;
784 Q_EMIT q->creatingDir(q, m_currentDestURL);
785 emitCreatingDir(q, m_currentDestURL);
786 }
787 break;
788
789 case STATE_STATING:
790 case STATE_LISTING:
791 if (m_bURLDirty) {
792 m_bURLDirty = false;
793 if (m_mode == CopyJob::Move) {
794 emitMoving(q, m_currentSrcURL, m_currentDestURL);
795 } else {
796 emitCopying(q, m_currentSrcURL, m_currentDestURL);
797 }
798 }
799 q->setProgressUnit(KJob::Bytes);
800 q->setTotalAmount(KJob::Bytes, m_totalSize);
801 q->setTotalAmount(KJob::Files, files.count() + m_filesHandledByDirectRename);
802 q->setTotalAmount(KJob::Directories, dirs.count());
803 break;
804
805 default:
806 break;
807 }
808}
809
810void CopyJobPrivate::slotEntries(KIO::Job *job, const UDSEntryList &list)
811{
812 // Q_Q(CopyJob);
815 for (; it != end; ++it) {
816 const UDSEntry &entry = *it;
817 addCopyInfoFromUDSEntry(entry, static_cast<SimpleJob *>(job)->url(), m_bCurrentSrcIsDir, m_currentDest);
818 }
819}
820
821void CopyJobPrivate::slotSubError(ListJob *job, ListJob *subJob)
822{
823 const QUrl &url = subJob->url();
824 qCWarning(KIO_CORE) << url << subJob->errorString();
825
826 Q_Q(CopyJob);
827
828 Q_EMIT q->warning(job, subJob->errorString());
829 skip(url, true);
830}
831
832void CopyJobPrivate::addCopyInfoFromUDSEntry(const UDSEntry &entry, const QUrl &srcUrl, bool srcIsDir, const QUrl &currentDest)
833{
834 struct CopyInfo info;
835 info.permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, -1);
836 const auto timeVal = entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
837 if (timeVal != -1) {
838 info.mtime = QDateTime::fromSecsSinceEpoch(timeVal, QTimeZone::UTC);
839 }
841 info.size = static_cast<KIO::filesize_t>(entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1));
842 const bool isDir = entry.isDir();
843
844 if (!isDir && info.size != KIO::invalidFilesize) {
845 m_totalSize += info.size;
846 }
847
848 // recursive listing, displayName can be a/b/c/d
849 const QString fileName = entry.stringValue(KIO::UDSEntry::UDS_NAME);
850 const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL);
851 QUrl url;
852 if (!urlStr.isEmpty()) {
853 url = QUrl(urlStr);
854 }
855 QString localPath = srcUrl.scheme() != QStringLiteral("trash") ? entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH) : QString();
856 info.linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
857
858 if (fileName != QLatin1String("..") && fileName != QLatin1String(".")) {
859 const bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty();
860 if (!hasCustomURL) {
861 // Make URL from displayName
862 url = srcUrl;
863 if (srcIsDir) { // Only if src is a directory. Otherwise uSource is fine as is
864 qCDebug(KIO_COPYJOB_DEBUG) << "adding path" << fileName;
865 url = addPathToUrl(url, fileName);
866 }
867 }
868 qCDebug(KIO_COPYJOB_DEBUG) << "fileName=" << fileName << "url=" << url;
869 if (!localPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST) {
870 url = QUrl::fromLocalFile(localPath);
871 }
872
873 info.uSource = url;
874 info.uDest = currentDest;
875 qCDebug(KIO_COPYJOB_DEBUG) << "uSource=" << info.uSource << "uDest(1)=" << info.uDest;
876 // Append filename or dirname to destination URL, if allowed
877 if (destinationState == DEST_IS_DIR &&
878 // "copy/move as <foo>" means 'foo' is the dest for the base srcurl
879 // (passed here during stating) but not its children (during listing)
880 (!(m_asMethod && state == STATE_STATING))) {
881 QString destFileName;
882 KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(url);
883 if (hasCustomURL && fnu == KProtocolInfo::FromUrl) {
884 // destFileName = url.fileName(); // Doesn't work for recursive listing
885 // Count the number of prefixes used by the recursive listjob
886 int numberOfSlashes = fileName.count(QLatin1Char('/')); // don't make this a find()!
887 QString path = url.path();
888 int pos = 0;
889 for (int n = 0; n < numberOfSlashes + 1; ++n) {
890 pos = path.lastIndexOf(QLatin1Char('/'), pos - 1);
891 if (pos == -1) { // error
892 qCWarning(KIO_CORE) << "KIO worker bug: not enough slashes in UDS_URL" << path << "- looking for" << numberOfSlashes << "slashes";
893 break;
894 }
895 }
896 if (pos >= 0) {
897 destFileName = path.mid(pos + 1);
898 }
899
900 } else if (fnu == KProtocolInfo::Name) { // destination filename taken from UDS_NAME
901 destFileName = fileName;
902 } else { // from display name (with fallback to name)
904 destFileName = displayName.isEmpty() ? fileName : displayName;
905 }
906
907 // Here we _really_ have to add some filename to the dest.
908 // Otherwise, we end up with e.g. dest=..../Desktop/ itself.
909 // (This can happen when dropping a link to a webpage with no path)
910 if (destFileName.isEmpty()) {
911 destFileName = KIO::encodeFileName(info.uSource.toDisplayString());
912 }
913
914 qCDebug(KIO_COPYJOB_DEBUG) << " adding destFileName=" << destFileName;
915 info.uDest = addPathToUrl(info.uDest, destFileName);
916 }
917 qCDebug(KIO_COPYJOB_DEBUG) << " uDest(2)=" << info.uDest;
918 qCDebug(KIO_COPYJOB_DEBUG) << " " << info.uSource << "->" << info.uDest;
919 if (info.linkDest.isEmpty() && isDir && m_mode != CopyJob::Link) { // Dir
920 dirs.append(info); // Directories
921 if (m_mode == CopyJob::Move) {
922 dirsToRemove.append(info.uSource);
923 }
924 } else {
925 files.append(info); // Files and any symlinks
926 }
927 }
928}
929
930// Adjust for kio_trash choosing its own dest url...
931QUrl CopyJobPrivate::finalDestUrl(const QUrl &src, const QUrl &dest) const
932{
933 Q_Q(const CopyJob);
934 if (dest.scheme() == QLatin1String("trash")) {
935 const QMap<QString, QString> &metaData = q->metaData();
936 QMap<QString, QString>::ConstIterator it = metaData.find(QLatin1String("trashURL-") + src.path());
937 if (it != metaData.constEnd()) {
938 qCDebug(KIO_COPYJOB_DEBUG) << "finalDestUrl=" << it.value();
939 return QUrl(it.value());
940 }
941 }
942 return dest;
943}
944
945void CopyJobPrivate::skipSrc(bool isDir)
946{
947 m_dest = m_globalDest;
948 destinationState = m_globalDestinationState;
949 skip(*m_currentStatSrc, isDir);
950 ++m_currentStatSrc;
951 statCurrentSrc();
952}
953
954void CopyJobPrivate::statNextSrc()
955{
956 /* Revert to the global destination, the one that applies to all source urls.
957 * Imagine you copy the items a b and c into /d, but /d/b exists so the user uses "Rename" to put it in /foo/b instead.
958 * d->m_dest is /foo/b for b, but we have to revert to /d for item c and following.
959 */
960 m_dest = m_globalDest;
961 qCDebug(KIO_COPYJOB_DEBUG) << "Setting m_dest to" << m_dest;
962 destinationState = m_globalDestinationState;
963 ++m_currentStatSrc;
964 statCurrentSrc();
965}
966
967void CopyJobPrivate::statCurrentSrc()
968{
969 Q_Q(CopyJob);
970 if (m_currentStatSrc != m_srcList.constEnd()) {
971 m_currentSrcURL = (*m_currentStatSrc);
972 m_bURLDirty = true;
973 m_ignoreSourcePermissions = !KProtocolManager::supportsListing(m_currentSrcURL) || m_currentSrcURL.scheme() == QLatin1String("trash");
974
975 if (m_mode == CopyJob::Link) {
976 // Skip the "stating the source" stage, we don't need it for linking
977 m_currentDest = m_dest;
978 struct CopyInfo info;
979 info.permissions = -1;
980 info.size = KIO::invalidFilesize;
981 info.uSource = m_currentSrcURL;
982 info.uDest = m_currentDest;
983 // Append filename or dirname to destination URL, if allowed
984 if (destinationState == DEST_IS_DIR && !m_asMethod) {
985 if (compareUrls(m_currentSrcURL, info.uDest)) {
986 // This is the case of creating a real symlink
987 info.uDest = addPathToUrl(info.uDest, m_currentSrcURL.fileName());
988 } else {
989 // Different protocols, we'll create a .desktop file
990 // We have to change the extension anyway, so while we're at it,
991 // name the file like the URL
992 QByteArray encodedFilename = QFile::encodeName(m_currentSrcURL.toDisplayString());
993 const int truncatePos = NAME_MAX - (info.uDest.toDisplayString().length() + 8); // length(.desktop) = 8
994 if (truncatePos > 0) {
995 encodedFilename.truncate(truncatePos);
996 }
997 const QString decodedFilename = QFile::decodeName(encodedFilename);
998 info.uDest = addPathToUrl(info.uDest, KIO::encodeFileName(decodedFilename) + QLatin1String(".desktop"));
999 }
1000 }
1001 files.append(info); // Files and any symlinks
1002 statNextSrc(); // we could use a loop instead of a recursive call :)
1003 return;
1004 }
1005
1006 // Let's see if we can skip stat'ing, for the case where a directory view has the info already
1007 KIO::UDSEntry entry;
1008 const KFileItem cachedItem = KCoreDirLister::cachedItemForUrl(m_currentSrcURL);
1009 if (!cachedItem.isNull()) {
1010 entry = cachedItem.entry();
1011 if (destinationState != DEST_DOESNT_EXIST
1012 && m_currentSrcURL.scheme() != QStringLiteral("trash")) { // only resolve src if we could resolve dest (#218719)
1013
1014 m_currentSrcURL = cachedItem.mostLocalUrl(); // #183585
1015 }
1016 }
1017
1018 // Don't go renaming right away if we need a stat() to find out the destination filename
1019 const bool needStat =
1020 KProtocolManager::fileNameUsedForCopying(m_currentSrcURL) == KProtocolInfo::FromUrl || destinationState != DEST_IS_DIR || m_asMethod;
1021 if (m_mode == CopyJob::Move && needStat) {
1022 // If moving, before going for the full stat+[list+]copy+del thing, try to rename
1023 // The logic is pretty similar to FileCopyJobPrivate::slotStart()
1024 if (compareUrls(m_currentSrcURL, m_dest)) {
1025 startRenameJob(m_currentSrcURL);
1026 return;
1027 } else if (m_currentSrcURL.isLocalFile() && KProtocolManager::canRenameFromFile(m_dest)) {
1028 startRenameJob(m_dest);
1029 return;
1030 } else if (m_dest.isLocalFile() && KProtocolManager::canRenameToFile(m_currentSrcURL)) {
1031 startRenameJob(m_currentSrcURL);
1032 return;
1033 }
1034 }
1035
1036 // if the source file system doesn't support deleting, we do not even stat
1037 if (m_mode == CopyJob::Move && !KProtocolManager::supportsDeleting(m_currentSrcURL)) {
1038 QPointer<CopyJob> that = q;
1039 Q_EMIT q->warning(q, buildErrorString(ERR_CANNOT_DELETE, m_currentSrcURL.toDisplayString()));
1040 if (that) {
1041 statNextSrc(); // we could use a loop instead of a recursive call :)
1042 }
1043 return;
1044 }
1045
1046 m_bOnlyRenames = false;
1047
1048 // Testing for entry.count()>0 here is not good enough; KFileItem inserts
1049 // entries for UDS_USER and UDS_GROUP even on initially empty UDSEntries (#192185)
1050 if (entry.contains(KIO::UDSEntry::UDS_NAME)) {
1051 qCDebug(KIO_COPYJOB_DEBUG) << "fast path! found info about" << m_currentSrcURL << "in KCoreDirLister";
1052 // sourceStated(entry, m_currentSrcURL); // don't recurse, see #319747, use queued invokeMethod instead
1053 auto srcStatedFunc = [this, entry]() {
1054 sourceStated(entry, m_currentSrcURL);
1055 };
1057 return;
1058 }
1059
1060 // Stat the next src url
1061 Job *job = KIO::stat(m_currentSrcURL, KIO::HideProgressInfo);
1062 qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat on" << m_currentSrcURL;
1063 state = STATE_STATING;
1064 q->addSubjob(job);
1065 m_currentDestURL = m_dest;
1066 m_bURLDirty = true;
1067 } else {
1068 // Finished the stat'ing phase
1069 // First make sure that the totals were correctly emitted
1070 m_bURLDirty = true;
1071 slotReport();
1072
1073 qCDebug(KIO_COPYJOB_DEBUG) << "Stating finished. To copy:" << m_totalSize << ", available:" << m_freeSpace;
1074
1075 if (m_totalSize > m_freeSpace && m_freeSpace != static_cast<KIO::filesize_t>(-1)) {
1076 q->setError(ERR_DISK_FULL);
1077 q->setErrorText(m_currentSrcURL.toDisplayString());
1078 q->emitResult();
1079 return;
1080 }
1081
1082 // Check if we are copying a single file
1083 m_bSingleFileCopy = (files.count() == 1 && dirs.isEmpty());
1084 // Then start copying things
1085 state = STATE_CREATING_DIRS;
1086 createNextDir();
1087 }
1088}
1089
1090void CopyJobPrivate::startRenameJob(const QUrl &workerUrl)
1091{
1092 Q_Q(CopyJob);
1093
1094 // Silence KDirWatch notifications, otherwise performance is horrible
1095 if (m_currentSrcURL.isLocalFile()) {
1096 const QString parentDir = m_currentSrcURL.adjusted(QUrl::RemoveFilename).path();
1097 const auto [it, isInserted] = m_parentDirs.insert(parentDir);
1098 if (isInserted) {
1099 KDirWatch::self()->stopDirScan(parentDir);
1100 }
1101 }
1102
1103 QUrl dest = m_dest;
1104 // Append filename or dirname to destination URL, if allowed
1105 if (destinationState == DEST_IS_DIR && !m_asMethod) {
1106 dest = addPathToUrl(dest, m_currentSrcURL.fileName());
1107 }
1108 m_currentDestURL = dest;
1109 qCDebug(KIO_COPYJOB_DEBUG) << m_currentSrcURL << "->" << dest << "trying direct rename first";
1110 if (state != STATE_RENAMING) {
1111 q->setTotalAmount(KJob::Files, m_srcList.count());
1112 }
1113 state = STATE_RENAMING;
1114
1115 struct CopyInfo info;
1116 info.permissions = -1;
1117 info.size = KIO::invalidFilesize;
1118 info.uSource = m_currentSrcURL;
1119 info.uDest = dest;
1120
1121 KIO_ARGS << m_currentSrcURL << dest << (qint8) false /*no overwrite*/;
1122 SimpleJob *newJob = SimpleJobPrivate::newJobNoUi(workerUrl, CMD_RENAME, packedArgs);
1123 newJob->setParentJob(q);
1124 q->addSubjob(newJob);
1125 if (m_currentSrcURL.adjusted(QUrl::RemoveFilename) != dest.adjusted(QUrl::RemoveFilename)) { // For the user, moving isn't renaming. Only renaming is.
1126 m_bOnlyRenames = false;
1127 }
1128}
1129
1130void CopyJobPrivate::startListing(const QUrl &src)
1131{
1132 Q_Q(CopyJob);
1133 state = STATE_LISTING;
1134 m_bURLDirty = true;
1136 newjob->setUnrestricted(true);
1137 q->connect(newjob, &ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {
1138 slotEntries(job, list);
1139 });
1140 q->connect(newjob, &ListJob::subError, q, [this](KIO::ListJob *job, KIO::ListJob *subJob) {
1141 slotSubError(job, subJob);
1142 });
1143 q->addSubjob(newjob);
1144}
1145
1146void CopyJobPrivate::skip(const QUrl &sourceUrl, bool isDir)
1147{
1148 QUrl dir(sourceUrl);
1149 if (!isDir) {
1150 // Skipping a file: make sure not to delete the parent dir (#208418)
1152 }
1153 while (dirsToRemove.removeAll(dir) > 0) {
1154 // Do not rely on rmdir() on the parent directories aborting.
1155 // Exclude the parent dirs explicitly.
1157 }
1158}
1159
1160bool CopyJobPrivate::shouldOverwriteDir(const QString &path) const
1161{
1162 if (m_bOverwriteAllDirs) {
1163 return true;
1164 }
1165 return m_overwriteList.contains(path);
1166}
1167
1168bool CopyJobPrivate::shouldOverwriteFile(const QString &path) const
1169{
1170 if (m_bOverwriteAllFiles) {
1171 return true;
1172 }
1173 return m_overwriteList.contains(path);
1174}
1175
1176bool CopyJobPrivate::shouldSkip(const QString &path) const
1177{
1178 for (const QString &skipPath : std::as_const(m_skipList)) {
1179 if (path.startsWith(skipPath)) {
1180 return true;
1181 }
1182 }
1183 return false;
1184}
1185
1186void CopyJobPrivate::renameDirectory(const QList<CopyInfo>::iterator &it, const QUrl &newUrl)
1187{
1188 Q_Q(CopyJob);
1189 Q_EMIT q->renamed(q, (*it).uDest, newUrl); // for e.g. KPropertiesDialog
1190
1191 const QString oldPath = Utils::slashAppended((*it).uDest.path());
1192
1193 // Change the current one and strip the trailing '/'
1194 (*it).uDest = newUrl.adjusted(QUrl::StripTrailingSlash);
1195
1196 const QString newPath = Utils::slashAppended(newUrl.path()); // With trailing slash
1197
1198 QList<CopyInfo>::Iterator renamedirit = it;
1199 ++renamedirit;
1200 // Change the name of subdirectories inside the directory
1201 for (; renamedirit != dirs.end(); ++renamedirit) {
1202 QString path = (*renamedirit).uDest.path();
1203 if (path.startsWith(oldPath)) {
1204 QString n = path;
1205 n.replace(0, oldPath.length(), newPath);
1206 /*qDebug() << "dirs list:" << (*renamedirit).uSource.path()
1207 << "was going to be" << path
1208 << ", changed into" << n;*/
1209 (*renamedirit).uDest.setPath(n, QUrl::DecodedMode);
1210 }
1211 }
1212 // Change filenames inside the directory
1213 QList<CopyInfo>::Iterator renamefileit = files.begin();
1214 for (; renamefileit != files.end(); ++renamefileit) {
1215 QString path = (*renamefileit).uDest.path(QUrl::FullyDecoded);
1216 if (path.startsWith(oldPath)) {
1217 QString n = path;
1218 n.replace(0, oldPath.length(), newPath);
1219 /*qDebug() << "files list:" << (*renamefileit).uSource.path()
1220 << "was going to be" << path
1221 << ", changed into" << n;*/
1222 (*renamefileit).uDest.setPath(n, QUrl::DecodedMode);
1223 }
1224 }
1225}
1226
1227void CopyJobPrivate::slotResultCreatingDirs(KJob *job)
1228{
1229 Q_Q(CopyJob);
1230 // The dir we are trying to create:
1231 QList<CopyInfo>::Iterator it = dirs.begin();
1232 // Was there an error creating a dir ?
1233 if (job->error()) {
1234 m_conflictError = job->error();
1235 if (m_conflictError == ERR_DIR_ALREADY_EXIST //
1236 || m_conflictError == ERR_FILE_ALREADY_EXIST) { // can't happen?
1237 QUrl oldURL = ((SimpleJob *)job)->url();
1238 // Should we skip automatically ?
1239 if (m_bAutoSkipDirs) {
1240 // We don't want to copy files in this directory, so we put it on the skip list
1241 const QString path = Utils::slashAppended(oldURL.path());
1242 m_skipList.append(path);
1243 skip(oldURL, true);
1244 dirs.erase(it); // Move on to next dir
1245 } else {
1246 // Did the user choose to overwrite already?
1247 const QString destDir = (*it).uDest.path();
1248 if (shouldOverwriteDir(destDir)) { // overwrite => just skip
1249 Q_EMIT q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */);
1250 dirs.erase(it); // Move on to next dir
1251 ++m_processedDirs;
1252 } else {
1253 if (m_bAutoRenameDirs) {
1254 const QUrl destDirectory = (*it).uDest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
1255 const QString newName = KFileUtils::suggestName(destDirectory, (*it).uDest.fileName());
1256 QUrl newUrl(destDirectory);
1257 newUrl.setPath(Utils::concatPaths(newUrl.path(), newName));
1258 renameDirectory(it, newUrl);
1259 } else {
1261 q->Job::slotResult(job); // will set the error and emit result(this)
1262 return;
1263 }
1264
1265 Q_ASSERT(((SimpleJob *)job)->url() == (*it).uDest);
1266 q->removeSubjob(job);
1267 Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ...
1268
1269 // We need to stat the existing dir, to get its last-modification time
1270 QUrl existingDest((*it).uDest);
1271 SimpleJob *newJob = KIO::stat(existingDest, StatJob::DestinationSide, KIO::StatDefaultDetails, KIO::HideProgressInfo);
1272 qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat for resolving conflict on" << existingDest;
1273 state = STATE_CONFLICT_CREATING_DIRS;
1274 q->addSubjob(newJob);
1275 return; // Don't move to next dir yet !
1276 }
1277 }
1278 }
1279 } else {
1280 // Severe error, abort
1281 q->Job::slotResult(job); // will set the error and emit result(this)
1282 return;
1283 }
1284 } else { // no error : remove from list, to move on to next dir
1285 // this is required for the undo feature
1286 Q_EMIT q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true, false);
1287 m_directoriesCopied.push_back(*it);
1288 dirs.erase(it);
1289 ++m_processedDirs;
1290 }
1291
1292 q->removeSubjob(job);
1293 Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ...
1294 createNextDir();
1295}
1296
1297void CopyJobPrivate::slotResultConflictCreatingDirs(KJob *job)
1298{
1299 Q_Q(CopyJob);
1300 // We come here after a conflict has been detected and we've stated the existing dir
1301
1302 // The dir we were trying to create:
1303 QList<CopyInfo>::Iterator it = dirs.begin();
1304
1305 const UDSEntry entry = ((KIO::StatJob *)job)->statResult();
1306
1307 QDateTime destmtime;
1308 QDateTime destctime;
1309 const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE);
1310 const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
1311
1312 q->removeSubjob(job);
1313 Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ...
1314
1315 // Always multi and skip (since there are files after that)
1317 // Overwrite only if the existing thing is a dir (no chance with a file)
1318 if (m_conflictError == ERR_DIR_ALREADY_EXIST) {
1319 // We are in slotResultConflictCreatingDirs(), so the source is a dir
1321
1322 if ((*it).uSource == (*it).uDest
1323 || ((*it).uSource.scheme() == (*it).uDest.scheme() && (*it).uSource.adjusted(QUrl::StripTrailingSlash).path() == linkDest)) {
1325 } else {
1326 options |= RenameDialog_Overwrite;
1329 }
1330 }
1331
1332 if (m_reportTimer) {
1333 m_reportTimer->stop();
1334 }
1335
1336 auto *askUserActionInterface = KIO::delegateExtension<KIO::AskUserActionInterface *>(q);
1337
1339 QObject::connect(askUserActionInterface, renameSignal, q, [=, this](RenameDialog_Result result, const QUrl &newUrl, KJob *parentJob) {
1340 Q_ASSERT(parentJob == q);
1341 // Only receive askUserRenameResult once per rename dialog
1342 QObject::disconnect(askUserActionInterface, renameSignal, q, nullptr);
1343
1344 if (m_reportTimer) {
1345 m_reportTimer->start(s_reportTimeout);
1346 }
1347
1348 const QString existingDest = (*it).uDest.path();
1349
1350 switch (result) {
1351 case Result_Cancel:
1352 q->setError(ERR_USER_CANCELED);
1353 q->emitResult();
1354 return;
1355 case Result_AutoRename:
1356 m_bAutoRenameDirs = true;
1357 // fall through
1358 Q_FALLTHROUGH();
1359 case Result_Rename:
1360 renameDirectory(it, newUrl);
1361 break;
1362 case Result_AutoSkip:
1363 m_bAutoSkipDirs = true;
1364 // fall through
1365 Q_FALLTHROUGH();
1366 case Result_Skip:
1367 m_skipList.append(Utils::slashAppended(existingDest));
1368 skip((*it).uSource, true);
1369 // Move on to next dir
1370 dirs.erase(it);
1371 ++m_processedDirs;
1372 break;
1373 case Result_Overwrite:
1374 m_overwriteList.insert(existingDest);
1375 Q_EMIT q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */);
1376 // Move on to next dir
1377 dirs.erase(it);
1378 ++m_processedDirs;
1379 break;
1380 case Result_OverwriteAll:
1381 m_bOverwriteAllDirs = true;
1382 Q_EMIT q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */);
1383 // Move on to next dir
1384 dirs.erase(it);
1385 ++m_processedDirs;
1386 break;
1387 default:
1388 Q_ASSERT(0);
1389 }
1390 state = STATE_CREATING_DIRS;
1391 createNextDir();
1392 });
1393
1394 /* clang-format off */
1395 askUserActionInterface->askUserRename(q, i18n("Folder Already Exists"),
1396 (*it).uSource, (*it).uDest,
1397 options,
1398 (*it).size, destsize,
1399 (*it).ctime, destctime,
1400 (*it).mtime, destmtime);
1401 /* clang-format on */
1402}
1403
1404void CopyJobPrivate::createNextDir()
1405{
1406 Q_Q(CopyJob);
1407
1408 // Take first dir to create out of list
1409 QList<CopyInfo>::Iterator it = dirs.begin();
1410 // Is this URL on the skip list or the overwrite list ?
1411 while (it != dirs.end()) {
1412 const QString dir = it->uDest.path();
1413 if (shouldSkip(dir)) {
1414 it = dirs.erase(it);
1415 } else {
1416 break;
1417 }
1418 }
1419
1420 if (it != dirs.end()) { // any dir to create, finally ?
1421 if (it->uDest.isLocalFile()) {
1422 // uDest doesn't exist yet, check the filesystem of the parent dir
1423 const auto destFileSystem = KFileSystemType::fileSystemType(it->uDest.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename).toLocalFile());
1424 if (isFatOrNtfs(destFileSystem)) {
1425 const QString dirName = it->uDest.adjusted(QUrl::StripTrailingSlash).fileName();
1426 if (hasInvalidChars(dirName)) {
1427 // We already asked the user?
1428 if (m_autoReplaceInvalidChars) {
1429 processCreateNextDir(it, KIO::Result_ReplaceInvalidChars);
1430 return;
1431 } else if (m_autoSkipDirsWithInvalidChars) {
1432 processCreateNextDir(it, KIO::Result_Skip);
1433 return;
1434 }
1435
1436 const QString msg = invalidCharsSupportMsg(it->uDest.toDisplayString(QUrl::PreferLocalFile),
1437 KFileSystemType::fileSystemName(destFileSystem),
1438 true /* isDir */);
1439
1440 if (auto *askUserActionInterface = KIO::delegateExtension<KIO::AskUserActionInterface *>(q)) {
1442 if (dirs.size() > 1) {
1443 options |= SkipDialog_MultipleItems;
1444 }
1445
1447 QObject::connect(askUserActionInterface, skipSignal, q, [=, this](SkipDialog_Result result, KJob *parentJob) {
1448 Q_ASSERT(parentJob == q);
1449
1450 // Only receive askUserSkipResult once per skip dialog
1451 QObject::disconnect(askUserActionInterface, skipSignal, q, nullptr);
1452
1453 processCreateNextDir(it, result);
1454 });
1455
1456 askUserActionInterface->askUserSkip(q, options, msg);
1457
1458 return;
1459 } else { // No Job Ui delegate
1460 qCWarning(KIO_COPYJOB_DEBUG) << msg;
1461 q->emitResult();
1462 return;
1463 }
1464 }
1465 }
1466 }
1467
1468 processCreateNextDir(it, -1);
1469 } else { // we have finished creating dirs
1470 q->setProcessedAmount(KJob::Directories, m_processedDirs); // make sure final number appears
1471
1472 if (m_mode == CopyJob::Move) {
1473 // Now we know which dirs hold the files we're going to delete.
1474 // To speed things up and prevent double-notification, we disable KDirWatch
1475 // on those dirs temporarily (using KDirWatch::self, that's the instance
1476 // used by e.g. kdirlister).
1477 for (const auto &dir : m_parentDirs) {
1479 }
1480 }
1481
1482 state = STATE_COPYING_FILES;
1483 ++m_processedFiles; // Ralf wants it to start at 1, not 0
1484 copyNextFile();
1485 }
1486}
1487
1488void CopyJobPrivate::processCreateNextDir(const QList<CopyInfo>::Iterator &it, int result)
1489{
1490 Q_Q(CopyJob);
1491
1492 switch (result) {
1493 case Result_Cancel:
1494 q->setError(ERR_USER_CANCELED);
1495 q->emitResult();
1496 return;
1498 m_autoReplaceInvalidChars = true;
1499 Q_FALLTHROUGH();
1501 it->uDest = it->uDest.adjusted(QUrl::StripTrailingSlash);
1502 QString dirName = it->uDest.fileName();
1503 const int len = dirName.size();
1504 cleanMsdosDestName(dirName);
1505 QString path = it->uDest.path();
1506 path.replace(path.size() - len, len, dirName);
1507 it->uDest.setPath(path);
1508 break;
1509 }
1510 case KIO::Result_AutoSkip:
1511 m_autoSkipDirsWithInvalidChars = true;
1512 Q_FALLTHROUGH();
1513 case KIO::Result_Skip:
1514 m_skipList.append(Utils::slashAppended(it->uDest.path()));
1515 skip(it->uSource, true);
1516 dirs.erase(it); // Move on to next dir
1517 ++m_processedDirs;
1518 createNextDir();
1519 return;
1520 default:
1521 break;
1522 }
1523
1524 // Create the directory - with default permissions so that we can put files into it
1525 // TODO : change permissions once all is finished; but for stuff coming from CDROM it sucks...
1526 KIO::SimpleJob *newjob = KIO::mkdir(it->uDest, -1);
1527 newjob->setParentJob(q);
1528 if (shouldOverwriteFile(it->uDest.path())) { // if we are overwriting an existing file or symlink
1529 newjob->addMetaData(QStringLiteral("overwrite"), QStringLiteral("true"));
1530 }
1531
1532 m_currentDestURL = it->uDest;
1533 m_bURLDirty = true;
1534
1535 q->addSubjob(newjob);
1536}
1537
1538void CopyJobPrivate::slotResultCopyingFiles(KJob *job)
1539{
1540 Q_Q(CopyJob);
1541 // The file we were trying to copy:
1542 QList<CopyInfo>::Iterator it = files.begin();
1543 if (job->error()) {
1544 // Should we skip automatically ?
1545 if (m_bAutoSkipFiles) {
1546 skip((*it).uSource, false);
1547 m_fileProcessedSize = (*it).size;
1548 files.erase(it); // Move on to next file
1549 } else {
1550 m_conflictError = job->error(); // save for later
1551 // Existing dest ?
1552 if (m_conflictError == ERR_FILE_ALREADY_EXIST //
1553 || m_conflictError == ERR_DIR_ALREADY_EXIST //
1554 || m_conflictError == ERR_IDENTICAL_FILES) {
1555 if (m_bAutoRenameFiles) {
1556 QUrl destDirectory = (*it).uDest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
1557 const QString newName = KFileUtils::suggestName(destDirectory, (*it).uDest.fileName());
1558 QUrl newDest(destDirectory);
1559 newDest.setPath(Utils::concatPaths(newDest.path(), newName));
1560 Q_EMIT q->renamed(q, (*it).uDest, newDest); // for e.g. kpropsdlg
1561 (*it).uDest = newDest;
1562 } else {
1564 q->Job::slotResult(job); // will set the error and emit result(this)
1565 return;
1566 }
1567
1568 q->removeSubjob(job);
1569 Q_ASSERT(!q->hasSubjobs());
1570 // We need to stat the existing file, to get its last-modification time
1571 QUrl existingFile((*it).uDest);
1572 SimpleJob *newJob =
1573 KIO::stat(existingFile, StatJob::DestinationSide, KIO::StatDetail::StatBasic | KIO::StatDetail::StatTime, KIO::HideProgressInfo);
1574 qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat for resolving conflict on" << existingFile;
1575 state = STATE_CONFLICT_COPYING_FILES;
1576 q->addSubjob(newJob);
1577 return; // Don't move to next file yet !
1578 }
1579 } else {
1580 if (m_bCurrentOperationIsLink && qobject_cast<KIO::DeleteJob *>(job)) {
1581 // Very special case, see a few lines below
1582 // We are deleting the source of a symlink we successfully moved... ignore error
1583 m_fileProcessedSize = (*it).size;
1584 ++m_processedFiles;
1585 files.erase(it);
1586 } else {
1588 q->Job::slotResult(job); // will set the error and emit result(this)
1589 return;
1590 }
1591
1592 // Go directly to the conflict resolution, there is nothing to stat
1593 slotResultErrorCopyingFiles(job);
1594 return;
1595 }
1596 }
1597 }
1598 } else { // no error
1599 // Special case for moving links. That operation needs two jobs, unlike others.
1600 if (m_bCurrentOperationIsLink //
1601 && m_mode == CopyJob::Move //
1602 && !qobject_cast<KIO::DeleteJob *>(job) // Deleting source not already done
1603 ) {
1604 q->removeSubjob(job);
1605 Q_ASSERT(!q->hasSubjobs());
1606 // The only problem with this trick is that the error handling for this del operation
1607 // is not going to be right... see 'Very special case' above.
1608 KIO::Job *newjob = KIO::del((*it).uSource, HideProgressInfo);
1609 newjob->setParentJob(q);
1610 q->addSubjob(newjob);
1611 return; // Don't move to next file yet !
1612 }
1613
1614 const QUrl finalUrl = finalDestUrl((*it).uSource, (*it).uDest);
1615
1616 if (m_bCurrentOperationIsLink) {
1617 QString target = (m_mode == CopyJob::Link ? (*it).uSource.path() : (*it).linkDest);
1618 // required for the undo feature
1619 Q_EMIT q->copyingLinkDone(q, (*it).uSource, target, finalUrl);
1620 } else {
1621 // required for the undo feature
1622 Q_EMIT q->copyingDone(q, (*it).uSource, finalUrl, (*it).mtime, false, false);
1623 if (m_mode == CopyJob::Move) {
1624#ifdef WITH_QTDBUS
1625 org::kde::KDirNotify::emitFileMoved((*it).uSource, finalUrl);
1626#endif
1627 }
1628 m_successSrcList.append((*it).uSource);
1629 if (m_freeSpace != KIO::invalidFilesize && (*it).size != KIO::invalidFilesize) {
1630 m_freeSpace -= (*it).size;
1631 }
1632 }
1633 // remove from list, to move on to next file
1634 files.erase(it);
1635 ++m_processedFiles;
1636 }
1637
1638 // clear processed size for last file and add it to overall processed size
1639 m_processedSize += m_fileProcessedSize;
1640 m_fileProcessedSize = 0;
1641
1642 qCDebug(KIO_COPYJOB_DEBUG) << files.count() << "files remaining";
1643
1644 // Merge metadata from subjob
1645 KIO::Job *kiojob = qobject_cast<KIO::Job *>(job);
1646 Q_ASSERT(kiojob);
1647 m_incomingMetaData += kiojob->metaData();
1648 q->removeSubjob(job);
1649 Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ...
1650 copyNextFile();
1651}
1652
1653void CopyJobPrivate::slotResultErrorCopyingFiles(KJob *job)
1654{
1655 Q_Q(CopyJob);
1656 // We come here after a conflict has been detected and we've stated the existing file
1657 // The file we were trying to create:
1658 QList<CopyInfo>::Iterator it = files.begin();
1659
1660 RenameDialog_Result res = Result_Cancel;
1661
1662 if (m_reportTimer) {
1663 m_reportTimer->stop();
1664 }
1665
1666 q->removeSubjob(job);
1667 Q_ASSERT(!q->hasSubjobs());
1668 auto *askUserActionInterface = KIO::delegateExtension<KIO::AskUserActionInterface *>(q);
1669
1670 if (m_conflictError == ERR_FILE_ALREADY_EXIST //
1671 || m_conflictError == ERR_DIR_ALREADY_EXIST //
1672 || m_conflictError == ERR_IDENTICAL_FILES) {
1673 // Its modification time:
1674 const UDSEntry entry = static_cast<KIO::StatJob *>(job)->statResult();
1675
1676 QDateTime destmtime;
1677 QDateTime destctime;
1678 const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE);
1679 const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
1680
1681 // Offer overwrite only if the existing thing is a file
1682 // If src==dest, use "overwrite-itself"
1683 RenameDialog_Options options;
1684 bool isDir = true;
1685
1686 if (m_conflictError == ERR_DIR_ALREADY_EXIST) {
1688 } else {
1689 if ((*it).uSource == (*it).uDest
1690 || ((*it).uSource.scheme() == (*it).uDest.scheme() && (*it).uSource.adjusted(QUrl::StripTrailingSlash).path() == linkDest)) {
1692 } else {
1693 const qint64 destMTimeStamp = entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
1694 if (m_bOverwriteWhenOlder && (*it).mtime.isValid() && destMTimeStamp != -1) {
1695 if ((*it).mtime.currentSecsSinceEpoch() > destMTimeStamp) {
1696 qCDebug(KIO_COPYJOB_DEBUG) << "dest is older, overwriting" << (*it).uDest;
1697 res = Result_Overwrite;
1698 } else {
1699 qCDebug(KIO_COPYJOB_DEBUG) << "dest is newer, skipping" << (*it).uDest;
1700 res = Result_Skip;
1701 }
1702 } else {
1703 // These timestamps are used only when RenameDialog_Overwrite is set.
1704 destmtime = QDateTime::fromSecsSinceEpoch(destMTimeStamp, QTimeZone::UTC);
1706
1707 options = RenameDialog_Overwrite;
1708 }
1709 }
1710 isDir = false;
1711 }
1712
1713 // if no preset value was set
1714 if (res == Result_Cancel) {
1715 if (!m_bSingleFileCopy) {
1717 }
1718
1719 const QString title = !isDir ? i18n("File Already Exists") : i18n("Already Exists as Folder");
1720
1722 QObject::connect(askUserActionInterface, renameSignal, q, [=, this](RenameDialog_Result result, const QUrl &newUrl, KJob *parentJob) {
1723 Q_ASSERT(parentJob == q);
1724 // Only receive askUserRenameResult once per rename dialog
1725 QObject::disconnect(askUserActionInterface, renameSignal, q, nullptr);
1726 processFileRenameDialogResult(it, result, newUrl, destmtime);
1727 });
1728
1729 /* clang-format off */
1730 askUserActionInterface->askUserRename(q, title,
1731 (*it).uSource, (*it).uDest,
1732 options,
1733 (*it).size, destsize,
1734 (*it).ctime, destctime,
1735 (*it).mtime, destmtime); /* clang-format on */
1736 return;
1737 }
1738 } else {
1739 if (job->error() == ERR_USER_CANCELED) {
1740 res = Result_Cancel;
1741 } else if (!askUserActionInterface) {
1742 q->Job::slotResult(job); // will set the error and emit result(this)
1743 return;
1744 } else {
1745 SkipDialog_Options options;
1746 if (files.count() > 1) {
1747 options |= SkipDialog_MultipleItems;
1748 }
1749
1751 QObject::connect(askUserActionInterface, skipSignal, q, [=, this](SkipDialog_Result result, KJob *parentJob) {
1752 Q_ASSERT(parentJob == q);
1753 // Only receive askUserSkipResult once per skip dialog
1754 QObject::disconnect(askUserActionInterface, skipSignal, q, nullptr);
1755 processFileRenameDialogResult(it, result, QUrl() /* no new url in skip */, QDateTime{});
1756 });
1757
1758 askUserActionInterface->askUserSkip(q, options, job->errorString());
1759 return;
1760 }
1761 }
1762
1763 processFileRenameDialogResult(it, res, QUrl{}, QDateTime{});
1764}
1765
1766void CopyJobPrivate::processFileRenameDialogResult(const QList<CopyInfo>::Iterator &it,
1767 RenameDialog_Result result,
1768 const QUrl &newUrl,
1769 const QDateTime &destmtime)
1770{
1771 Q_Q(CopyJob);
1772
1773 if (m_reportTimer) {
1774 m_reportTimer->start(s_reportTimeout);
1775 }
1776
1777 if (result == Result_OverwriteWhenOlder) {
1778 m_bOverwriteWhenOlder = true;
1779 if ((*it).mtime > destmtime) {
1780 qCDebug(KIO_COPYJOB_DEBUG) << "dest is older, overwriting" << (*it).uDest;
1781 result = Result_Overwrite;
1782 } else {
1783 qCDebug(KIO_COPYJOB_DEBUG) << "dest is newer, skipping" << (*it).uDest;
1784 result = Result_Skip;
1785 }
1786 }
1787
1788 switch (result) {
1789 case Result_Cancel:
1790 q->setError(ERR_USER_CANCELED);
1791 q->emitResult();
1792 return;
1793 case Result_AutoRename:
1794 m_bAutoRenameFiles = true;
1795 // fall through
1796 Q_FALLTHROUGH();
1797 case Result_Rename: {
1798 Q_EMIT q->renamed(q, (*it).uDest, newUrl); // for e.g. kpropsdlg
1799 (*it).uDest = newUrl;
1800 m_bURLDirty = true;
1801 break;
1802 }
1803 case Result_AutoSkip:
1804 m_bAutoSkipFiles = true;
1805 // fall through
1806 Q_FALLTHROUGH();
1807 case Result_Skip:
1808 // Move on to next file
1809 skip((*it).uSource, false);
1810 m_processedSize += (*it).size;
1811 files.erase(it);
1812 break;
1813 case Result_OverwriteAll:
1814 m_bOverwriteAllFiles = true;
1815 break;
1816 case Result_Overwrite:
1817 // Add to overwrite list, so that copyNextFile knows to overwrite
1818 m_overwriteList.insert((*it).uDest.path());
1819 break;
1820 case Result_Retry:
1821 // Do nothing, copy file again
1822 break;
1823 default:
1824 Q_ASSERT(0);
1825 }
1826 state = STATE_COPYING_FILES;
1827 copyNextFile();
1828}
1829
1830KIO::Job *CopyJobPrivate::linkNextFile(const QUrl &uSource, const QUrl &uDest, JobFlags flags)
1831{
1832 qCDebug(KIO_COPYJOB_DEBUG) << "Linking";
1833 if (compareUrls(uSource, uDest)) {
1834 // This is the case of creating a real symlink
1835 KIO::SimpleJob *newJob = KIO::symlink(uSource.path(), uDest, flags | HideProgressInfo /*no GUI*/);
1836 newJob->setParentJob(q_func());
1837 qCDebug(KIO_COPYJOB_DEBUG) << "Linking target=" << uSource.path() << "link=" << uDest;
1838 // emit linking( this, uSource.path(), uDest );
1839 m_bCurrentOperationIsLink = true;
1840 m_currentSrcURL = uSource;
1841 m_currentDestURL = uDest;
1842 m_bURLDirty = true;
1843 // Observer::self()->slotCopying( this, uSource, uDest ); // should be slotLinking perhaps
1844 return newJob;
1845 } else {
1846 Q_Q(CopyJob);
1847 qCDebug(KIO_COPYJOB_DEBUG) << "Linking URL=" << uSource << "link=" << uDest;
1848 if (uDest.isLocalFile()) {
1849 // if the source is a devices url, handle it a littlebit special
1850
1851 QString path = uDest.toLocalFile();
1852 qCDebug(KIO_COPYJOB_DEBUG) << "path=" << path;
1853 QFile f(path);
1854 if (f.open(QIODevice::ReadWrite)) {
1855 f.close();
1856 KDesktopFile desktopFile(path);
1857 KConfigGroup config = desktopFile.desktopGroup();
1858 QUrl url = uSource;
1859 url.setPassword(QString());
1860 config.writePathEntry("URL", url.toString());
1861 config.writeEntry("Name", url.toString());
1862 config.writeEntry("Type", QStringLiteral("Link"));
1863 QString protocol = uSource.scheme();
1864 if (protocol == QLatin1String("ftp")) {
1865 config.writeEntry("Icon", QStringLiteral("folder-remote"));
1866 } else if (protocol == QLatin1String("http") || protocol == QLatin1String("https")) {
1867 config.writeEntry("Icon", QStringLiteral("text-html"));
1868 } else if (protocol == QLatin1String("info")) {
1869 config.writeEntry("Icon", QStringLiteral("text-x-texinfo"));
1870 } else if (protocol == QLatin1String("mailto")) { // sven:
1871 config.writeEntry("Icon", QStringLiteral("internet-mail")); // added mailto: support
1872 } else if (protocol == QLatin1String("trash") && url.path().length() <= 1) { // trash:/ link
1873 config.writeEntry("Name", i18n("Trash"));
1874 config.writeEntry("Icon", QStringLiteral("user-trash-full"));
1875 config.writeEntry("EmptyIcon", QStringLiteral("user-trash"));
1876 } else {
1877 config.writeEntry("Icon", QStringLiteral("unknown"));
1878 }
1879 config.sync();
1880 files.erase(files.begin()); // done with this one, move on
1881 ++m_processedFiles;
1882 copyNextFile();
1883 return nullptr;
1884 } else {
1885 qCDebug(KIO_COPYJOB_DEBUG) << "ERR_CANNOT_OPEN_FOR_WRITING";
1886 q->setError(ERR_CANNOT_OPEN_FOR_WRITING);
1887 q->setErrorText(uDest.toLocalFile());
1888 q->emitResult();
1889 return nullptr;
1890 }
1891 } else {
1892 // Todo: not show "link" on remote dirs if the src urls are not from the same protocol+host+...
1893 q->setError(ERR_CANNOT_SYMLINK);
1894 q->setErrorText(uDest.toDisplayString());
1895 q->emitResult();
1896 return nullptr;
1897 }
1898 }
1899}
1900
1901bool CopyJobPrivate::handleMsdosFsQuirks(QList<CopyInfo>::Iterator it, KFileSystemType::Type fsType)
1902{
1903 Q_Q(CopyJob);
1904
1905 QString msg;
1906 SkipDialog_Options options;
1907 SkipType skipType = NoSkipType;
1908
1909 if (isFatFs(fsType) && !it->linkDest.isEmpty()) { // Copying a symlink
1910 skipType = SkipFatSymlinks;
1911 if (m_autoSkipFatSymlinks) { // Have we already asked the user?
1912 processCopyNextFile(it, KIO::Result_Skip, skipType);
1913 return true;
1914 }
1916 msg = symlinkSupportMsg(it->uDest.toLocalFile(), KFileSystemType::fileSystemName(fsType));
1917 } else if (hasInvalidChars(it->uDest.fileName())) {
1918 skipType = SkipInvalidChars;
1919 if (m_autoReplaceInvalidChars) { // Have we already asked the user?
1920 processCopyNextFile(it, KIO::Result_ReplaceInvalidChars, skipType);
1921 return true;
1922 } else if (m_autoSkipFilesWithInvalidChars) { // Have we already asked the user?
1923 processCopyNextFile(it, KIO::Result_Skip, skipType);
1924 return true;
1925 }
1926
1928 msg = invalidCharsSupportMsg(it->uDest.toDisplayString(QUrl::PreferLocalFile), KFileSystemType::fileSystemName(fsType));
1929 }
1930
1931 if (!msg.isEmpty()) {
1932 if (auto *askUserActionInterface = KIO::delegateExtension<KIO::AskUserActionInterface *>(q)) {
1933 if (files.size() > 1) {
1934 options |= SkipDialog_MultipleItems;
1935 }
1936
1938 QObject::connect(askUserActionInterface, skipSignal, q, [=, this](SkipDialog_Result result, KJob *parentJob) {
1939 Q_ASSERT(parentJob == q);
1940 // Only receive askUserSkipResult once per skip dialog
1941 QObject::disconnect(askUserActionInterface, skipSignal, q, nullptr);
1942
1943 processCopyNextFile(it, result, skipType);
1944 });
1945
1946 askUserActionInterface->askUserSkip(q, options, msg);
1947
1948 return true;
1949 } else { // No Job Ui delegate
1950 qCWarning(KIO_COPYJOB_DEBUG) << msg;
1951 q->emitResult();
1952 return true;
1953 }
1954 }
1955
1956 return false; // Not handled, move on
1957}
1958
1959void CopyJobPrivate::copyNextFile()
1960{
1961 Q_Q(CopyJob);
1962 bool bCopyFile = false;
1963 qCDebug(KIO_COPYJOB_DEBUG);
1964
1965 bool isDestLocal = m_globalDest.isLocalFile();
1966
1967 // Take the first file in the list
1968 QList<CopyInfo>::Iterator it = files.begin();
1969 // Is this URL on the skip list ?
1970 while (it != files.end() && !bCopyFile) {
1971 const QString destFile = (*it).uDest.path();
1972 bCopyFile = !shouldSkip(destFile);
1973 if (!bCopyFile) {
1974 it = files.erase(it);
1975 }
1976
1977 if (it != files.end() && isDestLocal && (*it).size > 0xFFFFFFFF) { // 4GB-1
1978 const auto destFileSystem = KFileSystemType::fileSystemType(m_globalDest.toLocalFile());
1979 if (destFileSystem == KFileSystemType::Fat) {
1980 q->setError(ERR_FILE_TOO_LARGE_FOR_FAT32);
1981 q->setErrorText((*it).uDest.toDisplayString());
1982 q->emitResult();
1983 return;
1984 }
1985 }
1986 }
1987
1988 if (bCopyFile) { // any file to create, finally ?
1989 if (isDestLocal) {
1990 const auto destFileSystem = KFileSystemType::fileSystemType(m_globalDest.toLocalFile());
1991 if (isFatOrNtfs(destFileSystem)) {
1992 if (handleMsdosFsQuirks(it, destFileSystem)) {
1993 return;
1994 }
1995 }
1996 }
1997
1998 processCopyNextFile(it, -1, NoSkipType);
1999 } else {
2000 // We're done
2001 qCDebug(KIO_COPYJOB_DEBUG) << "copyNextFile finished";
2002 --m_processedFiles; // undo the "start at 1" hack
2003 slotReport(); // display final numbers, important if progress dialog stays up
2004
2005 deleteNextDir();
2006 }
2007}
2008
2009void CopyJobPrivate::processCopyNextFile(const QList<CopyInfo>::Iterator &it, int result, SkipType skipType)
2010{
2011 Q_Q(CopyJob);
2012
2013 switch (result) {
2014 case Result_Cancel:
2015 q->setError(ERR_USER_CANCELED);
2016 q->emitResult();
2017 return;
2019 m_autoReplaceInvalidChars = true;
2020 Q_FALLTHROUGH();
2022 QString fileName = it->uDest.fileName();
2023 const int len = fileName.size();
2024 cleanMsdosDestName(fileName);
2025 QString path = it->uDest.path();
2026 path.replace(path.size() - len, len, fileName);
2027 it->uDest.setPath(path);
2028 break;
2029 }
2030 case KIO::Result_AutoSkip:
2031 if (skipType == SkipInvalidChars) {
2032 m_autoSkipFilesWithInvalidChars = true;
2033 } else if (skipType == SkipFatSymlinks) {
2034 m_autoSkipFatSymlinks = true;
2035 }
2036 Q_FALLTHROUGH();
2037 case KIO::Result_Skip:
2038 // Move on the next file
2039 files.erase(it);
2040 copyNextFile();
2041 return;
2042 default:
2043 break;
2044 }
2045
2046 qCDebug(KIO_COPYJOB_DEBUG) << "preparing to copy" << (*it).uSource << (*it).size << m_freeSpace;
2047 if (m_freeSpace != KIO::invalidFilesize && (*it).size != KIO::invalidFilesize) {
2048 if (m_freeSpace < (*it).size) {
2049 q->setError(ERR_DISK_FULL);
2050 q->emitResult();
2051 return;
2052 }
2053 }
2054
2055 const QUrl &uSource = (*it).uSource;
2056 const QUrl &uDest = (*it).uDest;
2057 // Do we set overwrite ?
2058 bool bOverwrite;
2059 const QString destFile = uDest.path();
2060 qCDebug(KIO_COPYJOB_DEBUG) << "copying" << destFile;
2061 if (uDest == uSource) {
2062 bOverwrite = false;
2063 } else {
2064 bOverwrite = shouldOverwriteFile(destFile);
2065 }
2066
2067 // If source isn't local and target is local, we ignore the original permissions
2068 // Otherwise, files downloaded from HTTP end up with -r--r--r--
2069 int permissions = (*it).permissions;
2070 if (m_defaultPermissions || (m_ignoreSourcePermissions && uDest.isLocalFile())) {
2071 permissions = -1;
2072 }
2073 const JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
2074
2075 m_bCurrentOperationIsLink = false;
2076 KIO::Job *newjob = nullptr;
2077 if (m_mode == CopyJob::Link) {
2078 // User requested that a symlink be made
2079 newjob = linkNextFile(uSource, uDest, flags);
2080 if (!newjob) {
2081 return;
2082 }
2083 } else if (!(*it).linkDest.isEmpty() && compareUrls(uSource, uDest))
2084 // Copying a symlink - only on the same protocol/host/etc. (#5601, downloading an FTP file through its link),
2085 {
2086 KIO::SimpleJob *newJob = KIO::symlink((*it).linkDest, uDest, flags | HideProgressInfo /*no GUI*/);
2087 newJob->setParentJob(q);
2088 newjob = newJob;
2089 qCDebug(KIO_COPYJOB_DEBUG) << "Linking target=" << (*it).linkDest << "link=" << uDest;
2090 m_currentSrcURL = QUrl::fromUserInput((*it).linkDest);
2091 m_currentDestURL = uDest;
2092 m_bURLDirty = true;
2093 // emit linking( this, (*it).linkDest, uDest );
2094 // Observer::self()->slotCopying( this, m_currentSrcURL, uDest ); // should be slotLinking perhaps
2095 m_bCurrentOperationIsLink = true;
2096 // NOTE: if we are moving stuff, the deletion of the source will be done in slotResultCopyingFiles
2097 } else if (m_mode == CopyJob::Move) { // Moving a file
2098 KIO::FileCopyJob *moveJob = KIO::file_move(uSource, uDest, permissions, flags | HideProgressInfo /*no GUI*/);
2099 moveJob->setParentJob(q);
2100 moveJob->setSourceSize((*it).size);
2101 moveJob->setModificationTime((*it).mtime); // #55804
2102 newjob = moveJob;
2103 qCDebug(KIO_COPYJOB_DEBUG) << "Moving" << uSource << "to" << uDest;
2104 // emit moving( this, uSource, uDest );
2105 m_currentSrcURL = uSource;
2106 m_currentDestURL = uDest;
2107 m_bURLDirty = true;
2108 // Observer::self()->slotMoving( this, uSource, uDest );
2109 } else { // Copying a file
2110 KIO::FileCopyJob *copyJob = KIO::file_copy(uSource, uDest, permissions, flags | HideProgressInfo /*no GUI*/);
2111 copyJob->setParentJob(q); // in case of rename dialog
2112 copyJob->setSourceSize((*it).size);
2113 copyJob->setModificationTime((*it).mtime);
2114 newjob = copyJob;
2115 qCDebug(KIO_COPYJOB_DEBUG) << "Copying" << uSource << "to" << uDest;
2116 m_currentSrcURL = uSource;
2117 m_currentDestURL = uDest;
2118 m_bURLDirty = true;
2119 }
2120
2121 // speed is computed locally
2122 QObject::disconnect(newjob, &KJob::speed, q, nullptr);
2123 q->addSubjob(newjob);
2124 q->connect(newjob, &Job::processedSize, q, [this](KJob *job, qulonglong processedSize) {
2125 slotProcessedSize(job, processedSize);
2126 });
2127 q->connect(newjob, &Job::totalSize, q, [this](KJob *job, qulonglong totalSize) {
2128 slotTotalSize(job, totalSize);
2129 });
2130}
2131
2132void CopyJobPrivate::deleteNextDir()
2133{
2134 Q_Q(CopyJob);
2135 if (m_mode == CopyJob::Move && !dirsToRemove.isEmpty()) { // some dirs to delete ?
2136 state = STATE_DELETING_DIRS;
2137 m_bURLDirty = true;
2138 // Take first dir to delete out of list - last ones first !
2139 QList<QUrl>::Iterator it = --dirsToRemove.end();
2140 SimpleJob *job = KIO::rmdir(*it);
2141 job->setParentJob(q);
2142 dirsToRemove.erase(it);
2143 q->addSubjob(job);
2144 } else {
2145 // This step is done, move on
2146 state = STATE_SETTING_DIR_ATTRIBUTES;
2147 m_directoriesCopiedIterator = m_directoriesCopied.cbegin();
2148 setNextDirAttribute();
2149 }
2150}
2151
2152void CopyJobPrivate::setNextDirAttribute()
2153{
2154 Q_Q(CopyJob);
2155 while (m_directoriesCopiedIterator != m_directoriesCopied.cend() && !(*m_directoriesCopiedIterator).mtime.isValid()) {
2156 ++m_directoriesCopiedIterator;
2157 }
2158 if (m_directoriesCopiedIterator != m_directoriesCopied.cend()) {
2159 const QUrl url = (*m_directoriesCopiedIterator).uDest;
2160 const QDateTime dt = (*m_directoriesCopiedIterator).mtime;
2161 ++m_directoriesCopiedIterator;
2162
2164 job->setParentJob(q);
2165 q->addSubjob(job);
2166 } else {
2167 if (m_reportTimer) {
2168 m_reportTimer->stop();
2169 }
2170
2171 q->emitResult();
2172 }
2173}
2174
2175void CopyJob::emitResult()
2176{
2177 Q_D(CopyJob);
2178 // Before we go, tell the world about the changes that were made.
2179 // Even if some error made us abort midway, we might still have done
2180 // part of the job so we better update the views! (#118583)
2181 if (!d->m_bOnlyRenames) {
2182 // If only renaming happened, KDirNotify::FileRenamed was emitted by the rename jobs
2183 QUrl url(d->m_globalDest);
2184 if (d->m_globalDestinationState != DEST_IS_DIR || d->m_asMethod) {
2186 }
2187 qCDebug(KIO_COPYJOB_DEBUG) << "KDirNotify'ing FilesAdded" << url;
2188#ifdef WITH_QTDBUS
2189 org::kde::KDirNotify::emitFilesAdded(url);
2190#endif
2191
2192 if (d->m_mode == CopyJob::Move && !d->m_successSrcList.isEmpty()) {
2193 qCDebug(KIO_COPYJOB_DEBUG) << "KDirNotify'ing FilesRemoved" << d->m_successSrcList;
2194#ifdef WITH_QTDBUS
2195 org::kde::KDirNotify::emitFilesRemoved(d->m_successSrcList);
2196#endif
2197 }
2198 }
2199
2200 // Re-enable watching on the dirs that held the deleted/moved files
2201 if (d->m_mode == CopyJob::Move) {
2202 for (const auto &dir : d->m_parentDirs) {
2204 }
2205 }
2207}
2208
2209void CopyJobPrivate::slotProcessedSize(KJob *, qulonglong data_size)
2210{
2211 Q_Q(CopyJob);
2212 qCDebug(KIO_COPYJOB_DEBUG) << data_size;
2213 m_fileProcessedSize = data_size;
2214
2215 if (m_processedSize + m_fileProcessedSize > m_totalSize) {
2216 // Example: download any attachment from bugs.kde.org
2217 m_totalSize = m_processedSize + m_fileProcessedSize;
2218 qCDebug(KIO_COPYJOB_DEBUG) << "Adjusting m_totalSize to" << m_totalSize;
2219 q->setTotalAmount(KJob::Bytes, m_totalSize); // safety
2220 }
2221
2222 const auto processed = m_fileProcessedSize + m_processedSize;
2223 const auto elapsed = m_speedMeasurementTimer.elapsed();
2224 if (m_speedMeasurementPoints.size() == 0) {
2225 m_speedMeasurementPoints.enqueue({elapsed, processed});
2226 } else {
2227 const auto headMeasurementPoint = m_speedMeasurementPoints.head();
2228 if ((m_speedMeasurementTimer.elapsed() - headMeasurementPoint.elapsedTime) >= 1000) {
2229 if (m_speedMeasurementPoints.size() >= 8) {
2230 m_speedMeasurementPoints.dequeue();
2231 }
2232
2233 q->emitSpeed(1000 * (processed - headMeasurementPoint.processedSize) / (elapsed - headMeasurementPoint.elapsedTime));
2234
2235 m_speedMeasurementPoints.enqueue({elapsed, processed});
2236 }
2237 }
2238
2239 qCDebug(KIO_COPYJOB_DEBUG) << "emit processedSize" << (unsigned long)(m_processedSize + m_fileProcessedSize);
2240}
2241
2242void CopyJobPrivate::slotTotalSize(KJob *, qulonglong size)
2243{
2244 Q_Q(CopyJob);
2245 qCDebug(KIO_COPYJOB_DEBUG) << size;
2246 // Special case for copying a single file
2247 // This is because some protocols don't implement stat properly
2248 // (e.g. HTTP), and don't give us a size in some cases (redirection)
2249 // so we'd rather rely on the size given for the transfer
2250 if (m_bSingleFileCopy && size != m_totalSize) {
2251 qCDebug(KIO_COPYJOB_DEBUG) << "slotTotalSize: updating totalsize to" << size;
2252 m_totalSize = size;
2253 q->setTotalAmount(KJob::Bytes, size);
2254 }
2255}
2256
2257void CopyJobPrivate::slotResultDeletingDirs(KJob *job)
2258{
2259 Q_Q(CopyJob);
2260 if (job->error()) {
2261 // Couldn't remove directory. Well, perhaps it's not empty
2262 // because the user pressed Skip for a given file in it.
2263 // Let's not display "Could not remove dir ..." for each of those dir !
2264 } else {
2265 m_successSrcList.append(static_cast<KIO::SimpleJob *>(job)->url());
2266 }
2267 q->removeSubjob(job);
2268 Q_ASSERT(!q->hasSubjobs());
2269 deleteNextDir();
2270}
2271
2272void CopyJobPrivate::slotResultSettingDirAttributes(KJob *job)
2273{
2274 Q_Q(CopyJob);
2275 if (job->error()) {
2276 // Couldn't set directory attributes. Ignore the error, it can happen
2277 // with inferior file systems like VFAT.
2278 // Let's not display warnings for each dir like "cp -a" does.
2279 }
2280 q->removeSubjob(job);
2281 Q_ASSERT(!q->hasSubjobs());
2282 setNextDirAttribute();
2283}
2284
2285void CopyJobPrivate::directRenamingFailed(const QUrl &dest)
2286{
2287 Q_Q(CopyJob);
2288
2289 qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", reverting to normal way, starting with stat";
2290 qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat on" << m_currentSrcURL;
2291
2292 KIO::Job *job = KIO::stat(m_currentSrcURL, KIO::HideProgressInfo);
2293 state = STATE_STATING;
2294 q->addSubjob(job);
2295 m_bOnlyRenames = false;
2296}
2297
2298// We were trying to do a direct renaming, before even stat'ing
2299void CopyJobPrivate::slotResultRenaming(KJob *job)
2300{
2301 Q_Q(CopyJob);
2302 int err = job->error();
2303 const QString errText = job->errorText();
2304 // Merge metadata from subjob
2305 KIO::Job *kiojob = qobject_cast<KIO::Job *>(job);
2306 Q_ASSERT(kiojob);
2307 m_incomingMetaData += kiojob->metaData();
2308 q->removeSubjob(job);
2309 Q_ASSERT(!q->hasSubjobs());
2310 // Determine dest again
2311 QUrl dest = m_dest;
2312 if (destinationState == DEST_IS_DIR && !m_asMethod) {
2313 dest = addPathToUrl(dest, m_currentSrcURL.fileName());
2314 }
2315 auto *askUserActionInterface = KIO::delegateExtension<KIO::AskUserActionInterface *>(q);
2316
2317 if (err) {
2318 // This code is similar to CopyJobPrivate::slotResultErrorCopyingFiles
2319 // but here it's about the base src url being moved/renamed
2320 // (m_currentSrcURL) and its dest (m_dest), not about a single file.
2321 // It also means we already stated the dest, here.
2322 // On the other hand we haven't stated the src yet (we skipped doing it
2323 // to save time, since it's not necessary to rename directly!)...
2324
2325 // Existing dest?
2326 if (err == ERR_DIR_ALREADY_EXIST || err == ERR_FILE_ALREADY_EXIST || err == ERR_IDENTICAL_FILES) {
2327 // Should we skip automatically ?
2328 bool isDir = (err == ERR_DIR_ALREADY_EXIST); // ## technically, isDir means "source is dir", not "dest is dir" #######
2329 if ((isDir && m_bAutoSkipDirs) || (!isDir && m_bAutoSkipFiles)) {
2330 // Move on to next source url
2331 ++m_filesHandledByDirectRename;
2332 skipSrc(isDir);
2333 return;
2334 } else if ((isDir && m_bOverwriteAllDirs) || (!isDir && m_bOverwriteAllFiles)) {
2335 ; // nothing to do, stat+copy+del will overwrite
2336 } else if ((isDir && m_bAutoRenameDirs) || (!isDir && m_bAutoRenameFiles)) {
2337 QUrl destDirectory = m_currentDestURL.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); // m_currendDestURL includes filename
2338 const QString newName = KFileUtils::suggestName(destDirectory, m_currentDestURL.fileName());
2339
2340 m_dest = destDirectory;
2341 m_dest.setPath(Utils::concatPaths(m_dest.path(), newName));
2342 Q_EMIT q->renamed(q, dest, m_dest);
2343 KIO::Job *job = KIO::stat(m_dest, StatJob::DestinationSide, KIO::StatDefaultDetails, KIO::HideProgressInfo);
2344 state = STATE_STATING;
2345 destinationState = DEST_NOT_STATED;
2346 q->addSubjob(job);
2347 return;
2348 } else if (askUserActionInterface) {
2349 // we lack mtime info for both the src (not stated)
2350 // and the dest (stated but this info wasn't stored)
2351 // Let's do it for local files, at least
2352 KIO::filesize_t sizeSrc = KIO::invalidFilesize;
2353 KIO::filesize_t sizeDest = KIO::invalidFilesize;
2354 QDateTime ctimeSrc;
2355 QDateTime ctimeDest;
2356 QDateTime mtimeSrc;
2357 QDateTime mtimeDest;
2358
2359 bool destIsDir = err == ERR_DIR_ALREADY_EXIST;
2360
2361 // ## TODO we need to stat the source using KIO::stat
2362 // so that this code is properly network-transparent.
2363
2364 if (m_currentSrcURL.isLocalFile()) {
2365 QFileInfo info(m_currentSrcURL.toLocalFile());
2366 if (info.exists()) {
2367 sizeSrc = info.size();
2368 ctimeSrc = info.birthTime();
2369 mtimeSrc = info.lastModified();
2370 isDir = info.isDir();
2371 }
2372 }
2373 if (dest.isLocalFile()) {
2374 QFileInfo destInfo(dest.toLocalFile());
2375 if (destInfo.exists()) {
2376 sizeDest = destInfo.size();
2377 ctimeDest = destInfo.birthTime();
2378 mtimeDest = destInfo.lastModified();
2379 destIsDir = destInfo.isDir();
2380 }
2381 }
2382
2383 // If src==dest, use "overwrite-itself"
2384 RenameDialog_Options options = (m_currentSrcURL == dest) ? RenameDialog_OverwriteItself : RenameDialog_Overwrite;
2385 if (!isDir && destIsDir) {
2386 // We can't overwrite a dir with a file.
2387 options = RenameDialog_Options();
2388 }
2389
2390 if (m_srcList.count() > 1) {
2392 }
2393
2394 if (destIsDir) {
2396 }
2397
2398 if (m_reportTimer) {
2399 m_reportTimer->stop();
2400 }
2401
2403 if (m_bOverwriteWhenOlder && mtimeSrc.isValid() && mtimeDest.isValid()) {
2404 if (mtimeSrc > mtimeDest) {
2405 qCDebug(KIO_COPYJOB_DEBUG) << "dest is older, overwriting" << dest;
2406 r = Result_Overwrite;
2407 } else {
2408 qCDebug(KIO_COPYJOB_DEBUG) << "dest is newer, skipping" << dest;
2409 r = Result_Skip;
2410 }
2411
2412 processDirectRenamingConflictResult(r, isDir, destIsDir, mtimeSrc, mtimeDest, dest, QUrl{});
2413 return;
2414 } else {
2416 QObject::connect(askUserActionInterface, renameSignal, q, [=, this](RenameDialog_Result result, const QUrl &newUrl, KJob *parentJob) {
2417 Q_ASSERT(parentJob == q);
2418 // Only receive askUserRenameResult once per rename dialog
2419 QObject::disconnect(askUserActionInterface, renameSignal, q, nullptr);
2420
2421 processDirectRenamingConflictResult(result, isDir, destIsDir, mtimeSrc, mtimeDest, dest, newUrl);
2422 });
2423
2424 const QString title = err != ERR_DIR_ALREADY_EXIST ? i18n("File Already Exists") : i18n("Already Exists as Folder");
2425
2426 /* clang-format off */
2427 askUserActionInterface->askUserRename(q, title,
2428 m_currentSrcURL, dest,
2429 options,
2430 sizeSrc, sizeDest,
2431 ctimeSrc, ctimeDest,
2432 mtimeSrc, mtimeDest);
2433 /* clang-format on */
2434
2435 return;
2436 }
2437 } else if (err != KIO::ERR_UNSUPPORTED_ACTION) {
2438 // Dest already exists, and job is not interactive -> abort with error
2439 q->setError(err);
2440 q->setErrorText(errText);
2441 q->emitResult();
2442 return;
2443 }
2444 } else if (err != KIO::ERR_UNSUPPORTED_ACTION) {
2445 qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", aborting";
2446 q->setError(err);
2447 q->setErrorText(errText);
2448 q->emitResult();
2449 return;
2450 }
2451
2452 directRenamingFailed(dest);
2453 return;
2454 }
2455
2456 // No error
2457 qCDebug(KIO_COPYJOB_DEBUG) << "Renaming succeeded, move on";
2458 ++m_processedFiles;
2459 ++m_filesHandledByDirectRename;
2460 // Emit copyingDone for FileUndoManager to remember what we did.
2461 // Use resolved URL m_currentSrcURL since that's what we just used for renaming. See bug 391606 and kio_desktop's testTrashAndUndo().
2462 const bool srcIsDir = false; // # TODO: we just don't know, since we never stat'ed it
2463 Q_EMIT q->copyingDone(q, m_currentSrcURL, finalDestUrl(m_currentSrcURL, dest), QDateTime() /*mtime unknown, and not needed*/, srcIsDir, true);
2464 m_successSrcList.append(*m_currentStatSrc);
2465 statNextSrc();
2466}
2467
2468void CopyJobPrivate::processDirectRenamingConflictResult(RenameDialog_Result result,
2469 bool srcIsDir,
2470 bool destIsDir,
2471 const QDateTime &mtimeSrc,
2472 const QDateTime &mtimeDest,
2473 const QUrl &dest,
2474 const QUrl &newUrl)
2475{
2476 Q_Q(CopyJob);
2477
2478 if (m_reportTimer) {
2479 m_reportTimer->start(s_reportTimeout);
2480 }
2481
2482 if (result == Result_OverwriteWhenOlder) {
2483 m_bOverwriteWhenOlder = true;
2484 if (mtimeSrc > mtimeDest) {
2485 qCDebug(KIO_COPYJOB_DEBUG) << "dest is older, overwriting" << dest;
2486 result = Result_Overwrite;
2487 } else {
2488 qCDebug(KIO_COPYJOB_DEBUG) << "dest is newer, skipping" << dest;
2489 result = Result_Skip;
2490 }
2491 }
2492
2493 switch (result) {
2494 case Result_Cancel: {
2495 q->setError(ERR_USER_CANCELED);
2496 q->emitResult();
2497 return;
2498 }
2499 case Result_AutoRename:
2500 if (srcIsDir) {
2501 m_bAutoRenameDirs = true;
2502 } else {
2503 m_bAutoRenameFiles = true;
2504 }
2505 // fall through
2506 Q_FALLTHROUGH();
2507 case Result_Rename: {
2508 // Set m_dest to the chosen destination
2509 // This is only for this src url; the next one will revert to m_globalDest
2510 m_dest = newUrl;
2511 Q_EMIT q->renamed(q, dest, m_dest); // For e.g. KPropertiesDialog
2512 KIO::Job *job = KIO::stat(m_dest, StatJob::DestinationSide, KIO::StatDefaultDetails, KIO::HideProgressInfo);
2513 state = STATE_STATING;
2514 destinationState = DEST_NOT_STATED;
2515 q->addSubjob(job);
2516 return;
2517 }
2518 case Result_AutoSkip:
2519 if (srcIsDir) {
2520 m_bAutoSkipDirs = true;
2521 } else {
2522 m_bAutoSkipFiles = true;
2523 }
2524 // fall through
2525 Q_FALLTHROUGH();
2526 case Result_Skip:
2527 // Move on to next url
2528 ++m_filesHandledByDirectRename;
2529 skipSrc(srcIsDir);
2530 return;
2531 case Result_OverwriteAll:
2532 if (destIsDir) {
2533 m_bOverwriteAllDirs = true;
2534 } else {
2535 m_bOverwriteAllFiles = true;
2536 }
2537 break;
2538 case Result_Overwrite:
2539 // Add to overwrite list
2540 // Note that we add dest, not m_dest.
2541 // This ensures that when moving several urls into a dir (m_dest),
2542 // we only overwrite for the current one, not for all.
2543 // When renaming a single file (m_asMethod), it makes no difference.
2544 qCDebug(KIO_COPYJOB_DEBUG) << "adding to overwrite list: " << dest.path();
2545 m_overwriteList.insert(dest.path());
2546 break;
2547 default:
2548 // Q_ASSERT( 0 );
2549 break;
2550 }
2551
2552 directRenamingFailed(dest);
2553}
2554
2555void CopyJob::slotResult(KJob *job)
2556{
2557 Q_D(CopyJob);
2558 qCDebug(KIO_COPYJOB_DEBUG) << "d->state=" << (int)d->state;
2559 // In each case, what we have to do is :
2560 // 1 - check for errors and treat them
2561 // 2 - removeSubjob(job);
2562 // 3 - decide what to do next
2563
2564 switch (d->state) {
2565 case STATE_STATING: // We were trying to stat a src url or the dest
2566 d->slotResultStating(job);
2567 break;
2568 case STATE_RENAMING: { // We were trying to do a direct renaming, before even stat'ing
2569 d->slotResultRenaming(job);
2570 break;
2571 }
2572 case STATE_LISTING: // recursive listing finished
2573 qCDebug(KIO_COPYJOB_DEBUG) << "totalSize:" << (unsigned int)d->m_totalSize << "files:" << d->files.count() << "d->dirs:" << d->dirs.count();
2574 // Was there an error ?
2575 if (job->error()) {
2576 Job::slotResult(job); // will set the error and emit result(this)
2577 return;
2578 }
2579
2580 removeSubjob(job);
2581 Q_ASSERT(!hasSubjobs());
2582
2583 d->statNextSrc();
2584 break;
2585 case STATE_CREATING_DIRS:
2586 d->slotResultCreatingDirs(job);
2587 break;
2588 case STATE_CONFLICT_CREATING_DIRS:
2589 d->slotResultConflictCreatingDirs(job);
2590 break;
2591 case STATE_COPYING_FILES:
2592 d->slotResultCopyingFiles(job);
2593 break;
2594 case STATE_CONFLICT_COPYING_FILES:
2595 d->slotResultErrorCopyingFiles(job);
2596 break;
2597 case STATE_DELETING_DIRS:
2598 d->slotResultDeletingDirs(job);
2599 break;
2600 case STATE_SETTING_DIR_ATTRIBUTES:
2601 d->slotResultSettingDirAttributes(job);
2602 break;
2603 default:
2604 Q_ASSERT(0);
2605 }
2606}
2607
2609{
2610 d_func()->m_defaultPermissions = b;
2611}
2612
2614{
2615 return d_func()->m_mode;
2616}
2617
2618void KIO::CopyJob::setAutoSkip(bool autoSkip)
2619{
2620 d_func()->m_bAutoSkipFiles = autoSkip;
2621 d_func()->m_bAutoSkipDirs = autoSkip;
2622}
2623
2624void KIO::CopyJob::setAutoRename(bool autoRename)
2625{
2626 d_func()->m_bAutoRenameFiles = autoRename;
2627 d_func()->m_bAutoRenameDirs = autoRename;
2628}
2629
2630void KIO::CopyJob::setWriteIntoExistingDirectories(bool overwriteAll) // #65926
2631{
2632 d_func()->m_bOverwriteAllDirs = overwriteAll;
2633}
2634
2635CopyJob *KIO::copy(const QUrl &src, const QUrl &dest, JobFlags flags)
2636{
2637 qCDebug(KIO_COPYJOB_DEBUG) << "src=" << src << "dest=" << dest;
2638 QList<QUrl> srcList;
2639 srcList.append(src);
2640 return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, false, flags);
2641}
2642
2643CopyJob *KIO::copyAs(const QUrl &src, const QUrl &dest, JobFlags flags)
2644{
2645 qCDebug(KIO_COPYJOB_DEBUG) << "src=" << src << "dest=" << dest;
2646 QList<QUrl> srcList;
2647 srcList.append(src);
2648 return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, true, flags);
2649}
2650
2651CopyJob *KIO::copy(const QList<QUrl> &src, const QUrl &dest, JobFlags flags)
2652{
2653 qCDebug(KIO_COPYJOB_DEBUG) << src << dest;
2654 return CopyJobPrivate::newJob(src, dest, CopyJob::Copy, false, flags);
2655}
2656
2657CopyJob *KIO::move(const QUrl &src, const QUrl &dest, JobFlags flags)
2658{
2659 qCDebug(KIO_COPYJOB_DEBUG) << src << dest;
2660 QList<QUrl> srcList;
2661 srcList.append(src);
2662 CopyJob *job = CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, false, flags);
2663 if (job->uiDelegateExtension()) {
2664 job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent);
2665 }
2666 return job;
2667}
2668
2669CopyJob *KIO::moveAs(const QUrl &src, const QUrl &dest, JobFlags flags)
2670{
2671 qCDebug(KIO_COPYJOB_DEBUG) << src << dest;
2672 QList<QUrl> srcList;
2673 srcList.append(src);
2674 CopyJob *job = CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, true, flags);
2675 if (job->uiDelegateExtension()) {
2676 job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent);
2677 }
2678 return job;
2679}
2680
2681CopyJob *KIO::move(const QList<QUrl> &src, const QUrl &dest, JobFlags flags)
2682{
2683 qCDebug(KIO_COPYJOB_DEBUG) << src << dest;
2684 CopyJob *job = CopyJobPrivate::newJob(src, dest, CopyJob::Move, false, flags);
2685 if (job->uiDelegateExtension()) {
2686 job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent);
2687 }
2688 return job;
2689}
2690
2691CopyJob *KIO::link(const QUrl &src, const QUrl &destDir, JobFlags flags)
2692{
2693 QList<QUrl> srcList;
2694 srcList.append(src);
2695 return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags);
2696}
2697
2698CopyJob *KIO::link(const QList<QUrl> &srcList, const QUrl &destDir, JobFlags flags)
2699{
2700 return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags);
2701}
2702
2703CopyJob *KIO::linkAs(const QUrl &src, const QUrl &destDir, JobFlags flags)
2704{
2705 QList<QUrl> srcList;
2706 srcList.append(src);
2707 return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, true, flags);
2708}
2709
2710CopyJob *KIO::trash(const QUrl &src, JobFlags flags)
2711{
2712 QList<QUrl> srcList;
2713 srcList.append(src);
2714 return CopyJobPrivate::newJob(srcList, QUrl(QStringLiteral("trash:/")), CopyJob::Move, false, flags);
2715}
2716
2718{
2719 return CopyJobPrivate::newJob(srcList, QUrl(QStringLiteral("trash:/")), CopyJob::Move, false, flags);
2720}
2721
2722#include "moc_copyjob.cpp"
bool hasSubjobs() const
virtual void slotResult(KJob *job)
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
void writePathEntry(const char *Key, const QString &path, WriteConfigFlags pFlags=Normal)
bool sync() override
static KFileItem cachedItemForUrl(const QUrl &url)
Return the KFileItem for the given URL, if it was listed recently and it's still in the cache,...
bool stopDirScan(const QString &path)
static KDirWatch * self()
bool restartDirScan(const QString &path)
A KFileItem is a generic class to handle a file, local or remote.
Definition kfileitem.h:36
QUrl mostLocalUrl(bool *local=nullptr) const
Tries to return a local URL for this file item if possible.
bool isNull() const
Return true if default-constructed.
KIO::UDSEntry entry() const
Returns the UDS entry.
void askUserRenameResult(KIO::RenameDialog_Result result, const QUrl &newUrl, KJob *parentJob)
Implementations of this interface must emit this signal when the rename dialog finishes,...
void askUserSkipResult(KIO::SkipDialog_Result result, KJob *parentJob)
Implementations of this interface must emit this signal when the skip dialog finishes,...
CopyJob is used to move, copy or symlink files and directories.
QList< QUrl > srcUrls() const
Returns the list of source URLs.
Definition copyjob.cpp:452
CopyMode
Defines the mode of the operation.
Definition copyjob.h:48
void setDefaultPermissions(bool b)
By default the permissions of the copied files will be those of the source files.
Definition copyjob.cpp:2608
void setWriteIntoExistingDirectories(bool overwriteAllDirs)
Reuse any directory that already exists, instead of the default behavior (interactive mode: showing a...
Definition copyjob.cpp:2630
CopyMode operationMode() const
Returns the mode of the operation (copy, move, or link), depending on whether KIO::copy(),...
Definition copyjob.cpp:2613
bool doResume() override
Reimplemented for internal reasons.
Definition copyjob.cpp:723
void setAutoRename(bool autoRename)
Rename files automatically when the destination already exists, instead of the default behavior (inte...
Definition copyjob.cpp:2624
bool doSuspend() override
Reimplemented for internal reasons.
Definition copyjob.cpp:716
void setAutoSkip(bool autoSkip)
Skip copying or moving any file when the destination already exists, instead of the default behavior ...
Definition copyjob.cpp:2618
QUrl destUrl() const
Returns the destination URL.
Definition copyjob.cpp:457
The FileCopyJob copies data from one place to another.
void setModificationTime(const QDateTime &mtime)
Sets the modification time of the file.
void setSourceSize(KIO::filesize_t size)
If you know the size of the source file, call this method to inform this job.
A KIO job that retrieves the total and available size of a filesystem.
KIO::filesize_t availableSize() const
Get available amount of space.
virtual ClipboardUpdater * createClipboardUpdater(Job *job, ClipboardUpdaterMode mode)
Creates a clipboard updater as a child of the given job.
The base class for all jobs.
QString errorString() const override
Converts an error code and a non-i18n error message into an error message in the current language.
Definition job_error.cpp:26
void setParentJob(Job *parentJob)
Set the parent Job.
Definition job.cpp:192
JobUiDelegateExtension * uiDelegateExtension() const
Retrieves the UI delegate extension used by this job.
Definition job.cpp:44
bool removeSubjob(KJob *job) override
Mark a sub job as being done.
Definition job.cpp:80
bool doSuspend() override
Suspend this job.
Definition job.cpp:168
bool doResume() override
Resume this job.
Definition job.cpp:179
MetaData metaData() const
Get meta data received from the worker.
Definition job.cpp:205
void addMetaData(const QString &key, const QString &value)
Add key/value pair to the meta data that is sent to the worker.
Definition job.cpp:221
A ListJob is allows you to get the get the content of a directory.
void entries(KIO::Job *job, const KIO::UDSEntryList &list)
This signal emits the entry found by the job while listing.
void subError(KIO::ListJob *job, KIO::ListJob *subJob)
This signal is emitted when a sub-directory could not be listed.
void setUnrestricted(bool unrestricted)
Do not apply any KIOSK restrictions to this job.
Definition listjob.cpp:249
A simple job (one url and one command).
const QUrl & url() const
Returns the SimpleJob's URL.
Definition simplejob.cpp:70
A KIO job that retrieves information about a file or directory.
Universal Directory Service.
QString stringValue(uint field) const
Definition udsentry.cpp:365
long long numberValue(uint field, long long defaultValue=0) const
Definition udsentry.cpp:370
bool isLink() const
Definition udsentry.cpp:380
@ UDS_CREATION_TIME
The time the file was created. Required time format: seconds since UNIX epoch.
Definition udsentry.h:238
@ UDS_URL
An alternative URL (If different from the caption).
Definition udsentry.h:251
@ UDS_LINK_DEST
Name of the file where the link points to Allows to check for a symlink (don't use S_ISLNK !...
Definition udsentry.h:245
@ UDS_LOCAL_PATH
A local file path if the KIO worker display files sitting on the local filesystem (but in another hie...
Definition udsentry.h:227
@ UDS_MODIFICATION_TIME
The last time the file was modified. Required time format: seconds since UNIX epoch.
Definition udsentry.h:234
@ UDS_SIZE
Size of the file.
Definition udsentry.h:203
@ UDS_DISPLAY_NAME
If set, contains the label to display instead of the 'real name' in UDS_NAME.
Definition udsentry.h:272
@ UDS_NAME
Filename - as displayed in directory listings etc.
Definition udsentry.h:224
@ UDS_ACCESS
Access permissions (part of the mode returned by stat)
Definition udsentry.h:232
bool contains(uint field) const
check existence of a field
Definition udsentry.cpp:420
bool isDir() const
Definition udsentry.cpp:375
virtual void registerJob(KJob *job)
virtual QString errorString() const
void emitResult()
int error() const
void processedSize(KJob *job, qulonglong size)
void result(KJob *job)
void totalSize(KJob *job, qulonglong size)
QString errorText() const
void setUiDelegate(KJobUiDelegate *delegate)
void speed(KJob *job, unsigned long speed)
static bool canRenameToFile(const QUrl &url)
Returns whether the protocol can rename (i.e.
static bool canRenameFromFile(const QUrl &url)
Returns whether the protocol can rename (i.e.
static bool supportsListing(const QUrl &url)
Returns whether the protocol can list files/objects.
static bool supportsDeleting(const QUrl &url)
Returns whether the protocol can delete files/objects.
static KProtocolInfo::FileNameUsedForCopying fileNameUsedForCopying(const QUrl &url)
This setting defines the strategy to use for generating a filename, when copying a file or directory ...
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
KCOREADDONS_EXPORT Type fileSystemType(const QString &path)
KCOREADDONS_EXPORT QString fileSystemName(KFileSystemType::Type type)
KCOREADDONS_EXPORT QString suggestName(const QUrl &baseURL, const QString &oldName)
A namespace for KIO globals.
KIOCORE_EXPORT DeleteJob * del(const QUrl &src, JobFlags flags=DefaultFlags)
Delete a file or directory.
KIOCORE_EXPORT SimpleJob * rmdir(const QUrl &url)
Removes a single directory.
KIOCORE_EXPORT CopyJob * moveAs(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Moves a file or directory src to the given destination dest.
Definition copyjob.cpp:2669
@ SkipDialog_MultipleItems
Set if the current operation concerns multiple files, so it makes sense to offer buttons that apply t...
@ SkipDialog_Hide_Retry
Set if the current operation cannot be retried.
@ SkipDialog_Replace_Invalid_Chars
Set if the current operation involves copying files/folders with certain characters in their names th...
KIOCORE_EXPORT MkdirJob * mkdir(const QUrl &url, int permissions=-1)
Creates a single directory.
Definition mkdirjob.cpp:110
@ RenameDialog_MultipleItems
Set if the current operation concerns multiple files, so it makes sense to offer buttons that apply t...
@ RenameDialog_DestIsDirectory
The destination is a directory, the dialog updates labels and tooltips accordingly.
@ RenameDialog_OverwriteItself
Warn that the current operation would overwrite a file with itself, which is not allowed.
@ RenameDialog_Overwrite
We have an existing destination, show details about it and offer to overwrite it.
@ RenameDialog_Skip
Offer a "Skip" button, to skip other files too. Requires RenameDialog_MultipleItems.
@ RenameDialog_SourceIsDirectory
The source is a directory, the dialog updates labels and tooltips accordingly.
KIOCORE_EXPORT CopyJob * move(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Moves a file or directory src to the given destination dest.
Definition copyjob.cpp:2657
KIOCORE_EXPORT CopyJob * link(const QUrl &src, const QUrl &destDir, JobFlags flags=DefaultFlags)
Create a link.
Definition copyjob.cpp:2691
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
Find all details for one file or directory.
Definition statjob.cpp:203
KIOCORE_EXPORT QString buildErrorString(int errorCode, const QString &errorText)
Returns a translated error message for errorCode using the additional error information provided by e...
Definition job_error.cpp:31
T delegateExtension(KJob *job)
Returns the child of the job's uiDelegate() that implements the given extension, or nullptr if none w...
KIOCORE_EXPORT CopyJob * copy(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Copy a file or directory src into the destination dest, which can be a file (including the final file...
Definition copyjob.cpp:2635
RenameDialog_Result
The result of a rename or skip dialog.
@ Result_ReplaceAllInvalidChars
The same as Result_ReplaceInvalidChars, but the user selected to automatically replace any invalid ch...
@ Result_OverwriteWhenOlder
Can be returned only when multiple files are passed, Option overwrite is passed And files modificatio...
@ Result_ReplaceInvalidChars
Can be returned if the user selects to replace any character disallowed by the destination filesystem...
KIOCORE_EXPORT KJobUiDelegate * createDefaultJobUiDelegate()
Convenience method: use default factory, if there's one, to create a delegate and return it.
KIOCORE_EXPORT CopyJob * linkAs(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Create a link.
Definition copyjob.cpp:2703
QFlags< RenameDialog_Option > RenameDialog_Options
Stores a combination of RenameDialog_Option values.
KIOCORE_EXPORT CopyJob * trash(const QUrl &src, JobFlags flags=DefaultFlags)
Trash a file or directory.
Definition copyjob.cpp:2710
KIOCORE_EXPORT ListJob * listRecursive(const QUrl &url, JobFlags flags=DefaultFlags, ListJob::ListFlags listFlags=ListJob::ListFlag::IncludeHidden)
The same as the previous method, but recurses subdirectories.
Definition listjob.cpp:244
KIOCORE_EXPORT FileSystemFreeSpaceJob * fileSystemFreeSpace(const QUrl &url)
Get a filesystem's total and available space.
KIOCORE_EXPORT SimpleJob * symlink(const QString &target, const QUrl &dest, JobFlags flags=DefaultFlags)
Create or move a symlink.
KIOCORE_EXPORT SimpleJob * setModificationTime(const QUrl &url, const QDateTime &mtime)
Changes the modification time on a file or directory.
KIOCORE_EXPORT FileCopyJob * file_move(const QUrl &src, const QUrl &dest, int permissions=-1, JobFlags flags=DefaultFlags)
Move a single file.
KIOCORE_EXPORT FileCopyJob * file_copy(const QUrl &src, const QUrl &dest, int permissions=-1, JobFlags flags=DefaultFlags)
Copy a single file.
@ DefaultFlags
Show the progress info GUI, no Resume and no Overwrite.
Definition job_base.h:246
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition job_base.h:251
@ Overwrite
When set, automatically overwrite the destination if it exists already.
Definition job_base.h:267
@ NoPrivilegeExecution
When set, notifies the worker that application/job does not want privilege execution.
Definition job_base.h:276
qulonglong filesize_t
64-bit file size
Definition global.h:35
@ StatBasic
Filename, access, type, size, linkdest.
Definition global.h:255
@ StatDefaultDetails
Default StatDetail flag when creating a StatJob.
Definition global.h:275
@ StatTime
atime, mtime, btime
Definition global.h:259
@ StatResolveSymlink
Resolve symlinks.
Definition global.h:261
@ ERR_CANNOT_MOVE_INTO_ITSELF
emitted by KIO::move,
Definition global.h:195
@ ERR_IDENTICAL_FILES
src==dest when moving/copying
Definition global.h:174
@ ERR_FILE_TOO_LARGE_FOR_FAT32
Definition global.h:198
@ ERR_SYMLINKS_NOT_SUPPORTED
Indicates failure to create a symlink due to the underlying filesystem (FAT/ExFAT) not supporting the...
Definition global.h:208
KIOCORE_EXPORT KJobTrackerInterface * getJobTracker()
Returns the job tracker to be used by all KIO jobs (in which HideProgressInfo is not set)
KIOCORE_EXPORT CopyJob * copyAs(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Copy a file or directory src into the destination dest, which is the destination name in any case,...
Definition copyjob.cpp:2643
KIOCORE_EXPORT QString encodeFileName(const QString &str)
Encodes (from the text displayed to the real filename) This translates '/' into a "unicode fraction s...
Definition global.cpp:111
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
Returns the most recently used directory associated with this file-class.
KIOCORE_EXPORT QStringList list(const QString &fileClass)
Returns a list of directories associated with this file-class.
QString name(StandardAction id)
const QList< QKeySequence > & end()
QCA_EXPORT void setProperty(const QString &name, const QVariant &value)
void truncate(qsizetype pos)
QDateTime fromSecsSinceEpoch(qint64 secs)
bool isValid() const const
qint64 elapsed() const const
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
void append(QList< T > &&value)
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
iterator end()
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
qsizetype removeAll(const AT &t)
qsizetype size() const const
ConstIterator
const_iterator constEnd() const const
iterator find(const Key &key)
T value(const Key &key, const T &defaultValue) const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
T dequeue()
void enqueue(const T &t)
T & head()
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
qsizetype count() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QueuedConnection
void start()
void stop()
void timeout()
FullyDecoded
RemoveFilename
QUrl adjusted(FormattingOptions options) const const
QString fileName(ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
QUrl fromUserInput(const QString &userInput, const QString &workingDirectory, UserInputResolutionOptions options)
QString host(ComponentFormattingOptions options) const const
bool isEmpty() const const
bool isLocalFile() const const
QString password(ComponentFormattingOptions options) const const
QString path(ComponentFormattingOptions options) const const
int port(int defaultPort) const const
QString scheme() const const
void setPassword(const QString &password, ParsingMode mode)
void setPath(const QString &path, ParsingMode mode)
QString toDisplayString(FormattingOptions options) const const
QString toLocalFile() const const
QString toString(FormattingOptions options) const const
QString url(FormattingOptions options) const const
QString userName(ComponentFormattingOptions options) const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 13 2024 11:53:33 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.