KIO

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

KDE's Doxygen guidelines are available online.