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