Index: kio/kio/copyjob.cpp =================================================================== --- kio/kio/copyjob.cpp (revision 956991) +++ kio/kio/copyjob.cpp (working copy) @@ -20,6 +20,8 @@ */ #include "copyjob.h" +#include "kdirlister.h" +#include "kfileitem.h" #include "deletejob.h" #include <klocale.h> @@ -160,7 +162,7 @@ bool m_bSingleFileCopy; bool m_bOnlyRenames; KUrl m_dest; - KUrl m_currentDest; + KUrl m_currentDest; // set during listing, used by slotEntries // QStringList m_skipList; QStringList m_overwriteList; @@ -191,6 +193,7 @@ void copyNextFile(); void slotResultDeletingDirs( KJob * job ); void deleteNextDir(); + void sourceStated(const UDSEntry& entry, const KUrl& sourceUrl); void skip( const KUrl & sourceURL ); void slotResultRenaming( KJob * job ); void slotResultSettingDirAttributes( KJob * job ); @@ -204,6 +207,7 @@ void slotStart(); void slotEntries( KIO::Job*, const KIO::UDSEntryList& list ); + void addCopyInfoFromUDSEntry(const UDSEntry& entry, const KUrl& srcUrl, bool srcIsDir, const KUrl& currentDest); /** * Forward signal from subjob */ @@ -264,7 +268,7 @@ // Stat the dest KIO::Job * job = KIO::stat( m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo ); - //kDebug(7007) << "CopyJob:stating the dest " << d->m_dest; + //kDebug(7007) << "CopyJob:stating the dest " << m_dest; q->addSubjob(job); } @@ -278,7 +282,7 @@ // Was there an error while stating the src ? if (job->error() && destinationState != DEST_NOT_STATED ) { - KUrl srcurl = ((SimpleJob*)job)->url(); + const KUrl srcurl = static_cast<SimpleJob*>(job)->url(); if ( !srcurl.isLocalFile() ) { // Probably : src doesn't exist. Well, over some protocols (e.g. FTP) @@ -310,23 +314,22 @@ // Keep copy of the stat result const UDSEntry entry = static_cast<StatJob*>(job)->statResult(); - const QString sLocalPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH ); - const bool isDir = entry.isDir(); - if ( destinationState == DEST_NOT_STATED ) + if ( destinationState == DEST_NOT_STATED ) { + const bool isDir = entry.isDir(); // we were stating the dest - { if (job->error()) destinationState = DEST_DOESNT_EXIST; else { // Treat symlinks to dirs as dirs here, so no test on isLink destinationState = isDir ? DEST_IS_DIR : DEST_IS_FILE; - //kDebug(7007) << "dest is dir:" << bDir; + //kDebug(7007) << "dest is dir:" << isDir; } const bool isGlobalDest = m_dest == m_globalDest; if ( isGlobalDest ) m_globalDestinationState = destinationState; + const QString sLocalPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH ); if ( !sLocalPath.isEmpty() && kio_resolve_local_urls ) { m_dest = KUrl(); m_dest.setPath(sLocalPath); @@ -339,19 +342,21 @@ // After knowing what the dest is, we can start stat'ing the first src. statCurrentSrc(); - return; + } else { + sourceStated(entry, static_cast<SimpleJob*>(job)->url()); + q->removeSubjob( job ); } +} - // Is it a file or a dir ? - const QString sName = entry.stringValue( KIO::UDSEntry::UDS_NAME ); +void CopyJobPrivate::sourceStated(const UDSEntry& entry, const KUrl& sourceUrl) +{ + const QString sLocalPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH ); + const bool isDir = entry.isDir(); // We were stating the current source URL - m_currentDest = m_dest; // used by slotEntries - // Create a dummy list with it, for slotEntries - UDSEntryList lst; - lst.append(entry); + // Is it a file or a dir ? - // There 6 cases, and all end up calling slotEntries(job, lst) first : + // There 6 cases, and all end up calling addCopyInfoFromUDSEntry first : // 1 - src is a dir, destination is a directory, // slotEntries will append the source-dir-name to the destination // 2 - src is a dir, destination is a file -- will offer to overwrite, later on. @@ -362,18 +367,16 @@ // slotEntries will append the filename to the destination. // 5 - src is a file, destination is a file, m_dest is the exact destination name // 6 - src is a file, destination doesn't exist, m_dest is the exact destination name - // Tell slotEntries not to alter the src url - m_bCurrentSrcIsDir = false; - slotEntries(static_cast<KIO::Job*>( job ), lst); KUrl srcurl; if (!sLocalPath.isEmpty()) srcurl.setPath(sLocalPath); else - srcurl = ((SimpleJob*)job)->url(); + srcurl = sourceUrl; + addCopyInfoFromUDSEntry(entry, srcurl, false, m_dest); - q->removeSubjob( job ); - assert ( !q->hasSubjobs() ); // We should have only one job at a time ... + m_currentDest = m_dest; + m_bCurrentSrcIsDir = false; if ( isDir // treat symlinks as files (no recursion) @@ -389,8 +392,8 @@ { // Use <desturl>/<directory_copied> as destination, from now on QString directory = srcurl.fileName(); - if ( !sName.isEmpty() && KProtocolManager::fileNameUsedForCopying( srcurl ) == KProtocolInfo::Name ) - { + const QString sName = entry.stringValue( KIO::UDSEntry::UDS_NAME ); + if (!sName.isEmpty() && KProtocolManager::fileNameUsedForCopying(srcurl) == KProtocolInfo::Name) { directory = sName; } m_currentDest.addPath( directory ); @@ -498,67 +501,71 @@ UDSEntryList::ConstIterator end = list.constEnd(); for (; it != end; ++it) { const UDSEntry& entry = *it; + addCopyInfoFromUDSEntry(entry, static_cast<SimpleJob *>(job)->url(), m_bCurrentSrcIsDir, m_currentDest); + } +} + +void CopyJobPrivate::addCopyInfoFromUDSEntry(const UDSEntry& entry, const KUrl& srcUrl, bool srcIsDir, const KUrl& currentDest) +{ struct CopyInfo info; - info.permissions = entry.numberValue( KIO::UDSEntry::UDS_ACCESS, -1 ); - info.mtime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_MODIFICATION_TIME, -1 ); - info.ctime = (time_t) entry.numberValue( KIO::UDSEntry::UDS_CREATION_TIME, -1 ); - info.size = (KIO::filesize_t) entry.numberValue( KIO::UDSEntry::UDS_SIZE, -1 ); - if ( info.size != (KIO::filesize_t) -1 ) + info.permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, -1); + info.mtime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1); + info.ctime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1); + info.size = (KIO::filesize_t) entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1); + if (info.size != (KIO::filesize_t) -1) m_totalSize += info.size; // recursive listing, displayName can be a/b/c/d - const QString displayName = entry.stringValue( KIO::UDSEntry::UDS_NAME ); - const QString urlStr = entry.stringValue( KIO::UDSEntry::UDS_URL ); + const QString displayName = entry.stringValue(KIO::UDSEntry::UDS_NAME); + const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL); KUrl url; - if ( !urlStr.isEmpty() ) + if (!urlStr.isEmpty()) url = urlStr; - QString localPath = entry.stringValue( KIO::UDSEntry::UDS_LOCAL_PATH ); + QString localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); const bool isDir = entry.isDir(); - info.linkDest = entry.stringValue( KIO::UDSEntry::UDS_LINK_DEST ); + info.linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); - if (displayName != ".." && displayName != ".") - { - bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty(); - if( !hasCustomURL ) { + if (displayName != QLatin1String("..") && displayName != QLatin1String(".")) { + const bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty(); + if (!hasCustomURL) { // Make URL from displayName - url = static_cast<SimpleJob *>(job)->url(); - if ( m_bCurrentSrcIsDir ) { // Only if src is a directory. Otherwise uSource is fine as is - //kDebug(7007) << "adding path " << displayName; - url.addPath( displayName ); + url = srcUrl; + if (srcIsDir) { // Only if src is a directory. Otherwise uSource is fine as is + //kDebug(7007) << "adding path" << displayName; + url.addPath(displayName); } } //kDebug(7007) << "displayName=" << displayName << "url=" << url; if (!localPath.isEmpty() && kio_resolve_local_urls) { - url = KUrl(); - url.setPath(localPath); + url = KUrl(localPath); } info.uSource = url; - info.uDest = m_currentDest; + info.uDest = currentDest; //kDebug(7007) << "uSource=" << info.uSource << "uDest(1)=" << info.uDest; // Append filename or dirname to destination URL, if allowed - if ( destinationState == DEST_IS_DIR && + if (destinationState == DEST_IS_DIR && // "copy/move as <foo>" means 'foo' is the dest for the base srcurl // (passed here during stating) but not its children (during listing) - ( ! ( m_asMethod && state == STATE_STATING ) ) ) + (! (m_asMethod && state == STATE_STATING))) { QString destFileName; - if ( hasCustomURL && - KProtocolManager::fileNameUsedForCopying( url ) == KProtocolInfo::FromUrl ) { + if (hasCustomURL && + KProtocolManager::fileNameUsedForCopying(url) == KProtocolInfo::FromUrl) { //destFileName = url.fileName(); // Doesn't work for recursive listing // Count the number of prefixes used by the recursive listjob - int numberOfSlashes = displayName.count( '/' ); // don't make this a find()! + int numberOfSlashes = displayName.count('/'); // don't make this a find()! QString path = url.path(); int pos = 0; - for ( int n = 0; n < numberOfSlashes + 1; ++n ) { - pos = path.lastIndexOf( '/', pos - 1 ); - if ( pos == -1 ) { // error + for (int n = 0; n < numberOfSlashes + 1; ++n) { + pos = path.lastIndexOf('/', pos - 1); + if (pos == -1) { // error kWarning(7007) << "kioslave bug: not enough slashes in UDS_URL" << path << "- looking for" << numberOfSlashes << "slashes"; break; } } - if ( pos >= 0 ) { - destFileName = path.mid( pos + 1 ); + if (pos >= 0) { + destFileName = path.mid(pos + 1); } } else { // destination filename taken from UDS_NAME @@ -568,23 +575,22 @@ // Here we _really_ have to add some filename to the dest. // Otherwise, we end up with e.g. dest=..../Desktop/ itself. // (This can happen when dropping a link to a webpage with no path) - if ( destFileName.isEmpty() ) - destFileName = KIO::encodeFileName( info.uSource.prettyUrl() ); + if (destFileName.isEmpty()) { + destFileName = KIO::encodeFileName(info.uSource.prettyUrl()); + } //kDebug(7007) << " adding destFileName=" << destFileName; - info.uDest.addPath( destFileName ); + info.uDest.addPath(destFileName); } //kDebug(7007) << " uDest(2)=" << info.uDest; //kDebug(7007) << " " << info.uSource << "->" << info.uDest; - if ( info.linkDest.isEmpty() && isDir && m_mode != CopyJob::Link ) // Dir - { - dirs.append( info ); // Directories - if (m_mode == CopyJob::Move) - dirsToRemove.append( info.uSource ); + if (info.linkDest.isEmpty() && isDir && m_mode != CopyJob::Link) { // Dir + dirs.append(info); // Directories + if (m_mode == CopyJob::Move) { + dirsToRemove.append(info.uSource); } - else { - files.append( info ); // Files and any symlinks - } + } else { + files.append(info); // Files and any symlinks } } } @@ -613,12 +619,10 @@ void CopyJobPrivate::statCurrentSrc() { Q_Q(CopyJob); - if ( m_currentStatSrc != m_srcList.constEnd() ) - { + if (m_currentStatSrc != m_srcList.constEnd()) { m_currentSrcURL = (*m_currentStatSrc); m_bURLDirty = true; - if ( m_mode == CopyJob::Link ) - { + if (m_mode == CopyJob::Link) { // Skip the "stating the source" stage, we don't need it for linking m_currentDest = m_dest; struct CopyInfo info; @@ -629,36 +633,41 @@ info.uSource = m_currentSrcURL; info.uDest = m_currentDest; // Append filename or dirname to destination URL, if allowed - if ( destinationState == DEST_IS_DIR && !m_asMethod ) - { + if (destinationState == DEST_IS_DIR && !m_asMethod) { if ( (m_currentSrcURL.protocol() == info.uDest.protocol()) && (m_currentSrcURL.host() == info.uDest.host()) && (m_currentSrcURL.port() == info.uDest.port()) && (m_currentSrcURL.user() == info.uDest.user()) && - (m_currentSrcURL.pass() == info.uDest.pass()) ) - { + (m_currentSrcURL.pass() == info.uDest.pass()) ) { // This is the case of creating a real symlink info.uDest.addPath( m_currentSrcURL.fileName() ); - } - else - { + } else { // Different protocols, we'll create a .desktop file // We have to change the extension anyway, so while we're at it, // name the file like the URL - info.uDest.addPath( KIO::encodeFileName( m_currentSrcURL.prettyUrl() )+".desktop" ); + info.uDest.addPath(KIO::encodeFileName(m_currentSrcURL.prettyUrl()) + ".desktop"); } } files.append( info ); // Files and any symlinks statNextSrc(); // we could use a loop instead of a recursive call :) return; } - else if ( m_mode == CopyJob::Move && ( + + // Let's see if we can skip stat'ing, for the case where a directory view has the info already + const KFileItem cachedItem = KDirLister::cachedItemForUrl(m_currentSrcURL); + KIO::UDSEntry entry; + if (!cachedItem.isNull()) { + entry = cachedItem.entry(); + bool dummyIsLocal; + m_currentSrcURL = cachedItem.mostLocalUrl(dummyIsLocal); // #183585 + } + + if (m_mode == CopyJob::Move && ( // Don't go renaming right away if we need a stat() to find out the destination filename - KProtocolManager::fileNameUsedForCopying( m_currentSrcURL ) == KProtocolInfo::FromUrl || - destinationState != DEST_IS_DIR || m_asMethod ) - ) - { + KProtocolManager::fileNameUsedForCopying(m_currentSrcURL) == KProtocolInfo::FromUrl || + destinationState != DEST_IS_DIR || m_asMethod) + ) { // If moving, before going for the full stat+[list+]copy+del thing, try to rename // The logic is pretty similar to FileCopyJobPrivate::slotStart() if ( (m_currentSrcURL.protocol() == m_dest.protocol()) && @@ -691,13 +700,20 @@ return; } + m_bOnlyRenames = false; + + if (entry.count() > 0) { + kDebug(7007) << "fast path! found info about" << m_currentSrcURL << "in KDirLister"; + sourceStated(entry, m_currentSrcURL); + return; + } + // Stat the next src url Job * job = KIO::stat( m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo ); - //kDebug(7007) << "KIO::stat on " << m_currentSrcURL; + //kDebug(7007) << "KIO::stat on" << m_currentSrcURL; state = STATE_STATING; q->addSubjob(job); - m_currentDestURL=m_dest; - m_bOnlyRenames = false; + m_currentDestURL = m_dest; m_bURLDirty = true; } else @@ -1521,7 +1537,7 @@ KUrl url(d->m_globalDest); if (d->m_globalDestinationState != DEST_IS_DIR || d->m_asMethod) url.setPath(url.directory()); - //kDebug(7007) << "KDirNotify'ing FilesAdded " << url; + //kDebug(7007) << "KDirNotify'ing FilesAdded" << url; org::kde::KDirNotify::emitFilesAdded( url.url() ); if (d->m_mode == CopyJob::Move && !d->m_successSrcList.isEmpty()) { @@ -1543,24 +1559,24 @@ { // Example: download any attachment from bugs.kde.org m_totalSize = m_processedSize + m_fileProcessedSize; - //kDebug(7007) << "Adjusting m_totalSize to " << m_totalSize; + //kDebug(7007) << "Adjusting m_totalSize to" << m_totalSize; q->setTotalAmount(KJob::Bytes, m_totalSize); // safety } - //kDebug(7007) << "emit processedSize " << (unsigned long) (m_processedSize + m_fileProcessedSize); + //kDebug(7007) << "emit processedSize" << (unsigned long) (m_processedSize + m_fileProcessedSize); q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize); } void CopyJobPrivate::slotTotalSize( KJob*, qulonglong size ) { Q_Q(CopyJob); - //kDebug(7007) << "slotTotalSize: " << size; + //kDebug(7007) << size; // Special case for copying a single file // This is because some protocols don't implement stat properly // (e.g. HTTP), and don't give us a size in some cases (redirection) // so we'd rather rely on the size given for the transfer if ( m_bSingleFileCopy && size > m_totalSize) { - //kDebug(7007) << "slotTotalSize: updating totalsize to " << size; + //kDebug(7007) << "slotTotalSize: updating totalsize to" << size; m_totalSize = size; q->setTotalAmount(KJob::Bytes, size); } @@ -1791,7 +1807,7 @@ return; } kDebug(7007) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", reverting to normal way, starting with stat"; - //kDebug(7007) << "KIO::stat on " << m_currentSrcURL; + //kDebug(7007) << "KIO::stat on" << m_currentSrcURL; KIO::Job* job = KIO::stat( m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo ); state = STATE_STATING; q->addSubjob(job);