• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.8.5 API Reference
  • KDE Home
  • Contact Us
 

KIO

  • kio
  • kio
copyjob.cpp
Go to the documentation of this file.
1 /* This file is part of the KDE libraries
2  Copyright 2000 Stephan Kulow <coolo@kde.org>
3  Copyright 2000-2006 David Faure <faure@kde.org>
4  Copyright 2000 Waldo Bastian <bastian@kde.org>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License as published by the Free Software Foundation; either
9  version 2 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Library General Public License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to
18  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  Boston, MA 02110-1301, USA.
20 */
21 
22 #include "copyjob.h"
23 #include <errno.h>
24 #include "kdirlister.h"
25 #include "kfileitem.h"
26 #include "deletejob.h"
27 
28 #include <klocale.h>
29 #include <kdesktopfile.h>
30 #include <kdebug.h>
31 #include <kde_file.h>
32 
33 #include "slave.h"
34 #include "scheduler.h"
35 #include "kdirwatch.h"
36 #include "kprotocolmanager.h"
37 
38 #include "jobuidelegate.h"
39 
40 #include <kdirnotify.h>
41 #include <ktemporaryfile.h>
42 
43 #ifdef Q_OS_UNIX
44 #include <utime.h>
45 #endif
46 #include <assert.h>
47 
48 #include <QtCore/QTimer>
49 #include <QtCore/QFile>
50 #include <sys/stat.h> // mode_t
51 #include <QPointer>
52 
53 #include "job_p.h"
54 #include <kdiskfreespaceinfo.h>
55 #include <kfilesystemtype_p.h>
56 
57 using namespace KIO;
58 
59 //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
60 #define REPORT_TIMEOUT 200
61 
62 enum DestinationState {
63  DEST_NOT_STATED,
64  DEST_IS_DIR,
65  DEST_IS_FILE,
66  DEST_DOESNT_EXIST
67 };
68 
85 enum CopyJobState {
86  STATE_STATING,
87  STATE_RENAMING,
88  STATE_LISTING,
89  STATE_CREATING_DIRS,
90  STATE_CONFLICT_CREATING_DIRS,
91  STATE_COPYING_FILES,
92  STATE_CONFLICT_COPYING_FILES,
93  STATE_DELETING_DIRS,
94  STATE_SETTING_DIR_ATTRIBUTES
95 };
96 
98 class KIO::CopyJobPrivate: public KIO::JobPrivate
99 {
100 public:
101  CopyJobPrivate(const KUrl::List& src, const KUrl& dest,
102  CopyJob::CopyMode mode, bool asMethod)
103  : m_globalDest(dest)
104  , m_globalDestinationState(DEST_NOT_STATED)
105  , m_defaultPermissions(false)
106  , m_bURLDirty(false)
107  , m_mode(mode)
108  , m_asMethod(asMethod)
109  , destinationState(DEST_NOT_STATED)
110  , state(STATE_STATING)
111  , m_freeSpace(-1)
112  , m_totalSize(0)
113  , m_processedSize(0)
114  , m_fileProcessedSize(0)
115  , m_processedFiles(0)
116  , m_processedDirs(0)
117  , m_srcList(src)
118  , m_currentStatSrc(m_srcList.constBegin())
119  , m_bCurrentOperationIsLink(false)
120  , m_bSingleFileCopy(false)
121  , m_bOnlyRenames(mode==CopyJob::Move)
122  , m_dest(dest)
123  , m_bAutoRenameFiles(false)
124  , m_bAutoRenameDirs(false)
125  , m_bAutoSkipFiles( false )
126  , m_bAutoSkipDirs( false )
127  , m_bOverwriteAllFiles( false )
128  , m_bOverwriteAllDirs( false )
129  , m_conflictError(0)
130  , m_reportTimer(0)
131  {
132  }
133 
134  // This is the dest URL that was initially given to CopyJob
135  // It is copied into m_dest, which can be changed for a given src URL
136  // (when using the RENAME dialog in slotResult),
137  // and which will be reset for the next src URL.
138  KUrl m_globalDest;
139  // The state info about that global dest
140  DestinationState m_globalDestinationState;
141  // See setDefaultPermissions
142  bool m_defaultPermissions;
143  // Whether URLs changed (and need to be emitted by the next slotReport call)
144  bool m_bURLDirty;
145  // Used after copying all the files into the dirs, to set mtime (TODO: and permissions?)
146  // after the copy is done
147  QLinkedList<CopyInfo> m_directoriesCopied;
148  QLinkedList<CopyInfo>::const_iterator m_directoriesCopiedIterator;
149 
150  CopyJob::CopyMode m_mode;
151  bool m_asMethod;
152  DestinationState destinationState;
153  CopyJobState state;
154 
155  KIO::filesize_t m_freeSpace;
156 
157  KIO::filesize_t m_totalSize;
158  KIO::filesize_t m_processedSize;
159  KIO::filesize_t m_fileProcessedSize;
160  int m_processedFiles;
161  int m_processedDirs;
162  QList<CopyInfo> files;
163  QList<CopyInfo> dirs;
164  KUrl::List dirsToRemove;
165  KUrl::List m_srcList;
166  KUrl::List m_successSrcList; // Entries in m_srcList that have successfully been moved
167  KUrl::List::const_iterator m_currentStatSrc;
168  bool m_bCurrentSrcIsDir;
169  bool m_bCurrentOperationIsLink;
170  bool m_bSingleFileCopy;
171  bool m_bOnlyRenames;
172  KUrl m_dest;
173  KUrl m_currentDest; // set during listing, used by slotEntries
174  //
175  QStringList m_skipList;
176  QSet<QString> m_overwriteList;
177  bool m_bAutoRenameFiles;
178  bool m_bAutoRenameDirs;
179  bool m_bAutoSkipFiles;
180  bool m_bAutoSkipDirs;
181  bool m_bOverwriteAllFiles;
182  bool m_bOverwriteAllDirs;
183  int m_conflictError;
184 
185  QTimer *m_reportTimer;
186 
187  // The current src url being stat'ed or copied
188  // During the stat phase, this is initially equal to *m_currentStatSrc but it can be resolved to a local file equivalent (#188903).
189  KUrl m_currentSrcURL;
190  KUrl m_currentDestURL;
191 
192  QSet<QString> m_parentDirs;
193 
194  void statCurrentSrc();
195  void statNextSrc();
196 
197  // Those aren't slots but submethods for slotResult.
198  void slotResultStating( KJob * job );
199  void startListing( const KUrl & src );
200  void slotResultCreatingDirs( KJob * job );
201  void slotResultConflictCreatingDirs( KJob * job );
202  void createNextDir();
203  void slotResultCopyingFiles( KJob * job );
204  void slotResultConflictCopyingFiles( KJob * job );
205 // KIO::Job* linkNextFile( const KUrl& uSource, const KUrl& uDest, bool overwrite );
206  KIO::Job* linkNextFile( const KUrl& uSource, const KUrl& uDest, JobFlags flags );
207  void copyNextFile();
208  void slotResultDeletingDirs( KJob * job );
209  void deleteNextDir();
210  void sourceStated(const UDSEntry& entry, const KUrl& sourceUrl);
211  void skip(const KUrl & sourceURL, bool isDir);
212  void slotResultRenaming( KJob * job );
213  void slotResultSettingDirAttributes( KJob * job );
214  void setNextDirAttribute();
215 
216  void startRenameJob(const KUrl &slave_url);
217  bool shouldOverwriteDir( const QString& path ) const;
218  bool shouldOverwriteFile( const QString& path ) const;
219  bool shouldSkip( const QString& path ) const;
220  void skipSrc(bool isDir);
221 
222  void slotStart();
223  void slotEntries( KIO::Job*, const KIO::UDSEntryList& list );
224  void addCopyInfoFromUDSEntry(const UDSEntry& entry, const KUrl& srcUrl, bool srcIsDir, const KUrl& currentDest);
228  void slotProcessedSize( KJob*, qulonglong data_size );
233  void slotTotalSize( KJob*, qulonglong size );
234 
235  void slotReport();
236 
237  Q_DECLARE_PUBLIC(CopyJob)
238 
239  static inline CopyJob *newJob(const KUrl::List& src, const KUrl& dest,
240  CopyJob::CopyMode mode, bool asMethod, JobFlags flags)
241  {
242  CopyJob *job = new CopyJob(*new CopyJobPrivate(src,dest,mode,asMethod));
243  job->setUiDelegate(new JobUiDelegate);
244  if (!(flags & HideProgressInfo))
245  KIO::getJobTracker()->registerJob(job);
246  if (flags & KIO::Overwrite) {
247  job->d_func()->m_bOverwriteAllDirs = true;
248  job->d_func()->m_bOverwriteAllFiles = true;
249  }
250  return job;
251  }
252 };
253 
254 CopyJob::CopyJob(CopyJobPrivate &dd)
255  : Job(dd)
256 {
257  setProperty("destUrl", d_func()->m_dest.url());
258  QTimer::singleShot(0, this, SLOT(slotStart()));
259 }
260 
261 CopyJob::~CopyJob()
262 {
263 }
264 
265 KUrl::List CopyJob::srcUrls() const
266 {
267  return d_func()->m_srcList;
268 }
269 
270 KUrl CopyJob::destUrl() const
271 {
272  return d_func()->m_dest;
273 }
274 
275 void CopyJobPrivate::slotStart()
276 {
277  Q_Q(CopyJob);
283  m_reportTimer = new QTimer(q);
284 
285  q->connect(m_reportTimer,SIGNAL(timeout()),q,SLOT(slotReport()));
286  m_reportTimer->start(REPORT_TIMEOUT);
287 
288  // Stat the dest
289  KIO::Job * job = KIO::stat( m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo );
290  //kDebug(7007) << "CopyJob:stating the dest " << m_dest;
291  q->addSubjob(job);
292 }
293 
294 // For unit test purposes
295 KIO_EXPORT bool kio_resolve_local_urls = true;
296 
297 void CopyJobPrivate::slotResultStating( KJob *job )
298 {
299  Q_Q(CopyJob);
300  //kDebug(7007);
301  // Was there an error while stating the src ?
302  if (job->error() && destinationState != DEST_NOT_STATED )
303  {
304  const KUrl srcurl = static_cast<SimpleJob*>(job)->url();
305  if ( !srcurl.isLocalFile() )
306  {
307  // Probably : src doesn't exist. Well, over some protocols (e.g. FTP)
308  // this info isn't really reliable (thanks to MS FTP servers).
309  // We'll assume a file, and try to download anyway.
310  kDebug(7007) << "Error while stating source. Activating hack";
311  q->removeSubjob( job );
312  assert ( !q->hasSubjobs() ); // We should have only one job at a time ...
313  struct CopyInfo info;
314  info.permissions = (mode_t) -1;
315  info.mtime = (time_t) -1;
316  info.ctime = (time_t) -1;
317  info.size = (KIO::filesize_t)-1;
318  info.uSource = srcurl;
319  info.uDest = m_dest;
320  // Append filename or dirname to destination URL, if allowed
321  if ( destinationState == DEST_IS_DIR && !m_asMethod )
322  info.uDest.addPath( srcurl.fileName() );
323 
324  files.append( info );
325  statNextSrc();
326  return;
327  }
328  // Local file. If stat fails, the file definitely doesn't exist.
329  // yes, q->Job::, because we don't want to call our override
330  q->Job::slotResult( job ); // will set the error and emit result(this)
331  return;
332  }
333 
334  // Keep copy of the stat result
335  const UDSEntry entry = static_cast<StatJob*>(job)->statResult();
336 
337  if ( destinationState == DEST_NOT_STATED ) {
338  if ( m_dest.isLocalFile() ) { //works for dirs as well
339  QString path = m_dest.toLocalFile();
340  if (m_asMethod) {
341  // In copy-as mode, we want to check the directory to which we're
342  // copying. The target file or directory does not exist yet, which
343  // might confuse KDiskFreeSpaceInfo.
344  path = QFileInfo(path).absolutePath();
345  }
346  KFileSystemType::Type fsType = KFileSystemType::fileSystemType( path );
347  if ( fsType != KFileSystemType::Nfs && fsType != KFileSystemType::Smb ) {
348  m_freeSpace = KDiskFreeSpaceInfo::freeSpaceInfo( path ).available();
349  }
350  //TODO actually preliminary check is even more valuable for slow NFS/SMB mounts,
351  //but we need to find a way to report connection errors to user
352  }
353 
354  const bool isGlobalDest = m_dest == m_globalDest;
355  const bool isDir = entry.isDir();
356  // we were stating the dest
357  if (job->error()) {
358  destinationState = DEST_DOESNT_EXIST;
359  //kDebug(7007) << "dest does not exist";
360  } else {
361  // Treat symlinks to dirs as dirs here, so no test on isLink
362  destinationState = isDir ? DEST_IS_DIR : DEST_IS_FILE;
363  //kDebug(7007) << "dest is dir:" << isDir;
364 
365  const QString sLocalPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH );
366  if ( !sLocalPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST ) {
367  m_dest = KUrl();
368  m_dest.setPath(sLocalPath);
369  if ( isGlobalDest )
370  m_globalDest = m_dest;
371  }
372  }
373  if ( isGlobalDest )
374  m_globalDestinationState = destinationState;
375 
376  q->removeSubjob( job );
377  assert ( !q->hasSubjobs() );
378 
379  // After knowing what the dest is, we can start stat'ing the first src.
380  statCurrentSrc();
381  } else {
382  sourceStated(entry, static_cast<SimpleJob*>(job)->url());
383  q->removeSubjob( job );
384  }
385 }
386 
387 void CopyJobPrivate::sourceStated(const UDSEntry& entry, const KUrl& sourceUrl)
388 {
389  const QString sLocalPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH );
390  const bool isDir = entry.isDir();
391 
392  // We were stating the current source URL
393  // Is it a file or a dir ?
394 
395  // There 6 cases, and all end up calling addCopyInfoFromUDSEntry first :
396  // 1 - src is a dir, destination is a directory,
397  // slotEntries will append the source-dir-name to the destination
398  // 2 - src is a dir, destination is a file -- will offer to overwrite, later on.
399  // 3 - src is a dir, destination doesn't exist, then it's the destination dirname,
400  // so slotEntries will use it as destination.
401 
402  // 4 - src is a file, destination is a directory,
403  // slotEntries will append the filename to the destination.
404  // 5 - src is a file, destination is a file, m_dest is the exact destination name
405  // 6 - src is a file, destination doesn't exist, m_dest is the exact destination name
406 
407  KUrl srcurl;
408  if (!sLocalPath.isEmpty() && destinationState != DEST_DOESNT_EXIST) {
409  kDebug() << "Using sLocalPath. destinationState=" << destinationState;
410  // Prefer the local path -- but only if we were able to stat() the dest.
411  // Otherwise, renaming a desktop:/ url would copy from src=file to dest=desktop (#218719)
412  srcurl.setPath(sLocalPath);
413  } else {
414  srcurl = sourceUrl;
415  }
416  addCopyInfoFromUDSEntry(entry, srcurl, false, m_dest);
417 
418  m_currentDest = m_dest;
419  m_bCurrentSrcIsDir = false;
420 
421  if ( isDir
422  // treat symlinks as files (no recursion)
423  && !entry.isLink()
424  && m_mode != CopyJob::Link ) // No recursion in Link mode either.
425  {
426  //kDebug(7007) << "Source is a directory";
427 
428  if (srcurl.isLocalFile()) {
429  const QString parentDir = srcurl.toLocalFile(KUrl::RemoveTrailingSlash);
430  m_parentDirs.insert(parentDir);
431  }
432 
433  m_bCurrentSrcIsDir = true; // used by slotEntries
434  if ( destinationState == DEST_IS_DIR ) // (case 1)
435  {
436  if ( !m_asMethod )
437  {
438  // Use <desturl>/<directory_copied> as destination, from now on
439  QString directory = srcurl.fileName();
440  const QString sName = entry.stringValue( KIO::UDSEntry::UDS_NAME );
441  KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(srcurl);
442  if (fnu == KProtocolInfo::Name) {
443  if (!sName.isEmpty())
444  directory = sName;
445  } else if (fnu == KProtocolInfo::DisplayName) {
446  const QString dispName = entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME );
447  if (!dispName.isEmpty())
448  directory = dispName;
449  else if (!sName.isEmpty())
450  directory = sName;
451  }
452  m_currentDest.addPath( directory );
453  }
454  }
455  else // (case 3)
456  {
457  // otherwise dest is new name for toplevel dir
458  // so the destination exists, in fact, from now on.
459  // (This even works with other src urls in the list, since the
460  // dir has effectively been created)
461  destinationState = DEST_IS_DIR;
462  if ( m_dest == m_globalDest )
463  m_globalDestinationState = destinationState;
464  }
465 
466  startListing( srcurl );
467  }
468  else
469  {
470  //kDebug(7007) << "Source is a file (or a symlink), or we are linking -> no recursive listing";
471 
472  if (srcurl.isLocalFile()) {
473  const QString parentDir = srcurl.directory(KUrl::ObeyTrailingSlash);
474  m_parentDirs.insert(parentDir);
475  }
476 
477  statNextSrc();
478  }
479 }
480 
481 bool CopyJob::doSuspend()
482 {
483  Q_D(CopyJob);
484  d->slotReport();
485  return Job::doSuspend();
486 }
487 
488 void CopyJobPrivate::slotReport()
489 {
490  Q_Q(CopyJob);
491  if ( q->isSuspended() )
492  return;
493  // If showProgressInfo was set, progressId() is > 0.
494  switch (state) {
495  case STATE_RENAMING:
496  q->setTotalAmount(KJob::Files, m_srcList.count());
497  // fall-through intended
498  case STATE_COPYING_FILES:
499  q->setProcessedAmount( KJob::Files, m_processedFiles );
500  if (m_bURLDirty)
501  {
502  // Only emit urls when they changed. This saves time, and fixes #66281
503  m_bURLDirty = false;
504  if (m_mode==CopyJob::Move)
505  {
506  emitMoving(q, m_currentSrcURL, m_currentDestURL);
507  emit q->moving( q, m_currentSrcURL, m_currentDestURL);
508  }
509  else if (m_mode==CopyJob::Link)
510  {
511  emitCopying( q, m_currentSrcURL, m_currentDestURL ); // we don't have a delegate->linking
512  emit q->linking( q, m_currentSrcURL.path(), m_currentDestURL );
513  }
514  else
515  {
516  emitCopying( q, m_currentSrcURL, m_currentDestURL );
517  emit q->copying( q, m_currentSrcURL, m_currentDestURL );
518  }
519  }
520  break;
521 
522  case STATE_CREATING_DIRS:
523  q->setProcessedAmount( KJob::Directories, m_processedDirs );
524  if (m_bURLDirty)
525  {
526  m_bURLDirty = false;
527  emit q->creatingDir( q, m_currentDestURL );
528  emitCreatingDir( q, m_currentDestURL );
529  }
530  break;
531 
532  case STATE_STATING:
533  case STATE_LISTING:
534  if (m_bURLDirty)
535  {
536  m_bURLDirty = false;
537  if (m_mode==CopyJob::Move)
538  {
539  emitMoving( q, m_currentSrcURL, m_currentDestURL );
540  }
541  else
542  {
543  emitCopying( q, m_currentSrcURL, m_currentDestURL );
544  }
545  }
546  q->setTotalAmount(KJob::Bytes, m_totalSize);
547  q->setTotalAmount(KJob::Files, files.count());
548  q->setTotalAmount(KJob::Directories, dirs.count());
549  break;
550 
551  default:
552  break;
553  }
554 }
555 
556 void CopyJobPrivate::slotEntries(KIO::Job* job, const UDSEntryList& list)
557 {
558  //Q_Q(CopyJob);
559  UDSEntryList::ConstIterator it = list.constBegin();
560  UDSEntryList::ConstIterator end = list.constEnd();
561  for (; it != end; ++it) {
562  const UDSEntry& entry = *it;
563  addCopyInfoFromUDSEntry(entry, static_cast<SimpleJob *>(job)->url(), m_bCurrentSrcIsDir, m_currentDest);
564  }
565 }
566 
567 void CopyJobPrivate::addCopyInfoFromUDSEntry(const UDSEntry& entry, const KUrl& srcUrl, bool srcIsDir, const KUrl& currentDest)
568 {
569  struct CopyInfo info;
570  info.permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, -1);
571  info.mtime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
572  info.ctime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
573  info.size = (KIO::filesize_t) entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1);
574  if (info.size != (KIO::filesize_t) -1)
575  m_totalSize += info.size;
576 
577  // recursive listing, displayName can be a/b/c/d
578  const QString fileName = entry.stringValue(KIO::UDSEntry::UDS_NAME);
579  const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL);
580  KUrl url;
581  if (!urlStr.isEmpty())
582  url = urlStr;
583  QString localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
584  const bool isDir = entry.isDir();
585  info.linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
586 
587  if (fileName != QLatin1String("..") && fileName != QLatin1String(".")) {
588  const bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty();
589  if (!hasCustomURL) {
590  // Make URL from displayName
591  url = srcUrl;
592  if (srcIsDir) { // Only if src is a directory. Otherwise uSource is fine as is
593  //kDebug(7007) << "adding path" << displayName;
594  url.addPath(fileName);
595  }
596  }
597  //kDebug(7007) << "displayName=" << displayName << "url=" << url;
598  if (!localPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST) {
599  url = KUrl(localPath);
600  }
601 
602  info.uSource = url;
603  info.uDest = currentDest;
604  //kDebug(7007) << "uSource=" << info.uSource << "uDest(1)=" << info.uDest;
605  // Append filename or dirname to destination URL, if allowed
606  if (destinationState == DEST_IS_DIR &&
607  // "copy/move as <foo>" means 'foo' is the dest for the base srcurl
608  // (passed here during stating) but not its children (during listing)
609  (! (m_asMethod && state == STATE_STATING)))
610  {
611  QString destFileName;
612  KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(url);
613  if (hasCustomURL &&
614  fnu == KProtocolInfo::FromUrl) {
615  //destFileName = url.fileName(); // Doesn't work for recursive listing
616  // Count the number of prefixes used by the recursive listjob
617  int numberOfSlashes = fileName.count('/'); // don't make this a find()!
618  QString path = url.path();
619  int pos = 0;
620  for (int n = 0; n < numberOfSlashes + 1; ++n) {
621  pos = path.lastIndexOf('/', pos - 1);
622  if (pos == -1) { // error
623  kWarning(7007) << "kioslave bug: not enough slashes in UDS_URL" << path << "- looking for" << numberOfSlashes << "slashes";
624  break;
625  }
626  }
627  if (pos >= 0) {
628  destFileName = path.mid(pos + 1);
629  }
630 
631  } else if ( fnu == KProtocolInfo::Name ) { // destination filename taken from UDS_NAME
632  destFileName = fileName;
633  } else { // from display name (with fallback to name)
634  const QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
635  destFileName = displayName.isEmpty() ? fileName : displayName;
636  }
637 
638  // Here we _really_ have to add some filename to the dest.
639  // Otherwise, we end up with e.g. dest=..../Desktop/ itself.
640  // (This can happen when dropping a link to a webpage with no path)
641  if (destFileName.isEmpty()) {
642  destFileName = KIO::encodeFileName(info.uSource.prettyUrl());
643  }
644 
645  //kDebug(7007) << " adding destFileName=" << destFileName;
646  info.uDest.addPath(destFileName);
647  }
648  //kDebug(7007) << " uDest(2)=" << info.uDest;
649  //kDebug(7007) << " " << info.uSource << "->" << info.uDest;
650  if (info.linkDest.isEmpty() && isDir && m_mode != CopyJob::Link) { // Dir
651  dirs.append(info); // Directories
652  if (m_mode == CopyJob::Move) {
653  dirsToRemove.append(info.uSource);
654  }
655  } else {
656  files.append(info); // Files and any symlinks
657  }
658  }
659 }
660 
661 void CopyJobPrivate::skipSrc(bool isDir)
662 {
663  m_dest = m_globalDest;
664  destinationState = m_globalDestinationState;
665  skip(*m_currentStatSrc, isDir);
666  ++m_currentStatSrc;
667  statCurrentSrc();
668 }
669 
670 void CopyJobPrivate::statNextSrc()
671 {
672  /* Revert to the global destination, the one that applies to all source urls.
673  * 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.
674  * d->m_dest is /foo/b for b, but we have to revert to /d for item c and following.
675  */
676  m_dest = m_globalDest;
677  destinationState = m_globalDestinationState;
678  ++m_currentStatSrc;
679  statCurrentSrc();
680 }
681 
682 void CopyJobPrivate::statCurrentSrc()
683 {
684  Q_Q(CopyJob);
685  if (m_currentStatSrc != m_srcList.constEnd()) {
686  m_currentSrcURL = (*m_currentStatSrc);
687  m_bURLDirty = true;
688  if (m_mode == CopyJob::Link) {
689  // Skip the "stating the source" stage, we don't need it for linking
690  m_currentDest = m_dest;
691  struct CopyInfo info;
692  info.permissions = -1;
693  info.mtime = (time_t) -1;
694  info.ctime = (time_t) -1;
695  info.size = (KIO::filesize_t)-1;
696  info.uSource = m_currentSrcURL;
697  info.uDest = m_currentDest;
698  // Append filename or dirname to destination URL, if allowed
699  if (destinationState == DEST_IS_DIR && !m_asMethod) {
700  if (
701  (m_currentSrcURL.protocol() == info.uDest.protocol()) &&
702  (m_currentSrcURL.host() == info.uDest.host()) &&
703  (m_currentSrcURL.port() == info.uDest.port()) &&
704  (m_currentSrcURL.user() == info.uDest.user()) &&
705  (m_currentSrcURL.pass() == info.uDest.pass()) ) {
706  // This is the case of creating a real symlink
707  info.uDest.addPath( m_currentSrcURL.fileName() );
708  } else {
709  // Different protocols, we'll create a .desktop file
710  // We have to change the extension anyway, so while we're at it,
711  // name the file like the URL
712  info.uDest.addPath(KIO::encodeFileName(m_currentSrcURL.prettyUrl()) + ".desktop");
713  }
714  }
715  files.append( info ); // Files and any symlinks
716  statNextSrc(); // we could use a loop instead of a recursive call :)
717  return;
718  }
719 
720  // Let's see if we can skip stat'ing, for the case where a directory view has the info already
721  const KFileItem cachedItem = KDirLister::cachedItemForUrl(m_currentSrcURL);
722  KIO::UDSEntry entry;
723  if (!cachedItem.isNull()) {
724  entry = cachedItem.entry();
725  if (destinationState != DEST_DOESNT_EXIST) { // only resolve src if we could resolve dest (#218719)
726  bool dummyIsLocal;
727  m_currentSrcURL = cachedItem.mostLocalUrl(dummyIsLocal); // #183585
728  }
729  }
730 
731  if (m_mode == CopyJob::Move && (
732  // Don't go renaming right away if we need a stat() to find out the destination filename
733  KProtocolManager::fileNameUsedForCopying(m_currentSrcURL) == KProtocolInfo::FromUrl ||
734  destinationState != DEST_IS_DIR || m_asMethod)
735  ) {
736  // If moving, before going for the full stat+[list+]copy+del thing, try to rename
737  // The logic is pretty similar to FileCopyJobPrivate::slotStart()
738  if ( (m_currentSrcURL.protocol() == m_dest.protocol()) &&
739  (m_currentSrcURL.host() == m_dest.host()) &&
740  (m_currentSrcURL.port() == m_dest.port()) &&
741  (m_currentSrcURL.user() == m_dest.user()) &&
742  (m_currentSrcURL.pass() == m_dest.pass()) )
743  {
744  startRenameJob( m_currentSrcURL );
745  return;
746  }
747  else if ( m_currentSrcURL.isLocalFile() && KProtocolManager::canRenameFromFile( m_dest ) )
748  {
749  startRenameJob( m_dest );
750  return;
751  }
752  else if ( m_dest.isLocalFile() && KProtocolManager::canRenameToFile( m_currentSrcURL ) )
753  {
754  startRenameJob( m_currentSrcURL );
755  return;
756  }
757  }
758 
759  // if the file system doesn't support deleting, we do not even stat
760  if (m_mode == CopyJob::Move && !KProtocolManager::supportsDeleting(m_currentSrcURL)) {
761  QPointer<CopyJob> that = q;
762  emit q->warning( q, buildErrorString(ERR_CANNOT_DELETE, m_currentSrcURL.prettyUrl()) );
763  if (that)
764  statNextSrc(); // we could use a loop instead of a recursive call :)
765  return;
766  }
767 
768  m_bOnlyRenames = false;
769 
770  // Testing for entry.count()>0 here is not good enough; KFileItem inserts
771  // entries for UDS_USER and UDS_GROUP even on initially empty UDSEntries (#192185)
772  if (entry.contains(KIO::UDSEntry::UDS_NAME)) {
773  kDebug(7007) << "fast path! found info about" << m_currentSrcURL << "in KDirLister";
774  sourceStated(entry, m_currentSrcURL);
775  return;
776  }
777 
778  // Stat the next src url
779  Job * job = KIO::stat( m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo );
780  //kDebug(7007) << "KIO::stat on" << m_currentSrcURL;
781  state = STATE_STATING;
782  q->addSubjob(job);
783  m_currentDestURL = m_dest;
784  m_bURLDirty = true;
785  }
786  else
787  {
788  // Finished the stat'ing phase
789  // First make sure that the totals were correctly emitted
790  state = STATE_STATING;
791  m_bURLDirty = true;
792  slotReport();
793 
794  kDebug(7007)<<"Stating finished. To copy:"<<m_totalSize<<", available:"<<m_freeSpace;
795  //TODO warn user beforehand if space is not enough
796 
797  if (!dirs.isEmpty())
798  emit q->aboutToCreate( q, dirs );
799  if (!files.isEmpty())
800  emit q->aboutToCreate( q, files );
801  // Check if we are copying a single file
802  m_bSingleFileCopy = ( files.count() == 1 && dirs.isEmpty() );
803  // Then start copying things
804  state = STATE_CREATING_DIRS;
805  createNextDir();
806  }
807 }
808 
809 void CopyJobPrivate::startRenameJob( const KUrl& slave_url )
810 {
811  Q_Q(CopyJob);
812 
813  // Silence KDirWatch notifications, otherwise performance is horrible
814  if (m_currentSrcURL.isLocalFile()) {
815  const QString parentDir = m_currentSrcURL.directory(KUrl::ObeyTrailingSlash);
816  if (!m_parentDirs.contains(parentDir)) {
817  KDirWatch::self()->stopDirScan(parentDir);
818  m_parentDirs.insert(parentDir);
819  }
820  }
821 
822  KUrl dest = m_dest;
823  // Append filename or dirname to destination URL, if allowed
824  if ( destinationState == DEST_IS_DIR && !m_asMethod )
825  dest.addPath( m_currentSrcURL.fileName() );
826  m_currentDestURL = dest;
827  kDebug(7007) << m_currentSrcURL << "->" << dest << "trying direct rename first";
828  state = STATE_RENAMING;
829 
830  struct CopyInfo info;
831  info.permissions = -1;
832  info.mtime = (time_t) -1;
833  info.ctime = (time_t) -1;
834  info.size = (KIO::filesize_t)-1;
835  info.uSource = m_currentSrcURL;
836  info.uDest = dest;
837  QList<CopyInfo> files;
838  files.append(info);
839  emit q->aboutToCreate( q, files );
840 
841  KIO_ARGS << m_currentSrcURL << dest << (qint8) false /*no overwrite*/;
842  SimpleJob * newJob = SimpleJobPrivate::newJobNoUi(slave_url, CMD_RENAME, packedArgs);
843  Scheduler::setJobPriority(newJob, 1);
844  q->addSubjob( newJob );
845  if ( m_currentSrcURL.directory() != dest.directory() ) // For the user, moving isn't renaming. Only renaming is.
846  m_bOnlyRenames = false;
847 }
848 
849 void CopyJobPrivate::startListing( const KUrl & src )
850 {
851  Q_Q(CopyJob);
852  state = STATE_LISTING;
853  m_bURLDirty = true;
854  ListJob * newjob = listRecursive(src, KIO::HideProgressInfo);
855  newjob->setUnrestricted(true);
856  q->connect(newjob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
857  SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList)));
858  q->addSubjob( newjob );
859 }
860 
861 void CopyJobPrivate::skip(const KUrl & sourceUrl, bool isDir)
862 {
863  KUrl dir = sourceUrl;
864  if (!isDir) {
865  // Skipping a file: make sure not to delete the parent dir (#208418)
866  dir.setPath(dir.directory());
867  }
868  while (dirsToRemove.removeAll(dir) > 0) {
869  // Do not rely on rmdir() on the parent directories aborting.
870  // Exclude the parent dirs explicitly.
871  dir.setPath(dir.directory());
872  }
873 }
874 
875 bool CopyJobPrivate::shouldOverwriteDir( const QString& path ) const
876 {
877  if ( m_bOverwriteAllDirs )
878  return true;
879  return m_overwriteList.contains(path);
880 }
881 
882 bool CopyJobPrivate::shouldOverwriteFile( const QString& path ) const
883 {
884  if ( m_bOverwriteAllFiles )
885  return true;
886  return m_overwriteList.contains(path);
887 }
888 
889 bool CopyJobPrivate::shouldSkip( const QString& path ) const
890 {
891  Q_FOREACH(const QString& skipPath, m_skipList) {
892  if ( path.startsWith(skipPath) )
893  return true;
894  }
895  return false;
896 }
897 
898 void CopyJobPrivate::slotResultCreatingDirs( KJob * job )
899 {
900  Q_Q(CopyJob);
901  // The dir we are trying to create:
902  QList<CopyInfo>::Iterator it = dirs.begin();
903  // Was there an error creating a dir ?
904  if ( job->error() )
905  {
906  m_conflictError = job->error();
907  if ( (m_conflictError == ERR_DIR_ALREADY_EXIST)
908  || (m_conflictError == ERR_FILE_ALREADY_EXIST) ) // can't happen?
909  {
910  KUrl oldURL = ((SimpleJob*)job)->url();
911  // Should we skip automatically ?
912  if ( m_bAutoSkipDirs ) {
913  // We don't want to copy files in this directory, so we put it on the skip list
914  m_skipList.append( oldURL.path( KUrl::AddTrailingSlash ) );
915  skip(oldURL, true);
916  dirs.erase( it ); // Move on to next dir
917  } else {
918  // Did the user choose to overwrite already?
919  const QString destDir = (*it).uDest.path();
920  if ( shouldOverwriteDir( destDir ) ) { // overwrite => just skip
921  emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
922  dirs.erase( it ); // Move on to next dir
923  } else {
924  if (m_bAutoRenameDirs) {
925  QString oldPath = (*it).uDest.path(KUrl::AddTrailingSlash);
926 
927  KUrl destDirectory((*it).uDest);
928  destDirectory.setPath(destDirectory.directory());
929  QString newName = KIO::RenameDialog::suggestName(destDirectory, (*it).uDest.fileName());
930 
931  KUrl newUrl((*it).uDest);
932  newUrl.setFileName(newName);
933 
934  emit q->renamed(q, (*it).uDest, newUrl); // for e.g. kpropsdlg
935 
936  // Change the current one and strip the trailing '/'
937  (*it).uDest.setPath(newUrl.path(KUrl::RemoveTrailingSlash));
938 
939  QString newPath = newUrl.path(KUrl::AddTrailingSlash); // With trailing slash
940  QList<CopyInfo>::Iterator renamedirit = it;
941  ++renamedirit;
942  // Change the name of subdirectories inside the directory
943  for(; renamedirit != dirs.end() ; ++renamedirit) {
944  QString path = (*renamedirit).uDest.path();
945  if (path.startsWith(oldPath)) {
946  QString n = path;
947  n.replace(0, oldPath.length(), newPath);
948  kDebug(7007) << "dirs list:" << (*renamedirit).uSource.path()
949  << "was going to be" << path
950  << ", changed into" << n;
951  (*renamedirit).uDest.setPath(n);
952  }
953  }
954  // Change filenames inside the directory
955  QList<CopyInfo>::Iterator renamefileit = files.begin();
956  for(; renamefileit != files.end() ; ++renamefileit) {
957  QString path = (*renamefileit).uDest.path();
958  if (path.startsWith(oldPath)) {
959  QString n = path;
960  n.replace(0, oldPath.length(), newPath);
961  kDebug(7007) << "files list:" << (*renamefileit).uSource.path()
962  << "was going to be" << path
963  << ", changed into" << n;
964  (*renamefileit).uDest.setPath(n);
965  }
966  }
967  if (!dirs.isEmpty()) {
968  emit q->aboutToCreate(q, dirs);
969  }
970  if (!files.isEmpty()) {
971  emit q->aboutToCreate(q, files);
972  }
973 
974  }
975  else {
976  if (!q->isInteractive()) {
977  q->Job::slotResult(job); // will set the error and emit result(this)
978  return;
979  }
980 
981  assert(((SimpleJob*)job)->url().url() == (*it).uDest.url());
982  q->removeSubjob(job);
983  assert (!q->hasSubjobs()); // We should have only one job at a time ...
984 
985  // We need to stat the existing dir, to get its last-modification time
986  KUrl existingDest((*it).uDest);
987  SimpleJob * newJob = KIO::stat(existingDest, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
988  Scheduler::setJobPriority(newJob, 1);
989  kDebug(7007) << "KIO::stat for resolving conflict on " << existingDest;
990  state = STATE_CONFLICT_CREATING_DIRS;
991  q->addSubjob(newJob);
992  return; // Don't move to next dir yet !
993  }
994  }
995  }
996  }
997  else
998  {
999  // Severe error, abort
1000  q->Job::slotResult( job ); // will set the error and emit result(this)
1001  return;
1002  }
1003  }
1004  else // no error : remove from list, to move on to next dir
1005  {
1006  //this is required for the undo feature
1007  emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true, false );
1008  m_directoriesCopied.append( *it );
1009  dirs.erase( it );
1010  }
1011 
1012  m_processedDirs++;
1013  //emit processedAmount( this, KJob::Directories, m_processedDirs );
1014  q->removeSubjob( job );
1015  assert( !q->hasSubjobs() ); // We should have only one job at a time ...
1016  createNextDir();
1017 }
1018 
1019 void CopyJobPrivate::slotResultConflictCreatingDirs( KJob * job )
1020 {
1021  Q_Q(CopyJob);
1022  // We come here after a conflict has been detected and we've stated the existing dir
1023 
1024  // The dir we were trying to create:
1025  QList<CopyInfo>::Iterator it = dirs.begin();
1026 
1027  const UDSEntry entry = ((KIO::StatJob*)job)->statResult();
1028 
1029  // Its modification time:
1030  const time_t destmtime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, -1 );
1031  const time_t destctime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_CREATION_TIME, -1 );
1032 
1033  const KIO::filesize_t destsize = entry.numberValue( KIO::UDSEntry::UDS_SIZE );
1034  const QString linkDest = entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST );
1035 
1036  q->removeSubjob( job );
1037  assert ( !q->hasSubjobs() ); // We should have only one job at a time ...
1038 
1039  // Always multi and skip (since there are files after that)
1040  RenameDialog_Mode mode = (RenameDialog_Mode)( M_MULTI | M_SKIP | M_ISDIR );
1041  // Overwrite only if the existing thing is a dir (no chance with a file)
1042  if ( m_conflictError == ERR_DIR_ALREADY_EXIST )
1043  {
1044  if( (*it).uSource == (*it).uDest ||
1045  ((*it).uSource.protocol() == (*it).uDest.protocol() &&
1046  (*it).uSource.path( KUrl::RemoveTrailingSlash ) == linkDest) )
1047  mode = (RenameDialog_Mode)( mode | M_OVERWRITE_ITSELF);
1048  else
1049  mode = (RenameDialog_Mode)( mode | M_OVERWRITE );
1050  }
1051 
1052  QString existingDest = (*it).uDest.path();
1053  QString newPath;
1054  if (m_reportTimer)
1055  m_reportTimer->stop();
1056  RenameDialog_Result r = q->ui()->askFileRename( q, i18n("Folder Already Exists"),
1057  (*it).uSource.url(),
1058  (*it).uDest.url(),
1059  mode, newPath,
1060  (*it).size, destsize,
1061  (*it).ctime, destctime,
1062  (*it).mtime, destmtime );
1063  if (m_reportTimer)
1064  m_reportTimer->start(REPORT_TIMEOUT);
1065  switch ( r ) {
1066  case R_CANCEL:
1067  q->setError( ERR_USER_CANCELED );
1068  q->emitResult();
1069  return;
1070  case R_AUTO_RENAME:
1071  m_bAutoRenameDirs = true;
1072  // fall through
1073  case R_RENAME:
1074  {
1075  QString oldPath = (*it).uDest.path( KUrl::AddTrailingSlash );
1076  KUrl newUrl( (*it).uDest );
1077  newUrl.setPath( newPath );
1078  emit q->renamed( q, (*it).uDest, newUrl ); // for e.g. kpropsdlg
1079 
1080  // Change the current one and strip the trailing '/'
1081  (*it).uDest.setPath( newUrl.path( KUrl::RemoveTrailingSlash ) );
1082  newPath = newUrl.path( KUrl::AddTrailingSlash ); // With trailing slash
1083  QList<CopyInfo>::Iterator renamedirit = it;
1084  ++renamedirit;
1085  // Change the name of subdirectories inside the directory
1086  for( ; renamedirit != dirs.end() ; ++renamedirit )
1087  {
1088  QString path = (*renamedirit).uDest.path();
1089  if ( path.startsWith( oldPath ) ) {
1090  QString n = path;
1091  n.replace( 0, oldPath.length(), newPath );
1092  kDebug(7007) << "dirs list:" << (*renamedirit).uSource.path()
1093  << "was going to be" << path
1094  << ", changed into" << n;
1095  (*renamedirit).uDest.setPath( n );
1096  }
1097  }
1098  // Change filenames inside the directory
1099  QList<CopyInfo>::Iterator renamefileit = files.begin();
1100  for( ; renamefileit != files.end() ; ++renamefileit )
1101  {
1102  QString path = (*renamefileit).uDest.path();
1103  if ( path.startsWith( oldPath ) ) {
1104  QString n = path;
1105  n.replace( 0, oldPath.length(), newPath );
1106  kDebug(7007) << "files list:" << (*renamefileit).uSource.path()
1107  << "was going to be" << path
1108  << ", changed into" << n;
1109  (*renamefileit).uDest.setPath( n );
1110  }
1111  }
1112  if (!dirs.isEmpty())
1113  emit q->aboutToCreate( q, dirs );
1114  if (!files.isEmpty())
1115  emit q->aboutToCreate( q, files );
1116  }
1117  break;
1118  case R_AUTO_SKIP:
1119  m_bAutoSkipDirs = true;
1120  // fall through
1121  case R_SKIP:
1122  m_skipList.append( existingDest );
1123  skip((*it).uSource, true);
1124  // Move on to next dir
1125  dirs.erase( it );
1126  m_processedDirs++;
1127  break;
1128  case R_OVERWRITE:
1129  m_overwriteList.insert( existingDest );
1130  emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
1131  // Move on to next dir
1132  dirs.erase( it );
1133  m_processedDirs++;
1134  break;
1135  case R_OVERWRITE_ALL:
1136  m_bOverwriteAllDirs = true;
1137  emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
1138  // Move on to next dir
1139  dirs.erase( it );
1140  m_processedDirs++;
1141  break;
1142  default:
1143  assert( 0 );
1144  }
1145  state = STATE_CREATING_DIRS;
1146  //emit processedAmount( this, KJob::Directories, m_processedDirs );
1147  createNextDir();
1148 }
1149 
1150 void CopyJobPrivate::createNextDir()
1151 {
1152  Q_Q(CopyJob);
1153  KUrl udir;
1154  if ( !dirs.isEmpty() )
1155  {
1156  // Take first dir to create out of list
1157  QList<CopyInfo>::Iterator it = dirs.begin();
1158  // Is this URL on the skip list or the overwrite list ?
1159  while( it != dirs.end() && udir.isEmpty() )
1160  {
1161  const QString dir = (*it).uDest.path();
1162  if ( shouldSkip( dir ) ) {
1163  dirs.erase( it );
1164  it = dirs.begin();
1165  } else
1166  udir = (*it).uDest;
1167  }
1168  }
1169  if ( !udir.isEmpty() ) // any dir to create, finally ?
1170  {
1171  // Create the directory - with default permissions so that we can put files into it
1172  // TODO : change permissions once all is finished; but for stuff coming from CDROM it sucks...
1173  KIO::SimpleJob *newjob = KIO::mkdir( udir, -1 );
1174  Scheduler::setJobPriority(newjob, 1);
1175  if (shouldOverwriteFile(udir.path())) { // if we are overwriting an existing file or symlink
1176  newjob->addMetaData("overwrite", "true");
1177  }
1178 
1179  m_currentDestURL = udir;
1180  m_bURLDirty = true;
1181 
1182  q->addSubjob(newjob);
1183  return;
1184  }
1185  else // we have finished creating dirs
1186  {
1187  q->setProcessedAmount( KJob::Directories, m_processedDirs ); // make sure final number appears
1188 
1189  if (m_mode == CopyJob::Move) {
1190  // Now we know which dirs hold the files we're going to delete.
1191  // To speed things up and prevent double-notification, we disable KDirWatch
1192  // on those dirs temporarily (using KDirWatch::self, that's the instanced
1193  // used by e.g. kdirlister).
1194  for ( QSet<QString>::const_iterator it = m_parentDirs.constBegin() ; it != m_parentDirs.constEnd() ; ++it )
1195  KDirWatch::self()->stopDirScan( *it );
1196  }
1197 
1198  state = STATE_COPYING_FILES;
1199  m_processedFiles++; // Ralf wants it to start at 1, not 0
1200  copyNextFile();
1201  }
1202 }
1203 
1204 void CopyJobPrivate::slotResultCopyingFiles( KJob * job )
1205 {
1206  Q_Q(CopyJob);
1207  // The file we were trying to copy:
1208  QList<CopyInfo>::Iterator it = files.begin();
1209  if ( job->error() )
1210  {
1211  // Should we skip automatically ?
1212  if ( m_bAutoSkipFiles )
1213  {
1214  skip((*it).uSource, false);
1215  m_fileProcessedSize = (*it).size;
1216  files.erase( it ); // Move on to next file
1217  }
1218  else
1219  {
1220  m_conflictError = job->error(); // save for later
1221  // Existing dest ?
1222  if ( ( m_conflictError == ERR_FILE_ALREADY_EXIST )
1223  || ( m_conflictError == ERR_DIR_ALREADY_EXIST )
1224  || ( m_conflictError == ERR_IDENTICAL_FILES ) )
1225  {
1226  if (m_bAutoRenameFiles) {
1227  KUrl destDirectory((*it).uDest);
1228  destDirectory.setPath(destDirectory.directory());
1229  const QString newName = KIO::RenameDialog::suggestName(destDirectory, (*it).uDest.fileName());
1230 
1231  KUrl newUrl((*it).uDest);
1232  newUrl.setFileName(newName);
1233 
1234  emit q->renamed(q, (*it).uDest, newUrl); // for e.g. kpropsdlg
1235  (*it).uDest = newUrl;
1236 
1237  QList<CopyInfo> files;
1238  files.append(*it);
1239  emit q->aboutToCreate(q, files);
1240  }
1241  else {
1242  if ( !q->isInteractive() ) {
1243  q->Job::slotResult( job ); // will set the error and emit result(this)
1244  return;
1245  }
1246 
1247  q->removeSubjob(job);
1248  assert (!q->hasSubjobs());
1249  // We need to stat the existing file, to get its last-modification time
1250  KUrl existingFile((*it).uDest);
1251  SimpleJob * newJob = KIO::stat(existingFile, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
1252  Scheduler::setJobPriority(newJob, 1);
1253  kDebug(7007) << "KIO::stat for resolving conflict on " << existingFile;
1254  state = STATE_CONFLICT_COPYING_FILES;
1255  q->addSubjob(newJob);
1256  return; // Don't move to next file yet !
1257  }
1258  }
1259  else
1260  {
1261  if ( m_bCurrentOperationIsLink && qobject_cast<KIO::DeleteJob*>( job ) )
1262  {
1263  // Very special case, see a few lines below
1264  // We are deleting the source of a symlink we successfully moved... ignore error
1265  m_fileProcessedSize = (*it).size;
1266  files.erase( it );
1267  } else {
1268  if ( !q->isInteractive() ) {
1269  q->Job::slotResult( job ); // will set the error and emit result(this)
1270  return;
1271  }
1272 
1273  // Go directly to the conflict resolution, there is nothing to stat
1274  slotResultConflictCopyingFiles( job );
1275  return;
1276  }
1277  }
1278  }
1279  } else // no error
1280  {
1281  // Special case for moving links. That operation needs two jobs, unlike others.
1282  if ( m_bCurrentOperationIsLink && m_mode == CopyJob::Move
1283  && !qobject_cast<KIO::DeleteJob *>( job ) // Deleting source not already done
1284  )
1285  {
1286  q->removeSubjob( job );
1287  assert ( !q->hasSubjobs() );
1288  // The only problem with this trick is that the error handling for this del operation
1289  // is not going to be right... see 'Very special case' above.
1290  KIO::Job * newjob = KIO::del( (*it).uSource, HideProgressInfo );
1291  q->addSubjob( newjob );
1292  return; // Don't move to next file yet !
1293  }
1294 
1295  if ( m_bCurrentOperationIsLink )
1296  {
1297  QString target = ( m_mode == CopyJob::Link ? (*it).uSource.path() : (*it).linkDest );
1298  //required for the undo feature
1299  emit q->copyingLinkDone( q, (*it).uSource, target, (*it).uDest );
1300  }
1301  else {
1302  //required for the undo feature
1303  emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, false, false );
1304  if (m_mode == CopyJob::Move)
1305  {
1306  org::kde::KDirNotify::emitFileMoved( (*it).uSource.url(), (*it).uDest.url() );
1307  }
1308  m_successSrcList.append((*it).uSource);
1309  if (m_freeSpace != (KIO::filesize_t)-1 && (*it).size != (KIO::filesize_t)-1) {
1310  m_freeSpace -= (*it).size;
1311  }
1312 
1313  }
1314  // remove from list, to move on to next file
1315  files.erase( it );
1316  }
1317  m_processedFiles++;
1318 
1319  // clear processed size for last file and add it to overall processed size
1320  m_processedSize += m_fileProcessedSize;
1321  m_fileProcessedSize = 0;
1322 
1323  //kDebug(7007) << files.count() << "files remaining";
1324 
1325  // Merge metadata from subjob
1326  KIO::Job* kiojob = dynamic_cast<KIO::Job*>(job);
1327  Q_ASSERT(kiojob);
1328  m_incomingMetaData += kiojob->metaData();
1329  q->removeSubjob( job );
1330  assert( !q->hasSubjobs() ); // We should have only one job at a time ...
1331  copyNextFile();
1332 }
1333 
1334 void CopyJobPrivate::slotResultConflictCopyingFiles( KJob * job )
1335 {
1336  Q_Q(CopyJob);
1337  // We come here after a conflict has been detected and we've stated the existing file
1338  // The file we were trying to create:
1339  QList<CopyInfo>::Iterator it = files.begin();
1340 
1341  RenameDialog_Result res;
1342  QString newPath;
1343 
1344  if (m_reportTimer)
1345  m_reportTimer->stop();
1346 
1347  if ( ( m_conflictError == ERR_FILE_ALREADY_EXIST )
1348  || ( m_conflictError == ERR_DIR_ALREADY_EXIST )
1349  || ( m_conflictError == ERR_IDENTICAL_FILES ) )
1350  {
1351  // Its modification time:
1352  const UDSEntry entry = ((KIO::StatJob*)job)->statResult();
1353 
1354  const time_t destmtime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, -1 );
1355  const time_t destctime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_CREATION_TIME, -1 );
1356  const KIO::filesize_t destsize = entry.numberValue( KIO::UDSEntry::UDS_SIZE );
1357  const QString linkDest = entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST );
1358 
1359  // Offer overwrite only if the existing thing is a file
1360  // If src==dest, use "overwrite-itself"
1361  RenameDialog_Mode mode;
1362  bool isDir = true;
1363 
1364  if( m_conflictError == ERR_DIR_ALREADY_EXIST )
1365  mode = M_ISDIR;
1366  else
1367  {
1368  if ( (*it).uSource == (*it).uDest ||
1369  ((*it).uSource.protocol() == (*it).uDest.protocol() &&
1370  (*it).uSource.path( KUrl::RemoveTrailingSlash ) == linkDest) )
1371  mode = M_OVERWRITE_ITSELF;
1372  else
1373  mode = M_OVERWRITE;
1374  isDir = false;
1375  }
1376 
1377  if ( !m_bSingleFileCopy )
1378  mode = (RenameDialog_Mode) ( mode | M_MULTI | M_SKIP );
1379 
1380  res = q->ui()->askFileRename( q, !isDir ?
1381  i18n("File Already Exists") : i18n("Already Exists as Folder"),
1382  (*it).uSource.url(),
1383  (*it).uDest.url(),
1384  mode, newPath,
1385  (*it).size, destsize,
1386  (*it).ctime, destctime,
1387  (*it).mtime, destmtime );
1388 
1389  }
1390  else
1391  {
1392  if ( job->error() == ERR_USER_CANCELED )
1393  res = R_CANCEL;
1394  else if ( !q->isInteractive() ) {
1395  q->Job::slotResult( job ); // will set the error and emit result(this)
1396  return;
1397  }
1398  else
1399  {
1400  SkipDialog_Result skipResult = q->ui()->askSkip( q, files.count() > 1,
1401  job->errorString() );
1402 
1403  // Convert the return code from SkipDialog into a RenameDialog code
1404  res = ( skipResult == S_SKIP ) ? R_SKIP :
1405  ( skipResult == S_AUTO_SKIP ) ? R_AUTO_SKIP :
1406  R_CANCEL;
1407  }
1408  }
1409 
1410  if (m_reportTimer)
1411  m_reportTimer->start(REPORT_TIMEOUT);
1412 
1413  q->removeSubjob( job );
1414  assert ( !q->hasSubjobs() );
1415  switch ( res ) {
1416  case R_CANCEL:
1417  q->setError( ERR_USER_CANCELED );
1418  q->emitResult();
1419  return;
1420  case R_AUTO_RENAME:
1421  m_bAutoRenameFiles = true;
1422  // fall through
1423  case R_RENAME:
1424  {
1425  KUrl newUrl( (*it).uDest );
1426  newUrl.setPath( newPath );
1427  emit q->renamed( q, (*it).uDest, newUrl ); // for e.g. kpropsdlg
1428  (*it).uDest = newUrl;
1429 
1430  QList<CopyInfo> files;
1431  files.append(*it);
1432  emit q->aboutToCreate( q, files );
1433  }
1434  break;
1435  case R_AUTO_SKIP:
1436  m_bAutoSkipFiles = true;
1437  // fall through
1438  case R_SKIP:
1439  // Move on to next file
1440  skip((*it).uSource, false);
1441  m_processedSize += (*it).size;
1442  files.erase( it );
1443  m_processedFiles++;
1444  break;
1445  case R_OVERWRITE_ALL:
1446  m_bOverwriteAllFiles = true;
1447  break;
1448  case R_OVERWRITE:
1449  // Add to overwrite list, so that copyNextFile knows to overwrite
1450  m_overwriteList.insert( (*it).uDest.path() );
1451  break;
1452  default:
1453  assert( 0 );
1454  }
1455  state = STATE_COPYING_FILES;
1456  copyNextFile();
1457 }
1458 
1459 KIO::Job* CopyJobPrivate::linkNextFile( const KUrl& uSource, const KUrl& uDest, JobFlags flags )
1460 {
1461  //kDebug(7007) << "Linking";
1462  if (
1463  (uSource.protocol() == uDest.protocol()) &&
1464  (uSource.host() == uDest.host()) &&
1465  (uSource.port() == uDest.port()) &&
1466  (uSource.user() == uDest.user()) &&
1467  (uSource.pass() == uDest.pass()) )
1468  {
1469  // This is the case of creating a real symlink
1470  KIO::SimpleJob *newJob = KIO::symlink( uSource.path(), uDest, flags|HideProgressInfo /*no GUI*/ );
1471  Scheduler::setJobPriority(newJob, 1);
1472  //kDebug(7007) << "Linking target=" << uSource.path() << "link=" << uDest;
1473  //emit linking( this, uSource.path(), uDest );
1474  m_bCurrentOperationIsLink = true;
1475  m_currentSrcURL=uSource;
1476  m_currentDestURL=uDest;
1477  m_bURLDirty = true;
1478  //Observer::self()->slotCopying( this, uSource, uDest ); // should be slotLinking perhaps
1479  return newJob;
1480  } else {
1481  Q_Q(CopyJob);
1482  //kDebug(7007) << "Linking URL=" << uSource << "link=" << uDest;
1483  if ( uDest.isLocalFile() ) {
1484  // if the source is a devices url, handle it a littlebit special
1485 
1486  QString path = uDest.toLocalFile();
1487  //kDebug(7007) << "path=" << path;
1488  QFile f( path );
1489  if ( f.open( QIODevice::ReadWrite ) )
1490  {
1491  f.close();
1492  KDesktopFile desktopFile( path );
1493  KConfigGroup config = desktopFile.desktopGroup();
1494  KUrl url = uSource;
1495  url.setPass( "" );
1496  config.writePathEntry( "URL", url.url() );
1497  config.writeEntry( "Name", url.url() );
1498  config.writeEntry( "Type", QString::fromLatin1("Link") );
1499  QString protocol = uSource.protocol();
1500  if ( protocol == QLatin1String("ftp") )
1501  config.writeEntry( "Icon", QString::fromLatin1("folder-remote") );
1502  else if ( protocol == QLatin1String("http") )
1503  config.writeEntry( "Icon", QString::fromLatin1("text-html") );
1504  else if ( protocol == QLatin1String("info") )
1505  config.writeEntry( "Icon", QString::fromLatin1("text-x-texinfo") );
1506  else if ( protocol == QLatin1String("mailto") ) // sven:
1507  config.writeEntry( "Icon", QString::fromLatin1("internet-mail") ); // added mailto: support
1508  else
1509  config.writeEntry( "Icon", QString::fromLatin1("unknown") );
1510  config.sync();
1511  files.erase( files.begin() ); // done with this one, move on
1512  m_processedFiles++;
1513  //emit processedAmount( this, KJob::Files, m_processedFiles );
1514  copyNextFile();
1515  return 0;
1516  }
1517  else
1518  {
1519  kDebug(7007) << "ERR_CANNOT_OPEN_FOR_WRITING";
1520  q->setError( ERR_CANNOT_OPEN_FOR_WRITING );
1521  q->setErrorText( uDest.toLocalFile() );
1522  q->emitResult();
1523  return 0;
1524  }
1525  } else {
1526  // Todo: not show "link" on remote dirs if the src urls are not from the same protocol+host+...
1527  q->setError( ERR_CANNOT_SYMLINK );
1528  q->setErrorText( uDest.prettyUrl() );
1529  q->emitResult();
1530  return 0;
1531  }
1532  }
1533 }
1534 
1535 void CopyJobPrivate::copyNextFile()
1536 {
1537  Q_Q(CopyJob);
1538  bool bCopyFile = false;
1539  //kDebug(7007);
1540  // Take the first file in the list
1541  QList<CopyInfo>::Iterator it = files.begin();
1542  // Is this URL on the skip list ?
1543  while (it != files.end() && !bCopyFile)
1544  {
1545  const QString destFile = (*it).uDest.path();
1546  bCopyFile = !shouldSkip( destFile );
1547  if ( !bCopyFile ) {
1548  files.erase( it );
1549  it = files.begin();
1550  }
1551  }
1552 
1553  if (bCopyFile) // any file to create, finally ?
1554  {
1555  //kDebug()<<"preparing to copy"<<(*it).uSource<<(*it).size<<m_freeSpace;
1556  if (m_freeSpace != (KIO::filesize_t)-1 && (*it).size != (KIO::filesize_t)-1) {
1557  if (m_freeSpace < (*it).size) {
1558  q->setError( ERR_DISK_FULL );
1559  q->emitResult();
1560  return;
1561  }
1562  //TODO check if dst mount is msdos and (*it).size exceeds it's limits
1563  }
1564 
1565  const KUrl& uSource = (*it).uSource;
1566  const KUrl& uDest = (*it).uDest;
1567  // Do we set overwrite ?
1568  bool bOverwrite;
1569  const QString destFile = uDest.path();
1570  // kDebug(7007) << "copying" << destFile;
1571  if ( uDest == uSource )
1572  bOverwrite = false;
1573  else
1574  bOverwrite = shouldOverwriteFile( destFile );
1575 
1576  m_bCurrentOperationIsLink = false;
1577  KIO::Job * newjob = 0;
1578  if ( m_mode == CopyJob::Link ) {
1579  // User requested that a symlink be made
1580  const JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
1581  newjob = linkNextFile(uSource, uDest, flags);
1582  if (!newjob)
1583  return;
1584  } else if ( !(*it).linkDest.isEmpty() &&
1585  (uSource.protocol() == uDest.protocol()) &&
1586  (uSource.host() == uDest.host()) &&
1587  (uSource.port() == uDest.port()) &&
1588  (uSource.user() == uDest.user()) &&
1589  (uSource.pass() == uDest.pass()))
1590  // Copying a symlink - only on the same protocol/host/etc. (#5601, downloading an FTP file through its link),
1591  {
1592  const JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
1593  KIO::SimpleJob *newJob = KIO::symlink( (*it).linkDest, uDest, flags | HideProgressInfo /*no GUI*/ );
1594  Scheduler::setJobPriority(newJob, 1);
1595  newjob = newJob;
1596  //kDebug(7007) << "Linking target=" << (*it).linkDest << "link=" << uDest;
1597  m_currentSrcURL = KUrl( (*it).linkDest );
1598  m_currentDestURL = uDest;
1599  m_bURLDirty = true;
1600  //emit linking( this, (*it).linkDest, uDest );
1601  //Observer::self()->slotCopying( this, m_currentSrcURL, uDest ); // should be slotLinking perhaps
1602  m_bCurrentOperationIsLink = true;
1603  // NOTE: if we are moving stuff, the deletion of the source will be done in slotResultCopyingFiles
1604  } else if (m_mode == CopyJob::Move) // Moving a file
1605  {
1606  JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
1607  KIO::FileCopyJob * moveJob = KIO::file_move( uSource, uDest, (*it).permissions, flags | HideProgressInfo/*no GUI*/ );
1608  moveJob->setSourceSize( (*it).size );
1609  if ((*it).mtime != -1) {
1610  moveJob->setModificationTime( QDateTime::fromTime_t( (*it).mtime ) ); // #55804
1611  }
1612  newjob = moveJob;
1613  //kDebug(7007) << "Moving" << uSource << "to" << uDest;
1614  //emit moving( this, uSource, uDest );
1615  m_currentSrcURL=uSource;
1616  m_currentDestURL=uDest;
1617  m_bURLDirty = true;
1618  //Observer::self()->slotMoving( this, uSource, uDest );
1619  }
1620  else // Copying a file
1621  {
1622  // If source isn't local and target is local, we ignore the original permissions
1623  // Otherwise, files downloaded from HTTP end up with -r--r--r--
1624  bool remoteSource = !KProtocolManager::supportsListing(uSource);
1625  int permissions = (*it).permissions;
1626  if ( m_defaultPermissions || ( remoteSource && uDest.isLocalFile() ) )
1627  permissions = -1;
1628  JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
1629  KIO::FileCopyJob * copyJob = KIO::file_copy( uSource, uDest, permissions, flags | HideProgressInfo/*no GUI*/ );
1630  copyJob->setParentJob( q ); // in case of rename dialog
1631  copyJob->setSourceSize( (*it).size );
1632  if ((*it).mtime != -1) {
1633  copyJob->setModificationTime( QDateTime::fromTime_t( (*it).mtime ) );
1634  }
1635  newjob = copyJob;
1636  //kDebug(7007) << "Copying" << uSource << "to" << uDest;
1637  m_currentSrcURL=uSource;
1638  m_currentDestURL=uDest;
1639  m_bURLDirty = true;
1640  }
1641  q->addSubjob(newjob);
1642  q->connect( newjob, SIGNAL(processedSize(KJob*,qulonglong)),
1643  SLOT(slotProcessedSize(KJob*,qulonglong)) );
1644  q->connect( newjob, SIGNAL(totalSize(KJob*,qulonglong)),
1645  SLOT(slotTotalSize(KJob*,qulonglong)) );
1646  }
1647  else
1648  {
1649  // We're done
1650  //kDebug(7007) << "copyNextFile finished";
1651  deleteNextDir();
1652  }
1653 }
1654 
1655 void CopyJobPrivate::deleteNextDir()
1656 {
1657  Q_Q(CopyJob);
1658  if ( m_mode == CopyJob::Move && !dirsToRemove.isEmpty() ) // some dirs to delete ?
1659  {
1660  state = STATE_DELETING_DIRS;
1661  m_bURLDirty = true;
1662  // Take first dir to delete out of list - last ones first !
1663  KUrl::List::Iterator it = --dirsToRemove.end();
1664  SimpleJob *job = KIO::rmdir( *it );
1665  Scheduler::setJobPriority(job, 1);
1666  dirsToRemove.erase(it);
1667  q->addSubjob( job );
1668  }
1669  else
1670  {
1671  // This step is done, move on
1672  state = STATE_SETTING_DIR_ATTRIBUTES;
1673  m_directoriesCopiedIterator = m_directoriesCopied.constBegin();
1674  setNextDirAttribute();
1675  }
1676 }
1677 
1678 void CopyJobPrivate::setNextDirAttribute()
1679 {
1680  Q_Q(CopyJob);
1681  while (m_directoriesCopiedIterator != m_directoriesCopied.constEnd() &&
1682  (*m_directoriesCopiedIterator).mtime == -1) {
1683  ++m_directoriesCopiedIterator;
1684  }
1685  if ( m_directoriesCopiedIterator != m_directoriesCopied.constEnd() ) {
1686  const KUrl url = (*m_directoriesCopiedIterator).uDest;
1687  const time_t mtime = (*m_directoriesCopiedIterator).mtime;
1688  const QDateTime dt = QDateTime::fromTime_t(mtime);
1689  ++m_directoriesCopiedIterator;
1690 
1691  KIO::SimpleJob *job = KIO::setModificationTime( url, dt );
1692  Scheduler::setJobPriority(job, 1);
1693  q->addSubjob( job );
1694 
1695 
1696 #if 0 // ifdef Q_OS_UNIX
1697  // TODO: can be removed now. Or reintroduced as a fast path for local files
1698  // if launching even more jobs as done above is a performance problem.
1699  //
1700  QLinkedList<CopyInfo>::const_iterator it = m_directoriesCopied.constBegin();
1701  for ( ; it != m_directoriesCopied.constEnd() ; ++it ) {
1702  const KUrl& url = (*it).uDest;
1703  if ( url.isLocalFile() && (*it).mtime != (time_t)-1 ) {
1704  KDE_struct_stat statbuf;
1705  if (KDE::lstat(url.path(), &statbuf) == 0) {
1706  struct utimbuf utbuf;
1707  utbuf.actime = statbuf.st_atime; // access time, unchanged
1708  utbuf.modtime = (*it).mtime; // modification time
1709  utime( path, &utbuf );
1710  }
1711 
1712  }
1713  }
1714  m_directoriesCopied.clear();
1715  // but then we need to jump to the else part below. Maybe with a recursive call?
1716 #endif
1717  } else {
1718  if (m_reportTimer)
1719  m_reportTimer->stop();
1720  --m_processedFiles; // undo the "start at 1" hack
1721  slotReport(); // display final numbers, important if progress dialog stays up
1722 
1723  q->emitResult();
1724  }
1725 }
1726 
1727 void CopyJob::emitResult()
1728 {
1729  Q_D(CopyJob);
1730  // Before we go, tell the world about the changes that were made.
1731  // Even if some error made us abort midway, we might still have done
1732  // part of the job so we better update the views! (#118583)
1733  if (!d->m_bOnlyRenames) {
1734  KUrl url(d->m_globalDest);
1735  if (d->m_globalDestinationState != DEST_IS_DIR || d->m_asMethod)
1736  url.setPath(url.directory());
1737  //kDebug(7007) << "KDirNotify'ing FilesAdded" << url;
1738  org::kde::KDirNotify::emitFilesAdded( url.url() );
1739 
1740  if (d->m_mode == CopyJob::Move && !d->m_successSrcList.isEmpty()) {
1741  kDebug(7007) << "KDirNotify'ing FilesRemoved" << d->m_successSrcList.toStringList();
1742  org::kde::KDirNotify::emitFilesRemoved(d->m_successSrcList.toStringList());
1743  }
1744 
1745  // Re-enable watching on the dirs that held the deleted files
1746  if (d->m_mode == CopyJob::Move) {
1747  for (QSet<QString>::const_iterator it = d->m_parentDirs.constBegin() ; it != d->m_parentDirs.constEnd() ; ++it)
1748  KDirWatch::self()->restartDirScan( *it );
1749  }
1750  }
1751  Job::emitResult();
1752 }
1753 
1754 void CopyJobPrivate::slotProcessedSize( KJob*, qulonglong data_size )
1755 {
1756  Q_Q(CopyJob);
1757  //kDebug(7007) << data_size;
1758  m_fileProcessedSize = data_size;
1759  q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize);
1760 
1761  if ( m_processedSize + m_fileProcessedSize > m_totalSize )
1762  {
1763  // Example: download any attachment from bugs.kde.org
1764  m_totalSize = m_processedSize + m_fileProcessedSize;
1765  //kDebug(7007) << "Adjusting m_totalSize to" << m_totalSize;
1766  q->setTotalAmount(KJob::Bytes, m_totalSize); // safety
1767  }
1768  //kDebug(7007) << "emit processedSize" << (unsigned long) (m_processedSize + m_fileProcessedSize);
1769  q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize);
1770 }
1771 
1772 void CopyJobPrivate::slotTotalSize( KJob*, qulonglong size )
1773 {
1774  Q_Q(CopyJob);
1775  //kDebug(7007) << size;
1776  // Special case for copying a single file
1777  // This is because some protocols don't implement stat properly
1778  // (e.g. HTTP), and don't give us a size in some cases (redirection)
1779  // so we'd rather rely on the size given for the transfer
1780  if ( m_bSingleFileCopy && size != m_totalSize)
1781  {
1782  //kDebug(7007) << "slotTotalSize: updating totalsize to" << size;
1783  m_totalSize = size;
1784  q->setTotalAmount(KJob::Bytes, size);
1785  }
1786 }
1787 
1788 void CopyJobPrivate::slotResultDeletingDirs( KJob * job )
1789 {
1790  Q_Q(CopyJob);
1791  if (job->error()) {
1792  // Couldn't remove directory. Well, perhaps it's not empty
1793  // because the user pressed Skip for a given file in it.
1794  // Let's not display "Could not remove dir ..." for each of those dir !
1795  } else {
1796  m_successSrcList.append(static_cast<KIO::SimpleJob*>(job)->url());
1797  }
1798  q->removeSubjob( job );
1799  assert( !q->hasSubjobs() );
1800  deleteNextDir();
1801 }
1802 
1803 void CopyJobPrivate::slotResultSettingDirAttributes( KJob * job )
1804 {
1805  Q_Q(CopyJob);
1806  if (job->error())
1807  {
1808  // Couldn't set directory attributes. Ignore the error, it can happen
1809  // with inferior file systems like VFAT.
1810  // Let's not display warnings for each dir like "cp -a" does.
1811  }
1812  q->removeSubjob( job );
1813  assert( !q->hasSubjobs() );
1814  setNextDirAttribute();
1815 }
1816 
1817 // We were trying to do a direct renaming, before even stat'ing
1818 void CopyJobPrivate::slotResultRenaming( KJob* job )
1819 {
1820  Q_Q(CopyJob);
1821  int err = job->error();
1822  const QString errText = job->errorText();
1823  // Merge metadata from subjob
1824  KIO::Job* kiojob = dynamic_cast<KIO::Job*>(job);
1825  Q_ASSERT(kiojob);
1826  m_incomingMetaData += kiojob->metaData();
1827  q->removeSubjob( job );
1828  assert ( !q->hasSubjobs() );
1829  // Determine dest again
1830  KUrl dest = m_dest;
1831  if ( destinationState == DEST_IS_DIR && !m_asMethod )
1832  dest.addPath( m_currentSrcURL.fileName() );
1833  if ( err )
1834  {
1835  // Direct renaming didn't work. Try renaming to a temp name,
1836  // this can help e.g. when renaming 'a' to 'A' on a VFAT partition.
1837  // In that case it's the _same_ dir, we don't want to copy+del (data loss!)
1838  if ( m_currentSrcURL.isLocalFile() && m_currentSrcURL.url(KUrl::RemoveTrailingSlash) != dest.url(KUrl::RemoveTrailingSlash) &&
1839  m_currentSrcURL.url(KUrl::RemoveTrailingSlash).toLower() == dest.url(KUrl::RemoveTrailingSlash).toLower() &&
1840  ( err == ERR_FILE_ALREADY_EXIST ||
1841  err == ERR_DIR_ALREADY_EXIST ||
1842  err == ERR_IDENTICAL_FILES ) )
1843  {
1844  kDebug(7007) << "Couldn't rename directly, dest already exists. Detected special case of lower/uppercase renaming in same dir, try with 2 rename calls";
1845  const QString _src( m_currentSrcURL.toLocalFile() );
1846  const QString _dest( dest.toLocalFile() );
1847  const QString _tmpPrefix = m_currentSrcURL.directory(KUrl::ObeyTrailingSlash|KUrl::AppendTrailingSlash);
1848  KTemporaryFile tmpFile;
1849  tmpFile.setPrefix(_tmpPrefix);
1850  const bool openOk = tmpFile.open();
1851  if (!openOk) {
1852  kWarning(7007) << "Couldn't open temp file in" << _tmpPrefix;
1853  } else {
1854  const QString _tmp( tmpFile.fileName() );
1855  tmpFile.close();
1856  tmpFile.remove();
1857  kDebug(7007) << "KTemporaryFile using" << _tmp << "as intermediary";
1858  if (KDE::rename( _src, _tmp ) == 0) {
1859  //kDebug(7007) << "Renaming" << _src << "to" << _tmp << "succeeded";
1860  if (!QFile::exists( _dest ) && KDE::rename(_tmp, _dest) == 0) {
1861  err = 0;
1862  org::kde::KDirNotify::emitFileRenamed(m_currentSrcURL.url(), dest.url());
1863  } else {
1864  kDebug(7007) << "Didn't manage to rename" << _tmp << "to" << _dest << ", reverting";
1865  // Revert back to original name!
1866  if (KDE::rename( _tmp, _src ) != 0) {
1867  kError(7007) << "Couldn't rename" << _tmp << "back to" << _src << '!';
1868  // Severe error, abort
1869  q->Job::slotResult(job); // will set the error and emit result(this)
1870  return;
1871  }
1872  }
1873  } else {
1874  kDebug(7007) << "mv" << _src << _tmp << "failed:" << strerror(errno);
1875  }
1876  }
1877  }
1878  }
1879  if ( err )
1880  {
1881  // This code is similar to CopyJobPrivate::slotResultConflictCopyingFiles
1882  // but here it's about the base src url being moved/renamed
1883  // (m_currentSrcURL) and its dest (m_dest), not about a single file.
1884  // It also means we already stated the dest, here.
1885  // On the other hand we haven't stated the src yet (we skipped doing it
1886  // to save time, since it's not necessary to rename directly!)...
1887 
1888  // Existing dest?
1889  if ( err == ERR_DIR_ALREADY_EXIST ||
1890  err == ERR_FILE_ALREADY_EXIST ||
1891  err == ERR_IDENTICAL_FILES )
1892  {
1893  // Should we skip automatically ?
1894  bool isDir = (err == ERR_DIR_ALREADY_EXIST); // ## technically, isDir means "source is dir", not "dest is dir" #######
1895  if ((isDir && m_bAutoSkipDirs) || (!isDir && m_bAutoSkipFiles)) {
1896  // Move on to next source url
1897  skipSrc(isDir);
1898  return;
1899  } else if ((isDir && m_bOverwriteAllDirs) || (!isDir && m_bOverwriteAllFiles)) {
1900  ; // nothing to do, stat+copy+del will overwrite
1901  } else if ((isDir && m_bAutoRenameDirs) || (!isDir && m_bAutoRenameFiles)) {
1902  KUrl destDirectory(m_currentDestURL); // dest including filename
1903  destDirectory.setPath(destDirectory.directory());
1904  const QString newName = KIO::RenameDialog::suggestName(destDirectory, m_currentDestURL.fileName());
1905 
1906  m_dest.setPath(m_currentDestURL.path());
1907  m_dest.setFileName(newName);
1908  KIO::Job* job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
1909  state = STATE_STATING;
1910  destinationState = DEST_NOT_STATED;
1911  q->addSubjob(job);
1912  return;
1913  } else if ( q->isInteractive() ) {
1914  QString newPath;
1915  // we lack mtime info for both the src (not stated)
1916  // and the dest (stated but this info wasn't stored)
1917  // Let's do it for local files, at least
1918  KIO::filesize_t sizeSrc = (KIO::filesize_t) -1;
1919  KIO::filesize_t sizeDest = (KIO::filesize_t) -1;
1920  time_t ctimeSrc = (time_t) -1;
1921  time_t ctimeDest = (time_t) -1;
1922  time_t mtimeSrc = (time_t) -1;
1923  time_t mtimeDest = (time_t) -1;
1924 
1925  bool destIsDir = err == ERR_DIR_ALREADY_EXIST;
1926 
1927  // ## TODO we need to stat the source using KIO::stat
1928  // so that this code is properly network-transparent.
1929 
1930  KDE_struct_stat stat_buf;
1931  if ( m_currentSrcURL.isLocalFile() &&
1932  KDE::stat(m_currentSrcURL.toLocalFile(), &stat_buf) == 0 ) {
1933  sizeSrc = stat_buf.st_size;
1934  ctimeSrc = stat_buf.st_ctime;
1935  mtimeSrc = stat_buf.st_mtime;
1936  isDir = S_ISDIR(stat_buf.st_mode);
1937  }
1938  if ( dest.isLocalFile() &&
1939  KDE::stat(dest.toLocalFile(), &stat_buf) == 0 ) {
1940  sizeDest = stat_buf.st_size;
1941  ctimeDest = stat_buf.st_ctime;
1942  mtimeDest = stat_buf.st_mtime;
1943  destIsDir = S_ISDIR(stat_buf.st_mode);
1944  }
1945 
1946  // If src==dest, use "overwrite-itself"
1947  RenameDialog_Mode mode = ( m_currentSrcURL == dest ) ? M_OVERWRITE_ITSELF : M_OVERWRITE;
1948  if (!isDir && destIsDir) {
1949  // We can't overwrite a dir with a file.
1950  mode = (RenameDialog_Mode) 0;
1951  }
1952 
1953  if ( m_srcList.count() > 1 )
1954  mode = (RenameDialog_Mode) ( mode | M_MULTI | M_SKIP );
1955  if (destIsDir)
1956  mode = (RenameDialog_Mode) ( mode | M_ISDIR );
1957 
1958  if (m_reportTimer)
1959  m_reportTimer->stop();
1960 
1961  RenameDialog_Result r = q->ui()->askFileRename(
1962  q,
1963  err != ERR_DIR_ALREADY_EXIST ? i18n("File Already Exists") : i18n("Already Exists as Folder"),
1964  m_currentSrcURL.url(),
1965  dest.url(),
1966  mode, newPath,
1967  sizeSrc, sizeDest,
1968  ctimeSrc, ctimeDest,
1969  mtimeSrc, mtimeDest );
1970 
1971  if (m_reportTimer)
1972  m_reportTimer->start(REPORT_TIMEOUT);
1973 
1974  switch ( r )
1975  {
1976  case R_CANCEL:
1977  {
1978  q->setError( ERR_USER_CANCELED );
1979  q->emitResult();
1980  return;
1981  }
1982  case R_AUTO_RENAME:
1983  if (isDir) {
1984  m_bAutoRenameDirs = true;
1985  }
1986  else {
1987  m_bAutoRenameFiles = true;
1988  }
1989  // fall through
1990  case R_RENAME:
1991  {
1992  // Set m_dest to the chosen destination
1993  // This is only for this src url; the next one will revert to m_globalDest
1994  m_dest.setPath( newPath );
1995  KIO::Job* job = KIO::stat( m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo );
1996  state = STATE_STATING;
1997  destinationState = DEST_NOT_STATED;
1998  q->addSubjob(job);
1999  return;
2000  }
2001  case R_AUTO_SKIP:
2002  if (isDir)
2003  m_bAutoSkipDirs = true;
2004  else
2005  m_bAutoSkipFiles = true;
2006  // fall through
2007  case R_SKIP:
2008  // Move on to next url
2009  skipSrc(isDir);
2010  return;
2011  case R_OVERWRITE_ALL:
2012  if (destIsDir)
2013  m_bOverwriteAllDirs = true;
2014  else
2015  m_bOverwriteAllFiles = true;
2016  break;
2017  case R_OVERWRITE:
2018  // Add to overwrite list
2019  // Note that we add dest, not m_dest.
2020  // This ensures that when moving several urls into a dir (m_dest),
2021  // we only overwrite for the current one, not for all.
2022  // When renaming a single file (m_asMethod), it makes no difference.
2023  kDebug(7007) << "adding to overwrite list: " << dest.path();
2024  m_overwriteList.insert( dest.path() );
2025  break;
2026  default:
2027  //assert( 0 );
2028  break;
2029  }
2030  } else if ( err != KIO::ERR_UNSUPPORTED_ACTION ) {
2031  // Dest already exists, and job is not interactive -> abort with error
2032  q->setError( err );
2033  q->setErrorText( errText );
2034  q->emitResult();
2035  return;
2036  }
2037  } else if ( err != KIO::ERR_UNSUPPORTED_ACTION ) {
2038  kDebug(7007) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", aborting";
2039  q->setError( err );
2040  q->setErrorText( errText );
2041  q->emitResult();
2042  return;
2043  }
2044  kDebug(7007) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", reverting to normal way, starting with stat";
2045  //kDebug(7007) << "KIO::stat on" << m_currentSrcURL;
2046  KIO::Job* job = KIO::stat( m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo );
2047  state = STATE_STATING;
2048  q->addSubjob(job);
2049  m_bOnlyRenames = false;
2050  }
2051  else
2052  {
2053  kDebug(7007) << "Renaming succeeded, move on";
2054  ++m_processedFiles;
2055  emit q->copyingDone( q, *m_currentStatSrc, dest, -1 /*mtime unknown, and not needed*/, true, true );
2056  m_successSrcList.append(*m_currentStatSrc);
2057  statNextSrc();
2058  }
2059 }
2060 
2061 void CopyJob::slotResult( KJob *job )
2062 {
2063  Q_D(CopyJob);
2064  //kDebug(7007) << "d->state=" << (int) d->state;
2065  // In each case, what we have to do is :
2066  // 1 - check for errors and treat them
2067  // 2 - removeSubjob(job);
2068  // 3 - decide what to do next
2069 
2070  switch ( d->state ) {
2071  case STATE_STATING: // We were trying to stat a src url or the dest
2072  d->slotResultStating( job );
2073  break;
2074  case STATE_RENAMING: // We were trying to do a direct renaming, before even stat'ing
2075  {
2076  d->slotResultRenaming( job );
2077  break;
2078  }
2079  case STATE_LISTING: // recursive listing finished
2080  //kDebug(7007) << "totalSize:" << (unsigned int) d->m_totalSize << "files:" << d->files.count() << "d->dirs:" << d->dirs.count();
2081  // Was there an error ?
2082  if (job->error())
2083  {
2084  Job::slotResult( job ); // will set the error and emit result(this)
2085  return;
2086  }
2087 
2088  removeSubjob( job );
2089  assert ( !hasSubjobs() );
2090 
2091  d->statNextSrc();
2092  break;
2093  case STATE_CREATING_DIRS:
2094  d->slotResultCreatingDirs( job );
2095  break;
2096  case STATE_CONFLICT_CREATING_DIRS:
2097  d->slotResultConflictCreatingDirs( job );
2098  break;
2099  case STATE_COPYING_FILES:
2100  d->slotResultCopyingFiles( job );
2101  break;
2102  case STATE_CONFLICT_COPYING_FILES:
2103  d->slotResultConflictCopyingFiles( job );
2104  break;
2105  case STATE_DELETING_DIRS:
2106  d->slotResultDeletingDirs( job );
2107  break;
2108  case STATE_SETTING_DIR_ATTRIBUTES:
2109  d->slotResultSettingDirAttributes( job );
2110  break;
2111  default:
2112  assert( 0 );
2113  }
2114 }
2115 
2116 void KIO::CopyJob::setDefaultPermissions( bool b )
2117 {
2118  d_func()->m_defaultPermissions = b;
2119 }
2120 
2121 KIO::CopyJob::CopyMode KIO::CopyJob::operationMode() const
2122 {
2123  return d_func()->m_mode;
2124 }
2125 
2126 void KIO::CopyJob::setAutoSkip(bool autoSkip)
2127 {
2128  d_func()->m_bAutoSkipFiles = autoSkip;
2129  d_func()->m_bAutoSkipDirs = autoSkip;
2130 }
2131 
2132 void KIO::CopyJob::setAutoRename(bool autoRename)
2133 {
2134  d_func()->m_bAutoRenameFiles = autoRename;
2135  d_func()->m_bAutoRenameDirs = autoRename;
2136 }
2137 
2138 void KIO::CopyJob::setWriteIntoExistingDirectories(bool overwriteAll) // #65926
2139 {
2140  d_func()->m_bOverwriteAllDirs = overwriteAll;
2141 }
2142 
2143 CopyJob *KIO::copy(const KUrl& src, const KUrl& dest, JobFlags flags)
2144 {
2145  //kDebug(7007) << "src=" << src << "dest=" << dest;
2146  KUrl::List srcList;
2147  srcList.append( src );
2148  return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, false, flags);
2149 }
2150 
2151 CopyJob *KIO::copyAs(const KUrl& src, const KUrl& dest, JobFlags flags)
2152 {
2153  //kDebug(7007) << "src=" << src << "dest=" << dest;
2154  KUrl::List srcList;
2155  srcList.append( src );
2156  return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, true, flags);
2157 }
2158 
2159 CopyJob *KIO::copy( const KUrl::List& src, const KUrl& dest, JobFlags flags )
2160 {
2161  //kDebug(7007) << src << dest;
2162  return CopyJobPrivate::newJob(src, dest, CopyJob::Copy, false, flags);
2163 }
2164 
2165 CopyJob *KIO::move(const KUrl& src, const KUrl& dest, JobFlags flags)
2166 {
2167  //kDebug(7007) << src << dest;
2168  KUrl::List srcList;
2169  srcList.append( src );
2170  return CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, false, flags);
2171 }
2172 
2173 CopyJob *KIO::moveAs(const KUrl& src, const KUrl& dest, JobFlags flags)
2174 {
2175  //kDebug(7007) << src << dest;
2176  KUrl::List srcList;
2177  srcList.append( src );
2178  return CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, true, flags);
2179 }
2180 
2181 CopyJob *KIO::move( const KUrl::List& src, const KUrl& dest, JobFlags flags)
2182 {
2183  //kDebug(7007) << src << dest;
2184  return CopyJobPrivate::newJob(src, dest, CopyJob::Move, false, flags);
2185 }
2186 
2187 CopyJob *KIO::link(const KUrl& src, const KUrl& destDir, JobFlags flags)
2188 {
2189  KUrl::List srcList;
2190  srcList.append( src );
2191  return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags);
2192 }
2193 
2194 CopyJob *KIO::link(const KUrl::List& srcList, const KUrl& destDir, JobFlags flags)
2195 {
2196  return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags);
2197 }
2198 
2199 CopyJob *KIO::linkAs(const KUrl& src, const KUrl& destDir, JobFlags flags )
2200 {
2201  KUrl::List srcList;
2202  srcList.append( src );
2203  return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags);
2204 }
2205 
2206 CopyJob *KIO::trash(const KUrl& src, JobFlags flags)
2207 {
2208  KUrl::List srcList;
2209  srcList.append( src );
2210  return CopyJobPrivate::newJob(srcList, KUrl( "trash:/" ), CopyJob::Move, false, flags);
2211 }
2212 
2213 CopyJob *KIO::trash(const KUrl::List& srcList, JobFlags flags)
2214 {
2215  return CopyJobPrivate::newJob(srcList, KUrl( "trash:/" ), CopyJob::Move, false, flags);
2216 }
2217 
2218 #include "copyjob.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Fri Nov 16 2012 15:09:54 by doxygen 1.8.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.8.5 API Reference

Skip menu "kdelibs-4.8.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal