• Skip to content
  • Skip to link menu
KDE 4.0 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • 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 "deletejob.h"
00024 
00025 #include <klocale.h>
00026 #include <kdesktopfile.h>
00027 #include <kdebug.h>
00028 #include <kde_file.h>
00029 
00030 #include "slave.h"
00031 #include "scheduler.h"
00032 #include "kdirwatch.h"
00033 #include "kprotocolmanager.h"
00034 
00035 #include "jobuidelegate.h"
00036 
00037 #include <kdirnotify.h>
00038 #include <ktemporaryfile.h>
00039 #include <kuiserverjobtracker.h>
00040 
00041 #ifdef Q_OS_UNIX
00042 #include <utime.h>
00043 #endif
00044 #include <assert.h>
00045 
00046 #include <QtCore/QTimer>
00047 #include <QtCore/QFile>
00048 #include <sys/stat.h> // mode_t
00049 #include <QPointer>
00050 
00051 #include "job_p.h"
00052 
00053 using namespace KIO;
00054 
00055 //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
00056 #define REPORT_TIMEOUT 200
00057 
00058 #define KIO_ARGS QByteArray packedArgs; QDataStream stream( &packedArgs, QIODevice::WriteOnly ); stream
00059 
00060 enum DestinationState {
00061     DEST_NOT_STATED,
00062     DEST_IS_DIR,
00063     DEST_IS_FILE,
00064     DEST_DOESNT_EXIST
00065 };
00066 
00081 enum CopyJobState {
00082     STATE_STATING,
00083     STATE_RENAMING,
00084     STATE_LISTING,
00085     STATE_CREATING_DIRS,
00086     STATE_CONFLICT_CREATING_DIRS,
00087     STATE_COPYING_FILES,
00088     STATE_CONFLICT_COPYING_FILES,
00089     STATE_DELETING_DIRS,
00090     STATE_SETTING_DIR_ATTRIBUTES
00091 };
00092 
00094 class KIO::CopyJobPrivate: public KIO::JobPrivate
00095 {
00096 public:
00097     CopyJobPrivate(const KUrl::List& src, const KUrl& dest,
00098                    CopyJob::CopyMode mode, bool asMethod)
00099         : m_globalDest(dest)
00100         , m_globalDestinationState(DEST_NOT_STATED)
00101         , m_defaultPermissions(false)
00102         , m_bURLDirty(false)
00103         , m_mode(mode)
00104         , m_asMethod(asMethod)
00105         , destinationState(DEST_NOT_STATED)
00106         , state(STATE_STATING)
00107         , m_totalSize(0)
00108         , m_processedSize(0)
00109         , m_fileProcessedSize(0)
00110         , m_processedFiles(0)
00111         , m_processedDirs(0)
00112         , m_srcList(src)
00113         , m_currentStatSrc(m_srcList.begin())
00114         , m_bCurrentOperationIsLink(false)
00115         , m_bSingleFileCopy(false)
00116         , m_bOnlyRenames(mode==CopyJob::Move)
00117         , m_dest(dest)
00118         , m_bAutoSkip( false )
00119         , m_bOverwriteAll( false )
00120         , m_conflictError(0)
00121         , m_reportTimer(0)
00122     {
00123     }
00124 
00125     // This is the dest URL that was initially given to CopyJob
00126     // It is copied into m_dest, which can be changed for a given src URL
00127     // (when using the RENAME dialog in slotResult),
00128     // and which will be reset for the next src URL.
00129     KUrl m_globalDest;
00130     // The state info about that global dest
00131     DestinationState m_globalDestinationState;
00132     // See setDefaultPermissions
00133     bool m_defaultPermissions;
00134     // Whether URLs changed (and need to be emitted by the next slotReport call)
00135     bool m_bURLDirty;
00136     // Used after copying all the files into the dirs, to set mtime (TODO: and permissions?)
00137     // after the copy is done
00138     QLinkedList<CopyInfo> m_directoriesCopied;
00139     QLinkedList<CopyInfo>::const_iterator m_directoriesCopiedIterator;
00140 
00141     CopyJob::CopyMode m_mode;
00142     bool m_asMethod;
00143     DestinationState destinationState;
00144     CopyJobState state;
00145     KIO::filesize_t m_totalSize;
00146     KIO::filesize_t m_processedSize;
00147     KIO::filesize_t m_fileProcessedSize;
00148     int m_processedFiles;
00149     int m_processedDirs;
00150     QList<CopyInfo> files;
00151     QList<CopyInfo> dirs;
00152     KUrl::List dirsToRemove;
00153     KUrl::List m_srcList;
00154     KUrl::List::Iterator m_currentStatSrc;
00155     bool m_bCurrentSrcIsDir;
00156     bool m_bCurrentOperationIsLink;
00157     bool m_bSingleFileCopy;
00158     bool m_bOnlyRenames;
00159     KUrl m_dest;
00160     KUrl m_currentDest;
00161     //
00162     QStringList m_skipList;
00163     QStringList m_overwriteList;
00164     bool m_bAutoSkip;
00165     bool m_bOverwriteAll;
00166     int m_conflictError;
00167 
00168     QTimer *m_reportTimer;
00169     //these both are used for progress dialog reporting
00170     KUrl m_currentSrcURL;
00171     KUrl m_currentDestURL;
00172 
00173     void statCurrentSrc();
00174     void statNextSrc();
00175 
00176     // Those aren't slots but submethods for slotResult.
00177     void slotResultStating( KJob * job );
00178     void startListing( const KUrl & src );
00179     void slotResultCreatingDirs( KJob * job );
00180     void slotResultConflictCreatingDirs( KJob * job );
00181     void createNextDir();
00182     void slotResultCopyingFiles( KJob * job );
00183     void slotResultConflictCopyingFiles( KJob * job );
00184 //     KIO::Job* linkNextFile( const KUrl& uSource, const KUrl& uDest, bool overwrite );
00185     KIO::Job* linkNextFile( const KUrl& uSource, const KUrl& uDest, JobFlags flags );
00186     void copyNextFile();
00187     void slotResultDeletingDirs( KJob * job );
00188     void deleteNextDir();
00189     void skip( const KUrl & sourceURL );
00190     void slotResultRenaming( KJob * job );
00191     void slotResultSettingDirAttributes( KJob * job );
00192     void setNextDirAttribute();
00193 
00194     void startRenameJob(const KUrl &slave_url);
00195     bool shouldOverwrite( const QString& path ) const;
00196     bool shouldSkip( const QString& path ) const;
00197     void skipSrc();
00198 
00199     void slotStart();
00200     void slotEntries( KIO::Job*, const KIO::UDSEntryList& list );
00204     void slotProcessedSize( KJob*, qulonglong data_size );
00209     void slotTotalSize( KJob*, qulonglong size );
00210 
00211     void slotReport();
00212 
00213     Q_DECLARE_PUBLIC(CopyJob)
00214 
00215     static inline CopyJob *newJob(const KUrl::List& src, const KUrl& dest,
00216                                   CopyJob::CopyMode mode, bool asMethod, JobFlags flags)
00217     {
00218         CopyJob *job = new CopyJob(*new CopyJobPrivate(src,dest,mode,asMethod));
00219         job->setUiDelegate(new JobUiDelegate);
00220         if (!(flags & HideProgressInfo))
00221             KIO::getJobTracker()->registerJob(job);
00222         return job;
00223     }
00224 };
00225 
00226 CopyJob::CopyJob(CopyJobPrivate &dd)
00227     : Job(dd)
00228 {
00229     QTimer::singleShot(0, this, SLOT(slotStart()));
00230 }
00231 
00232 CopyJob::~CopyJob()
00233 {
00234 }
00235 
00236 KUrl::List CopyJob::srcUrls() const
00237 {
00238     return d_func()->m_srcList;
00239 }
00240 
00241 KUrl CopyJob::destUrl() const
00242 {
00243     return d_func()->m_dest;
00244 }
00245 
00246 void CopyJobPrivate::slotStart()
00247 {
00248     Q_Q(CopyJob);
00254     m_reportTimer = new QTimer(q);
00255 
00256     q->connect(m_reportTimer,SIGNAL(timeout()),q,SLOT(slotReport()));
00257     m_reportTimer->start(REPORT_TIMEOUT);
00258 
00259     // Stat the dest
00260     KIO::Job * job = KIO::stat( m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo );
00261     //kDebug(7007) << "CopyJob:stating the dest " << d->m_dest;
00262     q->addSubjob(job);
00263 }
00264 
00265 // For unit test purposes
00266 KIO_EXPORT bool kio_resolve_local_urls = true;
00267 
00268 void CopyJobPrivate::slotResultStating( KJob *job )
00269 {
00270     Q_Q(CopyJob);
00271     //kDebug(7007) << "CopyJobPrivate::slotResultStating";
00272     // Was there an error while stating the src ?
00273     if (job->error() && destinationState != DEST_NOT_STATED )
00274     {
00275         KUrl srcurl = ((SimpleJob*)job)->url();
00276         if ( !srcurl.isLocalFile() )
00277         {
00278             // Probably : src doesn't exist. Well, over some protocols (e.g. FTP)
00279             // this info isn't really reliable (thanks to MS FTP servers).
00280             // We'll assume a file, and try to download anyway.
00281             kDebug(7007) << "Error while stating source. Activating hack";
00282             q->removeSubjob( job );
00283             assert ( !q->hasSubjobs() ); // We should have only one job at a time ...
00284             struct CopyInfo info;
00285             info.permissions = (mode_t) -1;
00286             info.mtime = (time_t) -1;
00287             info.ctime = (time_t) -1;
00288             info.size = (KIO::filesize_t)-1;
00289             info.uSource = srcurl;
00290             info.uDest = m_dest;
00291             // Append filename or dirname to destination URL, if allowed
00292             if ( destinationState == DEST_IS_DIR && !m_asMethod )
00293                 info.uDest.addPath( srcurl.fileName() );
00294 
00295             files.append( info );
00296             statNextSrc();
00297             return;
00298         }
00299         // Local file. If stat fails, the file definitely doesn't exist.
00300         // yes, q->Job::, because we don't want to call our override
00301         q->Job::slotResult( job ); // will set the error and emit result(this)
00302         return;
00303     }
00304 
00305     // Keep copy of the stat result
00306     const UDSEntry entry = static_cast<StatJob*>(job)->statResult();
00307     const QString sLocalPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH );
00308     const bool isDir = entry.isDir();
00309 
00310     if ( destinationState == DEST_NOT_STATED )
00311         // we were stating the dest
00312     {
00313         if (job->error())
00314             destinationState = DEST_DOESNT_EXIST;
00315         else {
00316             // Treat symlinks to dirs as dirs here, so no test on isLink
00317             destinationState = isDir ? DEST_IS_DIR : DEST_IS_FILE;
00318             //kDebug(7007) << "CopyJobPrivate::slotResultStating dest is dir:" << bDir;
00319         }
00320         const bool isGlobalDest = m_dest == m_globalDest;
00321         if ( isGlobalDest )
00322             m_globalDestinationState = destinationState;
00323 
00324         if ( !sLocalPath.isEmpty() && kio_resolve_local_urls ) {
00325             m_dest = KUrl();
00326             m_dest.setPath(sLocalPath);
00327             if ( isGlobalDest )
00328                 m_globalDest = m_dest;
00329         }
00330 
00331         q->removeSubjob( job );
00332         assert ( !q->hasSubjobs() );
00333 
00334         // After knowing what the dest is, we can start stat'ing the first src.
00335         statCurrentSrc();
00336         return;
00337     }
00338 
00339     // Is it a file or a dir ?
00340     const QString sName = entry.stringValue( KIO::UDSEntry::UDS_NAME );
00341 
00342     // We were stating the current source URL
00343     m_currentDest = m_dest; // used by slotEntries
00344     // Create a dummy list with it, for slotEntries
00345     UDSEntryList lst;
00346     lst.append(entry);
00347 
00348     // There 6 cases, and all end up calling slotEntries(job, lst) first :
00349     // 1 - src is a dir, destination is a directory,
00350     // slotEntries will append the source-dir-name to the destination
00351     // 2 - src is a dir, destination is a file, ERROR (done later on)
00352     // 3 - src is a dir, destination doesn't exist, then it's the destination dirname,
00353     // so slotEntries will use it as destination.
00354 
00355     // 4 - src is a file, destination is a directory,
00356     // slotEntries will append the filename to the destination.
00357     // 5 - src is a file, destination is a file, m_dest is the exact destination name
00358     // 6 - src is a file, destination doesn't exist, m_dest is the exact destination name
00359     // Tell slotEntries not to alter the src url
00360     m_bCurrentSrcIsDir = false;
00361     slotEntries(static_cast<KIO::Job*>( job ), lst);
00362 
00363     KUrl srcurl;
00364     if (!sLocalPath.isEmpty())
00365         srcurl.setPath(sLocalPath);
00366     else
00367         srcurl = ((SimpleJob*)job)->url();
00368 
00369     q->removeSubjob( job );
00370     assert ( !q->hasSubjobs() ); // We should have only one job at a time ...
00371 
00372     if ( isDir
00373          // treat symlinks as files (no recursion)
00374          && !entry.isLink()
00375          && m_mode != CopyJob::Link ) // No recursion in Link mode either.
00376     {
00377         //kDebug(7007) << " Source is a directory ";
00378 
00379         m_bCurrentSrcIsDir = true; // used by slotEntries
00380         if ( destinationState == DEST_IS_DIR ) // (case 1)
00381         {
00382             if ( !m_asMethod )
00383             {
00384                 // Use <desturl>/<directory_copied> as destination, from now on
00385                 QString directory = srcurl.fileName();
00386                 if ( !sName.isEmpty() && KProtocolManager::fileNameUsedForCopying( srcurl ) == KProtocolInfo::Name )
00387                 {
00388                     directory = sName;
00389                 }
00390                 m_currentDest.addPath( directory );
00391             }
00392         }
00393         else if ( destinationState == DEST_IS_FILE ) // (case 2)
00394         {
00395             q->setError( ERR_IS_FILE );
00396             q->setErrorText( m_dest.prettyUrl() );
00397             q->emitResult();
00398             return;
00399         }
00400         else // (case 3)
00401         {
00402             // otherwise dest is new name for toplevel dir
00403             // so the destination exists, in fact, from now on.
00404             // (This even works with other src urls in the list, since the
00405             //  dir has effectively been created)
00406             destinationState = DEST_IS_DIR;
00407             if ( m_dest == m_globalDest )
00408                 m_globalDestinationState = destinationState;
00409         }
00410 
00411         startListing( srcurl );
00412     }
00413     else
00414     {
00415         //kDebug(7007) << " Source is a file (or a symlink), or we are linking -> no recursive listing ";
00416         statNextSrc();
00417     }
00418 }
00419 
00420 bool CopyJob::doSuspend()
00421 {
00422     Q_D(CopyJob);
00423     d->slotReport();
00424     return Job::doSuspend();
00425 }
00426 
00427 void CopyJobPrivate::slotReport()
00428 {
00429     Q_Q(CopyJob);
00430     if ( q->isSuspended() )
00431         return;
00432     // If showProgressInfo was set, progressId() is > 0.
00433     switch (state) {
00434         case STATE_COPYING_FILES:
00435             q->setProcessedAmount( KJob::Files, m_processedFiles );
00436             if (m_bURLDirty)
00437             {
00438                 // Only emit urls when they changed. This saves time, and fixes #66281
00439                 m_bURLDirty = false;
00440                 if (m_mode==CopyJob::Move)
00441                 {
00442                     emitMoving(q, m_currentSrcURL, m_currentDestURL);
00443                     emit q->moving( q, m_currentSrcURL, m_currentDestURL);
00444                 }
00445                 else if (m_mode==CopyJob::Link)
00446                 {
00447                     emitCopying( q, m_currentSrcURL, m_currentDestURL ); // we don't have a delegate->linking
00448                     emit q->linking( q, m_currentSrcURL.path(), m_currentDestURL );
00449                 }
00450                 else
00451                 {
00452                     emitCopying( q, m_currentSrcURL, m_currentDestURL );
00453                     emit q->copying( q, m_currentSrcURL, m_currentDestURL );
00454                 }
00455             }
00456             break;
00457 
00458         case STATE_CREATING_DIRS:
00459             q->setProcessedAmount( KJob::Directories, m_processedDirs );
00460             if (m_bURLDirty)
00461             {
00462                 m_bURLDirty = false;
00463                 emit q->creatingDir( q, m_currentDestURL );
00464                 emitCreatingDir( q, m_currentDestURL );
00465             }
00466             break;
00467 
00468         case STATE_STATING:
00469         case STATE_LISTING:
00470             if (m_bURLDirty)
00471             {
00472                 m_bURLDirty = false;
00473                 emitCopying( q, m_currentSrcURL, m_currentDestURL );
00474             }
00475             q->setTotalAmount(KJob::Bytes, m_totalSize);
00476             q->setTotalAmount(KJob::Files, files.count());
00477             q->setTotalAmount(KJob::Directories, dirs.count());
00478             break;
00479 
00480         default:
00481             break;
00482     }
00483 }
00484 
00485 void CopyJobPrivate::slotEntries(KIO::Job* job, const UDSEntryList& list)
00486 {
00487     //Q_Q(CopyJob);
00488     UDSEntryList::ConstIterator it = list.begin();
00489     UDSEntryList::ConstIterator end = list.end();
00490     for (; it != end; ++it) {
00491         const UDSEntry& entry = *it;
00492         struct CopyInfo info;
00493         info.permissions = entry.numberValue( KIO::UDSEntry::UDS_ACCESS, -1 );
00494         info.mtime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, -1 );
00495         info.ctime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_CREATION_TIME, -1 );
00496         info.size = (KIO::filesize_t) entry.numberValue( KIO::UDSEntry::UDS_SIZE, -1 );
00497         if ( info.size != (KIO::filesize_t) -1 )
00498             m_totalSize += info.size;
00499 
00500         // recursive listing, displayName can be a/b/c/d
00501         const QString displayName = entry.stringValue( KIO::UDSEntry::UDS_NAME );
00502         const QString urlStr = entry.stringValue( KIO::UDSEntry::UDS_URL );
00503         KUrl url;
00504         if ( !urlStr.isEmpty() )
00505             url = urlStr;
00506         QString localPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH );
00507         const bool isDir = entry.isDir();
00508         info.linkDest = entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST );
00509 
00510         if (displayName != ".." && displayName != ".")
00511         {
00512             bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty();
00513             if( !hasCustomURL ) {
00514                 // Make URL from displayName
00515                 url = static_cast<SimpleJob *>(job)->url();
00516                 if ( m_bCurrentSrcIsDir ) { // Only if src is a directory. Otherwise uSource is fine as is
00517                     //kDebug(7007) << "adding path " << displayName;
00518                     url.addPath( displayName );
00519                 }
00520             }
00521             //kDebug(7007) << "displayName=" << displayName << " url=" << url;
00522             if (!localPath.isEmpty() && kio_resolve_local_urls) {
00523                 url = KUrl();
00524                 url.setPath(localPath);
00525             }
00526 
00527             info.uSource = url;
00528             info.uDest = m_currentDest;
00529             //kDebug(7007) << " uSource=" << info.uSource << " uDest(1)=" << info.uDest;
00530             // Append filename or dirname to destination URL, if allowed
00531             if ( destinationState == DEST_IS_DIR &&
00532                  // "copy/move as <foo>" means 'foo' is the dest for the base srcurl
00533                  // (passed here during stating) but not its children (during listing)
00534                  ( ! ( m_asMethod && state == STATE_STATING ) ) )
00535             {
00536                 QString destFileName;
00537                 if ( hasCustomURL &&
00538                      KProtocolManager::fileNameUsedForCopying( url ) == KProtocolInfo::FromUrl ) {
00539                     //destFileName = url.fileName(); // Doesn't work for recursive listing
00540                     // Count the number of prefixes used by the recursive listjob
00541                     int numberOfSlashes = displayName.count( '/' ); // don't make this a find()!
00542                     QString path = url.path();
00543                     int pos = 0;
00544                     for ( int n = 0; n < numberOfSlashes + 1; ++n ) {
00545                         pos = path.lastIndexOf( '/', pos - 1 );
00546                         if ( pos == -1 ) { // error
00547                             kWarning(7007) << "kioslave bug: not enough slashes in UDS_URL " << path << " - looking for " << numberOfSlashes << " slashes";
00548                             break;
00549                         }
00550                     }
00551                     if ( pos >= 0 ) {
00552                         destFileName = path.mid( pos + 1 );
00553                     }
00554 
00555                 } else { // destination filename taken from UDS_NAME
00556                     destFileName = displayName;
00557                 }
00558 
00559                 // Here we _really_ have to add some filename to the dest.
00560                 // Otherwise, we end up with e.g. dest=..../Desktop/ itself.
00561                 // (This can happen when dropping a link to a webpage with no path)
00562                 if ( destFileName.isEmpty() )
00563                     destFileName = KIO::encodeFileName( info.uSource.prettyUrl() );
00564 
00565                 //kDebug(7007) << " adding destFileName=" << destFileName;
00566                 info.uDest.addPath( destFileName );
00567             }
00568             //kDebug(7007) << " uDest(2)=" << info.uDest;
00569             //kDebug(7007) << " " << info.uSource << " -> " << info.uDest;
00570             if ( info.linkDest.isEmpty() && isDir && m_mode != CopyJob::Link ) // Dir
00571             {
00572                 dirs.append( info ); // Directories
00573                 if (m_mode == CopyJob::Move)
00574                     dirsToRemove.append( info.uSource );
00575             }
00576             else {
00577                 files.append( info ); // Files and any symlinks
00578             }
00579         }
00580     }
00581 }
00582 
00583 void CopyJobPrivate::skipSrc()
00584 {
00585     m_dest = m_globalDest;
00586     destinationState = m_globalDestinationState;
00587     ++m_currentStatSrc;
00588     skip( m_currentSrcURL );
00589     statCurrentSrc();
00590 }
00591 
00592 void CopyJobPrivate::statNextSrc()
00593 {
00594     /* Revert to the global destination, the one that applies to all source urls.
00595      * 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.
00596      * d->m_dest is /foo/b for b, but we have to revert to /d for item c and following.
00597      */
00598     m_dest = m_globalDest;
00599     destinationState = m_globalDestinationState;
00600     ++m_currentStatSrc;
00601     statCurrentSrc();
00602 }
00603 
00604 void CopyJobPrivate::statCurrentSrc()
00605 {
00606     Q_Q(CopyJob);
00607     if ( m_currentStatSrc != m_srcList.end() )
00608     {
00609         m_currentSrcURL = (*m_currentStatSrc);
00610         m_bURLDirty = true;
00611         if ( m_mode == CopyJob::Link )
00612         {
00613             // Skip the "stating the source" stage, we don't need it for linking
00614             m_currentDest = m_dest;
00615             struct CopyInfo info;
00616             info.permissions = -1;
00617             info.mtime = (time_t) -1;
00618             info.ctime = (time_t) -1;
00619             info.size = (KIO::filesize_t)-1;
00620             info.uSource = m_currentSrcURL;
00621             info.uDest = m_currentDest;
00622             // Append filename or dirname to destination URL, if allowed
00623             if ( destinationState == DEST_IS_DIR && !m_asMethod )
00624             {
00625                 if (
00626                     (m_currentSrcURL.protocol() == info.uDest.protocol()) &&
00627                     (m_currentSrcURL.host() == info.uDest.host()) &&
00628                     (m_currentSrcURL.port() == info.uDest.port()) &&
00629                     (m_currentSrcURL.user() == info.uDest.user()) &&
00630                     (m_currentSrcURL.pass() == info.uDest.pass()) )
00631                 {
00632                     // This is the case of creating a real symlink
00633                     info.uDest.addPath( m_currentSrcURL.fileName() );
00634                 }
00635                 else
00636                 {
00637                     // Different protocols, we'll create a .desktop file
00638                     // We have to change the extension anyway, so while we're at it,
00639                     // name the file like the URL
00640                     info.uDest.addPath( KIO::encodeFileName( m_currentSrcURL.prettyUrl() )+".desktop" );
00641                 }
00642             }
00643             files.append( info ); // Files and any symlinks
00644             statNextSrc(); // we could use a loop instead of a recursive call :)
00645             return;
00646         }
00647         else if ( m_mode == CopyJob::Move && (
00648                 // Don't go renaming right away if we need a stat() to find out the destination filename
00649                 KProtocolManager::fileNameUsedForCopying( m_currentSrcURL ) == KProtocolInfo::FromUrl ||
00650                 destinationState != DEST_IS_DIR || m_asMethod )
00651             )
00652         {
00653            // If moving, before going for the full stat+[list+]copy+del thing, try to rename
00654            // The logic is pretty similar to FileCopyJobPrivate::slotStart()
00655            if ( (m_currentSrcURL.protocol() == m_dest.protocol()) &&
00656               (m_currentSrcURL.host() == m_dest.host()) &&
00657               (m_currentSrcURL.port() == m_dest.port()) &&
00658               (m_currentSrcURL.user() == m_dest.user()) &&
00659               (m_currentSrcURL.pass() == m_dest.pass()) )
00660            {
00661               startRenameJob( m_currentSrcURL );
00662               return;
00663            }
00664            else if ( m_currentSrcURL.isLocalFile() && KProtocolManager::canRenameFromFile( m_dest ) )
00665            {
00666               startRenameJob( m_dest );
00667               return;
00668            }
00669            else if ( m_dest.isLocalFile() && KProtocolManager::canRenameToFile( m_currentSrcURL ) )
00670            {
00671               startRenameJob( m_currentSrcURL );
00672               return;
00673            }
00674         }
00675 
00676         // if the file system doesn't support deleting, we do not even stat
00677         if (m_mode == CopyJob::Move && !KProtocolManager::supportsDeleting(m_currentSrcURL)) {
00678             QPointer<CopyJob> that = q;
00679             emit q->warning( q, buildErrorString(ERR_CANNOT_DELETE, m_currentSrcURL.prettyUrl()) );
00680             if (that)
00681                 statNextSrc(); // we could use a loop instead of a recursive call :)
00682             return;
00683         }
00684 
00685         // Stat the next src url
00686         Job * job = KIO::stat( m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo );
00687         //kDebug(7007) << "KIO::stat on " << m_currentSrcURL;
00688         state = STATE_STATING;
00689         q->addSubjob(job);
00690         m_currentDestURL=m_dest;
00691         m_bOnlyRenames = false;
00692         m_bURLDirty = true;
00693     }
00694     else
00695     {
00696         // Finished the stat'ing phase
00697         // First make sure that the totals were correctly emitted
00698         state = STATE_STATING;
00699         m_bURLDirty = true;
00700         slotReport();
00701         if (!dirs.isEmpty())
00702            emit q->aboutToCreate( q, dirs );
00703         if (!files.isEmpty())
00704            emit q->aboutToCreate( q, files );
00705         // Check if we are copying a single file
00706         m_bSingleFileCopy = ( files.count() == 1 && dirs.isEmpty() );
00707         // Then start copying things
00708         state = STATE_CREATING_DIRS;
00709         createNextDir();
00710     }
00711 }
00712 
00713 void CopyJobPrivate::startRenameJob( const KUrl& slave_url )
00714 {
00715     Q_Q(CopyJob);
00716     KUrl dest = m_dest;
00717     // Append filename or dirname to destination URL, if allowed
00718     if ( destinationState == DEST_IS_DIR && !m_asMethod )
00719         dest.addPath( m_currentSrcURL.fileName() );
00720     kDebug(7007) << "This seems to be a suitable case for trying to rename before stat+[list+]copy+del";
00721     state = STATE_RENAMING;
00722 
00723     struct CopyInfo info;
00724     info.permissions = -1;
00725     info.mtime = (time_t) -1;
00726     info.ctime = (time_t) -1;
00727     info.size = (KIO::filesize_t)-1;
00728     info.uSource = m_currentSrcURL;
00729     info.uDest = dest;
00730     QList<CopyInfo> files;
00731     files.append(info);
00732     emit q->aboutToCreate( q, files );
00733 
00734     KIO_ARGS << m_currentSrcURL << dest << (qint8) false /*no overwrite*/;
00735     SimpleJob * newJob = SimpleJobPrivate::newJob(slave_url, CMD_RENAME, packedArgs);
00736     newJob->setUiDelegate(new JobUiDelegate());
00737     Scheduler::scheduleJob(newJob);
00738     q->addSubjob( newJob );
00739     if ( m_currentSrcURL.directory() != dest.directory() ) // For the user, moving isn't renaming. Only renaming is.
00740         m_bOnlyRenames = false;
00741 }
00742 
00743 void CopyJobPrivate::startListing( const KUrl & src )
00744 {
00745     Q_Q(CopyJob);
00746     state = STATE_LISTING;
00747     m_bURLDirty = true;
00748     ListJob * newjob = listRecursive(src, KIO::HideProgressInfo);
00749     newjob->setUiDelegate(new JobUiDelegate());
00750     newjob->setUnrestricted(true);
00751     q->connect(newjob, SIGNAL(entries( KIO::Job *,const KIO::UDSEntryList& )),
00752                SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& )));
00753     q->addSubjob( newjob );
00754 }
00755 
00756 void CopyJobPrivate::skip( const KUrl & sourceUrl )
00757 {
00758     // If this is one if toplevel sources,
00759     // remove it from d->m_srcList, for a correct FilesRemoved() signal
00760     //kDebug(7007) << "CopyJob::skip: looking for " << sourceUrl;
00761     m_srcList.removeAll( sourceUrl );
00762     dirsToRemove.removeAll( sourceUrl );
00763 }
00764 
00765 bool CopyJobPrivate::shouldOverwrite( const QString& path ) const
00766 {
00767     if ( m_bOverwriteAll )
00768         return true;
00769     QStringList::ConstIterator sit = m_overwriteList.begin();
00770     for( ; sit != m_overwriteList.end(); ++sit )
00771         if ( path.startsWith( *sit ) )
00772             return true;
00773     return false;
00774 }
00775 
00776 bool CopyJobPrivate::shouldSkip( const QString& path ) const
00777 {
00778     QStringList::ConstIterator sit = m_skipList.begin();
00779     for( ; sit != m_skipList.end(); ++sit )
00780         if ( path.startsWith( *sit ) )
00781             return true;
00782     return false;
00783 }
00784 
00785 void CopyJobPrivate::slotResultCreatingDirs( KJob * job )
00786 {
00787     Q_Q(CopyJob);
00788     // The dir we are trying to create:
00789     QList<CopyInfo>::Iterator it = dirs.begin();
00790     // Was there an error creating a dir ?
00791     if ( job->error() )
00792     {
00793         m_conflictError = job->error();
00794         if ( (m_conflictError == ERR_DIR_ALREADY_EXIST)
00795              || (m_conflictError == ERR_FILE_ALREADY_EXIST) ) // can't happen?
00796         {
00797             KUrl oldURL = ((SimpleJob*)job)->url();
00798             // Should we skip automatically ?
00799             if ( m_bAutoSkip ) {
00800                 // We don't want to copy files in this directory, so we put it on the skip list
00801               m_skipList.append( oldURL.path( KUrl::AddTrailingSlash ) );
00802                 skip( oldURL );
00803                 dirs.erase( it ); // Move on to next dir
00804             } else {
00805                 // Did the user choose to overwrite already?
00806                 const QString destFile = (*it).uDest.path();
00807                 if ( shouldOverwrite( destFile ) ) { // overwrite => just skip
00808                     emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
00809                     dirs.erase( it ); // Move on to next dir
00810                 } else {
00811                     if ( !q->isInteractive() ) {
00812                         q->Job::slotResult( job ); // will set the error and emit result(this)
00813                         return;
00814                     }
00815 
00816                     assert( ((SimpleJob*)job)->url().url() == (*it).uDest.url() );
00817                     q->removeSubjob( job );
00818                     assert ( !q->hasSubjobs() ); // We should have only one job at a time ...
00819 
00820                     // We need to stat the existing dir, to get its last-modification time
00821                     KUrl existingDest( (*it).uDest );
00822                     SimpleJob * newJob = KIO::stat( existingDest, StatJob::DestinationSide, 2, KIO::HideProgressInfo );
00823                     Scheduler::scheduleJob(newJob);
00824                     kDebug(7007) << "KIO::stat for resolving conflict on " << existingDest;
00825                     state = STATE_CONFLICT_CREATING_DIRS;
00826                     q->addSubjob(newJob);
00827                     return; // Don't move to next dir yet !
00828                 }
00829             }
00830         }
00831         else
00832         {
00833             // Severe error, abort
00834             q->Job::slotResult( job ); // will set the error and emit result(this)
00835             return;
00836         }
00837     }
00838     else // no error : remove from list, to move on to next dir
00839     {
00840         //this is required for the undo feature
00841         emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true, false );
00842         m_directoriesCopied.append( *it );
00843         dirs.erase( it );
00844     }
00845 
00846     m_processedDirs++;
00847     //emit processedAmount( this, KJob::Directories, m_processedDirs );
00848     q->removeSubjob( job );
00849     assert( !q->hasSubjobs() ); // We should have only one job at a time ...
00850     createNextDir();
00851 }
00852 
00853 void CopyJobPrivate::slotResultConflictCreatingDirs( KJob * job )
00854 {
00855     Q_Q(CopyJob);
00856     // We come here after a conflict has been detected and we've stated the existing dir
00857 
00858     // The dir we were trying to create:
00859     QList<CopyInfo>::Iterator it = dirs.begin();
00860 
00861     const UDSEntry entry = ((KIO::StatJob*)job)->statResult();
00862 
00863     // Its modification time:
00864     const time_t destmtime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, -1 );
00865     const time_t destctime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_CREATION_TIME, -1 );
00866 
00867     const KIO::filesize_t destsize = entry.numberValue( KIO::UDSEntry::UDS_SIZE );
00868     const QString linkDest = entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST );
00869 
00870     q->removeSubjob( job );
00871     assert ( !q->hasSubjobs() ); // We should have only one job at a time ...
00872 
00873     // Always multi and skip (since there are files after that)
00874     RenameDialog_Mode mode = (RenameDialog_Mode)( M_MULTI | M_SKIP );
00875     // Overwrite only if the existing thing is a dir (no chance with a file)
00876     if ( m_conflictError == ERR_DIR_ALREADY_EXIST )
00877     {
00878         if( (*it).uSource == (*it).uDest ||
00879             ((*it).uSource.protocol() == (*it).uDest.protocol() &&
00880               (*it).uSource.path( KUrl::RemoveTrailingSlash ) == linkDest) )
00881           mode = (RenameDialog_Mode)( mode | M_OVERWRITE_ITSELF);
00882         else
00883           mode = (RenameDialog_Mode)( mode | M_OVERWRITE );
00884     }
00885 
00886     QString existingDest = (*it).uDest.path();
00887     QString newPath;
00888     if (m_reportTimer)
00889         m_reportTimer->stop();
00890     RenameDialog_Result r = q->ui()->askFileRename( q, i18n("Folder Already Exists"),
00891                                          (*it).uSource.url(),
00892                                          (*it).uDest.url(),
00893                                          mode, newPath,
00894                                          (*it).size, destsize,
00895                                          (*it).ctime, destctime,
00896                                          (*it).mtime, destmtime );
00897     if (m_reportTimer)
00898         m_reportTimer->start(REPORT_TIMEOUT);
00899     switch ( r ) {
00900         case R_CANCEL:
00901             q->setError( ERR_USER_CANCELED );
00902             q->emitResult();
00903             return;
00904         case R_RENAME:
00905         {
00906           QString oldPath = (*it).uDest.path( KUrl::AddTrailingSlash );
00907             KUrl newUrl( (*it).uDest );
00908             newUrl.setPath( newPath );
00909             emit q->renamed( q, (*it).uDest, newUrl ); // for e.g. kpropsdlg
00910 
00911             // Change the current one and strip the trailing '/'
00912             (*it).uDest.setPath( newUrl.path( KUrl::RemoveTrailingSlash ) );
00913             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             {
00919                 QString path = (*renamedirit).uDest.path();
00920                 if ( path.startsWith( oldPath ) ) {
00921                     QString n = path;
00922                     n.replace( 0, oldPath.length(), newPath );
00923                     kDebug(7007) << "dirs list: " << (*renamedirit).uSource.path()
00924                                   << " was going to be " << path
00925                                   << ", changed into " << n << endl;
00926                     (*renamedirit).uDest.setPath( n );
00927                 }
00928             }
00929             // Change filenames inside the directory
00930             QList<CopyInfo>::Iterator renamefileit = files.begin();
00931             for( ; renamefileit != files.end() ; ++renamefileit )
00932             {
00933                 QString path = (*renamefileit).uDest.path();
00934                 if ( path.startsWith( oldPath ) ) {
00935                     QString n = path;
00936                     n.replace( 0, oldPath.length(), newPath );
00937                     kDebug(7007) << "files list: " << (*renamefileit).uSource.path()
00938                                   << " was going to be " << path
00939                                   << ", changed into " << n << endl;
00940                     (*renamefileit).uDest.setPath( n );
00941                 }
00942             }
00943             if (!dirs.isEmpty())
00944                 emit q->aboutToCreate( q, dirs );
00945             if (!files.isEmpty())
00946                 emit q->aboutToCreate( q, files );
00947         }
00948         break;
00949         case R_AUTO_SKIP:
00950             m_bAutoSkip = true;
00951             // fall through
00952         case R_SKIP:
00953             m_skipList.append( existingDest );
00954             skip( (*it).uSource );
00955             // Move on to next dir
00956             dirs.erase( it );
00957             m_processedDirs++;
00958             break;
00959         case R_OVERWRITE:
00960             m_overwriteList.append( existingDest );
00961             emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
00962             // Move on to next dir
00963             dirs.erase( it );
00964             m_processedDirs++;
00965             break;
00966         case R_OVERWRITE_ALL:
00967             m_bOverwriteAll = true;
00968             emit q->copyingDone( q, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */ );
00969             // Move on to next dir
00970             dirs.erase( it );
00971             m_processedDirs++;
00972             break;
00973         default:
00974             assert( 0 );
00975     }
00976     state = STATE_CREATING_DIRS;
00977     //emit processedAmount( this, KJob::Directories, m_processedDirs );
00978     createNextDir();
00979 }
00980 
00981 void CopyJobPrivate::createNextDir()
00982 {
00983     Q_Q(CopyJob);
00984     KUrl udir;
00985     if ( !