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

KIO

copyjob.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002     Copyright 2000       Stephan Kulow <coolo@kde.org>
00003     Copyright 2000-2006  David Faure <faure@kde.org>
00004     Copyright 2000       Waldo Bastian <bastian@kde.org>
00005 
00006     This library is free software; you can redistribute it and/or
00007     modify it under the terms of the GNU Library General Public
00008     License as published by the Free Software Foundation; either
00009     version 2 of the License, or (at your option) any later version.
00010 
00011     This library is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014     Library General Public License for more details.
00015 
00016     You should have received a copy of the GNU Library General Public License
00017     along with this library; see the file COPYING.LIB.  If not, write to
00018     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019     Boston, MA 02110-1301, USA.
00020 */
00021 
00022 #include "copyjob.h"
00023 #include <errno.h>
00024 #include "kdirlister.h"
00025 #include "kfileitem.h"
00026 #include "deletejob.h"
00027 
00028 #include <klocale.h>
00029 #include <kdesktopfile.h>
00030 #include <kdebug.h>
00031 #include <kde_file.h>
00032 
00033 #include "slave.h"
00034 #include "scheduler.h"
00035 #include "kdirwatch.h"
00036 #include "kprotocolmanager.h"
00037 
00038 #include "jobuidelegate.h"
00039 
00040 #include <kdirnotify.h>
00041 #include <ktemporaryfile.h>
00042 
00043 #ifdef Q_OS_UNIX
00044 #include <utime.h>
00045 #endif
00046 #include <assert.h>
00047 
00048 #include <QtCore/QTimer>
00049 #include <QtCore/QFile>
00050 #include <sys/stat.h> // mode_t
00051 #include <QPointer>
00052 
00053 #include "job_p.h"
00054 
00055 using namespace KIO;
00056 
00057 //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
00058 #define REPORT_TIMEOUT 200
00059 
00060 enum DestinationState {
00061     DEST_NOT_STATED,
00062     DEST_IS_DIR,
00063     DEST_IS_FILE,
00064     DEST_DOESNT_EXIST
00065 };
00066 
00083 enum CopyJobState {
00084     STATE_STATING,
00085     STATE_RENAMING,
00086     STATE_LISTING,
00087     STATE_CREATING_DIRS,
00088     STATE_CONFLICT_CREATING_DIRS,
00089     STATE_COPYING_FILES,
00090     STATE_CONFLICT_COPYING_FILES,
00091     STATE_DELETING_DIRS,
00092     STATE_SETTING_DIR_ATTRIBUTES
00093 };
00094 
00096 class KIO::CopyJobPrivate: public KIO::JobPrivate
00097 {
00098 public:
00099     CopyJobPrivate(const KUrl::List& src, const KUrl& dest,
00100                    CopyJob::CopyMode mode, bool asMethod)
00101         : m_globalDest(dest)
00102         , m_globalDestinationState(DEST_NOT_STATED)
00103         , m_defaultPermissions(false)
00104         , m_bURLDirty(false)
00105         , m_mode(mode)
00106         , m_asMethod(asMethod)
00107         , destinationState(DEST_NOT_STATED)
00108         , state(STATE_STATING)
00109         , m_totalSize(0)
00110         , m_processedSize(0)
00111         , m_fileProcessedSize(0)
00112         , m_processedFiles(0)
00113         , m_processedDirs(0)
00114         , m_srcList(src)
00115         , m_currentStatSrc(m_srcList.constBegin())
00116         , m_bCurrentOperationIsLink(false)
00117         , m_bSingleFileCopy(false)
00118         , m_bOnlyRenames(mode==CopyJob::Move)
00119         , m_dest(dest)
00120         , m_bAutoRenameFiles(false)
00121         , m_bAutoRenameDirs(false)
00122         , m_bAutoSkipFiles( false )
00123         , m_bAutoSkipDirs( false )
00124         , m_bOverwriteAllFiles( false )
00125         , m_bOverwriteAllDirs( false )
00126         , m_conflictError(0)
00127         , m_reportTimer(0)
00128     {
00129     }
00130 
00131     // This is the dest URL that was initially given to CopyJob
00132     // It is copied into m_dest, which can be changed for a given src URL
00133     // (when using the RENAME dialog in slotResult),
00134     // and which will be reset for the next src URL.
00135     KUrl m_globalDest;
00136     // The state info about that global dest
00137     DestinationState m_globalDestinationState;
00138     // See setDefaultPermissions
00139     bool m_defaultPermissions;
00140     // Whether URLs changed (and need to be emitted by the next slotReport call)
00141     bool m_bURLDirty;
00142     // Used after copying all the files into the dirs, to set mtime (TODO: and permissions?)
00143     // after the copy is done
00144     QLinkedList<CopyInfo> m_directoriesCopied;
00145     QLinkedList<CopyInfo>::const_iterator m_directoriesCopiedIterator;
00146 
00147     CopyJob::CopyMode m_mode;
00148     bool m_asMethod;
00149     DestinationState destinationState;
00150     CopyJobState state;
00151     KIO::filesize_t m_totalSize;
00152     KIO::filesize_t m_processedSize;
00153     KIO::filesize_t m_fileProcessedSize;
00154     int m_processedFiles;
00155     int m_processedDirs;
00156     QList<CopyInfo> files;
00157     QList<CopyInfo> dirs;
00158     KUrl::List dirsToRemove;
00159     KUrl::List m_srcList;
00160     KUrl::List m_successSrcList; // Entries in m_srcList that have successfully been moved
00161     KUrl::List::const_iterator m_currentStatSrc;
00162     bool m_bCurrentSrcIsDir;
00163     bool m_bCurrentOperationIsLink;
00164     bool m_bSingleFileCopy;
00165     bool m_bOnlyRenames;
00166     KUrl m_dest;
00167     KUrl m_currentDest; // set during listing, used by slotEntries
00168     //
00169     QStringList m_skipList;
00170     QStringList m_overwriteList;
00171     bool m_bAutoRenameFiles;
00172     bool m_bAutoRenameDirs;
00173     bool m_bAutoSkipFiles;
00174     bool m_bAutoSkipDirs;
00175     bool m_bOverwriteAllFiles;
00176     bool m_bOverwriteAllDirs;
00177     int m_conflictError;
00178 
00179     QTimer *m_reportTimer;
00180 
00181     // The current src url being stat'ed or copied
00182     // During the stat phase, this is initially equal to *m_currentStatSrc but it can be resolved to a local file equivalent (#188903).
00183     KUrl m_currentSrcURL;
00184     KUrl m_currentDestURL;
00185 
00186     QSet<QString> m_parentDirs;
00187 
00188     void statCurrentSrc();
00189     void statNextSrc();
00190 
00191     // Those aren't slots but submethods for slotResult.
00192     void slotResultStating( KJob * job );
00193     void startListing( const KUrl & src );
00194     void slotResultCreatingDirs( KJob * job );
00195     void slotResultConflictCreatingDirs( KJob * job );
00196     void createNextDir();
00197     void slotResultCopyingFiles( KJob * job );
00198     void slotResultConflictCopyingFiles( KJob * job );
00199 //     KIO::Job* linkNextFile( const KUrl& uSource, const KUrl& uDest, bool overwrite );
00200     KIO::Job* linkNextFile( const KUrl& uSource, const KUrl& uDest, JobFlags flags );
00201     void copyNextFile();
00202     void slotResultDeletingDirs( KJob * job );
00203     void deleteNextDir();
00204     void sourceStated(const UDSEntry& entry, const KUrl& sourceUrl);
00205     void skip(const KUrl & sourceURL, bool isDir);
00206     void slotResultRenaming( KJob * job );
00207     void slotResultSettingDirAttributes( KJob * job );
00208     void setNextDirAttribute();
00209 
00210     void startRenameJob(const KUrl &slave_url);
00211     bool shouldOverwriteDir( const QString& path ) const;
00212     bool shouldOverwriteFile( const QString& path ) const;
00213     bool shouldSkip( const QString& path ) const;
00214     void skipSrc(bool isDir);
00215 
00216     void slotStart();
00217     void slotEntries( KIO::Job*, const KIO::UDSEntryList& list );
00218     void addCopyInfoFromUDSEntry(const UDSEntry& entry, const KUrl& srcUrl, bool srcIsDir, const KUrl& currentDest);
00222     void slotProcessedSize( KJob*, qulonglong data_size );
00227     void slotTotalSize( KJob*, qulonglong size );
00228 
00229     void slotReport();
00230 
00231     Q_DECLARE_PUBLIC(CopyJob)
00232 
00233     static inline CopyJob *newJob(const KUrl::List& src, const KUrl& dest,
00234                                   CopyJob::CopyMode mode, bool asMethod, JobFlags flags)
00235     {
00236         CopyJob *job = new CopyJob(*new CopyJobPrivate(src,dest,mode,asMethod));
00237         job->setUiDelegate(new JobUiDelegate);
00238         if (!(flags & HideProgressInfo))
00239             KIO::getJobTracker()->registerJob(job);
00240         if (flags & KIO::Overwrite) {
00241             job->d_func()->m_bOverwriteAllDirs = true;
00242             job->d_func()->m_bOverwriteAllFiles = true;
00243         }
00244         return job;
00245     }
00246 };
00247 
00248 CopyJob::CopyJob(CopyJobPrivate &dd)
00249     : Job(dd)
00250 {
00251     setProperty("destUrl", d_func()->m_dest.url());
00252     QTimer::singleShot(0, this, SLOT(slotStart()));
00253 }
00254 
00255 CopyJob::~CopyJob()
00256 {
00257 }
00258 
00259 KUrl::List CopyJob::srcUrls() const
00260 {
00261     return d_func()->m_srcList;
00262 }
00263 
00264 KUrl CopyJob::destUrl() const
00265 {
00266     return d_func()->m_dest;
00267 }
00268 
00269 void CopyJobPrivate::slotStart()
00270 {
00271     Q_Q(CopyJob);
00277     m_reportTimer = new QTimer(q);
00278 
00279     q->connect(m_reportTimer,SIGNAL(timeout()),q,SLOT(slotReport()));
00280     m_reportTimer->start(REPORT_TIMEOUT);
00281 
00282     // Stat the dest
00283     KIO::Job * job = KIO::stat( m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo );
00284     //kDebug(7007) << "CopyJob:stating the dest " << m_dest;
00285     q->addSubjob(job);
00286 }
00287 
00288 // For unit test purposes
00289 KIO_EXPORT bool kio_resolve_local_urls = true;
00290 
00291 void CopyJobPrivate::slotResultStating( KJob *job )
00292 {
00293     Q_Q(CopyJob);
00294     //kDebug(7007);
00295     // Was there an error while stating the src ?
00296     if (job->error() && destinationState != DEST_NOT_STATED )
00297     {
00298         const KUrl srcurl = static_cast<SimpleJob*>(job)->url();
00299         if ( !srcurl.isLocalFile() )
00300         {
00301             // Probably : src doesn't exist. Well, over some protocols (e.g. FTP)
00302             // this info isn't really reliable (thanks to MS FTP servers).
00303             // We'll assume a file, and try to download anyway.
00304             kDebug(7007) << "Error while stating source. Activating hack";
00305             q->removeSubjob( job );
00306             assert ( !q->hasSubjobs() ); // We should have only one job at a time ...
00307             struct CopyInfo info;
00308             info.permissions = (mode_t) -1;
00309             info.mtime = (time_t) -1;
00310             info.ctime = (time_t) -1;
00311             info.size = (KIO::filesize_t)-1;
00312             info.uSource = srcurl;
00313             info.uDest = m_dest;
00314             // Append filename or dirname to destination URL, if allowed
00315             if ( destinationState == DEST_IS_DIR && !m_asMethod )
00316                 info.uDest.addPath( srcurl.fileName() );
00317 
00318             files.append( info );
00319             statNextSrc();
00320             return;
00321         }
00322         // Local file. If stat fails, the file definitely doesn't exist.
00323         // yes, q->Job::, because we don't want to call our override
00324         q->Job::slotResult( job ); // will set the error and emit result(this)
00325         return;
00326     }
00327 
00328     // Keep copy of the stat result
00329     const UDSEntry entry = static_cast<StatJob*>(job)->statResult();
00330 
00331     if ( destinationState == DEST_NOT_STATED ) {
00332         const bool isGlobalDest = m_dest == m_globalDest;
00333         const bool isDir = entry.isDir();
00334         // we were stating the dest
00335         if (job->error()) {
00336             destinationState = DEST_DOESNT_EXIST;
00337             //kDebug(7007) << "dest does not exist";
00338         } else {
00339             // Treat symlinks to dirs as dirs here, so no test on isLink
00340             destinationState = isDir ? DEST_IS_DIR : DEST_IS_FILE;
00341             //kDebug(7007) << "dest is dir:" << isDir;
00342 
00343             const QString sLocalPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH );
00344             if ( !sLocalPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST ) {
00345                 m_dest = KUrl();
00346                 m_dest.setPath(sLocalPath);
00347                 if ( isGlobalDest )
00348                     m_globalDest = m_dest;
00349             }
00350         }
00351         if ( isGlobalDest )
00352             m_globalDestinationState = destinationState;
00353 
00354         q->removeSubjob( job );
00355         assert ( !q->hasSubjobs() );
00356 
00357         // After knowing what the dest is, we can start stat'ing the first src.
00358         statCurrentSrc();
00359     } else {
00360         sourceStated(entry, static_cast<SimpleJob*>(job)->url());
00361         q->removeSubjob( job );
00362     }
00363 }
00364 
00365 void CopyJobPrivate::sourceStated(const UDSEntry& entry, const KUrl& sourceUrl)
00366 {
00367     const QString sLocalPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH );
00368     const bool isDir = entry.isDir();
00369 
00370     // We were stating the current source URL
00371     // Is it a file or a dir ?
00372 
00373     // There 6 cases, and all end up calling addCopyInfoFromUDSEntry first :
00374     // 1 - src is a dir, destination is a directory,
00375     // slotEntries will append the source-dir-name to the destination
00376     // 2 - src is a dir, destination is a file -- will offer to overwrite, later on.
00377     // 3 - src is a dir, destination doesn't exist, then it's the destination dirname,
00378     // so slotEntries will use it as destination.
00379 
00380     // 4 - src is a file, destination is a directory,
00381     // slotEntries will append the filename to the destination.
00382     // 5 - src is a file, destination is a file, m_dest is the exact destination name
00383     // 6 - src is a file, destination doesn't exist, m_dest is the exact destination name
00384 
00385     KUrl srcurl;
00386     if (!sLocalPath.isEmpty() && destinationState != DEST_DOESNT_EXIST) {
00387         kDebug() << "Using sLocalPath. destinationState=" << destinationState;
00388         // Prefer the local path -- but only if we were able to stat() the dest.
00389         // Otherwise, renaming a desktop:/ url would copy from src=file to dest=desktop (#218719)
00390         srcurl.setPath(sLocalPath);
00391     } else {
00392         srcurl = sourceUrl;
00393     }
00394     addCopyInfoFromUDSEntry(entry, srcurl, false, m_dest);
00395 
00396     m_currentDest = m_dest;
00397     m_bCurrentSrcIsDir = false;
00398 
00399     if ( isDir
00400          // treat symlinks as files (no recursion)
00401          && !entry.isLink()
00402          && m_mode != CopyJob::Link ) // No recursion in Link mode either.
00403     {
00404         //kDebug(7007) << "Source is a directory";
00405 
00406         if (srcurl.isLocalFile()) {
00407             const QString parentDir = srcurl.toLocalFile(KUrl::RemoveTrailingSlash);
00408             m_parentDirs.insert(parentDir);
00409         }
00410 
00411         m_bCurrentSrcIsDir = true; // used by slotEntries
00412         if ( destinationState == DEST_IS_DIR ) // (case 1)
00413         {
00414             if ( !m_asMethod )
00415             {
00416                 // Use <desturl>/<directory_copied> as destination, from now on
00417                 QString directory = srcurl.fileName();
00418                 const QString sName = entry.stringValue( KIO::UDSEntry::UDS_NAME );
00419                 KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(srcurl);
00420                 if (fnu == KProtocolInfo::Name) {
00421                     if (!sName.isEmpty())
00422                         directory = sName;
00423                 } else if (fnu == KProtocolInfo::DisplayName) {
00424                     const QString dispName = entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME );
00425                     if (!dispName.isEmpty())
00426                         directory = dispName;
00427                     else if (!sName.isEmpty())
00428                         directory = sName;
00429                 }
00430                 m_currentDest.addPath( directory );
00431             }
00432         }
00433         else // (case 3)
00434         {
00435             // otherwise dest is new name for toplevel dir
00436             // so the destination exists, in fact, from now on.
00437             // (This even works with other src urls in the list, since the
00438             //  dir has effectively been created)
00439             destinationState = DEST_IS_DIR;
00440             if ( m_dest == m_globalDest )
00441                 m_globalDestinationState = destinationState;
00442         }
00443 
00444         startListing( srcurl );
00445     }
00446     else
00447     {
00448         //kDebug(7007) << "Source is a file (or a symlink), or we are linking -> no recursive listing";
00449 
00450         if (srcurl.isLocalFile()) {
00451             const QString parentDir = srcurl.directory(KUrl::ObeyTrailingSlash);
00452             m_parentDirs.insert(parentDir);
00453         }
00454 
00455         statNextSrc();
00456     }
00457 }
00458 
00459 bool CopyJob::doSuspend()
00460 {
00461     Q_D(CopyJob);
00462     d->slotReport();
00463     return Job::doSuspend();
00464 }
00465 
00466 void CopyJobPrivate::slotReport()
00467 {
00468     Q_Q(CopyJob);
00469     if ( q->isSuspended() )
00470         return;
00471     // If showProgressInfo was set, progressId() is > 0.
00472     switch (state) {
00473         case STATE_RENAMING:
00474             q->setTotalAmount(KJob::Files, m_srcList.count());
00475             // fall-through intended
00476         case STATE_COPYING_FILES:
00477             q->setProcessedAmount( KJob::Files, m_processedFiles );
00478             if (m_bURLDirty)
00479             {
00480                 // Only emit urls when they changed. This saves time, and fixes #66281
00481                 m_bURLDirty = false;
00482                 if (m_mode==CopyJob::Move)
00483                 {
00484                     emitMoving(q, m_currentSrcURL, m_currentDestURL);
00485                     emit q->moving( q, m_currentSrcURL, m_currentDestURL);
00486                 }
00487                 else if (m_mode==CopyJob::Link)
00488                 {
00489                     emitCopying( q, m_currentSrcURL, m_currentDestURL ); // we don't have a delegate->linking
00490                     emit q->linking( q, m_currentSrcURL.path(), m_currentDestURL );
00491                 }
00492                 else
00493                 {
00494                     emitCopying( q, m_currentSrcURL, m_currentDestURL );
00495                     emit q->copying( q, m_currentSrcURL, m_currentDestURL );
00496                 }
00497             }
00498             break;
00499 
00500         case STATE_CREATING_DIRS:
00501             q->setProcessedAmount( KJob::Directories, m_processedDirs );
00502             if (m_bURLDirty)
00503             {
00504                 m_bURLDirty = false;
00505                 emit q->creatingDir( q, m_currentDestURL );
00506                 emitCreatingDir( q, m_currentDestURL );
00507             }
00508             break;
00509 
00510         case STATE_STATING:
00511         case STATE_LISTING:
00512             if (m_bURLDirty)
00513             {
00514                 m_bURLDirty = false;
00515                 if (m_mode==CopyJob::Move)
00516                 {
00517                     emitMoving( q, m_currentSrcURL, m_currentDestURL );
00518                 }
00519                 else
00520                 {
00521                     emitCopying( q, m_currentSrcURL, m_currentDestURL );
00522                 }
00523             }
00524             q->setTotalAmount(KJob::Bytes, m_totalSize);
00525             q->setTotalAmount(KJob::Files, files.count());
00526             q->setTotalAmount(KJob::Directories, dirs.count());
00527             break;
00528 
00529         default:
00530             break;
00531     }
00532 }
00533 
00534 void CopyJobPrivate::slotEntries(KIO::Job* job, const UDSEntryList& list)
00535 {
00536     //Q_Q(CopyJob);
00537     UDSEntryList::ConstIterator it = list.constBegin();
00538     UDSEntryList::ConstIterator end = list.constEnd();
00539     for (; it != end; ++it) {
00540         const UDSEntry& entry = *it;
00541         addCopyInfoFromUDSEntry(entry, static_cast<SimpleJob *>(job)->url(), m_bCurrentSrcIsDir, m_currentDest);
00542     }
00543 }
00544 
00545 void CopyJobPrivate::addCopyInfoFromUDSEntry(const UDSEntry& entry, const KUrl& srcUrl, bool srcIsDir, const KUrl& currentDest)
00546 {
00547     struct CopyInfo info;
00548     info.permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, -1);
00549     info.mtime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
00550     info.ctime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
00551     info.size = (KIO::filesize_t) entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1);
00552     if (info.size != (KIO::filesize_t) -1)
00553         m_totalSize += info.size;
00554 
00555     // recursive listing, displayName can be a/b/c/d
00556     const QString fileName = entry.stringValue(KIO::UDSEntry::UDS_NAME);
00557     const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL);
00558     KUrl url;
00559     if (!urlStr.isEmpty())
00560         url = urlStr;
00561     QString localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
00562     const bool isDir = entry.isDir();
00563     info.linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
00564 
00565     if (fileName != QLatin1String("..") && fileName != QLatin1String(".")) {
00566         const bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty();
00567         if (!hasCustomURL) {
00568             // Make URL from displayName
00569             url = srcUrl;
00570             if (srcIsDir) { // Only if src is a directory. Otherwise uSource is fine as is
00571                 //kDebug(7007) << "adding path" << displayName;
00572                 url.addPath(fileName);
00573             }
00574         }
00575         //kDebug(7007) << "displayName=" << displayName << "url=" << url;
00576         if (!localPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST) {
00577             url = KUrl(localPath);
00578         }
00579 
00580         info.uSource = url;
00581         info.uDest = currentDest;
00582         //kDebug(7007) << "uSource=" << info.uSource << "uDest(1)=" << info.uDest;
00583         // Append filename or dirname to destination URL, if allowed
00584         if (destinationState == DEST_IS_DIR &&
00585              // "copy/move as <foo>" means 'foo' is the dest for the base srcurl
00586              // (passed here during stating) but not its children (during listing)
00587              (! (m_asMethod && state == STATE_STATING)))
00588         {
00589             QString destFileName;
00590         KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(url);
00591             if (hasCustomURL &&
00592                  fnu == KProtocolInfo::FromUrl) {
00593                 //destFileName = url.fileName(); // Doesn't work for recursive listing
00594                 // Count the number of prefixes used by the recursive listjob
00595                 int numberOfSlashes = fileName.count('/'); // don't make this a find()!
00596                 QString path = url.path();
00597                 int pos = 0;
00598                 for (int n = 0; n < numberOfSlashes + 1; ++n) {
00599                     pos = path.lastIndexOf('/', pos - 1);
00600                     if (pos == -1) { // error
00601                         kWarning(7007) << "kioslave bug: not enough slashes in UDS_URL" << path << "- looking for" << numberOfSlashes << "slashes";
00602                         break;
00603                     }
00604                 }
00605                 if (pos >= 0) {
00606                     destFileName = path.mid(pos + 1);
00607                 }
00608 
00609             } else if ( fnu == KProtocolInfo::Name ) { // destination filename taken from UDS_NAME
00610                 destFileName = fileName;
00611             } else { // from display name (with fallback to name)
00612                 const QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
00613                 destFileName = displayName.isEmpty() ? fileName : displayName;
00614             }
00615 
00616             // Here we _really_ have to add some filename to the dest.
00617             // Otherwise, we end up with e.g. dest=..../Desktop/ itself.
00618             // (This can happen when dropping a link to a webpage with no path)
00619             if (destFileName.isEmpty()) {
00620                 destFileName = KIO::encodeFileName(info.uSource.prettyUrl());
00621             }
00622 
00623             //kDebug(7007) << " adding destFileName=" << destFileName;
00624             info.uDest.addPath(destFileName);
00625         }
00626         //kDebug(7007) << " uDest(2)=" << info.uDest;
00627         //kDebug(7007) << " " << info.uSource << "->" << info.uDest;
00628         if (info.linkDest.isEmpty() && isDir && m_mode != CopyJob::Link) { // Dir
00629             dirs.append(info); // Directories
00630             if (m_mode == CopyJob::Move) {
00631                 dirsToRemove.append(info.uSource);
00632             }
00633         } else {
00634             files.append(info); // Files and any symlinks
00635         }
00636     }
00637 }
00638 
00639 void CopyJobPrivate::skipSrc(bool isDir)
00640 {
00641     m_dest = m_globalDest;
00642     destinationState = m_globalDestinationState;
00643     skip(*m_currentStatSrc, isDir);
00644     ++m_currentStatSrc;
00645     statCurrentSrc();
00646 }
00647 
00648 void CopyJobPrivate::statNextSrc()
00649 {
00650     /* Revert to the global destination, the one that applies to all source urls.
00651      * 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.
00652      * d->m_dest is /foo/b for b, but we have to revert to /d for item c and following.
00653      */
00654     m_dest = m_globalDest;
00655     destinationState = m_globalDestinationState;
00656     ++m_currentStatSrc;
00657     statCurrentSrc();
00658 }
00659 
00660 void CopyJobPrivate::statCurrentSrc()
00661 {
00662     Q_Q(CopyJob);
00663     if (m_currentStatSrc != m_srcList.constEnd()) {
00664         m_currentSrcURL = (*m_currentStatSrc);
00665         m_bURLDirty = true;
00666         if (m_mode == CopyJob::Link) {
00667             // Skip the "stating the source" stage, we don't need it for linking
00668             m_currentDest = m_dest;
00669             struct CopyInfo info;
00670             info.permissions = -1;
00671             info.mtime = (time_t) -1;
00672             info.ctime = (time_t) -1;
00673             info.size = (KIO::filesize_t)-1;
00674             info.uSource = m_currentSrcURL;
00675             info.uDest = m_currentDest;
00676             // Append filename or dirname to destination URL, if allowed
00677             if (destinationState == DEST_IS_DIR && !m_asMethod) {
00678                 if (
00679                     (m_currentSrcURL.protocol() == info.uDest.protocol()) &&
00680                     (m_currentSrcURL.host() == info.uDest.host()) &&
00681                     (m_currentSrcURL.port() == info.uDest.port()) &&
00682                     (m_currentSrcURL.user() == info.uDest.user()) &&
00683                     (m_currentSrcURL.pass() == info.uDest.pass()) ) {
00684                     // This is the case of creating a real symlink
00685                     info.uDest.addPath( m_currentSrcURL.fileName() );
00686                 } else {
00687                     // Different protocols, we'll create a .desktop file
00688                     // We have to change the extension anyway, so while we're at it,
00689                     // name the file like the URL
00690                     info.uDest.addPath(KIO::encodeFileName(m_currentSrcURL.prettyUrl()) + ".desktop");
00691                 }
00692             }
00693             files.append( info ); // Files and any symlinks
00694             statNextSrc(); // we could use a loop instead of a recursive call :)
00695             return;
00696         }
00697 
00698         // Let's see if we can skip stat'ing, for the case where a directory view has the info already
00699         const KFileItem cachedItem = KDirLister::cachedItemForUrl(m_currentSrcURL);
00700         KIO::UDSEntry entry;
00701         if (!cachedItem.isNull()) {
00702             entry = cachedItem.entry();
00703             if (destinationState != DEST_DOESNT_EXIST) { // only resolve src if we could resolve dest (#218719)
00704                 bool dummyIsLocal;
00705                 m_currentSrcURL = cachedItem.mostLocalUrl(dummyIsLocal); // #183585
00706             }
00707         }
00708 
00709         if (m_mode == CopyJob::Move && (
00710                 // Don't go renaming right away if we need a stat() to find out the destination filename
00711                 KProtocolManager::fileNameUsedForCopying(m_currentSrcURL) == KProtocolInfo::FromUrl ||
00712                 destinationState != DEST_IS_DIR || m_asMethod)
00713             ) {
00714            // If moving, before going for the full stat+[list+]copy+del thing, try to rename
00715            // The logic is pretty similar to FileCopyJobPrivate::slotStart()
00716            if ( (m_currentSrcURL.protocol() == m_dest.protocol()) &&
00717               (m_currentSrcURL.host() == m_dest.host()) &&
00718               (m_currentSrcURL.port() == m_dest.port()) &&
00719               (m_currentSrcURL.user() == m_dest.user()) &&
00720               (m_currentSrcURL.pass() == m_dest.pass()) )
00721            {
00722               startRenameJob( m_currentSrcURL );
00723               return;
00724            }
00725            else if ( m_currentSrcURL.isLocalFile() && KProtocolManager::canRenameFromFile( m_dest ) )
00726            {
00727               startRenameJob( m_dest );
00728               return;
00729            }
00730            else if ( m_dest.isLocalFile() && KProtocolManager::canRenameToFile( m_currentSrcURL ) )
00731            {
00732               startRenameJob( m_currentSrcURL );
00733               return;
00734            }
00735         }
00736 
00737         // if the file system doesn't support deleting, we do not even stat
00738         if (m_mode == CopyJob::Move && !KProtocolManager::supportsDeleting(m_currentSrcURL)) {
00739             QPointer<CopyJob> that = q;
00740             emit q->warning( q, buildErrorString(ERR_CANNOT_DELETE, m_currentSrcURL.prettyUrl()) );
00741             if (that)
00742                 statNextSrc(); // we could use a loop instead of a recursive call :)
00743             return;
00744         }
00745 
00746         m_bOnlyRenames = false;
00747 
00748         // Testing for entry.count()>0 here is not good enough; KFileItem inserts
00749         // entries for UDS_USER and UDS_GROUP even on initially empty UDSEntries (#192185)
00750         if (entry.contains(KIO::UDSEntry::UDS_NAME)) {
00751             kDebug(7007) << "fast path! found info about" << m_currentSrcURL << "in KDirLister";
00752             sourceStated(entry, m_currentSrcURL);
00753             return;
00754         }
00755 
00756         // Stat the next src url
00757         Job * job = KIO::stat( m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo );
00758         //kDebug(7007) << "KIO::stat on" << m_currentSrcURL;
00759         state = STATE_STATING;
00760         q->addSubjob(job);
00761         m_currentDestURL = m_dest;
00762         m_bURLDirty = true;
00763     }
00764     else
00765     {
00766         // Finished the stat'ing phase
00767         // First make sure that the totals were correctly emitted
00768         state = STATE_STATING;
00769         m_bURLDirty = true;
00770         slotReport();
00771         if (!dirs.isEmpty())
00772            emit q->aboutToCreate( q, dirs );
00773         if (!files.isEmpty())
00774            emit q->aboutToCreate( q, files );
00775         // Check if we are copying a single file
00776         m_bSingleFileCopy = ( files.count() == 1 && dirs.isEmpty() );
00777         // Then start copying things
00778         state = STATE_CREATING_DIRS;
00779         createNextDir();
00780     }
00781 }
00782 
00783 void CopyJobPrivate::startRenameJob( const KUrl& slave_url )
00784 {
00785     Q_Q(CopyJob);
00786 
00787     // Silence KDirWatch notifications, otherwise performance is horrible
00788     if (m_currentSrcURL.isLocalFile()) {
00789         const QString parentDir = m_currentSrcURL.directory(KUrl::ObeyTrailingSlash);
00790         if (!m_parentDirs.contains(parentDir)) {
00791             KDirWatch::self()->stopDirScan(parentDir);
00792             m_parentDirs.insert(parentDir);
00793         }
00794     }
00795 
00796     KUrl dest = m_dest;
00797     // Append filename or dirname to destination URL, if allowed
00798     if ( destinationState == DEST_IS_DIR && !m_asMethod )
00799         dest.addPath( m_currentSrcURL.fileName() );
00800     m_currentDestURL = dest;
00801     kDebug(7007) << m_currentSrcURL << "->" << dest << "trying direct rename first";
00802     state = STATE_RENAMING;
00803 
00804     struct CopyInfo info;
00805     info.permissions = -1;
00806     info.mtime = (time_t) -1;
00807     info.ctime = (time_t) -1;
00808     info.size = (KIO::filesize_t)-1;
00809     info.uSource = m_currentSrcURL;
00810     info.uDest = dest;
00811     QList<CopyInfo> files;
00812     files.append(info);
00813     emit q->aboutToCreate( q, files );
00814 
00815     KIO_ARGS << m_currentSrcURL << dest << (qint8) false /*no overwrite*/;
00816     SimpleJob * newJob = SimpleJobPrivate::newJobNoUi(slave_url, CMD_RENAME, packedArgs);
00817     Scheduler::setJobPriority(newJob, 1);
00818     q->addSubjob( newJob );
00819     if ( m_currentSrcURL.directory() != dest.directory() ) // For the user, moving isn't renaming. Only renaming is.
00820         m_bOnlyRenames = false;
00821 }
00822 
00823 void CopyJobPrivate::startListing( const KUrl & src )
00824 {
00825     Q_Q(CopyJob);
00826     state = STATE_LISTING;
00827     m_bURLDirty = true;
00828     ListJob * newjob = listRecursive(src, KIO::HideProgressInfo);
00829     newjob->setUnrestricted(true);
00830     q->connect(newjob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
00831                SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList)));
00832     q->addSubjob( newjob );
00833 }
00834 
00835 void CopyJobPrivate::skip(const KUrl & sourceUrl, bool isDir)
00836 {
00837     KUrl dir = sourceUrl;
00838     if (!isDir) {
00839         // Skipping a file: make sure not to delete the parent dir (#208418)
00840         dir.setPath(dir.directory());
00841     }
00842     while (dirsToRemove.removeAll(dir) > 0) {
00843         // Do not rely on rmdir() on the parent directories aborting.
00844         // Exclude the parent dirs explicitly.
00845         dir.setPath(dir.directory());
00846     }
00847 }
00848 
00849 bool CopyJobPrivate::shouldOverwriteDir( const QString& path ) const
00850 {
00851     if ( m_bOverwriteAllDirs )
00852         return true;
00853     return m_overwriteList.contains(path);
00854 }
00855 
00856 bool CopyJobPrivate::shouldOverwriteFile( const QString& path ) const
00857 {
00858     if ( m_bOverwriteAllFiles )
00859         return true;
00860     return m_overwriteList.contains(path);
00861 }
00862 
00863 bool CopyJobPrivate::shouldSkip( const QString& path ) const
00864 {
00865     Q_FOREACH(const QString& skipPath, m_skipList) {
00866         if ( path.startsWith(skipPath) )
00867             return true;
00868     }
00869     return false;
00870 }
00871 
00872 void CopyJobPrivate::slotResultCreatingDirs( KJob * job )
00873 {
00874     Q_Q(CopyJob);
00875     // The dir we are trying to create:
00876     QList<CopyInfo>::Iterator it = dirs.begin();
00877     // Was there an error creating a dir ?
00878     if ( job->error() )
00879     {
00880         m_conflictError = job->error();
00881         if ( (m_conflictError == ERR_DIR_ALREADY_EXIST)
00882              || (m_conflictError == ERR_FILE_ALREADY_EXIST) ) // can't happen?
00883         {
00884             KUrl oldURL = ((SimpleJob*)job)->url();
00885             // Should we skip automatically ?
00886             if ( m_bAutoSkipDirs ) {
00887                 // We don't want to copy files in this directory, so we put it on the skip list
00888                 m_skipList.append( oldURL.path( KUrl::AddTrailingSlash ) );
00889                 skip(oldURL, true);
00890                 dirs.erase( it ); // Move on to next dir
00891             } else {
00892                 // Did the user choose to overwrite already?
00893                 const QString destDir = (*it).uDest.path();
00894                 if ( shouldOverwriteDir( destDir ) ) { // overwrite => just skip
00895                     emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
00896                     dirs.erase( it ); // Move on to next dir
00897                 } else {
00898                     if (m_bAutoRenameDirs) {
00899                         QString oldPath = (*it).uDest.path(KUrl::AddTrailingSlash);
00900 
00901                         KUrl destDirectory((*it).uDest);
00902                         destDirectory.setPath(destDirectory.directory());
00903                         QString newName = KIO::RenameDialog::suggestName(destDirectory, (*it).uDest.fileName());
00904 
00905                         KUrl newUrl((*it).uDest);
00906                         newUrl.setFileName(newName);
00907 
00908                         emit q->renamed(q, (*it).uDest, newUrl); // for e.g. kpropsdlg
00909 
00910                         // Change the current one and strip the trailing '/'
00911                         (*it).uDest.setPath(newUrl.path(KUrl::RemoveTrailingSlash));
00912 
00913                         QString newPath = newUrl.path(KUrl::AddTrailingSlash); // With trailing slash
00914                         QList<CopyInfo>::Iterator renamedirit = it;
00915                         ++renamedirit;
00916                         // Change the name of subdirectories inside the directory
00917                         for(; renamedirit != dirs.end() ; ++renamedirit) {
00918                             QString path = (*renamedirit).uDest.path();
00919                             if (path.startsWith(oldPath)) {
00920                                 QString n = path;
00921                                 n.replace(0, oldPath.length(), newPath);
00922                                 kDebug(7007) << "dirs list:" << (*renamedirit).uSource.path()
00923                                               << "was going to be" << path
00924                                               << ", changed into" << n;
00925                                 (*renamedirit).uDest.setPath(n);
00926                             }
00927                         }
00928                         // Change filenames inside the directory
00929                         QList<CopyInfo>::Iterator renamefileit = files.begin();
00930                         for(; renamefileit != files.end() ; ++renamefileit) {
00931                             QString path = (*renamefileit).uDest.path();
00932                             if (path.startsWith(oldPath)) {
00933                                 QString n = path;
00934                                 n.replace(0, oldPath.length(), newPath);
00935                                 kDebug(7007) << "files list:" << (*renamefileit).uSource.path()
00936                                               << "was going to be" << path
00937                                               << ", changed into" << n;
00938                                 (*renamefileit).uDest.setPath(n);
00939                             }
00940                         }
00941                         if (!dirs.isEmpty()) {
00942                             emit q->aboutToCreate(q, dirs);
00943                         }
00944                         if (!files.isEmpty()) {
00945                             emit q->aboutToCreate(q, files);
00946                         }
00947 
00948                     }
00949                     else {
00950                         if (!q->isInteractive()) {
00951                             q->Job::slotResult(job); // will set the error and emit result(this)
00952                             return;
00953                         }
00954 
00955                         assert(((SimpleJob*)job)->url().url() == (*it).uDest.url());
00956                         q->removeSubjob(job);
00957                         assert (!q->hasSubjobs()); // We should have only one job at a time ...
00958 
00959                         // We need to stat the existing dir, to get its last-modification time
00960                         KUrl existingDest((*it).uDest);
00961                         SimpleJob * newJob = KIO::stat(existingDest, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
00962                         Scheduler::setJobPriority(newJob, 1);
00963                         kDebug(7007) << "KIO::stat for resolving conflict on " << existingDest;
00964                         state = STATE_CONFLICT_CREATING_DIRS;
00965                         q->addSubjob(newJob);
00966                         return; // Don't move to next dir yet !
00967                     }
00968                 }
00969             }
00970         }
00971         else
00972         {
00973             // Severe error, abort
00974             q->Job::slotResult( job ); // will set the error and emit result(this)
00975             return;
00976         }
00977     }
00978     else // no error : remove from list, to move on to next dir
00979     {
00980         //this is required for the undo feature
00981         emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true, false );
00982         m_directoriesCopied.append( *it );
00983         dirs.erase( it );
00984     }
00985 
00986     m_processedDirs++;
00987     //emit processedAmount( this, KJob::Directories, m_processedDirs );
00988     q->removeSubjob( job );
00989     assert( !q->hasSubjobs() ); // We should have only one job at a time ...
00990     createNextDir();
00991 }
00992 
00993 void CopyJobPrivate::slotResultConflictCreatingDirs( KJob * job )
00994 {
00995     Q_Q(CopyJob);
00996     // We come here after a conflict has been detected and we've stated the existing dir
00997 
00998     // The dir we were trying to create:
00999     QList<CopyInfo>::Iterator it = dirs.begin();
01000 
01001     const UDSEntry entry = ((KIO::StatJob*)job)->statResult();
01002 
01003     // Its modification time:
01004     const time_t destmtime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, -1 );
01005     const time_t destctime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_CREATION_TIME, -1 );
01006 
01007     const KIO::filesize_t destsize = entry.numberValue( KIO::UDSEntry::UDS_SIZE );
01008     const QString linkDest = entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST );
01009 
01010     q->removeSubjob( job );
01011     assert ( !q->hasSubjobs() ); // We should have only one job at a time ...
01012 
01013     // Always multi and skip (since there are files after that)
01014     RenameDialog_Mode mode = (RenameDialog_Mode)( M_MULTI | M_SKIP | M_ISDIR );
01015     // Overwrite only if the existing thing is a dir (no chance with a file)
01016     if ( m_conflictError == ERR_DIR_ALREADY_EXIST )
01017     {
01018         if( (*it).uSource == (*it).uDest ||
01019             ((*it).uSource.protocol() == (*it).uDest.protocol() &&
01020               (*it).uSource.path( KUrl::RemoveTrailingSlash ) == linkDest) )
01021           mode = (RenameDialog_Mode)( mode | M_OVERWRITE_ITSELF);
01022         else
01023           mode = (RenameDialog_Mode)( mode | M_OVERWRITE );
01024     }
01025 
01026     QString existingDest = (*it).uDest.path();
01027     QString newPath;
01028     if (m_reportTimer)
01029         m_reportTimer->stop();
01030     RenameDialog_Result r = q->ui()->askFileRename( q, i18n("Folder Already Exists"),
01031                                          (*it).uSource.url(),
01032                                          (*it).uDest.url(),
01033                                          mode, newPath,
01034                                          (*it).size, destsize,
01035                                          (*it).ctime, destctime,
01036                                          (*it).mtime, destmtime );
01037     if (m_reportTimer)
01038         m_reportTimer->start(REPORT_TIMEOUT);
01039     switch ( r ) {
01040         case R_CANCEL:
01041             q->setError( ERR_USER_CANCELED );
01042             q->emitResult();
01043             return;
01044         case R_AUTO_RENAME:
01045             m_bAutoRenameDirs = true;
01046             // fall through
01047         case R_RENAME:
01048         {
01049             QString oldPath = (*it).uDest.path( KUrl::AddTrailingSlash );
01050             KUrl newUrl( (*it).uDest );
01051             newUrl.setPath( newPath );
01052             emit q->renamed( q, (*it).uDest, newUrl ); // for e.g. kpropsdlg
01053 
01054             // Change the current one and strip the trailing '/'
01055             (*it).uDest.setPath( newUrl.path( KUrl::RemoveTrailingSlash ) );
01056             newPath = newUrl.path( KUrl::AddTrailingSlash ); // With trailing slash
01057             QList<CopyInfo>::Iterator renamedirit = it;
01058             ++renamedirit;
01059             // Change the name of subdirectories inside the directory
01060             for( ; renamedirit != dirs.end() ; ++renamedirit )
01061             {
01062                 QString path = (*renamedirit).uDest.path();
01063                 if ( path.startsWith( oldPath ) ) {
01064                     QString n = path;
01065                     n.replace( 0, oldPath.length(), newPath );
01066                     kDebug(7007) << "dirs list:" << (*renamedirit).uSource.path()
01067                                   << "was going to be" << path
01068                                   << ", changed into" << n;
01069                     (*renamedirit).uDest.setPath( n );
01070                 }
01071             }
01072             // Change filenames inside the directory
01073             QList<CopyInfo>::Iterator renamefileit = files.begin();
01074             for( ; renamefileit != files.end() ; ++renamefileit )
01075             {
01076                 QString path = (*renamefileit).uDest.path();
01077                 if ( path.startsWith( oldPath ) ) {
01078                     QString n = path;
01079                     n.replace( 0, oldPath.length(), newPath );
01080                     kDebug(7007) << "files list:" << (*renamefileit).uSource.path()
01081                                   << "was going to be" << path
01082                                   << ", changed into" << n;
01083                     (*renamefileit).uDest.setPath( n );
01084                 }
01085             }
01086             if (!dirs.isEmpty())
01087                 emit q->aboutToCreate( q, dirs );
01088             if (!files.isEmpty())
01089                 emit q->aboutToCreate( q, files );
01090         }
01091         break;
01092         case R_AUTO_SKIP:
01093             m_bAutoSkipDirs = true;
01094             // fall through
01095         case R_SKIP:
01096             m_skipList.append( existingDest );
01097             skip((*it).uSource, true);
01098             // Move on to next dir
01099             dirs.erase( it );
01100             m_processedDirs++;
01101             break;
01102         case R_OVERWRITE:
01103             m_overwriteList.append( existingDest );
01104             emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
01105             // Move on to next dir
01106             dirs.erase( it );
01107             m_processedDirs++;
01108             break;
01109         case R_OVERWRITE_ALL:
01110             m_bOverwriteAllDirs = true;
01111             emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
01112             // Move on to next dir
01113             dirs.erase( it );
01114             m_processedDirs++;
01115             break;
01116         default:
01117             assert( 0 );
01118     }
01119     state = STATE_CREATING_DIRS;
01120     //emit processedAmount( this, KJob::Directories, m_processedDirs );
01121     createNextDir();
01122 }
01123 
01124 void CopyJobPrivate::createNextDir()
01125 {
01126     Q_Q(CopyJob);
01127     KUrl udir;
01128     if ( !dirs.isEmpty() )
01129     {
01130         // Take first dir to create out of list
01131         QList<CopyInfo>::Iterator it = dirs.begin();
01132         // Is this URL on the skip list or the overwrite list ?
01133         while( it != dirs.end() && udir.isEmpty() )
01134         {
01135             const QString dir = (*it).uDest.path();
01136             if ( shouldSkip( dir ) ) {
01137                 dirs.erase( it );
01138                 it = dirs.begin();
01139             } else
01140                 udir = (*it).uDest;
01141         }
01142     }
01143     if ( !udir.isEmpty() ) // any dir to create, finally ?
01144     {
01145         // Create the directory - with default permissions so that we can put files into it
01146         // TODO : change permissions once all is finished; but for stuff coming from CDROM it sucks...
01147         KIO::SimpleJob *newjob = KIO::mkdir( udir, -1 );
01148         Scheduler::setJobPriority(newjob, 1);
01149         if (shouldOverwriteFile(udir.path())) { // if we are overwriting an existing file or symlink
01150             newjob->addMetaData("overwrite", "true");
01151         }
01152 
01153         m_currentDestURL = udir;
01154         m_bURLDirty = true;
01155 
01156         q->addSubjob(newjob);
01157         return;
01158     }
01159     else // we have finished creating dirs
01160     {
01161         q->setProcessedAmount( KJob::Directories, m_processedDirs ); // make sure final number appears
01162 
01163         if (m_mode == CopyJob::Move) {
01164             // Now we know which dirs hold the files we're going to delete.
01165             // To speed things up and prevent double-notification, we disable KDirWatch
01166             // on those dirs temporarily (using KDirWatch::self, that's the instanced
01167             // used by e.g. kdirlister).
01168             for ( QSet<QString>::const_iterator it = m_parentDirs.constBegin() ; it != m_parentDirs.constEnd() ; ++it )
01169                 KDirWatch::self()->stopDirScan( *it );
01170         }
01171 
01172         state = STATE_COPYING_FILES;
01173         m_processedFiles++; // Ralf wants it to start at 1, not 0
01174         copyNextFile();
01175     }
01176 }
01177 
01178 void CopyJobPrivate::slotResultCopyingFiles( KJob * job )
01179 {
01180     Q_Q(CopyJob);
01181     // The file we were trying to copy:
01182     QList<CopyInfo>::Iterator it = files.begin();
01183     if ( job->error() )
01184     {
01185         // Should we skip automatically ?
01186         if ( m_bAutoSkipFiles )
01187         {
01188             skip((*it).uSource, false);
01189             m_fileProcessedSize = (*it).size;
01190             files.erase( it ); // Move on to next file
01191         }
01192         else
01193         {
01194             m_conflictError = job->error(); // save for later
01195             // Existing dest ?
01196             if ( ( m_conflictError == ERR_FILE_ALREADY_EXIST )
01197                  || ( m_conflictError == ERR_DIR_ALREADY_EXIST )
01198                  || ( m_conflictError == ERR_IDENTICAL_FILES ) )
01199             {
01200                 if (m_bAutoRenameFiles) {
01201                     KUrl destDirectory((*it).uDest);
01202                     destDirectory.setPath(destDirectory.directory());
01203                     const QString newName = KIO::RenameDialog::suggestName(destDirectory, (*it).uDest.fileName());
01204 
01205                     KUrl newUrl((*it).uDest);
01206                     newUrl.setFileName(newName);
01207 
01208                     emit q->renamed(q, (*it).uDest, newUrl); // for e.g. kpropsdlg
01209                     (*it).uDest = newUrl;
01210 
01211                     QList<CopyInfo> files;
01212                     files.append(*it);
01213                     emit q->aboutToCreate(q, files);
01214                 }
01215                 else {
01216                     if ( !q->isInteractive() ) {
01217                         q->Job::slotResult( job ); // will set the error and emit result(this)
01218                         return;
01219                     }
01220 
01221                     q->removeSubjob(job);
01222                     assert (!q->hasSubjobs());
01223                     // We need to stat the existing file, to get its last-modification time
01224                     KUrl existingFile((*it).uDest);
01225                     SimpleJob * newJob = KIO::stat(existingFile, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
01226                     Scheduler::setJobPriority(newJob, 1);
01227                     kDebug(7007) << "KIO::stat for resolving conflict on " << existingFile;
01228                     state = STATE_CONFLICT_COPYING_FILES;
01229                     q->addSubjob(newJob);
01230                     return; // Don't move to next file yet !
01231                 }
01232             }
01233             else
01234             {
01235                 if ( m_bCurrentOperationIsLink && qobject_cast<KIO::DeleteJob*>( job ) )
01236                 {
01237                     // Very special case, see a few lines below
01238                     // We are deleting the source of a symlink we successfully moved... ignore error
01239                     m_fileProcessedSize = (*it).size;
01240                     files.erase( it );
01241                 } else {
01242                     if ( !q->isInteractive() ) {
01243                         q->Job::slotResult( job ); // will set the error and emit result(this)
01244                         return;
01245                     }
01246 
01247                     // Go directly to the conflict resolution, there is nothing to stat
01248                     slotResultConflictCopyingFiles( job );
01249                     return;
01250                 }
01251             }
01252         }
01253     } else // no error
01254     {
01255         // Special case for moving links. That operation needs two jobs, unlike others.
01256         if ( m_bCurrentOperationIsLink && m_mode == CopyJob::Move
01257              && !qobject_cast<KIO::DeleteJob *>( job ) // Deleting source not already done
01258              )
01259         {
01260             q->removeSubjob( job );
01261             assert ( !q->hasSubjobs() );
01262             // The only problem with this trick is that the error handling for this del operation
01263             // is not going to be right... see 'Very special case' above.
01264             KIO::Job * newjob = KIO::del( (*it).uSource, HideProgressInfo );
01265             q->addSubjob( newjob );
01266             return; // Don't move to next file yet !
01267         }
01268 
01269         if ( m_bCurrentOperationIsLink )
01270         {
01271             QString target = ( m_mode == CopyJob::Link ? (*it).uSource.path() : (*it).linkDest );
01272             //required for the undo feature
01273             emit q->copyingLinkDone( q, (*it).uSource, target, (*it).uDest );
01274         }
01275         else {
01276             //required for the undo feature
01277             emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, false, false );
01278             if (m_mode == CopyJob::Move)
01279                 org::kde::KDirNotify::emitFileMoved( (*it).uSource.url(), (*it).uDest.url() );
01280             m_successSrcList.append((*it).uSource);
01281         }
01282         // remove from list, to move on to next file
01283         files.erase( it );
01284     }
01285     m_processedFiles++;
01286 
01287     // clear processed size for last file and add it to overall processed size
01288     m_processedSize += m_fileProcessedSize;
01289     m_fileProcessedSize = 0;
01290 
01291     //kDebug(7007) << files.count() << "files remaining";
01292 
01293     // Merge metadata from subjob
01294     KIO::Job* kiojob = dynamic_cast<KIO::Job*>(job);
01295     Q_ASSERT(kiojob);
01296     m_incomingMetaData += kiojob->metaData();
01297     q->removeSubjob( job );
01298     assert( !q->hasSubjobs() ); // We should have only one job at a time ...
01299     copyNextFile();
01300 }
01301 
01302 void CopyJobPrivate::slotResultConflictCopyingFiles( KJob * job )
01303 {
01304     Q_Q(CopyJob);
01305     // We come here after a conflict has been detected and we've stated the existing file
01306     // The file we were trying to create:
01307     QList<CopyInfo>::Iterator it = files.begin();
01308 
01309     RenameDialog_Result res;
01310     QString newPath;
01311 
01312     if (m_reportTimer)
01313         m_reportTimer->stop();
01314 
01315     if ( ( m_conflictError == ERR_FILE_ALREADY_EXIST )
01316          || ( m_conflictError == ERR_DIR_ALREADY_EXIST )
01317          || ( m_conflictError == ERR_IDENTICAL_FILES ) )
01318     {
01319         // Its modification time:
01320         const UDSEntry entry = ((KIO::StatJob*)job)->statResult();
01321 
01322         const time_t destmtime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, -1 );
01323         const time_t destctime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_CREATION_TIME, -1 );
01324         const KIO::filesize_t destsize = entry.numberValue( KIO::UDSEntry::UDS_SIZE );
01325         const QString linkDest = entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST );
01326 
01327         // Offer overwrite only if the existing thing is a file
01328         // If src==dest, use "overwrite-itself"
01329         RenameDialog_Mode mode;
01330         bool isDir = true;
01331 
01332         if( m_conflictError == ERR_DIR_ALREADY_EXIST )
01333             mode = M_ISDIR;
01334         else
01335         {
01336             if ( (*it).uSource == (*it).uDest  ||
01337                  ((*it).uSource.protocol() == (*it).uDest.protocol() &&
01338                    (*it).uSource.path( KUrl::RemoveTrailingSlash ) == linkDest) )
01339                 mode = M_OVERWRITE_ITSELF;
01340             else
01341                 mode = M_OVERWRITE;
01342             isDir = false;
01343         }
01344 
01345         if ( !m_bSingleFileCopy )
01346             mode = (RenameDialog_Mode) ( mode | M_MULTI | M_SKIP );
01347 
01348         res = q->ui()->askFileRename( q, !isDir ?
01349                                    i18n("File Already Exists") : i18n("Already Exists as Folder"),
01350                                    (*it).uSource.url(),
01351                                    (*it).uDest.url(),
01352                                    mode, newPath,
01353                                    (*it).size, destsize,
01354                                    (*it).ctime, destctime,
01355                                    (*it).mtime, destmtime );
01356 
01357     }
01358     else
01359     {
01360         if ( job->error() == ERR_USER_CANCELED )
01361             res = R_CANCEL;
01362         else if ( !q->isInteractive() ) {
01363             q->Job::slotResult( job ); // will set the error and emit result(this)
01364             return;
01365         }
01366         else
01367         {
01368             SkipDialog_Result skipResult = q->ui()->askSkip( q, files.count() > 1,
01369                                                           job->errorString() );
01370 
01371             // Convert the return code from SkipDialog into a RenameDialog code
01372             res = ( skipResult == S_SKIP ) ? R_SKIP :
01373                          ( skipResult == S_AUTO_SKIP ) ? R_AUTO_SKIP :
01374                                         R_CANCEL;
01375         }
01376     }
01377 
01378     if (m_reportTimer)
01379         m_reportTimer->start(REPORT_TIMEOUT);
01380 
01381     q->removeSubjob( job );
01382     assert ( !q->hasSubjobs() );
01383     switch ( res ) {
01384         case R_CANCEL:
01385             q->setError( ERR_USER_CANCELED );
01386             q->emitResult();
01387             return;
01388         case R_AUTO_RENAME:
01389             m_bAutoRenameFiles = true;
01390             // fall through
01391         case R_RENAME:
01392         {
01393             KUrl newUrl( (*it).uDest );
01394             newUrl.setPath( newPath );
01395             emit q->renamed( q, (*it).uDest, newUrl ); // for e.g. kpropsdlg
01396             (*it).uDest = newUrl;
01397 
01398             QList<CopyInfo> files;
01399             files.append(*it);
01400             emit q->aboutToCreate( q, files );
01401         }
01402         break;
01403         case R_AUTO_SKIP:
01404             m_bAutoSkipFiles = true;
01405             // fall through
01406         case R_SKIP:
01407             // Move on to next file
01408             skip((*it).uSource, false);
01409             m_processedSize += (*it).size;
01410             files.erase( it );
01411             m_processedFiles++;
01412             break;
01413        case R_OVERWRITE_ALL:
01414             m_bOverwriteAllFiles = true;
01415             break;
01416         case R_OVERWRITE:
01417             // Add to overwrite list, so that copyNextFile knows to overwrite
01418             m_overwriteList.append( (*it).uDest.path() );
01419             break;
01420         default:
01421             assert( 0 );
01422     }
01423     state = STATE_COPYING_FILES;
01424     copyNextFile();
01425 }
01426 
01427 KIO::Job* CopyJobPrivate::linkNextFile( const KUrl& uSource, const KUrl& uDest, JobFlags flags )
01428 {
01429     //kDebug(7007) << "Linking";
01430     if (
01431         (uSource.protocol() == uDest.protocol()) &&
01432         (uSource.host() == uDest.host()) &&
01433         (uSource.port() == uDest.port()) &&
01434         (uSource.user() == uDest.user()) &&
01435         (uSource.pass() == uDest.pass()) )
01436     {
01437         // This is the case of creating a real symlink
01438         KIO::SimpleJob *newJob = KIO::symlink( uSource.path(), uDest, flags|HideProgressInfo /*no GUI*/ );
01439         Scheduler::setJobPriority(newJob, 1);
01440         //kDebug(7007) << "Linking target=" << uSource.path() << "link=" << uDest;
01441         //emit linking( this, uSource.path(), uDest );
01442         m_bCurrentOperationIsLink = true;
01443         m_currentSrcURL=uSource;
01444         m_currentDestURL=uDest;
01445         m_bURLDirty = true;
01446         //Observer::self()->slotCopying( this, uSource, uDest ); // should be slotLinking perhaps
01447         return newJob;
01448     } else {
01449         Q_Q(CopyJob);
01450         //kDebug(7007) << "Linking URL=" << uSource << "link=" << uDest;
01451         if ( uDest.isLocalFile() ) {
01452             // if the source is a devices url, handle it a littlebit special
01453 
01454             QString path = uDest.toLocalFile();
01455             //kDebug(7007) << "path=" << path;
01456             QFile f( path );
01457             if ( f.open( QIODevice::ReadWrite ) )
01458             {
01459                 f.close();
01460                 KDesktopFile desktopFile( path );
01461                 KConfigGroup config = desktopFile.desktopGroup();
01462                 KUrl url = uSource;
01463                 url.setPass( "" );
01464                 config.writePathEntry( "URL", url.url() );
01465                 config.writeEntry( "Name", url.url() );
01466                 config.writeEntry( "Type", QString::fromLatin1("Link") );
01467                 QString protocol = uSource.protocol();
01468                 if ( protocol == QLatin1String("ftp") )
01469                     config.writeEntry( "Icon", QString::fromLatin1("folder-remote") );
01470                 else if ( protocol == QLatin1String("http") )
01471                     config.writeEntry( "Icon", QString::fromLatin1("text-html") );
01472                 else if ( protocol == QLatin1String("info") )
01473                     config.writeEntry( "Icon", QString::fromLatin1("text-x-texinfo") );
01474                 else if ( protocol == QLatin1String("mailto") )   // sven:
01475                     config.writeEntry( "Icon", QString::fromLatin1("internet-mail") ); // added mailto: support
01476                 else
01477                     config.writeEntry( "Icon", QString::fromLatin1("unknown") );
01478                 config.sync();
01479                 files.erase( files.begin() ); // done with this one, move on
01480                 m_processedFiles++;
01481                 //emit processedAmount( this, KJob::Files, m_processedFiles );
01482                 copyNextFile();
01483                 return 0;
01484             }
01485             else
01486             {
01487                 kDebug(7007) << "ERR_CANNOT_OPEN_FOR_WRITING";
01488                 q->setError( ERR_CANNOT_OPEN_FOR_WRITING );
01489                 q->setErrorText( uDest.toLocalFile() );
01490                 q->emitResult();
01491                 return 0;
01492             }
01493         } else {
01494             // Todo: not show "link" on remote dirs if the src urls are not from the same protocol+host+...
01495             q->setError( ERR_CANNOT_SYMLINK );
01496             q->setErrorText( uDest.prettyUrl() );
01497             q->emitResult();
01498             return 0;
01499         }
01500     }
01501 }
01502 
01503 void CopyJobPrivate::copyNextFile()
01504 {
01505     Q_Q(CopyJob);
01506     bool bCopyFile = false;
01507     //kDebug(7007);
01508     // Take the first file in the list
01509     QList<CopyInfo>::Iterator it = files.begin();
01510     // Is this URL on the skip list ?
01511     while (it != files.end() && !bCopyFile)
01512     {
01513         const QString destFile = (*it).uDest.path();
01514         bCopyFile = !shouldSkip( destFile );
01515         if ( !bCopyFile ) {
01516             files.erase( it );
01517             it = files.begin();
01518         }
01519     }
01520 
01521     if (bCopyFile) // any file to create, finally ?
01522     {
01523         const KUrl& uSource = (*it).uSource;
01524         const KUrl& uDest = (*it).uDest;
01525         // Do we set overwrite ?
01526         bool bOverwrite;
01527         const QString destFile = uDest.path();
01528         // kDebug(7007) << "copying" << destFile;
01529         if ( uDest == uSource )
01530             bOverwrite = false;
01531         else
01532             bOverwrite = shouldOverwriteFile( destFile );
01533 
01534         m_bCurrentOperationIsLink = false;
01535         KIO::Job * newjob = 0;
01536         if ( m_mode == CopyJob::Link ) {
01537             // User requested that a symlink be made
01538             const JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
01539             newjob = linkNextFile(uSource, uDest, flags);
01540             if (!newjob)
01541                 return;
01542         } else if ( !(*it).linkDest.isEmpty() &&
01543                   (uSource.protocol() == uDest.protocol()) &&
01544                   (uSource.host() == uDest.host()) &&
01545                   (uSource.port() == uDest.port()) &&
01546                   (uSource.user() == uDest.user()) &&
01547                   (uSource.pass() == uDest.pass()))
01548             // Copying a symlink - only on the same protocol/host/etc. (#5601, downloading an FTP file through its link),
01549         {
01550             const JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
01551             KIO::SimpleJob *newJob = KIO::symlink( (*it).linkDest, uDest, flags | HideProgressInfo /*no GUI*/ );
01552             Scheduler::setJobPriority(newJob, 1);
01553             newjob = newJob;
01554             //kDebug(7007) << "Linking target=" << (*it).linkDest << "link=" << uDest;
01555             m_currentSrcURL = KUrl( (*it).linkDest );
01556             m_currentDestURL = uDest;
01557             m_bURLDirty = true;
01558             //emit linking( this, (*it).linkDest, uDest );
01559             //Observer::self()->slotCopying( this, m_currentSrcURL, uDest ); // should be slotLinking perhaps
01560             m_bCurrentOperationIsLink = true;
01561             // NOTE: if we are moving stuff, the deletion of the source will be done in slotResultCopyingFiles
01562         } else if (m_mode == CopyJob::Move) // Moving a file
01563         {
01564             JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
01565             KIO::FileCopyJob * moveJob = KIO::file_move( uSource, uDest, (*it).permissions, flags | HideProgressInfo/*no GUI*/ );
01566             moveJob->setSourceSize( (*it).size );
01567             if ((*it).mtime != -1) {
01568                 moveJob->setModificationTime( QDateTime::fromTime_t( (*it).mtime ) ); // #55804
01569             }
01570             newjob = moveJob;
01571             //kDebug(7007) << "Moving" << uSource << "to" << uDest;
01572             //emit moving( this, uSource, uDest );
01573             m_currentSrcURL=uSource;
01574             m_currentDestURL=uDest;
01575             m_bURLDirty = true;
01576             //Observer::self()->slotMoving( this, uSource, uDest );
01577         }
01578         else // Copying a file
01579         {
01580             // If source isn't local and target is local, we ignore the original permissions
01581             // Otherwise, files downloaded from HTTP end up with -r--r--r--
01582             bool remoteSource = !KProtocolManager::supportsListing(uSource);
01583             int permissions = (*it).permissions;
01584             if ( m_defaultPermissions || ( remoteSource && uDest.isLocalFile() ) )
01585                 permissions = -1;
01586             JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
01587             KIO::FileCopyJob * copyJob = KIO::file_copy( uSource, uDest, permissions, flags | HideProgressInfo/*no GUI*/ );
01588             copyJob->setParentJob( q ); // in case of rename dialog
01589             copyJob->setSourceSize( (*it).size );
01590             if ((*it).mtime != -1) {
01591                 copyJob->setModificationTime( QDateTime::fromTime_t( (*it).mtime ) );
01592             }
01593             newjob = copyJob;
01594             //kDebug(7007) << "Copying" << uSource << "to" << uDest;
01595             m_currentSrcURL=uSource;
01596             m_currentDestURL=uDest;
01597             m_bURLDirty = true;
01598         }
01599         q->addSubjob(newjob);
01600         q->connect( newjob, SIGNAL( processedSize( KJob*, qulonglong ) ),
01601                     SLOT( slotProcessedSize( KJob*, qulonglong ) ) );
01602         q->connect( newjob, SIGNAL( totalSize( KJob*, qulonglong ) ),
01603                     SLOT( slotTotalSize( KJob*, qulonglong ) ) );
01604     }
01605     else
01606     {
01607         // We're done
01608         //kDebug(7007) << "copyNextFile finished";
01609         deleteNextDir();
01610     }
01611 }
01612 
01613 void CopyJobPrivate::deleteNextDir()
01614 {
01615     Q_Q(CopyJob);
01616     if ( m_mode == CopyJob::Move && !dirsToRemove.isEmpty() ) // some dirs to delete ?
01617     {
01618         state = STATE_DELETING_DIRS;
01619         m_bURLDirty = true;
01620         // Take first dir to delete out of list - last ones first !
01621         KUrl::List::Iterator it = --dirsToRemove.end();
01622         SimpleJob *job = KIO::rmdir( *it );
01623         Scheduler::setJobPriority(job, 1);
01624         dirsToRemove.erase(it);
01625         q->addSubjob( job );
01626     }
01627     else
01628     {
01629         // This step is done, move on
01630         state = STATE_SETTING_DIR_ATTRIBUTES;
01631         m_directoriesCopiedIterator = m_directoriesCopied.constBegin();
01632         setNextDirAttribute();
01633     }
01634 }
01635 
01636 void CopyJobPrivate::setNextDirAttribute()
01637 {
01638     Q_Q(CopyJob);
01639     while (m_directoriesCopiedIterator != m_directoriesCopied.constEnd() &&
01640            (*m_directoriesCopiedIterator).mtime == -1) {
01641         ++m_directoriesCopiedIterator;
01642     }
01643     if ( m_directoriesCopiedIterator != m_directoriesCopied.constEnd() ) {
01644         const KUrl url = (*m_directoriesCopiedIterator).uDest;
01645         const time_t mtime = (*m_directoriesCopiedIterator).mtime;
01646         const QDateTime dt = QDateTime::fromTime_t(mtime);
01647         ++m_directoriesCopiedIterator;
01648 
01649         KIO::SimpleJob *job = KIO::setModificationTime( url, dt );
01650         Scheduler::setJobPriority(job, 1);
01651         q->addSubjob( job );
01652 
01653 
01654 #if 0 // ifdef Q_OS_UNIX
01655         // TODO: can be removed now. Or reintroduced as a fast path for local files
01656         // if launching even more jobs as done above is a performance problem.
01657         //
01658         QLinkedList<CopyInfo>::const_iterator it = m_directoriesCopied.constBegin();
01659         for ( ; it != m_directoriesCopied.constEnd() ; ++it ) {
01660             const KUrl& url = (*it).uDest;
01661             if ( url.isLocalFile() && (*it).mtime != (time_t)-1 ) {
01662                 KDE_struct_stat statbuf;
01663                 if (KDE::lstat(url.path(), &statbuf) == 0) {
01664                     struct utimbuf utbuf;
01665                     utbuf.actime = statbuf.st_atime; // access time, unchanged
01666                     utbuf.modtime = (*it).mtime; // modification time
01667                     utime( path, &utbuf );
01668                 }
01669 
01670             }
01671         }
01672         m_directoriesCopied.clear();
01673         // but then we need to jump to the else part below. Maybe with a recursive call?
01674 #endif
01675     } else {
01676         if (m_reportTimer)
01677             m_reportTimer->stop();
01678         --m_processedFiles; // undo the "start at 1" hack
01679         slotReport(); // display final numbers, important if progress dialog stays up
01680 
01681         q->emitResult();
01682     }
01683 }
01684 
01685 void CopyJob::emitResult()
01686 {
01687     Q_D(CopyJob);
01688     // Before we go, tell the world about the changes that were made.
01689     // Even if some error made us abort midway, we might still have done
01690     // part of the job so we better update the views! (#118583)
01691     if (!d->m_bOnlyRenames) {
01692         KUrl url(d->m_globalDest);
01693         if (d->m_globalDestinationState != DEST_IS_DIR || d->m_asMethod)
01694             url.setPath(url.directory());
01695         //kDebug(7007) << "KDirNotify'ing FilesAdded" << url;
01696         org::kde::KDirNotify::emitFilesAdded( url.url() );
01697 
01698         if (d->m_mode == CopyJob::Move && !d->m_successSrcList.isEmpty()) {
01699             kDebug(7007) << "KDirNotify'ing FilesRemoved" << d->m_successSrcList.toStringList();
01700             org::kde::KDirNotify::emitFilesRemoved(d->m_successSrcList.toStringList());
01701         }
01702 
01703         // Re-enable watching on the dirs that held the deleted files
01704         if (d->m_mode == CopyJob::Move) {
01705             for (QSet<QString>::const_iterator it = d->m_parentDirs.constBegin() ; it != d->m_parentDirs.constEnd() ; ++it)
01706                 KDirWatch::self()->restartDirScan( *it );
01707         }
01708     }
01709     Job::emitResult();
01710 }
01711 
01712 void CopyJobPrivate::slotProcessedSize( KJob*, qulonglong data_size )
01713 {
01714   Q_Q(CopyJob);
01715   //kDebug(7007) << data_size;
01716   m_fileProcessedSize = data_size;
01717   q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize);
01718 
01719   if ( m_processedSize + m_fileProcessedSize > m_totalSize )
01720   {
01721     // Example: download any attachment from bugs.kde.org
01722     m_totalSize = m_processedSize + m_fileProcessedSize;
01723     //kDebug(7007) << "Adjusting m_totalSize to" << m_totalSize;
01724     q->setTotalAmount(KJob::Bytes, m_totalSize); // safety
01725   }
01726   //kDebug(7007) << "emit processedSize" << (unsigned long) (m_processedSize + m_fileProcessedSize);
01727   q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize);
01728 }
01729 
01730 void CopyJobPrivate::slotTotalSize( KJob*, qulonglong size )
01731 {
01732   Q_Q(CopyJob);
01733   //kDebug(7007) << size;
01734   // Special case for copying a single file
01735   // This is because some protocols don't implement stat properly
01736   // (e.g. HTTP), and don't give us a size in some cases (redirection)
01737   // so we'd rather rely on the size given for the transfer
01738   if ( m_bSingleFileCopy && size != m_totalSize)
01739   {
01740     //kDebug(7007) << "slotTotalSize: updating totalsize to" << size;
01741     m_totalSize = size;
01742     q->setTotalAmount(KJob::Bytes, size);
01743   }
01744 }
01745 
01746 void CopyJobPrivate::slotResultDeletingDirs( KJob * job )
01747 {
01748     Q_Q(CopyJob);
01749     if (job->error()) {
01750         // Couldn't remove directory. Well, perhaps it's not empty
01751         // because the user pressed Skip for a given file in it.
01752         // Let's not display "Could not remove dir ..." for each of those dir !
01753     } else {
01754         m_successSrcList.append(static_cast<KIO::SimpleJob*>(job)->url());
01755     }
01756     q->removeSubjob( job );
01757     assert( !q->hasSubjobs() );
01758     deleteNextDir();
01759 }
01760 
01761 void CopyJobPrivate::slotResultSettingDirAttributes( KJob * job )
01762 {
01763     Q_Q(CopyJob);
01764     if (job->error())
01765     {
01766         // Couldn't set directory attributes. Ignore the error, it can happen
01767         // with inferior file systems like VFAT.
01768         // Let's not display warnings for each dir like "cp -a" does.
01769     }
01770     q->removeSubjob( job );
01771     assert( !q->hasSubjobs() );
01772     setNextDirAttribute();
01773 }
01774 
01775 // We were trying to do a direct renaming, before even stat'ing
01776 void CopyJobPrivate::slotResultRenaming( KJob* job )
01777 {
01778     Q_Q(CopyJob);
01779     int err = job->error();
01780     const QString errText = job->errorText();
01781     // Merge metadata from subjob
01782     KIO::Job* kiojob = dynamic_cast<KIO::Job*>(job);
01783     Q_ASSERT(kiojob);
01784     m_incomingMetaData += kiojob->metaData();
01785     q->removeSubjob( job );
01786     assert ( !q->hasSubjobs() );
01787     // Determine dest again
01788     KUrl dest = m_dest;
01789     if ( destinationState == DEST_IS_DIR && !m_asMethod )
01790         dest.addPath( m_currentSrcURL.fileName() );
01791     if ( err )
01792     {
01793         // Direct renaming didn't work. Try renaming to a temp name,
01794         // this can help e.g. when renaming 'a' to 'A' on a VFAT partition.
01795         // In that case it's the _same_ dir, we don't want to copy+del (data loss!)
01796       if ( m_currentSrcURL.isLocalFile() && m_currentSrcURL.url(KUrl::RemoveTrailingSlash) != dest.url(KUrl::RemoveTrailingSlash) &&
01797            m_currentSrcURL.url(KUrl::RemoveTrailingSlash).toLower() == dest.url(KUrl::RemoveTrailingSlash).toLower() &&
01798              ( err == ERR_FILE_ALREADY_EXIST ||
01799                err == ERR_DIR_ALREADY_EXIST ||
01800                err == ERR_IDENTICAL_FILES ) )
01801         {
01802             kDebug(7007) << "Couldn't rename directly, dest already exists. Detected special case of lower/uppercase renaming in same dir, try with 2 rename calls";
01803             const QString _src( m_currentSrcURL.toLocalFile() );
01804             const QString _dest( dest.toLocalFile() );
01805             const QString _tmpPrefix = m_currentSrcURL.directory(KUrl::ObeyTrailingSlash|KUrl::AppendTrailingSlash);
01806             KTemporaryFile tmpFile;
01807             tmpFile.setPrefix(_tmpPrefix);
01808             const bool openOk = tmpFile.open();
01809             if (!openOk) {
01810                 kWarning(7007) << "Couldn't open temp file in" << _tmpPrefix;
01811             } else {
01812                 const QString _tmp( tmpFile.fileName() );
01813                 tmpFile.close();
01814                 tmpFile.remove();
01815                 kDebug(7007) << "KTemporaryFile using" << _tmp << "as intermediary";
01816                 if (KDE::rename( _src, _tmp ) == 0) {
01817                     //kDebug(7007) << "Renaming" << _src << "to" << _tmp << "succeeded";
01818                     if (!QFile::exists( _dest ) && KDE::rename(_tmp, _dest) == 0) {
01819                         err = 0;
01820                         org::kde::KDirNotify::emitFileRenamed(m_currentSrcURL.url(), dest.url());
01821                     } else {
01822                         kDebug(7007) << "Didn't manage to rename" << _tmp << "to" << _dest << ", reverting";
01823                         // Revert back to original name!
01824                         if (KDE::rename( _tmp, _src ) != 0) {
01825                             kError(7007) << "Couldn't rename" << _tmp << "back to" << _src << '!';
01826                             // Severe error, abort
01827                             q->Job::slotResult(job); // will set the error and emit result(this)
01828                             return;
01829                         }
01830                     }
01831                 } else {
01832                     kDebug(7007) << "mv" << _src << _tmp << "failed:" << strerror(errno);
01833                 }
01834             }
01835         }
01836     }
01837     if ( err )
01838     {
01839         // This code is similar to CopyJobPrivate::slotResultConflictCopyingFiles
01840         // but here it's about the base src url being moved/renamed
01841         // (m_currentSrcURL) and its dest (m_dest), not about a single file.
01842         // It also means we already stated the dest, here.
01843         // On the other hand we haven't stated the src yet (we skipped doing it
01844         // to save time, since it's not necessary to rename directly!)...
01845 
01846         // Existing dest?
01847         if ( err == ERR_DIR_ALREADY_EXIST ||
01848                err == ERR_FILE_ALREADY_EXIST ||
01849                err == ERR_IDENTICAL_FILES )
01850         {
01851             // Should we skip automatically ?
01852             bool isDir = (err == ERR_DIR_ALREADY_EXIST); // ## technically, isDir means "source is dir", not "dest is dir" #######
01853             if ((isDir && m_bAutoSkipDirs) || (!isDir && m_bAutoSkipFiles)) {
01854                 // Move on to next source url
01855                 skipSrc(isDir);
01856                 return;
01857             } else if ((isDir && m_bOverwriteAllDirs) || (!isDir && m_bOverwriteAllFiles)) {
01858                 ; // nothing to do, stat+copy+del will overwrite
01859             } else if ((isDir && m_bAutoRenameDirs) || (!isDir && m_bAutoRenameFiles)) {
01860                 KUrl destDirectory(m_currentDestURL); // dest including filename
01861                 destDirectory.setPath(destDirectory.directory());
01862                 const QString newName = KIO::RenameDialog::suggestName(destDirectory, m_currentDestURL.fileName());
01863 
01864                 m_dest.setFileName(newName);
01865                 KIO::Job* job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
01866                 state = STATE_STATING;
01867                 destinationState = DEST_NOT_STATED;
01868                 q->addSubjob(job);
01869                 return;
01870             } else if ( q->isInteractive() ) {
01871                 QString newPath;
01872                 // we lack mtime info for both the src (not stated)
01873                 // and the dest (stated but this info wasn't stored)
01874                 // Let's do it for local files, at least
01875                 KIO::filesize_t sizeSrc = (KIO::filesize_t) -1;
01876                 KIO::filesize_t sizeDest = (KIO::filesize_t) -1;
01877                 time_t ctimeSrc = (time_t) -1;
01878                 time_t ctimeDest = (time_t) -1;
01879                 time_t mtimeSrc = (time_t) -1;
01880                 time_t mtimeDest = (time_t) -1;
01881 
01882                 bool destIsDir = err == ERR_DIR_ALREADY_EXIST;
01883 
01884                 // ## TODO we need to stat the source using KIO::stat
01885                 // so that this code is properly network-transparent.
01886 
01887                 KDE_struct_stat stat_buf;
01888                 if ( m_currentSrcURL.isLocalFile() &&
01889                     KDE::stat(m_currentSrcURL.toLocalFile(), &stat_buf) == 0 ) {
01890                     sizeSrc = stat_buf.st_size;
01891                     ctimeSrc = stat_buf.st_ctime;
01892                     mtimeSrc = stat_buf.st_mtime;
01893                     isDir = S_ISDIR(stat_buf.st_mode);
01894                 }
01895                 if ( dest.isLocalFile() &&
01896                     KDE::stat(dest.toLocalFile(), &stat_buf) == 0 ) {
01897                     sizeDest = stat_buf.st_size;
01898                     ctimeDest = stat_buf.st_ctime;
01899                     mtimeDest = stat_buf.st_mtime;
01900                     destIsDir = S_ISDIR(stat_buf.st_mode);
01901                 }
01902 
01903                 // If src==dest, use "overwrite-itself"
01904                 RenameDialog_Mode mode = ( m_currentSrcURL == dest ) ? M_OVERWRITE_ITSELF : M_OVERWRITE;
01905                 if (!isDir && destIsDir) {
01906                     // We can't overwrite a dir with a file.
01907                     mode = (RenameDialog_Mode) 0;
01908                 }
01909 
01910                 if ( m_srcList.count() > 1 )
01911                     mode = (RenameDialog_Mode) ( mode | M_MULTI | M_SKIP );
01912                 if (destIsDir)
01913                     mode = (RenameDialog_Mode) ( mode | M_ISDIR );
01914 
01915                 if (m_reportTimer)
01916                     m_reportTimer->stop();
01917 
01918                 RenameDialog_Result r = q->ui()->askFileRename(
01919                     q,
01920                     err != ERR_DIR_ALREADY_EXIST ? i18n("File Already Exists") : i18n("Already Exists as Folder"),
01921                     m_currentSrcURL.url(),
01922                     dest.url(),
01923                     mode, newPath,
01924                     sizeSrc, sizeDest,
01925                     ctimeSrc, ctimeDest,
01926                     mtimeSrc, mtimeDest );
01927 
01928                 if (m_reportTimer)
01929                     m_reportTimer->start(REPORT_TIMEOUT);
01930 
01931                 switch ( r )
01932                 {
01933                 case R_CANCEL:
01934                 {
01935                     q->setError( ERR_USER_CANCELED );
01936                     q->emitResult();
01937                     return;
01938                 }
01939                 case R_AUTO_RENAME:
01940                     if (isDir) {
01941                         m_bAutoRenameDirs = true;
01942                     }
01943                     else {
01944                         m_bAutoRenameFiles = true;
01945                     }
01946                     // fall through
01947                 case R_RENAME:
01948                 {
01949                     // Set m_dest to the chosen destination
01950                     // This is only for this src url; the next one will revert to m_globalDest
01951                     m_dest.setPath( newPath );
01952                     KIO::Job* job = KIO::stat( m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo );
01953                     state = STATE_STATING;
01954                     destinationState = DEST_NOT_STATED;
01955                     q->addSubjob(job);
01956                     return;
01957                 }
01958                 case R_AUTO_SKIP:
01959                     if (isDir)
01960                         m_bAutoSkipDirs = true;
01961                     else
01962                         m_bAutoSkipFiles = true;
01963                     // fall through
01964                 case R_SKIP:
01965                     // Move on to next url
01966                     skipSrc(isDir);
01967                     return;
01968                 case R_OVERWRITE_ALL:
01969                     if (destIsDir)
01970                         m_bOverwriteAllDirs = true;
01971                     else
01972                         m_bOverwriteAllFiles = true;
01973                     break;
01974                 case R_OVERWRITE:
01975                     // Add to overwrite list
01976                     // Note that we add dest, not m_dest.
01977                     // This ensures that when moving several urls into a dir (m_dest),
01978                     // we only overwrite for the current one, not for all.
01979                     // When renaming a single file (m_asMethod), it makes no difference.
01980                     kDebug(7007) << "adding to overwrite list: " << dest.path();
01981                     m_overwriteList.append( dest.path() );
01982                     break;
01983                 default:
01984                     //assert( 0 );
01985                     break;
01986                 }
01987             } else if ( err != KIO::ERR_UNSUPPORTED_ACTION ) {
01988                 // Dest already exists, and job is not interactive -> abort with error
01989                 q->setError( err );
01990                 q->setErrorText( errText );
01991                 q->emitResult();
01992                 return;
01993             }
01994         } else if ( err != KIO::ERR_UNSUPPORTED_ACTION ) {
01995             kDebug(7007) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", aborting";
01996             q->setError( err );
01997             q->setErrorText( errText );
01998             q->emitResult();
01999             return;
02000         }
02001         kDebug(7007) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", reverting to normal way, starting with stat";
02002         //kDebug(7007) << "KIO::stat on" << m_currentSrcURL;
02003         KIO::Job* job = KIO::stat( m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo );
02004         state = STATE_STATING;
02005         q->addSubjob(job);
02006         m_bOnlyRenames = false;
02007     }
02008     else
02009     {
02010         kDebug(7007) << "Renaming succeeded, move on";
02011         ++m_processedFiles;
02012         emit q->copyingDone( q, *m_currentStatSrc, dest, -1 /*mtime unknown, and not needed*/, true, true );
02013         m_successSrcList.append(*m_currentStatSrc);
02014         statNextSrc();
02015     }
02016 }
02017 
02018 void CopyJob::slotResult( KJob *job )
02019 {
02020     Q_D(CopyJob);
02021     //kDebug(7007) << "d->state=" << (int) d->state;
02022     // In each case, what we have to do is :
02023     // 1 - check for errors and treat them
02024     // 2 - removeSubjob(job);
02025     // 3 - decide what to do next
02026 
02027     switch ( d->state ) {
02028         case STATE_STATING: // We were trying to stat a src url or the dest
02029             d->slotResultStating( job );
02030             break;
02031         case STATE_RENAMING: // We were trying to do a direct renaming, before even stat'ing
02032         {
02033             d->slotResultRenaming( job );
02034             break;
02035         }
02036         case STATE_LISTING: // recursive listing finished
02037             //kDebug(7007) << "totalSize:" << (unsigned int) d->m_totalSize << "files:" << d->files.count() << "d->dirs:" << d->dirs.count();
02038             // Was there an error ?
02039             if (job->error())
02040             {
02041                 Job::slotResult( job ); // will set the error and emit result(this)
02042                 return;
02043             }
02044 
02045             removeSubjob( job );
02046             assert ( !hasSubjobs() );
02047 
02048             d->statNextSrc();
02049             break;
02050         case STATE_CREATING_DIRS:
02051             d->slotResultCreatingDirs( job );
02052             break;
02053         case STATE_CONFLICT_CREATING_DIRS:
02054             d->slotResultConflictCreatingDirs( job );
02055             break;
02056         case STATE_COPYING_FILES:
02057             d->slotResultCopyingFiles( job );
02058             break;
02059         case STATE_CONFLICT_COPYING_FILES:
02060             d->slotResultConflictCopyingFiles( job );
02061             break;
02062         case STATE_DELETING_DIRS:
02063             d->slotResultDeletingDirs( job );
02064             break;
02065         case STATE_SETTING_DIR_ATTRIBUTES:
02066             d->slotResultSettingDirAttributes( job );
02067             break;
02068         default:
02069             assert( 0 );
02070     }
02071 }
02072 
02073 void KIO::CopyJob::setDefaultPermissions( bool b )
02074 {
02075     d_func()->m_defaultPermissions = b;
02076 }
02077 
02078 KIO::CopyJob::CopyMode KIO::CopyJob::operationMode() const
02079 {
02080     return d_func()->m_mode;
02081 }
02082 
02083 void KIO::CopyJob::setAutoSkip(bool autoSkip)
02084 {
02085     d_func()->m_bAutoSkipFiles = autoSkip;
02086     d_func()->m_bAutoSkipDirs = autoSkip;
02087 }
02088 
02089 void KIO::CopyJob::setWriteIntoExistingDirectories(bool overwriteAll) // #65926
02090 {
02091     d_func()->m_bOverwriteAllDirs = overwriteAll;
02092 }
02093 
02094 CopyJob *KIO::copy(const KUrl& src, const KUrl& dest, JobFlags flags)
02095 {
02096     //kDebug(7007) << "src=" << src << "dest=" << dest;
02097     KUrl::List srcList;
02098     srcList.append( src );
02099     return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, false, flags);
02100 }
02101 
02102 CopyJob *KIO::copyAs(const KUrl& src, const KUrl& dest, JobFlags flags)
02103 {
02104     //kDebug(7007) << "src=" << src << "dest=" << dest;
02105     KUrl::List srcList;
02106     srcList.append( src );
02107     return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, true, flags);
02108 }
02109 
02110 CopyJob *KIO::copy( const KUrl::List& src, const KUrl& dest, JobFlags flags )
02111 {
02112     //kDebug(7007) << src << dest;
02113     return CopyJobPrivate::newJob(src, dest, CopyJob::Copy, false, flags);
02114 }
02115 
02116 CopyJob *KIO::move(const KUrl& src, const KUrl& dest, JobFlags flags)
02117 {
02118     //kDebug(7007) << src << dest;
02119     KUrl::List srcList;
02120     srcList.append( src );
02121     return CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, false, flags);
02122 }
02123 
02124 CopyJob *KIO::moveAs(const KUrl& src, const KUrl& dest, JobFlags flags)
02125 {
02126     //kDebug(7007) << src << dest;
02127     KUrl::List srcList;
02128     srcList.append( src );
02129     return CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, true, flags);
02130 }
02131 
02132 CopyJob *KIO::move( const KUrl::List& src, const KUrl& dest, JobFlags flags)
02133 {
02134     //kDebug(7007) << src << dest;
02135     return CopyJobPrivate::newJob(src, dest, CopyJob::Move, false, flags);
02136 }
02137 
02138 CopyJob *KIO::link(const KUrl& src, const KUrl& destDir, JobFlags flags)
02139 {
02140     KUrl::List srcList;
02141     srcList.append( src );
02142     return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags);
02143 }
02144 
02145 CopyJob *KIO::link(const KUrl::List& srcList, const KUrl& destDir, JobFlags flags)
02146 {
02147     return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags);
02148 }
02149 
02150 CopyJob *KIO::linkAs(const KUrl& src, const KUrl& destDir, JobFlags flags )
02151 {
02152     KUrl::List srcList;
02153     srcList.append( src );
02154     return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags);
02155 }
02156 
02157 CopyJob *KIO::trash(const KUrl& src, JobFlags flags)
02158 {
02159     KUrl::List srcList;
02160     srcList.append( src );
02161     return CopyJobPrivate::newJob(srcList, KUrl( "trash:/" ), CopyJob::Move, false, flags);
02162 }
02163 
02164 CopyJob *KIO::trash(const KUrl::List& srcList, JobFlags flags)
02165 {
02166     return CopyJobPrivate::newJob(srcList, KUrl( "trash:/" ), CopyJob::Move, false, flags);
02167 }
02168 
02169 #include "copyjob.moc"

KIO

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • 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
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal