diff --git a/src/core/copyjob.cpp b/src/core/copyjob.cpp index 1d2e870a..0e218e46 100644 --- a/src/core/copyjob.cpp +++ b/src/core/copyjob.cpp @@ -1,2290 +1,2299 @@ /* This file is part of the KDE libraries Copyright 2000 Stephan Kulow Copyright 2000-2006 David Faure Copyright 2000 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "copyjob.h" #include "kiocoredebug.h" #include #include "kcoredirlister.h" #include "kfileitem.h" #include "job.h" // buildErrorString #include "mkdirjob.h" #include "listjob.h" #include "statjob.h" #include "deletejob.h" #include "filecopyjob.h" #include "../pathhelpers_p.h" #include #include #include #include "slave.h" #include "scheduler.h" #include "kdirwatch.h" #include "kprotocolmanager.h" #include #include #include #ifdef Q_OS_UNIX #include #endif #include #include #include #include #include #include // mode_t #include #include "job_p.h" #include #include #include Q_DECLARE_LOGGING_CATEGORY(KIO_COPYJOB_DEBUG) Q_LOGGING_CATEGORY(KIO_COPYJOB_DEBUG, "kf5.kio.core.copyjob", QtWarningMsg) using namespace KIO; //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX #define REPORT_TIMEOUT 200 enum DestinationState { DEST_NOT_STATED, DEST_IS_DIR, DEST_IS_FILE, DEST_DOESNT_EXIST }; /** * States: * STATE_INITIAL the constructor was called * STATE_STATING for the dest * statCurrentSrc then does, for each src url: * STATE_RENAMING if direct rename looks possible * (on already exists, and user chooses rename, TODO: go to STATE_RENAMING again) * STATE_STATING * and then, if dir -> STATE_LISTING (filling 'd->dirs' and 'd->files') * STATE_CREATING_DIRS (createNextDir, iterating over 'd->dirs') * if conflict: STATE_CONFLICT_CREATING_DIRS * STATE_COPYING_FILES (copyNextFile, iterating over 'd->files') * if conflict: STATE_CONFLICT_COPYING_FILES * STATE_DELETING_DIRS (deleteNextDir) (if moving) * STATE_SETTING_DIR_ATTRIBUTES (setNextDirAttribute, iterating over d->m_directoriesCopied) * done. */ enum CopyJobState { STATE_INITIAL, STATE_STATING, STATE_RENAMING, STATE_LISTING, STATE_CREATING_DIRS, STATE_CONFLICT_CREATING_DIRS, STATE_COPYING_FILES, STATE_CONFLICT_COPYING_FILES, STATE_DELETING_DIRS, STATE_SETTING_DIR_ATTRIBUTES }; static QUrl addPathToUrl(const QUrl &url, const QString &relPath) { QUrl u(url); u.setPath(concatPaths(url.path(), relPath)); return u; } /** @internal */ class KIO::CopyJobPrivate: public KIO::JobPrivate { public: CopyJobPrivate(const QList &src, const QUrl &dest, CopyJob::CopyMode mode, bool asMethod) : m_globalDest(dest) , m_globalDestinationState(DEST_NOT_STATED) , m_defaultPermissions(false) , m_bURLDirty(false) , m_mode(mode) , m_asMethod(asMethod) , destinationState(DEST_NOT_STATED) , state(STATE_INITIAL) , m_freeSpace(-1) , m_totalSize(0) , m_processedSize(0) , m_fileProcessedSize(0) , m_processedFiles(0) , m_processedDirs(0) , m_srcList(src) , m_currentStatSrc(m_srcList.constBegin()) , m_bCurrentOperationIsLink(false) , m_bSingleFileCopy(false) , m_bOnlyRenames(mode == CopyJob::Move) , m_dest(dest) , m_bAutoRenameFiles(false) , m_bAutoRenameDirs(false) , m_bAutoSkipFiles(false) , m_bAutoSkipDirs(false) , m_bOverwriteAllFiles(false) , m_bOverwriteAllDirs(false) , m_conflictError(0) , m_reportTimer(nullptr) { } // This is the dest URL that was initially given to CopyJob // It is copied into m_dest, which can be changed for a given src URL // (when using the RENAME dialog in slotResult), // and which will be reset for the next src URL. QUrl m_globalDest; // The state info about that global dest DestinationState m_globalDestinationState; // See setDefaultPermissions bool m_defaultPermissions; // Whether URLs changed (and need to be emitted by the next slotReport call) bool m_bURLDirty; // Used after copying all the files into the dirs, to set mtime (TODO: and permissions?) // after the copy is done QLinkedList m_directoriesCopied; QLinkedList::const_iterator m_directoriesCopiedIterator; CopyJob::CopyMode m_mode; bool m_asMethod; DestinationState destinationState; CopyJobState state; KIO::filesize_t m_freeSpace; KIO::filesize_t m_totalSize; KIO::filesize_t m_processedSize; KIO::filesize_t m_fileProcessedSize; int m_processedFiles; int m_processedDirs; QList files; QList dirs; QList dirsToRemove; QList m_srcList; QList m_successSrcList; // Entries in m_srcList that have successfully been moved QList::const_iterator m_currentStatSrc; bool m_bCurrentSrcIsDir; bool m_bCurrentOperationIsLink; bool m_bSingleFileCopy; bool m_bOnlyRenames; QUrl m_dest; QUrl m_currentDest; // set during listing, used by slotEntries // QStringList m_skipList; QSet m_overwriteList; bool m_bAutoRenameFiles; bool m_bAutoRenameDirs; bool m_bAutoSkipFiles; bool m_bAutoSkipDirs; bool m_bOverwriteAllFiles; bool m_bOverwriteAllDirs; int m_conflictError; QTimer *m_reportTimer; // The current src url being stat'ed or copied // During the stat phase, this is initially equal to *m_currentStatSrc but it can be resolved to a local file equivalent (#188903). QUrl m_currentSrcURL; QUrl m_currentDestURL; QSet m_parentDirs; void statCurrentSrc(); void statNextSrc(); // Those aren't slots but submethods for slotResult. void slotResultStating(KJob *job); void startListing(const QUrl &src); void slotResultCreatingDirs(KJob *job); void slotResultConflictCreatingDirs(KJob *job); void createNextDir(); void slotResultCopyingFiles(KJob *job); void slotResultErrorCopyingFiles(KJob *job); // KIO::Job* linkNextFile( const QUrl& uSource, const QUrl& uDest, bool overwrite ); KIO::Job *linkNextFile(const QUrl &uSource, const QUrl &uDest, JobFlags flags); void copyNextFile(); void slotResultDeletingDirs(KJob *job); void deleteNextDir(); void sourceStated(const UDSEntry &entry, const QUrl &sourceUrl); void skip(const QUrl &sourceURL, bool isDir); void slotResultRenaming(KJob *job); void slotResultSettingDirAttributes(KJob *job); void setNextDirAttribute(); void startRenameJob(const QUrl &slave_url); bool shouldOverwriteDir(const QString &path) const; bool shouldOverwriteFile(const QString &path) const; bool shouldSkip(const QString &path) const; void skipSrc(bool isDir); void renameDirectory(const QList::iterator &it, const QUrl &newUrl); QUrl finalDestUrl(const QUrl &src, const QUrl &dest) const; void slotStart(); void slotEntries(KIO::Job *, const KIO::UDSEntryList &list); void slotSubError(KIO::ListJob *job, KIO::ListJob *subJob); void addCopyInfoFromUDSEntry(const UDSEntry &entry, const QUrl &srcUrl, bool srcIsDir, const QUrl ¤tDest); /** * Forward signal from subjob */ void slotProcessedSize(KJob *, qulonglong data_size); /** * Forward signal from subjob * @param size the total size */ void slotTotalSize(KJob *, qulonglong size); void slotReport(); Q_DECLARE_PUBLIC(CopyJob) static inline CopyJob *newJob(const QList &src, const QUrl &dest, CopyJob::CopyMode mode, bool asMethod, JobFlags flags) { CopyJob *job = new CopyJob(*new CopyJobPrivate(src, dest, mode, asMethod)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } if (flags & KIO::Overwrite) { job->d_func()->m_bOverwriteAllDirs = true; job->d_func()->m_bOverwriteAllFiles = true; } if (!(flags & KIO::NoPrivilegeExecution)) { job->d_func()->m_privilegeExecutionEnabled = true; FileOperationType copyType; switch (mode) { case CopyJob::Copy: copyType = Copy; break; case CopyJob::Move: copyType = Move; break; case CopyJob::Link: copyType = Symlink; break; } job->d_func()->m_operationType = copyType; } return job; } }; CopyJob::CopyJob(CopyJobPrivate &dd) : Job(dd) { + Q_D(CopyJob); setProperty("destUrl", d_func()->m_dest.toString()); - QTimer::singleShot(0, this, SLOT(slotStart())); + QTimer::singleShot(0, this, [d]() { + d->slotStart(); + }); qRegisterMetaType(); } CopyJob::~CopyJob() { } QList CopyJob::srcUrls() const { return d_func()->m_srcList; } QUrl CopyJob::destUrl() const { return d_func()->m_dest; } void CopyJobPrivate::slotStart() { Q_Q(CopyJob); if (q->isSuspended()) { return; } if (m_mode == CopyJob::CopyMode::Move) { Q_FOREACH (const QUrl &url, m_srcList) { if (m_dest.scheme() == url.scheme() && m_dest.host() == url.host()) { QString srcPath = url.path(); if (!srcPath.endsWith(QLatin1Char('/'))) srcPath += QLatin1Char('/'); if (m_dest.path().startsWith(srcPath)) { q->setError(KIO::ERR_CANNOT_MOVE_INTO_ITSELF); q->emitResult(); return; } } } } /** We call the functions directly instead of using signals. Calling a function via a signal takes approx. 65 times the time compared to calling it directly (at least on my machine). aleXXX */ m_reportTimer = new QTimer(q); - q->connect(m_reportTimer, SIGNAL(timeout()), q, SLOT(slotReport())); + q->connect(m_reportTimer, &QTimer::timeout, q, [this]() { + slotReport(); + }); m_reportTimer->start(REPORT_TIMEOUT); // Stat the dest state = STATE_STATING; const QUrl dest = m_asMethod ? m_dest.adjusted(QUrl::RemoveFilename) : m_dest; KIO::Job *job = KIO::stat(dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); qCDebug(KIO_COPYJOB_DEBUG) << "CopyJob: stating the dest" << m_dest; q->addSubjob(job); } // For unit test purposes KIOCORE_EXPORT bool kio_resolve_local_urls = true; void CopyJobPrivate::slotResultStating(KJob *job) { Q_Q(CopyJob); qCDebug(KIO_COPYJOB_DEBUG); // Was there an error while stating the src ? if (job->error() && destinationState != DEST_NOT_STATED) { const QUrl srcurl = static_cast(job)->url(); if (!srcurl.isLocalFile()) { // Probably : src doesn't exist. Well, over some protocols (e.g. FTP) // this info isn't really reliable (thanks to MS FTP servers). // We'll assume a file, and try to download anyway. qCDebug(KIO_COPYJOB_DEBUG) << "Error while stating source. Activating hack"; q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ... struct CopyInfo info; info.permissions = (mode_t) - 1; info.size = (KIO::filesize_t) - 1; info.uSource = srcurl; info.uDest = m_dest; // Append filename or dirname to destination URL, if allowed if (destinationState == DEST_IS_DIR && !m_asMethod) { const QString fileName = srcurl.scheme() == QLatin1String("data") ? QStringLiteral("data") : srcurl.fileName(); // #379093 info.uDest = addPathToUrl(info.uDest, fileName); } files.append(info); statNextSrc(); return; } // Local file. If stat fails, the file definitely doesn't exist. // yes, q->Job::, because we don't want to call our override q->Job::slotResult(job); // will set the error and emit result(this) return; } // Keep copy of the stat result const UDSEntry entry = static_cast(job)->statResult(); if (destinationState == DEST_NOT_STATED) { if (m_dest.isLocalFile()) { //works for dirs as well QString path(m_dest.toLocalFile()); QFileInfo fileInfo(path); if (m_asMethod || !fileInfo.exists()) { // In copy-as mode, we want to check the directory to which we're // copying. The target file or directory does not exist yet, which // might confuse KDiskFreeSpaceInfo. path = fileInfo.absolutePath(); } KDiskFreeSpaceInfo freeSpaceInfo = KDiskFreeSpaceInfo::freeSpaceInfo(path); if (freeSpaceInfo.isValid()) { m_freeSpace = freeSpaceInfo.available(); } else { qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't determine free space information for" << path; } //TODO actually preliminary check is even more valuable for slow NFS/SMB mounts, //but we need to find a way to report connection errors to user } const bool isGlobalDest = m_dest == m_globalDest; const bool isDir = entry.isDir(); // we were stating the dest if (job->error()) { destinationState = DEST_DOESNT_EXIST; qCDebug(KIO_COPYJOB_DEBUG) << "dest does not exist"; } else { // Treat symlinks to dirs as dirs here, so no test on isLink destinationState = isDir ? DEST_IS_DIR : DEST_IS_FILE; qCDebug(KIO_COPYJOB_DEBUG) << "dest is dir:" << isDir; const QString sLocalPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); if (!sLocalPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST) { const QString fileName = m_dest.fileName(); m_dest = QUrl::fromLocalFile(sLocalPath); if (m_asMethod) { m_dest = addPathToUrl(m_dest, fileName); } qCDebug(KIO_COPYJOB_DEBUG) << "Setting m_dest to the local path:" << sLocalPath; if (isGlobalDest) { m_globalDest = m_dest; } } } if (isGlobalDest) { m_globalDestinationState = destinationState; } q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // After knowing what the dest is, we can start stat'ing the first src. statCurrentSrc(); } else { sourceStated(entry, static_cast(job)->url()); q->removeSubjob(job); } } void CopyJobPrivate::sourceStated(const UDSEntry &entry, const QUrl &sourceUrl) { const QString sLocalPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); const bool isDir = entry.isDir(); // We were stating the current source URL // Is it a file or a dir ? // 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. // 3 - src is a dir, destination doesn't exist, then it's the destination dirname, // so slotEntries will use it as destination. // 4 - src is a file, destination is a directory, // 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 QUrl srcurl; if (!sLocalPath.isEmpty() && destinationState != DEST_DOESNT_EXIST) { qCDebug(KIO_COPYJOB_DEBUG) << "Using sLocalPath. destinationState=" << destinationState; // Prefer the local path -- but only if we were able to stat() the dest. // Otherwise, renaming a desktop:/ url would copy from src=file to dest=desktop (#218719) srcurl = QUrl::fromLocalFile(sLocalPath); } else { srcurl = sourceUrl; } addCopyInfoFromUDSEntry(entry, srcurl, false, m_dest); m_currentDest = m_dest; m_bCurrentSrcIsDir = false; if (isDir // treat symlinks as files (no recursion) && !entry.isLink() && m_mode != CopyJob::Link) { // No recursion in Link mode either. qCDebug(KIO_COPYJOB_DEBUG) << "Source is a directory"; if (srcurl.isLocalFile()) { const QString parentDir = srcurl.adjusted(QUrl::StripTrailingSlash).toLocalFile(); m_parentDirs.insert(parentDir); } m_bCurrentSrcIsDir = true; // used by slotEntries if (destinationState == DEST_IS_DIR) { // (case 1) if (!m_asMethod) { // Use / as destination, from now on QString directory = srcurl.fileName(); const QString sName = entry.stringValue(KIO::UDSEntry::UDS_NAME); KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(srcurl); if (fnu == KProtocolInfo::Name) { if (!sName.isEmpty()) { directory = sName; } } else if (fnu == KProtocolInfo::DisplayName) { const QString dispName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); if (!dispName.isEmpty()) { directory = dispName; } else if (!sName.isEmpty()) { directory = sName; } } m_currentDest = addPathToUrl(m_currentDest, directory); } } else { // (case 3) // otherwise dest is new name for toplevel dir // so the destination exists, in fact, from now on. // (This even works with other src urls in the list, since the // dir has effectively been created) destinationState = DEST_IS_DIR; if (m_dest == m_globalDest) { m_globalDestinationState = destinationState; } } startListing(srcurl); } else { qCDebug(KIO_COPYJOB_DEBUG) << "Source is a file (or a symlink), or we are linking -> no recursive listing"; if (srcurl.isLocalFile()) { const QString parentDir = srcurl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); m_parentDirs.insert(parentDir); } statNextSrc(); } } bool CopyJob::doSuspend() { Q_D(CopyJob); d->slotReport(); return Job::doSuspend(); } bool CopyJob::doResume() { Q_D(CopyJob); switch (d->state) { case STATE_INITIAL: - QTimer::singleShot(0, this, SLOT(slotStart())); + QTimer::singleShot(0, this, [d]() { + d->slotStart(); + }); break; default: // not implemented break; } return Job::doResume(); } void CopyJobPrivate::slotReport() { Q_Q(CopyJob); if (q->isSuspended()) { return; } // If showProgressInfo was set, progressId() is > 0. switch (state) { case STATE_RENAMING: q->setTotalAmount(KJob::Files, m_srcList.count()); // fall-through intended Q_FALLTHROUGH(); case STATE_COPYING_FILES: q->setProcessedAmount(KJob::Files, m_processedFiles); if (m_bURLDirty) { // Only emit urls when they changed. This saves time, and fixes #66281 m_bURLDirty = false; if (m_mode == CopyJob::Move) { emitMoving(q, m_currentSrcURL, m_currentDestURL); emit q->moving(q, m_currentSrcURL, m_currentDestURL); } else if (m_mode == CopyJob::Link) { emitCopying(q, m_currentSrcURL, m_currentDestURL); // we don't have a delegate->linking emit q->linking(q, m_currentSrcURL.path(), m_currentDestURL); } else { emitCopying(q, m_currentSrcURL, m_currentDestURL); emit q->copying(q, m_currentSrcURL, m_currentDestURL); } } break; case STATE_CREATING_DIRS: q->setProcessedAmount(KJob::Directories, m_processedDirs); if (m_bURLDirty) { m_bURLDirty = false; emit q->creatingDir(q, m_currentDestURL); emitCreatingDir(q, m_currentDestURL); } break; case STATE_STATING: case STATE_LISTING: if (m_bURLDirty) { m_bURLDirty = false; if (m_mode == CopyJob::Move) { emitMoving(q, m_currentSrcURL, m_currentDestURL); } else { emitCopying(q, m_currentSrcURL, m_currentDestURL); } } q->setTotalAmount(KJob::Bytes, m_totalSize); q->setTotalAmount(KJob::Files, files.count()); q->setTotalAmount(KJob::Directories, dirs.count()); break; default: break; } } void CopyJobPrivate::slotEntries(KIO::Job *job, const UDSEntryList &list) { //Q_Q(CopyJob); UDSEntryList::ConstIterator it = list.constBegin(); UDSEntryList::ConstIterator end = list.constEnd(); for (; it != end; ++it) { const UDSEntry &entry = *it; addCopyInfoFromUDSEntry(entry, static_cast(job)->url(), m_bCurrentSrcIsDir, m_currentDest); } } void CopyJobPrivate::slotSubError(ListJob *job, ListJob *subJob) { const QUrl url = subJob->url(); qCWarning(KIO_CORE) << url << subJob->errorString(); Q_Q(CopyJob); emit q->warning(job, subJob->errorString(), QString()); skip(url, true); } void CopyJobPrivate::addCopyInfoFromUDSEntry(const UDSEntry &entry, const QUrl &srcUrl, bool srcIsDir, const QUrl ¤tDest) { struct CopyInfo info; info.permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, -1); info.mtime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1), Qt::UTC); info.ctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1), Qt::UTC); info.size = static_cast(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 fileName = entry.stringValue(KIO::UDSEntry::UDS_NAME); const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL); QUrl url; if (!urlStr.isEmpty()) { url = QUrl(urlStr); } QString localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); const bool isDir = entry.isDir(); info.linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); if (fileName != QLatin1String("..") && fileName != QLatin1String(".")) { const bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty(); if (!hasCustomURL) { // Make URL from displayName url = srcUrl; if (srcIsDir) { // Only if src is a directory. Otherwise uSource is fine as is qCDebug(KIO_COPYJOB_DEBUG) << "adding path" << fileName; url = addPathToUrl(url, fileName); } } qCDebug(KIO_COPYJOB_DEBUG) << "fileName=" << fileName << "url=" << url; if (!localPath.isEmpty() && kio_resolve_local_urls && destinationState != DEST_DOESNT_EXIST) { url = QUrl::fromLocalFile(localPath); } info.uSource = url; info.uDest = currentDest; qCDebug(KIO_COPYJOB_DEBUG) << "uSource=" << info.uSource << "uDest(1)=" << info.uDest; // Append filename or dirname to destination URL, if allowed if (destinationState == DEST_IS_DIR && // "copy/move as " means 'foo' is the dest for the base srcurl // (passed here during stating) but not its children (during listing) (!(m_asMethod && state == STATE_STATING))) { QString destFileName; KProtocolInfo::FileNameUsedForCopying fnu = KProtocolManager::fileNameUsedForCopying(url); if (hasCustomURL && fnu == KProtocolInfo::FromUrl) { //destFileName = url.fileName(); // Doesn't work for recursive listing // Count the number of prefixes used by the recursive listjob int numberOfSlashes = fileName.count(QLatin1Char('/')); // don't make this a find()! QString path = url.path(); int pos = 0; for (int n = 0; n < numberOfSlashes + 1; ++n) { pos = path.lastIndexOf(QLatin1Char('/'), pos - 1); if (pos == -1) { // error qCWarning(KIO_CORE) << "kioslave bug: not enough slashes in UDS_URL" << path << "- looking for" << numberOfSlashes << "slashes"; break; } } if (pos >= 0) { destFileName = path.mid(pos + 1); } } else if (fnu == KProtocolInfo::Name) { // destination filename taken from UDS_NAME destFileName = fileName; } else { // from display name (with fallback to name) const QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); destFileName = displayName.isEmpty() ? fileName : displayName; } // 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.toDisplayString()); } qCDebug(KIO_COPYJOB_DEBUG) << " adding destFileName=" << destFileName; info.uDest = addPathToUrl(info.uDest, destFileName); } qCDebug(KIO_COPYJOB_DEBUG) << " uDest(2)=" << info.uDest; qCDebug(KIO_COPYJOB_DEBUG) << " " << 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); } } else { files.append(info); // Files and any symlinks } } } // Adjust for kio_trash choosing its own dest url... QUrl CopyJobPrivate::finalDestUrl(const QUrl& src, const QUrl &dest) const { Q_Q(const CopyJob); if (dest.scheme() == QLatin1String("trash")) { const QMap& metaData = q->metaData(); QMap::ConstIterator it = metaData.find(QLatin1String("trashURL-") + src.path()); if (it != metaData.constEnd()) { qCDebug(KIO_COPYJOB_DEBUG) << "finalDestUrl=" << it.value(); return QUrl(it.value()); } } return dest; } void CopyJobPrivate::skipSrc(bool isDir) { m_dest = m_globalDest; destinationState = m_globalDestinationState; skip(*m_currentStatSrc, isDir); ++m_currentStatSrc; statCurrentSrc(); } void CopyJobPrivate::statNextSrc() { /* Revert to the global destination, the one that applies to all source urls. * 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. * d->m_dest is /foo/b for b, but we have to revert to /d for item c and following. */ m_dest = m_globalDest; qCDebug(KIO_COPYJOB_DEBUG) << "Setting m_dest to" << m_dest; destinationState = m_globalDestinationState; ++m_currentStatSrc; statCurrentSrc(); } void CopyJobPrivate::statCurrentSrc() { Q_Q(CopyJob); if (m_currentStatSrc != m_srcList.constEnd()) { m_currentSrcURL = (*m_currentStatSrc); m_bURLDirty = true; 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; info.permissions = -1; info.size = (KIO::filesize_t) - 1; 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 ( (m_currentSrcURL.scheme() == info.uDest.scheme()) && (m_currentSrcURL.host() == info.uDest.host()) && (m_currentSrcURL.port() == info.uDest.port()) && (m_currentSrcURL.userName() == info.uDest.userName()) && (m_currentSrcURL.password() == info.uDest.password())) { // This is the case of creating a real symlink info.uDest = addPathToUrl(info.uDest, m_currentSrcURL.fileName()); } 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 = addPathToUrl(info.uDest, KIO::encodeFileName(m_currentSrcURL.toDisplayString()) + QLatin1String(".desktop")); } } files.append(info); // Files and any symlinks statNextSrc(); // we could use a loop instead of a recursive call :) return; } // Let's see if we can skip stat'ing, for the case where a directory view has the info already KIO::UDSEntry entry; const KFileItem cachedItem = KCoreDirLister::cachedItemForUrl(m_currentSrcURL); if (!cachedItem.isNull()) { entry = cachedItem.entry(); if (destinationState != DEST_DOESNT_EXIST) { // only resolve src if we could resolve dest (#218719) 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) ) { // 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.scheme() == m_dest.scheme()) && (m_currentSrcURL.host() == m_dest.host()) && (m_currentSrcURL.port() == m_dest.port()) && (m_currentSrcURL.userName() == m_dest.userName()) && (m_currentSrcURL.password() == m_dest.password())) { startRenameJob(m_currentSrcURL); return; } else if (m_currentSrcURL.isLocalFile() && KProtocolManager::canRenameFromFile(m_dest)) { startRenameJob(m_dest); return; } else if (m_dest.isLocalFile() && KProtocolManager::canRenameToFile(m_currentSrcURL)) { startRenameJob(m_currentSrcURL); return; } } // if the file system doesn't support deleting, we do not even stat if (m_mode == CopyJob::Move && !KProtocolManager::supportsDeleting(m_currentSrcURL)) { QPointer that = q; emit q->warning(q, buildErrorString(ERR_CANNOT_DELETE, m_currentSrcURL.toDisplayString())); if (that) { statNextSrc(); // we could use a loop instead of a recursive call :) } return; } m_bOnlyRenames = false; // Testing for entry.count()>0 here is not good enough; KFileItem inserts // entries for UDS_USER and UDS_GROUP even on initially empty UDSEntries (#192185) if (entry.contains(KIO::UDSEntry::UDS_NAME)) { qCDebug(KIO_COPYJOB_DEBUG) << "fast path! found info about" << m_currentSrcURL << "in KCoreDirLister"; // sourceStated(entry, m_currentSrcURL); // don't recurse, see #319747, use queued invokeMethod instead QMetaObject::invokeMethod(q, "sourceStated", Qt::QueuedConnection, Q_ARG(KIO::UDSEntry, entry), Q_ARG(QUrl, m_currentSrcURL)); return; } // Stat the next src url Job *job = KIO::stat(m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat on" << m_currentSrcURL; state = STATE_STATING; q->addSubjob(job); m_currentDestURL = m_dest; m_bURLDirty = true; } else { // Finished the stat'ing phase // First make sure that the totals were correctly emitted state = STATE_STATING; m_bURLDirty = true; slotReport(); qCDebug(KIO_COPYJOB_DEBUG)<<"Stating finished. To copy:"< m_freeSpace && m_freeSpace != static_cast(-1)) { q->setError(ERR_DISK_FULL); q->setErrorText(m_currentSrcURL.toDisplayString()); q->emitResult(); return; } if (!dirs.isEmpty()) { emit q->aboutToCreate(q, dirs); } if (!files.isEmpty()) { emit q->aboutToCreate(q, files); } // Check if we are copying a single file m_bSingleFileCopy = (files.count() == 1 && dirs.isEmpty()); // Then start copying things state = STATE_CREATING_DIRS; createNextDir(); } } void CopyJobPrivate::startRenameJob(const QUrl &slave_url) { Q_Q(CopyJob); // Silence KDirWatch notifications, otherwise performance is horrible if (m_currentSrcURL.isLocalFile()) { const QString parentDir = m_currentSrcURL.adjusted(QUrl::RemoveFilename).path(); if (!m_parentDirs.contains(parentDir)) { KDirWatch::self()->stopDirScan(parentDir); m_parentDirs.insert(parentDir); } } QUrl dest = m_dest; // Append filename or dirname to destination URL, if allowed if (destinationState == DEST_IS_DIR && !m_asMethod) { dest = addPathToUrl(dest, m_currentSrcURL.fileName()); } m_currentDestURL = dest; qCDebug(KIO_COPYJOB_DEBUG) << m_currentSrcURL << "->" << dest << "trying direct rename first"; state = STATE_RENAMING; struct CopyInfo info; info.permissions = -1; info.size = (KIO::filesize_t) - 1; info.uSource = m_currentSrcURL; info.uDest = dest; QList files; files.append(info); emit q->aboutToCreate(q, files); KIO_ARGS << m_currentSrcURL << dest << (qint8) false /*no overwrite*/; SimpleJob *newJob = SimpleJobPrivate::newJobNoUi(slave_url, CMD_RENAME, packedArgs); newJob->setParentJob(q); Scheduler::setJobPriority(newJob, 1); q->addSubjob(newJob); if (m_currentSrcURL.adjusted(QUrl::RemoveFilename) != dest.adjusted(QUrl::RemoveFilename)) { // For the user, moving isn't renaming. Only renaming is. m_bOnlyRenames = false; } } void CopyJobPrivate::startListing(const QUrl &src) { Q_Q(CopyJob); state = STATE_LISTING; m_bURLDirty = true; ListJob *newjob = listRecursive(src, KIO::HideProgressInfo); newjob->setUnrestricted(true); - q->connect(newjob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), - SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); - q->connect(newjob, SIGNAL(subError(KIO::ListJob*,KIO::ListJob*)), - SLOT(slotSubError(KIO::ListJob*,KIO::ListJob*))); + q->connect(newjob, &ListJob::entries, q, [this](KIO::Job *job, KIO::UDSEntryList list) { + slotEntries(job, list); + }); + q->connect(newjob, &ListJob::subError, q, [this](KIO::ListJob *job, KIO::ListJob *subJob) { + slotSubError(job, subJob); + }); q->addSubjob(newjob); } void CopyJobPrivate::skip(const QUrl &sourceUrl, bool isDir) { QUrl dir(sourceUrl); if (!isDir) { // Skipping a file: make sure not to delete the parent dir (#208418) dir = dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } while (dirsToRemove.removeAll(dir) > 0) { // Do not rely on rmdir() on the parent directories aborting. // Exclude the parent dirs explicitly. dir = dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } } bool CopyJobPrivate::shouldOverwriteDir(const QString &path) const { if (m_bOverwriteAllDirs) { return true; } return m_overwriteList.contains(path); } bool CopyJobPrivate::shouldOverwriteFile(const QString &path) const { if (m_bOverwriteAllFiles) { return true; } return m_overwriteList.contains(path); } bool CopyJobPrivate::shouldSkip(const QString &path) const { Q_FOREACH (const QString &skipPath, m_skipList) { if (path.startsWith(skipPath)) { return true; } } return false; } void CopyJobPrivate::renameDirectory(const QList::iterator &it, const QUrl &newUrl) { Q_Q(CopyJob); emit q->renamed(q, (*it).uDest, newUrl); // for e.g. KPropertiesDialog QString oldPath = (*it).uDest.path(); if (!oldPath.endsWith(QLatin1Char('/'))) { oldPath += QLatin1Char('/'); } // Change the current one and strip the trailing '/' (*it).uDest = newUrl.adjusted(QUrl::StripTrailingSlash); QString newPath = newUrl.path(); // With trailing slash if (!newPath.endsWith(QLatin1Char('/'))) { newPath += QLatin1Char('/'); } QList::Iterator renamedirit = it; ++renamedirit; // Change the name of subdirectories inside the directory for (; renamedirit != dirs.end(); ++renamedirit) { QString path = (*renamedirit).uDest.path(); if (path.startsWith(oldPath)) { QString n = path; n.replace(0, oldPath.length(), newPath); /*qDebug() << "dirs list:" << (*renamedirit).uSource.path() << "was going to be" << path << ", changed into" << n;*/ (*renamedirit).uDest.setPath(n, QUrl::DecodedMode); } } // Change filenames inside the directory QList::Iterator renamefileit = files.begin(); for (; renamefileit != files.end(); ++renamefileit) { QString path = (*renamefileit).uDest.path(QUrl::FullyDecoded); if (path.startsWith(oldPath)) { QString n = path; n.replace(0, oldPath.length(), newPath); /*qDebug() << "files list:" << (*renamefileit).uSource.path() << "was going to be" << path << ", changed into" << n;*/ (*renamefileit).uDest.setPath(n, QUrl::DecodedMode); } } if (!dirs.isEmpty()) { emit q->aboutToCreate(q, dirs); } if (!files.isEmpty()) { emit q->aboutToCreate(q, files); } } void CopyJobPrivate::slotResultCreatingDirs(KJob *job) { Q_Q(CopyJob); // The dir we are trying to create: QList::Iterator it = dirs.begin(); // Was there an error creating a dir ? if (job->error()) { m_conflictError = job->error(); if ((m_conflictError == ERR_DIR_ALREADY_EXIST) || (m_conflictError == ERR_FILE_ALREADY_EXIST)) { // can't happen? QUrl oldURL = ((SimpleJob *)job)->url(); // Should we skip automatically ? if (m_bAutoSkipDirs) { // We don't want to copy files in this directory, so we put it on the skip list QString path = oldURL.path(); if (!path.endsWith(QLatin1Char('/'))) { path += QLatin1Char('/'); } m_skipList.append(path); skip(oldURL, true); dirs.erase(it); // Move on to next dir } else { // Did the user choose to overwrite already? const QString destDir = (*it).uDest.path(); if (shouldOverwriteDir(destDir)) { // overwrite => just skip emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */); dirs.erase(it); // Move on to next dir } else { if (m_bAutoRenameDirs) { const QUrl destDirectory = (*it).uDest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); const QString newName = KIO::suggestName(destDirectory, (*it).uDest.fileName()); QUrl newUrl(destDirectory); newUrl.setPath(concatPaths(newUrl.path(), newName)); renameDirectory(it, newUrl); } else { if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } Q_ASSERT(((SimpleJob *)job)->url() == (*it).uDest); q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ... // We need to stat the existing dir, to get its last-modification time QUrl existingDest((*it).uDest); SimpleJob *newJob = KIO::stat(existingDest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); Scheduler::setJobPriority(newJob, 1); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat for resolving conflict on" << existingDest; state = STATE_CONFLICT_CREATING_DIRS; q->addSubjob(newJob); return; // Don't move to next dir yet ! } } } } else { // Severe error, abort q->Job::slotResult(job); // will set the error and emit result(this) return; } } else { // no error : remove from list, to move on to next dir //this is required for the undo feature emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true, false); m_directoriesCopied.append(*it); dirs.erase(it); } m_processedDirs++; //emit processedAmount( this, KJob::Directories, m_processedDirs ); q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ... createNextDir(); } void CopyJobPrivate::slotResultConflictCreatingDirs(KJob *job) { Q_Q(CopyJob); // We come here after a conflict has been detected and we've stated the existing dir // The dir we were trying to create: QList::Iterator it = dirs.begin(); const UDSEntry entry = ((KIO::StatJob *)job)->statResult(); QDateTime destmtime, destctime; const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE); const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ... // Always multi and skip (since there are files after that) RenameDialog_Options options(RenameDialog_MultipleItems | RenameDialog_Skip | RenameDialog_IsDirectory); // Overwrite only if the existing thing is a dir (no chance with a file) if (m_conflictError == ERR_DIR_ALREADY_EXIST) { if ((*it).uSource == (*it).uDest || ((*it).uSource.scheme() == (*it).uDest.scheme() && (*it).uSource.adjusted(QUrl::StripTrailingSlash).path() == linkDest)) { options |= RenameDialog_OverwriteItself; } else { options |= RenameDialog_Overwrite; destmtime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1), Qt::UTC); destctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1), Qt::UTC); } } const QString existingDest = (*it).uDest.path(); QString newPath; if (m_reportTimer) { m_reportTimer->stop(); } RenameDialog_Result r = q->uiDelegateExtension()->askFileRename(q, i18n("Folder Already Exists"), (*it).uSource, (*it).uDest, options, newPath, (*it).size, destsize, (*it).ctime, destctime, (*it).mtime, destmtime); if (m_reportTimer) { m_reportTimer->start(REPORT_TIMEOUT); } switch (r) { case Result_Cancel: q->setError(ERR_USER_CANCELED); q->emitResult(); return; case Result_AutoRename: m_bAutoRenameDirs = true; // fall through case Result_Rename: { QUrl newUrl((*it).uDest); newUrl.setPath(newPath, QUrl::DecodedMode); renameDirectory(it, newUrl); } break; case Result_AutoSkip: m_bAutoSkipDirs = true; // fall through case Result_Skip: m_skipList.append(existingDest); skip((*it).uSource, true); // Move on to next dir dirs.erase(it); m_processedDirs++; break; case Result_Overwrite: m_overwriteList.insert(existingDest); emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */); // Move on to next dir dirs.erase(it); m_processedDirs++; break; case Result_OverwriteAll: m_bOverwriteAllDirs = true; emit q->copyingDone(q, (*it).uSource, finalDestUrl((*it).uSource, (*it).uDest), (*it).mtime, true /* directory */, false /* renamed */); // Move on to next dir dirs.erase(it); m_processedDirs++; break; default: Q_ASSERT(0); } state = STATE_CREATING_DIRS; //emit processedAmount( this, KJob::Directories, m_processedDirs ); createNextDir(); } void CopyJobPrivate::createNextDir() { Q_Q(CopyJob); QUrl udir; if (!dirs.isEmpty()) { // Take first dir to create out of list QList::Iterator it = dirs.begin(); // Is this URL on the skip list or the overwrite list ? while (it != dirs.end() && udir.isEmpty()) { const QString dir = (*it).uDest.path(); if (shouldSkip(dir)) { it = dirs.erase(it); } else { udir = (*it).uDest; } } } if (!udir.isEmpty()) { // any dir to create, finally ? // Create the directory - with default permissions so that we can put files into it // TODO : change permissions once all is finished; but for stuff coming from CDROM it sucks... KIO::SimpleJob *newjob = KIO::mkdir(udir, -1); newjob->setParentJob(q); Scheduler::setJobPriority(newjob, 1); if (shouldOverwriteFile(udir.path())) { // if we are overwriting an existing file or symlink newjob->addMetaData(QStringLiteral("overwrite"), QStringLiteral("true")); } m_currentDestURL = udir; m_bURLDirty = true; q->addSubjob(newjob); return; } else { // we have finished creating dirs q->setProcessedAmount(KJob::Directories, m_processedDirs); // make sure final number appears if (m_mode == CopyJob::Move) { // Now we know which dirs hold the files we're going to delete. // To speed things up and prevent double-notification, we disable KDirWatch // on those dirs temporarily (using KDirWatch::self, that's the instanced // used by e.g. kdirlister). for (QSet::const_iterator it = m_parentDirs.constBegin(); it != m_parentDirs.constEnd(); ++it) { KDirWatch::self()->stopDirScan(*it); } } state = STATE_COPYING_FILES; m_processedFiles++; // Ralf wants it to start at 1, not 0 copyNextFile(); } } void CopyJobPrivate::slotResultCopyingFiles(KJob *job) { Q_Q(CopyJob); // The file we were trying to copy: QList::Iterator it = files.begin(); if (job->error()) { // Should we skip automatically ? if (m_bAutoSkipFiles) { skip((*it).uSource, false); m_fileProcessedSize = (*it).size; files.erase(it); // Move on to next file } else { m_conflictError = job->error(); // save for later // Existing dest ? if ((m_conflictError == ERR_FILE_ALREADY_EXIST) || (m_conflictError == ERR_DIR_ALREADY_EXIST) || (m_conflictError == ERR_IDENTICAL_FILES)) { if (m_bAutoRenameFiles) { QUrl destDirectory = (*it).uDest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); const QString newName = KIO::suggestName(destDirectory, (*it).uDest.fileName()); QUrl newDest(destDirectory); newDest.setPath(concatPaths(newDest.path(), newName)); emit q->renamed(q, (*it).uDest, newDest); // for e.g. kpropsdlg (*it).uDest = newDest; QList files; files.append(*it); emit q->aboutToCreate(q, files); } else { if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We need to stat the existing file, to get its last-modification time QUrl existingFile((*it).uDest); SimpleJob *newJob = KIO::stat(existingFile, StatJob::DestinationSide, 2, KIO::HideProgressInfo); Scheduler::setJobPriority(newJob, 1); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat for resolving conflict on" << existingFile; state = STATE_CONFLICT_COPYING_FILES; q->addSubjob(newJob); return; // Don't move to next file yet ! } } else { if (m_bCurrentOperationIsLink && qobject_cast(job)) { // Very special case, see a few lines below // We are deleting the source of a symlink we successfully moved... ignore error m_fileProcessedSize = (*it).size; files.erase(it); } else { if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } // Go directly to the conflict resolution, there is nothing to stat slotResultErrorCopyingFiles(job); return; } } } } else { // no error // Special case for moving links. That operation needs two jobs, unlike others. if (m_bCurrentOperationIsLink && m_mode == CopyJob::Move && !qobject_cast(job) // Deleting source not already done ) { q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // The only problem with this trick is that the error handling for this del operation // is not going to be right... see 'Very special case' above. KIO::Job *newjob = KIO::del((*it).uSource, HideProgressInfo); newjob->setParentJob(q); q->addSubjob(newjob); return; // Don't move to next file yet ! } const QUrl finalUrl = finalDestUrl((*it).uSource, (*it).uDest); if (m_bCurrentOperationIsLink) { QString target = (m_mode == CopyJob::Link ? (*it).uSource.path() : (*it).linkDest); //required for the undo feature emit q->copyingLinkDone(q, (*it).uSource, target, finalUrl); } else { //required for the undo feature emit q->copyingDone(q, (*it).uSource, finalUrl, (*it).mtime, false, false); if (m_mode == CopyJob::Move) { org::kde::KDirNotify::emitFileMoved((*it).uSource, finalUrl); } m_successSrcList.append((*it).uSource); if (m_freeSpace != (KIO::filesize_t) - 1 && (*it).size != (KIO::filesize_t) - 1) { m_freeSpace -= (*it).size; } } // remove from list, to move on to next file files.erase(it); } m_processedFiles++; // clear processed size for last file and add it to overall processed size m_processedSize += m_fileProcessedSize; m_fileProcessedSize = 0; qCDebug(KIO_COPYJOB_DEBUG) << files.count() << "files remaining"; // Merge metadata from subjob KIO::Job *kiojob = qobject_cast(job); Q_ASSERT(kiojob); m_incomingMetaData += kiojob->metaData(); q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // We should have only one job at a time ... copyNextFile(); } void CopyJobPrivate::slotResultErrorCopyingFiles(KJob *job) { Q_Q(CopyJob); // We come here after a conflict has been detected and we've stated the existing file // The file we were trying to create: QList::Iterator it = files.begin(); RenameDialog_Result res; QString newPath; if (m_reportTimer) { m_reportTimer->stop(); } if ((m_conflictError == ERR_FILE_ALREADY_EXIST) || (m_conflictError == ERR_DIR_ALREADY_EXIST) || (m_conflictError == ERR_IDENTICAL_FILES)) { // Its modification time: const UDSEntry entry = static_cast(job)->statResult(); QDateTime destmtime, destctime; const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE); const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); // Offer overwrite only if the existing thing is a file // If src==dest, use "overwrite-itself" RenameDialog_Options options; bool isDir = true; if (m_conflictError == ERR_DIR_ALREADY_EXIST) { options = RenameDialog_IsDirectory; } else { if ((*it).uSource == (*it).uDest || ((*it).uSource.scheme() == (*it).uDest.scheme() && (*it).uSource.adjusted(QUrl::StripTrailingSlash).path() == linkDest)) { options = RenameDialog_OverwriteItself; } else { options = RenameDialog_Overwrite; // These timestamps are used only when RenameDialog_Overwrite is set. destmtime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1), Qt::UTC); destctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1), Qt::UTC); } isDir = false; } if (!m_bSingleFileCopy) { options = RenameDialog_Options(options | RenameDialog_MultipleItems | RenameDialog_Skip); } res = q->uiDelegateExtension()->askFileRename(q, !isDir ? i18n("File Already Exists") : i18n("Already Exists as Folder"), (*it).uSource, (*it).uDest, options, newPath, (*it).size, destsize, (*it).ctime, destctime, (*it).mtime, destmtime); } else { if (job->error() == ERR_USER_CANCELED) { res = Result_Cancel; } else if (!q->uiDelegateExtension()) { q->Job::slotResult(job); // will set the error and emit result(this) return; } else { SkipDialog_Options options; if (files.count() > 1) { options |= SkipDialog_MultipleItems; } res = q->uiDelegateExtension()->askSkip(q, options, job->errorString()); } } if (m_reportTimer) { m_reportTimer->start(REPORT_TIMEOUT); } q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); switch (res) { case Result_Cancel: q->setError(ERR_USER_CANCELED); q->emitResult(); return; case Result_AutoRename: m_bAutoRenameFiles = true; // fall through Q_FALLTHROUGH(); case Result_Rename: { QUrl newUrl((*it).uDest); newUrl.setPath(newPath); emit q->renamed(q, (*it).uDest, newUrl); // for e.g. kpropsdlg (*it).uDest = newUrl; QList files; files.append(*it); emit q->aboutToCreate(q, files); } break; case Result_AutoSkip: m_bAutoSkipFiles = true; // fall through Q_FALLTHROUGH(); case Result_Skip: // Move on to next file skip((*it).uSource, false); m_processedSize += (*it).size; files.erase(it); m_processedFiles++; break; case Result_OverwriteAll: m_bOverwriteAllFiles = true; break; case Result_Overwrite: // Add to overwrite list, so that copyNextFile knows to overwrite m_overwriteList.insert((*it).uDest.path()); break; case Result_Retry: // Do nothing, copy file again break; default: Q_ASSERT(0); } state = STATE_COPYING_FILES; copyNextFile(); } KIO::Job *CopyJobPrivate::linkNextFile(const QUrl &uSource, const QUrl &uDest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << "Linking"; if ( (uSource.scheme() == uDest.scheme()) && (uSource.host() == uDest.host()) && (uSource.port() == uDest.port()) && (uSource.userName() == uDest.userName()) && (uSource.password() == uDest.password())) { // This is the case of creating a real symlink KIO::SimpleJob *newJob = KIO::symlink(uSource.path(), uDest, flags | HideProgressInfo /*no GUI*/); newJob->setParentJob(q_func()); Scheduler::setJobPriority(newJob, 1); qCDebug(KIO_COPYJOB_DEBUG) << "Linking target=" << uSource.path() << "link=" << uDest; //emit linking( this, uSource.path(), uDest ); m_bCurrentOperationIsLink = true; m_currentSrcURL = uSource; m_currentDestURL = uDest; m_bURLDirty = true; //Observer::self()->slotCopying( this, uSource, uDest ); // should be slotLinking perhaps return newJob; } else { Q_Q(CopyJob); qCDebug(KIO_COPYJOB_DEBUG) << "Linking URL=" << uSource << "link=" << uDest; if (uDest.isLocalFile()) { // if the source is a devices url, handle it a littlebit special QString path = uDest.toLocalFile(); qCDebug(KIO_COPYJOB_DEBUG) << "path=" << path; QFile f(path); if (f.open(QIODevice::ReadWrite)) { f.close(); KDesktopFile desktopFile(path); KConfigGroup config = desktopFile.desktopGroup(); QUrl url = uSource; url.setPassword(QString()); config.writePathEntry("URL", url.toString()); config.writeEntry("Name", url.toString()); config.writeEntry("Type", QStringLiteral("Link")); QString protocol = uSource.scheme(); if (protocol == QLatin1String("ftp")) { config.writeEntry("Icon", QStringLiteral("folder-remote")); } else if (protocol == QLatin1String("http")) { config.writeEntry("Icon", QStringLiteral("text-html")); } else if (protocol == QLatin1String("info")) { config.writeEntry("Icon", QStringLiteral("text-x-texinfo")); } else if (protocol == QLatin1String("mailto")) { // sven: config.writeEntry("Icon", QStringLiteral("internet-mail")); // added mailto: support } else if (protocol == QLatin1String("trash") && url.path().length() <= 1) { // trash:/ link config.writeEntry("Name", i18n("Trash")); config.writeEntry("Icon", QStringLiteral("user-trash-full")); config.writeEntry("EmptyIcon", QStringLiteral("user-trash")); } else { config.writeEntry("Icon", QStringLiteral("unknown")); } config.sync(); files.erase(files.begin()); // done with this one, move on m_processedFiles++; //emit processedAmount( this, KJob::Files, m_processedFiles ); copyNextFile(); return nullptr; } else { qCDebug(KIO_COPYJOB_DEBUG) << "ERR_CANNOT_OPEN_FOR_WRITING"; q->setError(ERR_CANNOT_OPEN_FOR_WRITING); q->setErrorText(uDest.toLocalFile()); q->emitResult(); return nullptr; } } else { // Todo: not show "link" on remote dirs if the src urls are not from the same protocol+host+... q->setError(ERR_CANNOT_SYMLINK); q->setErrorText(uDest.toDisplayString()); q->emitResult(); return nullptr; } } } void CopyJobPrivate::copyNextFile() { Q_Q(CopyJob); bool bCopyFile = false; qCDebug(KIO_COPYJOB_DEBUG); // Take the first file in the list QList::Iterator it = files.begin(); // Is this URL on the skip list ? while (it != files.end() && !bCopyFile) { const QString destFile = (*it).uDest.path(); bCopyFile = !shouldSkip(destFile); if (!bCopyFile) { it = files.erase(it); } if ((*it).size > ((1ul << 32) -1)) { // (1ul << 32 -1) = 4 GB const auto fileSystem = KFileSystemType::fileSystemType(m_globalDest.toString()); if (fileSystem == KFileSystemType::Fat) { q->setError(ERR_FILE_TOO_LARGE_FOR_FAT32); q->setErrorText(m_globalDest.toDisplayString()); q->emitResult(); return; } } } if (bCopyFile) { // any file to create, finally ? qCDebug(KIO_COPYJOB_DEBUG)<<"preparing to copy"<<(*it).uSource<<(*it).size<setError(ERR_DISK_FULL); q->emitResult(); return; } } const QUrl &uSource = (*it).uSource; const QUrl &uDest = (*it).uDest; // Do we set overwrite ? bool bOverwrite; const QString destFile = uDest.path(); qCDebug(KIO_COPYJOB_DEBUG) << "copying" << destFile; if (uDest == uSource) { bOverwrite = false; } else { bOverwrite = shouldOverwriteFile(destFile); } // If source isn't local and target is local, we ignore the original permissions // Otherwise, files downloaded from HTTP end up with -r--r--r-- const bool remoteSource = !KProtocolManager::supportsListing(uSource) || uSource.scheme() == QLatin1String("trash"); int permissions = (*it).permissions; if (m_defaultPermissions || (remoteSource && uDest.isLocalFile())) { permissions = -1; } const JobFlags flags = bOverwrite ? Overwrite : DefaultFlags; m_bCurrentOperationIsLink = false; KIO::Job *newjob = nullptr; if (m_mode == CopyJob::Link) { // User requested that a symlink be made newjob = linkNextFile(uSource, uDest, flags); if (!newjob) { return; } } else if (!(*it).linkDest.isEmpty() && (uSource.scheme() == uDest.scheme()) && (uSource.host() == uDest.host()) && (uSource.port() == uDest.port()) && (uSource.userName() == uDest.userName()) && (uSource.password() == uDest.password())) // Copying a symlink - only on the same protocol/host/etc. (#5601, downloading an FTP file through its link), { KIO::SimpleJob *newJob = KIO::symlink((*it).linkDest, uDest, flags | HideProgressInfo /*no GUI*/); newJob->setParentJob(q); Scheduler::setJobPriority(newJob, 1); newjob = newJob; qCDebug(KIO_COPYJOB_DEBUG) << "Linking target=" << (*it).linkDest << "link=" << uDest; m_currentSrcURL = QUrl::fromUserInput((*it).linkDest); m_currentDestURL = uDest; m_bURLDirty = true; //emit linking( this, (*it).linkDest, uDest ); //Observer::self()->slotCopying( this, m_currentSrcURL, uDest ); // should be slotLinking perhaps m_bCurrentOperationIsLink = true; // NOTE: if we are moving stuff, the deletion of the source will be done in slotResultCopyingFiles } else if (m_mode == CopyJob::Move) { // Moving a file KIO::FileCopyJob *moveJob = KIO::file_move(uSource, uDest, permissions, flags | HideProgressInfo/*no GUI*/); moveJob->setParentJob(q); moveJob->setSourceSize((*it).size); moveJob->setModificationTime((*it).mtime); // #55804 newjob = moveJob; qCDebug(KIO_COPYJOB_DEBUG) << "Moving" << uSource << "to" << uDest; //emit moving( this, uSource, uDest ); m_currentSrcURL = uSource; m_currentDestURL = uDest; m_bURLDirty = true; //Observer::self()->slotMoving( this, uSource, uDest ); } else { // Copying a file KIO::FileCopyJob *copyJob = KIO::file_copy(uSource, uDest, permissions, flags | HideProgressInfo/*no GUI*/); copyJob->setParentJob(q); // in case of rename dialog copyJob->setSourceSize((*it).size); copyJob->setModificationTime((*it).mtime); newjob = copyJob; qCDebug(KIO_COPYJOB_DEBUG) << "Copying" << uSource << "to" << uDest; m_currentSrcURL = uSource; m_currentDestURL = uDest; m_bURLDirty = true; } q->addSubjob(newjob); q->connect(newjob, &Job::processedSize, q, [this](KJob *job, qulonglong processedSize) { slotProcessedSize(job, processedSize); }); q->connect(newjob, &Job::totalSize, q, [this](KJob *job, qulonglong totalSize) { slotTotalSize(job, totalSize); }); } else { // We're done qCDebug(KIO_COPYJOB_DEBUG) << "copyNextFile finished"; deleteNextDir(); } } void CopyJobPrivate::deleteNextDir() { Q_Q(CopyJob); if (m_mode == CopyJob::Move && !dirsToRemove.isEmpty()) { // some dirs to delete ? state = STATE_DELETING_DIRS; m_bURLDirty = true; // Take first dir to delete out of list - last ones first ! QList::Iterator it = --dirsToRemove.end(); SimpleJob *job = KIO::rmdir(*it); job->setParentJob(q); Scheduler::setJobPriority(job, 1); dirsToRemove.erase(it); q->addSubjob(job); } else { // This step is done, move on state = STATE_SETTING_DIR_ATTRIBUTES; m_directoriesCopiedIterator = m_directoriesCopied.constBegin(); setNextDirAttribute(); } } void CopyJobPrivate::setNextDirAttribute() { Q_Q(CopyJob); while (m_directoriesCopiedIterator != m_directoriesCopied.constEnd() && !(*m_directoriesCopiedIterator).mtime.isValid()) { ++m_directoriesCopiedIterator; } if (m_directoriesCopiedIterator != m_directoriesCopied.constEnd()) { const QUrl url = (*m_directoriesCopiedIterator).uDest; const QDateTime dt = (*m_directoriesCopiedIterator).mtime; ++m_directoriesCopiedIterator; KIO::SimpleJob *job = KIO::setModificationTime(url, dt); job->setParentJob(q); Scheduler::setJobPriority(job, 1); q->addSubjob(job); #if 0 // ifdef Q_OS_UNIX // TODO: can be removed now. Or reintroduced as a fast path for local files // if launching even more jobs as done above is a performance problem. // QLinkedList::const_iterator it = m_directoriesCopied.constBegin(); for (; it != m_directoriesCopied.constEnd(); ++it) { const QUrl &url = (*it).uDest; if (url.isLocalFile() && (*it).mtime != (time_t) - 1) { QT_STATBUF statbuf; if (QT_LSTAT(url.path(), &statbuf) == 0) { struct utimbuf utbuf; utbuf.actime = statbuf.st_atime; // access time, unchanged utbuf.modtime = (*it).mtime; // modification time utime(path, &utbuf); } } } m_directoriesCopied.clear(); // but then we need to jump to the else part below. Maybe with a recursive call? #endif } else { if (m_reportTimer) { m_reportTimer->stop(); } --m_processedFiles; // undo the "start at 1" hack slotReport(); // display final numbers, important if progress dialog stays up q->emitResult(); } } void CopyJob::emitResult() { Q_D(CopyJob); // Before we go, tell the world about the changes that were made. // Even if some error made us abort midway, we might still have done // part of the job so we better update the views! (#118583) if (!d->m_bOnlyRenames) { // If only renaming happened, KDirNotify::FileRenamed was emitted by the rename jobs QUrl url(d->m_globalDest); if (d->m_globalDestinationState != DEST_IS_DIR || d->m_asMethod) { url = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); } qCDebug(KIO_COPYJOB_DEBUG) << "KDirNotify'ing FilesAdded" << url; org::kde::KDirNotify::emitFilesAdded(url); if (d->m_mode == CopyJob::Move && !d->m_successSrcList.isEmpty()) { qCDebug(KIO_COPYJOB_DEBUG) << "KDirNotify'ing FilesRemoved" << d->m_successSrcList; org::kde::KDirNotify::emitFilesRemoved(d->m_successSrcList); } } // Re-enable watching on the dirs that held the deleted/moved files if (d->m_mode == CopyJob::Move) { for (QSet::const_iterator it = d->m_parentDirs.constBegin(); it != d->m_parentDirs.constEnd(); ++it) { KDirWatch::self()->restartDirScan(*it); } } Job::emitResult(); } void CopyJobPrivate::slotProcessedSize(KJob *, qulonglong data_size) { Q_Q(CopyJob); qCDebug(KIO_COPYJOB_DEBUG) << data_size; m_fileProcessedSize = data_size; q->setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize); if (m_processedSize + m_fileProcessedSize > m_totalSize) { // Example: download any attachment from bugs.kde.org m_totalSize = m_processedSize + m_fileProcessedSize; qCDebug(KIO_COPYJOB_DEBUG) << "Adjusting m_totalSize to" << m_totalSize; q->setTotalAmount(KJob::Bytes, m_totalSize); // safety } qCDebug(KIO_COPYJOB_DEBUG) << "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); qCDebug(KIO_COPYJOB_DEBUG) << 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) { qCDebug(KIO_COPYJOB_DEBUG) << "slotTotalSize: updating totalsize to" << size; m_totalSize = size; q->setTotalAmount(KJob::Bytes, size); } } void CopyJobPrivate::slotResultDeletingDirs(KJob *job) { Q_Q(CopyJob); if (job->error()) { // Couldn't remove directory. Well, perhaps it's not empty // because the user pressed Skip for a given file in it. // Let's not display "Could not remove dir ..." for each of those dir ! } else { m_successSrcList.append(static_cast(job)->url()); } q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); deleteNextDir(); } void CopyJobPrivate::slotResultSettingDirAttributes(KJob *job) { Q_Q(CopyJob); if (job->error()) { // Couldn't set directory attributes. Ignore the error, it can happen // with inferior file systems like VFAT. // Let's not display warnings for each dir like "cp -a" does. } q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); setNextDirAttribute(); } // We were trying to do a direct renaming, before even stat'ing void CopyJobPrivate::slotResultRenaming(KJob *job) { Q_Q(CopyJob); int err = job->error(); const QString errText = job->errorText(); // Merge metadata from subjob KIO::Job *kiojob = qobject_cast(job); Q_ASSERT(kiojob); m_incomingMetaData += kiojob->metaData(); q->removeSubjob(job); Q_ASSERT(!q->hasSubjobs()); // Determine dest again QUrl dest = m_dest; if (destinationState == DEST_IS_DIR && !m_asMethod) { dest = addPathToUrl(dest, m_currentSrcURL.fileName()); } if (err) { // Direct renaming didn't work. Try renaming to a temp name, // this can help e.g. when renaming 'a' to 'A' on a VFAT partition. // In that case it's the _same_ dir, we don't want to copy+del (data loss!) // TODO: replace all this code with QFile::rename once // https://codereview.qt-project.org/44823 is in if ((err == ERR_FILE_ALREADY_EXIST || err == ERR_DIR_ALREADY_EXIST || err == ERR_IDENTICAL_FILES) && m_currentSrcURL.isLocalFile() && dest.isLocalFile()) { const QString _src(m_currentSrcURL.adjusted(QUrl::StripTrailingSlash).toLocalFile()); const QString _dest(dest.adjusted(QUrl::StripTrailingSlash).toLocalFile()); if (_src != _dest && QString::compare(_src, _dest, Qt::CaseInsensitive) == 0) { qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename directly, dest already exists. Detected special case of lower/uppercase renaming in same dir, try with 2 rename calls"; const QString srcDir = QFileInfo(_src).absolutePath(); QTemporaryFile tmpFile(srcDir + QLatin1String("kio_XXXXXX")); const bool openOk = tmpFile.open(); if (!openOk) { qCWarning(KIO_CORE) << "Couldn't open temp file in" << srcDir; } else { const QString _tmp(tmpFile.fileName()); tmpFile.close(); tmpFile.remove(); qCDebug(KIO_COPYJOB_DEBUG) << "QTemporaryFile using" << _tmp << "as intermediary"; if (QFile::rename(_src, _tmp)) { qCDebug(KIO_COPYJOB_DEBUG) << "Renaming" << _src << "to" << _tmp << "succeeded"; if (!QFile::exists(_dest) && QFile::rename(_tmp, _dest)) { err = 0; org::kde::KDirNotify::emitFileRenamed(m_currentSrcURL, dest); } else { qCDebug(KIO_COPYJOB_DEBUG) << "Didn't manage to rename" << _tmp << "to" << _dest << ", reverting"; // Revert back to original name! if (!QFile::rename(_tmp, _src)) { qCWarning(KIO_CORE) << "Couldn't rename" << _tmp << "back to" << _src << '!'; // Severe error, abort q->Job::slotResult(job); // will set the error and emit result(this) return; } } } else { qCDebug(KIO_COPYJOB_DEBUG) << "mv" << _src << _tmp << "failed:" << strerror(errno); } } } } } if (err) { // This code is similar to CopyJobPrivate::slotResultErrorCopyingFiles // but here it's about the base src url being moved/renamed // (m_currentSrcURL) and its dest (m_dest), not about a single file. // It also means we already stated the dest, here. // On the other hand we haven't stated the src yet (we skipped doing it // to save time, since it's not necessary to rename directly!)... // Existing dest? if (err == ERR_DIR_ALREADY_EXIST || err == ERR_FILE_ALREADY_EXIST || err == ERR_IDENTICAL_FILES) { // Should we skip automatically ? bool isDir = (err == ERR_DIR_ALREADY_EXIST); // ## technically, isDir means "source is dir", not "dest is dir" ####### if ((isDir && m_bAutoSkipDirs) || (!isDir && m_bAutoSkipFiles)) { // Move on to next source url skipSrc(isDir); return; } else if ((isDir && m_bOverwriteAllDirs) || (!isDir && m_bOverwriteAllFiles)) { ; // nothing to do, stat+copy+del will overwrite } else if ((isDir && m_bAutoRenameDirs) || (!isDir && m_bAutoRenameFiles)) { QUrl destDirectory = m_currentDestURL.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); // m_currendDestURL includes filename const QString newName = KIO::suggestName(destDirectory, m_currentDestURL.fileName()); m_dest = destDirectory; m_dest.setPath(concatPaths(m_dest.path(), newName)); KIO::Job *job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); state = STATE_STATING; destinationState = DEST_NOT_STATED; q->addSubjob(job); return; } else if (q->uiDelegateExtension()) { QString newPath; // we lack mtime info for both the src (not stated) // and the dest (stated but this info wasn't stored) // Let's do it for local files, at least KIO::filesize_t sizeSrc = (KIO::filesize_t) - 1; KIO::filesize_t sizeDest = (KIO::filesize_t) - 1; QDateTime ctimeSrc; QDateTime ctimeDest; QDateTime mtimeSrc; QDateTime mtimeDest; bool destIsDir = err == ERR_DIR_ALREADY_EXIST; // ## TODO we need to stat the source using KIO::stat // so that this code is properly network-transparent. if (m_currentSrcURL.isLocalFile()) { QFileInfo info(m_currentSrcURL.toLocalFile()); if (info.exists()) { sizeSrc = info.size(); ctimeSrc = info.created(); mtimeSrc = info.lastModified(); isDir = info.isDir(); } } if (dest.isLocalFile()) { QFileInfo destInfo(dest.toLocalFile()); if (destInfo.exists()) { sizeDest = destInfo.size(); ctimeDest = destInfo.created(); mtimeDest = destInfo.lastModified(); destIsDir = destInfo.isDir(); } } // If src==dest, use "overwrite-itself" RenameDialog_Options options = (m_currentSrcURL == dest) ? RenameDialog_OverwriteItself : RenameDialog_Overwrite; if (!isDir && destIsDir) { // We can't overwrite a dir with a file. options = RenameDialog_Options(); } if (m_srcList.count() > 1) { options |= RenameDialog_Options(RenameDialog_MultipleItems | RenameDialog_Skip); } if (destIsDir) { options |= RenameDialog_IsDirectory; } if (m_reportTimer) { m_reportTimer->stop(); } RenameDialog_Result r = q->uiDelegateExtension()->askFileRename( q, err != ERR_DIR_ALREADY_EXIST ? i18n("File Already Exists") : i18n("Already Exists as Folder"), m_currentSrcURL, dest, options, newPath, sizeSrc, sizeDest, ctimeSrc, ctimeDest, mtimeSrc, mtimeDest); if (m_reportTimer) { m_reportTimer->start(REPORT_TIMEOUT); } switch (r) { case Result_Cancel: { q->setError(ERR_USER_CANCELED); q->emitResult(); return; } case Result_AutoRename: if (isDir) { m_bAutoRenameDirs = true; } else { m_bAutoRenameFiles = true; } // fall through Q_FALLTHROUGH(); case Result_Rename: { // Set m_dest to the chosen destination // This is only for this src url; the next one will revert to m_globalDest m_dest.setPath(newPath); KIO::Job *job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); state = STATE_STATING; destinationState = DEST_NOT_STATED; q->addSubjob(job); return; } case Result_AutoSkip: if (isDir) { m_bAutoSkipDirs = true; } else { m_bAutoSkipFiles = true; } // fall through Q_FALLTHROUGH(); case Result_Skip: // Move on to next url skipSrc(isDir); return; case Result_OverwriteAll: if (destIsDir) { m_bOverwriteAllDirs = true; } else { m_bOverwriteAllFiles = true; } break; case Result_Overwrite: // Add to overwrite list // Note that we add dest, not m_dest. // This ensures that when moving several urls into a dir (m_dest), // we only overwrite for the current one, not for all. // When renaming a single file (m_asMethod), it makes no difference. qCDebug(KIO_COPYJOB_DEBUG) << "adding to overwrite list: " << dest.path(); m_overwriteList.insert(dest.path()); break; default: //Q_ASSERT( 0 ); break; } } else if (err != KIO::ERR_UNSUPPORTED_ACTION) { // Dest already exists, and job is not interactive -> abort with error q->setError(err); q->setErrorText(errText); q->emitResult(); return; } } else if (err != KIO::ERR_UNSUPPORTED_ACTION) { qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", aborting"; q->setError(err); q->setErrorText(errText); q->emitResult(); return; } qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", reverting to normal way, starting with stat"; qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat on" << m_currentSrcURL; KIO::Job *job = KIO::stat(m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo); state = STATE_STATING; q->addSubjob(job); m_bOnlyRenames = false; } else { qCDebug(KIO_COPYJOB_DEBUG) << "Renaming succeeded, move on"; ++m_processedFiles; emit q->copyingDone(q, *m_currentStatSrc, finalDestUrl(*m_currentStatSrc, dest), QDateTime() /*mtime unknown, and not needed*/, true, true); m_successSrcList.append(*m_currentStatSrc); statNextSrc(); } } void CopyJob::slotResult(KJob *job) { Q_D(CopyJob); qCDebug(KIO_COPYJOB_DEBUG) << "d->state=" << (int) d->state; // In each case, what we have to do is : // 1 - check for errors and treat them // 2 - removeSubjob(job); // 3 - decide what to do next switch (d->state) { case STATE_STATING: // We were trying to stat a src url or the dest d->slotResultStating(job); break; case STATE_RENAMING: { // We were trying to do a direct renaming, before even stat'ing d->slotResultRenaming(job); break; } case STATE_LISTING: // recursive listing finished qCDebug(KIO_COPYJOB_DEBUG) << "totalSize:" << (unsigned int) d->m_totalSize << "files:" << d->files.count() << "d->dirs:" << d->dirs.count(); // Was there an error ? if (job->error()) { Job::slotResult(job); // will set the error and emit result(this) return; } removeSubjob(job); Q_ASSERT(!hasSubjobs()); d->statNextSrc(); break; case STATE_CREATING_DIRS: d->slotResultCreatingDirs(job); break; case STATE_CONFLICT_CREATING_DIRS: d->slotResultConflictCreatingDirs(job); break; case STATE_COPYING_FILES: d->slotResultCopyingFiles(job); break; case STATE_CONFLICT_COPYING_FILES: d->slotResultErrorCopyingFiles(job); break; case STATE_DELETING_DIRS: d->slotResultDeletingDirs(job); break; case STATE_SETTING_DIR_ATTRIBUTES: d->slotResultSettingDirAttributes(job); break; default: Q_ASSERT(0); } } void KIO::CopyJob::setDefaultPermissions(bool b) { d_func()->m_defaultPermissions = b; } KIO::CopyJob::CopyMode KIO::CopyJob::operationMode() const { return d_func()->m_mode; } void KIO::CopyJob::setAutoSkip(bool autoSkip) { d_func()->m_bAutoSkipFiles = autoSkip; d_func()->m_bAutoSkipDirs = autoSkip; } void KIO::CopyJob::setAutoRename(bool autoRename) { d_func()->m_bAutoRenameFiles = autoRename; d_func()->m_bAutoRenameDirs = autoRename; } void KIO::CopyJob::setWriteIntoExistingDirectories(bool overwriteAll) // #65926 { d_func()->m_bOverwriteAllDirs = overwriteAll; } CopyJob *KIO::copy(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << "src=" << src << "dest=" << dest; QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, false, flags); } CopyJob *KIO::copyAs(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << "src=" << src << "dest=" << dest; QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, dest, CopyJob::Copy, true, flags); } CopyJob *KIO::copy(const QList &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; return CopyJobPrivate::newJob(src, dest, CopyJob::Copy, false, flags); } CopyJob *KIO::move(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; QList srcList; srcList.append(src); CopyJob *job = CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, false, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent); } return job; } CopyJob *KIO::moveAs(const QUrl &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; QList srcList; srcList.append(src); CopyJob *job = CopyJobPrivate::newJob(srcList, dest, CopyJob::Move, true, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent); } return job; } CopyJob *KIO::move(const QList &src, const QUrl &dest, JobFlags flags) { qCDebug(KIO_COPYJOB_DEBUG) << src << dest; CopyJob *job = CopyJobPrivate::newJob(src, dest, CopyJob::Move, false, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent); } return job; } CopyJob *KIO::link(const QUrl &src, const QUrl &destDir, JobFlags flags) { QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags); } CopyJob *KIO::link(const QList &srcList, const QUrl &destDir, JobFlags flags) { return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, false, flags); } CopyJob *KIO::linkAs(const QUrl &src, const QUrl &destDir, JobFlags flags) { QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, destDir, CopyJob::Link, true, flags); } CopyJob *KIO::trash(const QUrl &src, JobFlags flags) { QList srcList; srcList.append(src); return CopyJobPrivate::newJob(srcList, QUrl(QStringLiteral("trash:/")), CopyJob::Move, false, flags); } CopyJob *KIO::trash(const QList &srcList, JobFlags flags) { return CopyJobPrivate::newJob(srcList, QUrl(QStringLiteral("trash:/")), CopyJob::Move, false, flags); } #include "moc_copyjob.cpp" diff --git a/src/core/copyjob.h b/src/core/copyjob.h index 482b0d54..74c0c816 100644 --- a/src/core/copyjob.h +++ b/src/core/copyjob.h @@ -1,434 +1,430 @@ // -*- c++ -*- /* This file is part of the KDE libraries Copyright 2000 Stephan Kulow Copyright 2000-2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_COPYJOB_H #define KIO_COPYJOB_H #include #include #include #include #include "kiocore_export.h" #include // filesize_t #include "job_base.h" class QTimer; namespace KIO { /// @internal /// KF6 TODO: move to .cpp and remove aboutToCreate signal struct CopyInfo { QUrl uSource; QUrl uDest; QString linkDest; // for symlinks only int permissions; QDateTime ctime; QDateTime mtime; KIO::filesize_t size; // 0 for dirs }; class CopyJobPrivate; /** * @class KIO::CopyJob copyjob.h * * CopyJob is used to move, copy or symlink files and directories. * Don't create the job directly, but use KIO::copy(), * KIO::move(), KIO::link() and friends. * * @see KIO::copy() * @see KIO::copyAs() * @see KIO::move() * @see KIO::moveAs() * @see KIO::link() * @see KIO::linkAs() */ class KIOCORE_EXPORT CopyJob : public Job { Q_OBJECT public: /** * Defines the mode of the operation */ enum CopyMode { Copy, Move, Link }; ~CopyJob() override; /** * Returns the mode of the operation (copy, move, or link), * depending on whether KIO::copy(), KIO::move() or KIO::link() was called. */ CopyMode operationMode() const; /** * Returns the list of source URLs. * @return the list of source URLs. */ QList srcUrls() const; /** * Returns the destination URL. * @return the destination URL */ QUrl destUrl() const; /** * By default the permissions of the copied files will be those of the source files. * * But when copying "template" files to "new" files, people prefer the umask * to apply, rather than the template's permissions. * For that case, call setDefaultPermissions(true) */ void setDefaultPermissions(bool b); /** * Skip copying or moving any file when the destination already exists, * instead of the default behavior (interactive mode: showing a dialog to the user, * non-interactive mode: aborting with an error). * Initially added for a unit test. * \since 4.2 */ void setAutoSkip(bool autoSkip); /** * Rename files automatically when the destination already exists, * instead of the default behavior (interactive mode: showing a dialog to the user, * non-interactive mode: aborting with an error). * Initially added for a unit test. * \since 4.7 */ void setAutoRename(bool autoRename); /** * Reuse any directory that already exists, instead of the default behavior * (interactive mode: showing a dialog to the user, * non-interactive mode: aborting with an error). * \since 4.2 */ void setWriteIntoExistingDirectories(bool overwriteAllDirs); /** * Reimplemented for internal reasons */ bool doSuspend() override; /** * Reimplemented for internal reasons */ bool doResume() override; Q_SIGNALS: /** * Emitted when the total number of files is known. * @param job the job that emitted this signal * @param files the total number of files */ void totalFiles(KJob *job, unsigned long files); /** * Emitted when the total number of directories is known. * @param job the job that emitted this signal * @param dirs the total number of directories */ void totalDirs(KJob *job, unsigned long dirs); /** * Emitted when it is known which files / directories are going * to be created. Note that this may still change e.g. when * existing files with the same name are discovered. * @param job the job that emitted this signal * @param files a list of items that are about to be created. * @deprecated since 5.2 -- this signal is unused since kde 3... */ QT_MOC_COMPAT void aboutToCreate(KIO::Job *job, const QList &files); /** * Sends the number of processed files. * @param job the job that emitted this signal * @param files the number of processed files */ void processedFiles(KIO::Job *job, unsigned long files); /** * Sends the number of processed directories. * @param job the job that emitted this signal * @param dirs the number of processed dirs */ void processedDirs(KIO::Job *job, unsigned long dirs); /** * The job is copying a file or directory. * * Note: This signal is used for progress dialogs, it's not emitted for * every file or directory (this would be too slow), but every 200ms. * * @param job the job that emitted this signal * @param src the URL of the file or directory that is currently * being copied * @param dest the destination of the current operation */ void copying(KIO::Job *job, const QUrl &src, const QUrl &dest); /** * The job is creating a symbolic link. * * Note: This signal is used for progress dialogs, it's not emitted for * every file or directory (this would be too slow), but every 200ms. * * @param job the job that emitted this signal * @param target the URL of the file or directory that is currently * being linked * @param to the destination of the current operation */ void linking(KIO::Job *job, const QString &target, const QUrl &to); /** * The job is moving a file or directory. * * Note: This signal is used for progress dialogs, it's not emitted for * every file or directory (this would be too slow), but every 200ms. * * @param job the job that emitted this signal * @param from the URL of the file or directory that is currently * being moved * @param to the destination of the current operation */ void moving(KIO::Job *job, const QUrl &from, const QUrl &to); /** * The job is creating the directory @p dir. * * This signal is emitted for every directory being created. * * @param job the job that emitted this signal * @param dir the directory that is currently being created */ void creatingDir(KIO::Job *job, const QUrl &dir); /** * The user chose to rename @p from to @p to. * * @param job the job that emitted this signal * @param from the original name * @param to the new name */ void renamed(KIO::Job *job, const QUrl &from, const QUrl &to); /** * The job emits this signal when copying or moving a file or directory successfully finished. * This signal is mainly for the Undo feature. * If you simply want to know when a copy job is done, use result(). * * @param job the job that emitted this signal * @param from the source URL * @param to the destination URL * @param mtime the modification time of the source file, hopefully set on the destination file * too (when the kioslave supports it). * @param directory indicates whether a file or directory was successfully copied/moved. * true for a directory, false for file * @param renamed indicates that the destination URL was created using a * rename operation (i.e. fast directory moving). true if is has been renamed */ void copyingDone(KIO::Job *job, const QUrl &from, const QUrl &to, const QDateTime &mtime, bool directory, bool renamed); /** * The job is copying or moving a symbolic link, that points to target. * The new link is created in @p to. The existing one is/was in @p from. * This signal is mainly for the Undo feature. * @param job the job that emitted this signal * @param from the source URL * @param target the target * @param to the destination URL */ void copyingLinkDone(KIO::Job *job, const QUrl &from, const QString &target, const QUrl &to); protected Q_SLOTS: void slotResult(KJob *job) override; protected: CopyJob(CopyJobPrivate &dd); void emitResult(); private: - Q_PRIVATE_SLOT(d_func(), void slotStart()) - Q_PRIVATE_SLOT(d_func(), void slotEntries(KIO::Job *, const KIO::UDSEntryList &list)) - Q_PRIVATE_SLOT(d_func(), void slotSubError(KIO::ListJob *, KIO::ListJob *)) - Q_PRIVATE_SLOT(d_func(), void slotReport()) Q_PRIVATE_SLOT(d_func(), void sourceStated(const KIO::UDSEntry &entry, const QUrl &sourceUrl)) Q_DECLARE_PRIVATE(CopyJob) }; /** * Copy a file or directory @p src into the destination @p dest, * which can be a file (including the final filename) or a directory * (into which @p src will be copied). * * This emulates the cp command completely. * * @param src the file or directory to copy * @param dest the destination * @param flags copy() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. Use copyAs if you don't want that. * * @return the job handling the operation * @see copyAs() */ KIOCORE_EXPORT CopyJob *copy(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Copy a file or directory @p src into the destination @p dest, * which is the destination name in any case, even for a directory. * * As opposed to copy(), this doesn't emulate cp, but is the only * way to copy a directory, giving it a new name and getting an error * box if a directory already exists with the same name (or writing the * contents of @p src into @p dest, when using Overwrite). * * @param src the file or directory to copy * @param dest the destination * @param flags copyAs() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". * * * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *copyAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Copy a list of file/dirs @p src into a destination directory @p dest. * * @param src the list of files and/or directories * @param dest the destination * @param flags copy() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *copy(const QList &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Moves a file or directory @p src to the given destination @p dest. * * @param src the file or directory to copy * @param dest the destination * @param flags move() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. * @return the job handling the operation * @see copy() * @see moveAs() */ KIOCORE_EXPORT CopyJob *move(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Moves a file or directory @p src to the given destination @p dest. Unlike move() * this operation will not move @p src into @p dest when @p dest exists: it will * either fail, or move the contents of @p src into it if Overwrite is set. * * @param src the file or directory to copy * @param dest the destination * @param flags moveAs() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". * @return the job handling the operation * @see copyAs() */ KIOCORE_EXPORT CopyJob *moveAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Moves a list of files or directories @p src to the given destination @p dest. * * @param src the list of files or directories to copy * @param dest the destination * @param flags move() supports HideProgressInfo and Overwrite. * Note: Overwrite has the meaning of both "write into existing directories" and * "overwrite existing files". However if "dest" exists, then src is copied * into a subdir of dest, just like "cp" does. * @return the job handling the operation * @see copy() */ KIOCORE_EXPORT CopyJob *move(const QList &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Create a link. * If the protocols and hosts are the same, a Unix symlink will be created. * Otherwise, a .desktop file of Type Link and pointing to the src URL will be created. * * @param src The existing file or directory, 'target' of the link. * @param destDir Destination directory where the link will be created. * @param flags link() supports HideProgressInfo only * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *link(const QUrl &src, const QUrl &destDir, JobFlags flags = DefaultFlags); /** * Create several links * If the protocols and hosts are the same, a Unix symlink will be created. * Otherwise, a .desktop file of Type Link and pointing to the src URL will be created. * * @param src The existing files or directories, 'targets' of the link. * @param destDir Destination directory where the links will be created. * @param flags link() supports HideProgressInfo only * @return the job handling the operation * @see link() */ KIOCORE_EXPORT CopyJob *link(const QList &src, const QUrl &destDir, JobFlags flags = DefaultFlags); /** * Create a link. Unlike link() this operation will fail when @p dest is an existing * directory rather than the final name for the link. * If the protocols and hosts are the same, a Unix symlink will be created. * Otherwise, a .desktop file of Type Link and pointing to the src URL will be created. * * @param src The existing file or directory, 'target' of the link. * @param dest Destination (i.e. the final symlink) * @param flags linkAs() supports HideProgressInfo only * @return the job handling the operation * @see link () * @see copyAs() */ KIOCORE_EXPORT CopyJob *linkAs(const QUrl &src, const QUrl &dest, JobFlags flags = DefaultFlags); /** * Trash a file or directory. * This is currently only supported for local files and directories. * Use QUrl::fromLocalFile to create a URL from a local file path. * * @param src file to delete * @param flags trash() supports HideProgressInfo only * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *trash(const QUrl &src, JobFlags flags = DefaultFlags); /** * Trash a list of files or directories. * This is currently only supported for local files and directories. * * @param src the files to delete * @param flags trash() supports HideProgressInfo only * @return the job handling the operation */ KIOCORE_EXPORT CopyJob *trash(const QList &src, JobFlags flags = DefaultFlags); } #endif diff --git a/src/core/job_p.h b/src/core/job_p.h index f68a7460..fdbe44ed 100644 --- a/src/core/job_p.h +++ b/src/core/job_p.h @@ -1,398 +1,395 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 David Faure Waldo Bastian Copyright (C) 2007 Thiago Macieira Copyright (C) 2013 Dawit Alemayehu This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_JOB_P_H #define KIO_JOB_P_H #include "simplejob.h" #include "transferjob.h" #include "commands_p.h" #include "kjobtrackerinterface.h" #include #include #include #include #include #include "kiocoredebug.h" #include "global.h" #define KIO_ARGS QByteArray packedArgs; QDataStream stream( &packedArgs, QIODevice::WriteOnly ); stream namespace KIO { class Slave; // Exported for KIOWidgets jobs class KIOCORE_EXPORT JobPrivate { public: JobPrivate() : m_parentJob(nullptr), m_extraFlags(0), m_uiDelegateExtension(KIO::defaultJobUiDelegateExtension()), m_privilegeExecutionEnabled(false) { } virtual ~JobPrivate(); /** * Some extra storage space for jobs that don't have their own * private d pointer. */ enum { EF_TransferJobAsync = (1 << 0), EF_TransferJobNeedData = (1 << 1), EF_TransferJobDataSent = (1 << 2), EF_ListJobUnrestricted = (1 << 3), EF_KillCalled = (1 << 4) }; enum FileOperationType { ChangeAttr, // chmod(), chown(), setModificationTime() Copy, Delete, MkDir, Move, Rename, Symlink, Transfer, // put() and get() Other // if other file operation set message, caption inside the job. }; // Maybe we could use the QObject parent/child mechanism instead // (requires a new ctor, and moving the ctor code to some init()). Job *m_parentJob; int m_extraFlags; MetaData m_incomingMetaData; MetaData m_internalMetaData; MetaData m_outgoingMetaData; JobUiDelegateExtension *m_uiDelegateExtension; Job *q_ptr; // For privilege operation bool m_privilegeExecutionEnabled; QString m_caption, m_message; FileOperationType m_operationType; QByteArray privilegeOperationData(); void slotSpeed(KJob *job, unsigned long speed); static void emitMoving(KIO::Job *, const QUrl &src, const QUrl &dest); static void emitCopying(KIO::Job *, const QUrl &src, const QUrl &dest); static void emitCreatingDir(KIO::Job *, const QUrl &dir); static void emitDeleting(KIO::Job *, const QUrl &url); static void emitStating(KIO::Job *, const QUrl &url); static void emitTransferring(KIO::Job *, const QUrl &url); static void emitMounting(KIO::Job *, const QString &dev, const QString &point); static void emitUnmounting(KIO::Job *, const QString &point); Q_DECLARE_PUBLIC(Job) }; class SimpleJobPrivate: public JobPrivate { public: /** * Creates a new simple job. * @param url the url of the job * @param command the command of the job * @param packedArgs the arguments */ SimpleJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs) : m_slave(nullptr), m_packedArgs(packedArgs), m_url(url), m_command(command), m_checkOnHold(false), m_schedSerial(0), m_redirectionHandlingEnabled(true) { #if 0 if (m_url.hasSubUrl()) { QList list = KUrl::split(m_url); list.removeLast(); m_subUrl = KUrl::join(list); //qDebug() << "New URL = " << m_url.url(); //qDebug() << "Sub URL = " << m_subUrl.url(); } #endif } Slave *m_slave; QByteArray m_packedArgs; QUrl m_url; QUrl m_subUrl; int m_command; // for use in KIO::Scheduler // // There are two kinds of protocol: // (1) The protocol of the url // (2) The actual protocol that the io-slave uses. // // These two often match, but not necessarily. Most notably, they don't // match when doing ftp via a proxy. // In that case (1) is ftp, but (2) is http. // // JobData::protocol stores (2) while Job::url().protocol() returns (1). // The ProtocolInfoDict is indexed with (2). // // We schedule slaves based on (2) but tell the slave about (1) via // Slave::setProtocol(). QString m_protocol; QStringList m_proxyList; bool m_checkOnHold; int m_schedSerial; bool m_redirectionHandlingEnabled; void simpleJobInit(); /** * Called on a slave's connected signal. * @see connected() */ void slotConnected(); /** * Forward signal from the slave. * @param data_size the processed size in bytes * @see processedSize() */ void slotProcessedSize(KIO::filesize_t data_size); /** * Forward signal from the slave. * @param speed the speed in bytes/s * @see speed() */ void slotSpeed(unsigned long speed); /** * Forward signal from the slave * Can also be called by the parent job, when it knows the size. * @param data_size the total size */ void slotTotalSize(KIO::filesize_t data_size); /** * Called on a slave's info message. * @param s the info message * @see infoMessage() */ void _k_slotSlaveInfoMessage(const QString &s); /** * Called when privilegeOperationRequested() is emitted by slave. */ void slotPrivilegeOperationRequested(); /** * @internal * Called by the scheduler when a slave gets to * work on this job. **/ virtual void start(KIO::Slave *slave); /** * @internal * Called to detach a slave from a job. **/ void slaveDone(); /** * Called by subclasses to restart the job after a redirection was signalled. * The m_redirectionURL data member can appear in several subclasses, so we have it * passed in. The regular URL will be set to the redirection URL which is then cleared. */ void restartAfterRedirection(QUrl *redirectionUrl); /** * Request the ui delegate to show a message box. * @internal */ int requestMessageBox(int type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo, const QString &iconYes = QString(), const QString &iconNo = QString(), const QString &dontAskAgainName = QString(), const KIO::MetaData &sslMetaData = KIO::MetaData()); Q_DECLARE_PUBLIC(SimpleJob) static inline SimpleJobPrivate *get(KIO::SimpleJob *job) { return job->d_func(); } static inline SimpleJob *newJobNoUi(const QUrl &url, int command, const QByteArray &packedArgs) { SimpleJob *job = new SimpleJob(*new SimpleJobPrivate(url, command, packedArgs)); return job; } static inline SimpleJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, JobFlags flags = HideProgressInfo) { SimpleJob *job = new SimpleJob(*new SimpleJobPrivate(url, command, packedArgs)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } if (!(flags & NoPrivilegeExecution)) { job->d_func()->m_privilegeExecutionEnabled = true; // Only delete, rename and symlink operation accept JobFlags. FileOperationType opType; switch (command) { case CMD_DEL: opType = Delete; break; case CMD_RENAME: opType = Rename; break; case CMD_SYMLINK: opType = Symlink; break; } job->d_func()->m_operationType = opType; } return job; } }; class TransferJobPrivate: public SimpleJobPrivate { public: inline TransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &_staticData) : SimpleJobPrivate(url, command, packedArgs), m_internalSuspended(false), m_errorPage(false), staticData(_staticData), m_isMimetypeEmitted(false), m_closedBeforeStart(false), m_subJob(nullptr) { } inline TransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice) : SimpleJobPrivate(url, command, packedArgs), m_internalSuspended(false), m_errorPage(false), m_isMimetypeEmitted(false), m_closedBeforeStart(false), m_subJob(nullptr), m_outgoingDataSource(QPointer(ioDevice)) { } bool m_internalSuspended; bool m_errorPage; QByteArray staticData; QUrl m_redirectionURL; QList m_redirectionList; QString m_mimetype; bool m_isMimetypeEmitted; bool m_closedBeforeStart; TransferJob *m_subJob; QPointer m_outgoingDataSource; /** * Flow control. Suspend data processing from the slave. */ void internalSuspend(); /** * Flow control. Resume data processing from the slave. */ void internalResume(); /** * @internal * Called by the scheduler when a slave gets to * work on this job. * @param slave the slave that works on the job */ void start(KIO::Slave *slave) override; /** * @internal * Called when the ioslave needs the data to send the server. This slot * is invoked when the data is to be sent is read from a QIODevice rather * instead of a QByteArray buffer. */ virtual void slotDataReqFromDevice(); void slotIODeviceClosed(); void slotIODeviceClosedBeforeStart(); - - void slotErrorPage(); - void slotCanResume(KIO::filesize_t offset); void slotPostRedirection(); void slotNeedSubUrlData(); void slotSubUrlData(KIO::Job *, const QByteArray &); Q_DECLARE_PUBLIC(TransferJob) static inline TransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &_staticData, JobFlags flags) { TransferJob *job = new TransferJob(*new TransferJobPrivate(url, command, packedArgs, _staticData)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } if (!(flags & NoPrivilegeExecution)) { job->d_func()->m_privilegeExecutionEnabled = true; job->d_func()->m_operationType = Transfer; } return job; } static inline TransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice, JobFlags flags) { TransferJob *job = new TransferJob(*new TransferJobPrivate(url, command, packedArgs, ioDevice)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } if (!(flags & NoPrivilegeExecution)) { job->d_func()->m_privilegeExecutionEnabled = true; job->d_func()->m_operationType = Transfer; } return job; } }; class DirectCopyJobPrivate; /** * @internal * Used for direct copy from or to the local filesystem (i.e. SlaveBase::copy()) */ class DirectCopyJob : public SimpleJob { Q_OBJECT public: DirectCopyJob(const QUrl &url, const QByteArray &packedArgs); ~DirectCopyJob(); public Q_SLOTS: void slotCanResume(KIO::filesize_t offset); Q_SIGNALS: /** * @internal * Emitted if the job found an existing partial file * and supports resuming. Used by FileCopyJob. */ void canResume(KIO::Job *job, KIO::filesize_t offset); private: Q_DECLARE_PRIVATE(DirectCopyJob) }; } #endif diff --git a/src/core/transferjob.cpp b/src/core/transferjob.cpp index fbed1557..10c728f9 100644 --- a/src/core/transferjob.cpp +++ b/src/core/transferjob.cpp @@ -1,498 +1,493 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2013 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "transferjob.h" #include "job_p.h" #include "slave.h" #include #include using namespace KIO; static const int MAX_READ_BUF_SIZE = (64 * 1024); // 64 KB at a time seems reasonable... TransferJob::TransferJob(TransferJobPrivate &dd) : SimpleJob(dd) { Q_D(TransferJob); if (d->m_command == CMD_PUT) { d->m_extraFlags |= JobPrivate::EF_TransferJobDataSent; } if (d->m_outgoingDataSource) { connect(d->m_outgoingDataSource, SIGNAL(readChannelFinished()), SLOT(slotIODeviceClosedBeforeStart())); } } TransferJob::~TransferJob() { } // Slave sends data void TransferJob::slotData(const QByteArray &_data) { Q_D(TransferJob); if (d->m_command == CMD_GET && !d->m_isMimetypeEmitted) { qCWarning(KIO_CORE) << "mimeType() not emitted when sending first data!; job URL =" << d->m_url << "data size =" << _data.size(); } // shut up the warning, HACK: downside is that it changes the meaning of the variable d->m_isMimetypeEmitted = true; if (d->m_redirectionURL.isEmpty() || !d->m_redirectionURL.isValid() || error()) { emit data(this, _data); } } void KIO::TransferJob::setTotalSize(KIO::filesize_t bytes) { setTotalAmount(KJob::Bytes, bytes); } // Slave got a redirection request void TransferJob::slotRedirection(const QUrl &url) { Q_D(TransferJob); //qDebug() << url; if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("redirect"), d->m_url, url)) { qCWarning(KIO_CORE) << "Redirection from" << d->m_url << "to" << url << "REJECTED!"; return; } // Some websites keep redirecting to themselves where each redirection // acts as the stage in a state-machine. We define "endless redirections" // as 5 redirections to the same URL. if (d->m_redirectionList.count(url) > 5) { //qDebug() << "CYCLIC REDIRECTION!"; setError(ERR_CYCLIC_LINK); setErrorText(d->m_url.toDisplayString()); } else { d->m_redirectionURL = url; // We'll remember that when the job finishes d->m_redirectionList.append(url); QString sslInUse = queryMetaData(QStringLiteral("ssl_in_use")); if (!sslInUse.isNull()) { // the key is present addMetaData(QStringLiteral("ssl_was_in_use"), sslInUse); } else { addMetaData(QStringLiteral("ssl_was_in_use"), QStringLiteral("FALSE")); } // Tell the user that we haven't finished yet emit redirection(this, d->m_redirectionURL); } } void TransferJob::slotFinished() { Q_D(TransferJob); //qDebug() << d->m_url; if (!d->m_redirectionURL.isEmpty() && d->m_redirectionURL.isValid()) { //qDebug() << "Redirection to" << m_redirectionURL; if (queryMetaData(QStringLiteral("permanent-redirect")) == QLatin1String("true")) { emit permanentRedirection(this, d->m_url, d->m_redirectionURL); } if (queryMetaData(QStringLiteral("redirect-to-get")) == QLatin1String("true")) { d->m_command = CMD_GET; d->m_outgoingMetaData.remove(QStringLiteral("CustomHTTPMethod")); d->m_outgoingMetaData.remove(QStringLiteral("content-type")); } if (d->m_redirectionHandlingEnabled) { // Honour the redirection // We take the approach of "redirecting this same job" // Another solution would be to create a subjob, but the same problem // happens (unpacking+repacking) d->staticData.truncate(0); d->m_incomingMetaData.clear(); if (queryMetaData(QStringLiteral("cache")) != QLatin1String("reload")) { addMetaData(QStringLiteral("cache"), QStringLiteral("refresh")); } d->m_internalSuspended = false; // The very tricky part is the packed arguments business QUrl dummyUrl; QDataStream istream(d->m_packedArgs); switch (d->m_command) { case CMD_GET: case CMD_STAT: case CMD_DEL: { d->m_packedArgs.truncate(0); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << d->m_redirectionURL; break; } case CMD_PUT: { int permissions; qint8 iOverwrite, iResume; istream >> dummyUrl >> iOverwrite >> iResume >> permissions; d->m_packedArgs.truncate(0); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << d->m_redirectionURL << iOverwrite << iResume << permissions; break; } case CMD_SPECIAL: { int specialcmd; istream >> specialcmd; if (specialcmd == 1) { // HTTP POST d->m_outgoingMetaData.remove(QStringLiteral("content-type")); addMetaData(QStringLiteral("cache"), QStringLiteral("reload")); d->m_packedArgs.truncate(0); QDataStream stream(&d->m_packedArgs, QIODevice::WriteOnly); stream << d->m_redirectionURL; d->m_command = CMD_GET; } break; } } d->restartAfterRedirection(&d->m_redirectionURL); return; } } SimpleJob::slotFinished(); } void TransferJob::setAsyncDataEnabled(bool enabled) { Q_D(TransferJob); if (enabled) { d->m_extraFlags |= JobPrivate::EF_TransferJobAsync; } else { d->m_extraFlags &= ~JobPrivate::EF_TransferJobAsync; } } void TransferJob::sendAsyncData(const QByteArray &dataForSlave) { Q_D(TransferJob); if (d->m_extraFlags & JobPrivate::EF_TransferJobNeedData) { if (d->m_slave) { d->m_slave->send(MSG_DATA, dataForSlave); } if (d->m_extraFlags & JobPrivate::EF_TransferJobDataSent) { // put job -> emit progress KIO::filesize_t size = processedAmount(KJob::Bytes) + dataForSlave.size(); setProcessedAmount(KJob::Bytes, size); } } d->m_extraFlags &= ~JobPrivate::EF_TransferJobNeedData; } #ifndef KIOCORE_NO_DEPRECATED void TransferJob::setReportDataSent(bool enabled) { Q_D(TransferJob); if (enabled) { d->m_extraFlags |= JobPrivate::EF_TransferJobDataSent; } else { d->m_extraFlags &= ~JobPrivate::EF_TransferJobDataSent; } } #endif #ifndef KIOCORE_NO_DEPRECATED bool TransferJob::reportDataSent() const { return (d_func()->m_extraFlags & JobPrivate::EF_TransferJobDataSent); } #endif QString TransferJob::mimetype() const { return d_func()->m_mimetype; } QUrl TransferJob::redirectUrl() const { return d_func()->m_redirectionURL; } // Slave requests data void TransferJob::slotDataReq() { Q_D(TransferJob); QByteArray dataForSlave; d->m_extraFlags |= JobPrivate::EF_TransferJobNeedData; if (!d->staticData.isEmpty()) { dataForSlave = d->staticData; d->staticData.clear(); } else { emit dataReq(this, dataForSlave); if (d->m_extraFlags & JobPrivate::EF_TransferJobAsync) { return; } } static const int max_size = 14 * 1024 * 1024; if (dataForSlave.size() > max_size) { //qDebug() << "send" << dataForSlave.size() / 1024 / 1024 << "MB of data in TransferJob::dataReq. This needs to be split, which requires a copy. Fix the application."; d->staticData = QByteArray(dataForSlave.data() + max_size, dataForSlave.size() - max_size); dataForSlave.truncate(max_size); } sendAsyncData(dataForSlave); if (d->m_subJob) { // Bitburger protocol in action d->internalSuspend(); // Wait for more data from subJob. d->m_subJob->d_func()->internalResume(); // Ask for more! } } void TransferJob::slotMimetype(const QString &type) { Q_D(TransferJob); d->m_mimetype = type; if (d->m_command == CMD_GET && d->m_isMimetypeEmitted) { qCWarning(KIO_CORE) << "mimetype() emitted again, or after sending first data!; job URL =" << d->m_url; } d->m_isMimetypeEmitted = true; emit mimetype(this, type); } void TransferJobPrivate::internalSuspend() { m_internalSuspended = true; if (m_slave) { m_slave->suspend(); } } void TransferJobPrivate::internalResume() { m_internalSuspended = false; if (m_slave && !q_func()->isSuspended()) { m_slave->resume(); } } bool TransferJob::doResume() { Q_D(TransferJob); if (!SimpleJob::doResume()) { return false; } if (d->m_internalSuspended) { d->internalSuspend(); } return true; } bool TransferJob::isErrorPage() const { return d_func()->m_errorPage; } void TransferJobPrivate::start(Slave *slave) { Q_Q(TransferJob); Q_ASSERT(slave); JobPrivate::emitTransferring(q, m_url); q->connect(slave, &SlaveInterface::data, q, &TransferJob::slotData); if (m_outgoingDataSource) { if (m_extraFlags & JobPrivate::EF_TransferJobAsync) { - q->connect(m_outgoingDataSource, SIGNAL(readyRead()), - SLOT(slotDataReqFromDevice())); + q->connect(m_outgoingDataSource, &QIODevice::readyRead, q, [this]() { + slotDataReqFromDevice(); + }); q->connect(m_outgoingDataSource, SIGNAL(readChannelFinished()), SLOT(slotIODeviceClosed())); // We don't really need to disconnect since we're never checking // m_closedBeforeStart again but it's the proper thing to do logically QObject::disconnect(m_outgoingDataSource, SIGNAL(readChannelFinished()), q, SLOT(slotIODeviceClosedBeforeStart())); if (m_closedBeforeStart) { QMetaObject::invokeMethod(q, "slotIODeviceClosed", Qt::QueuedConnection); } else if (m_outgoingDataSource->bytesAvailable() > 0) { QMetaObject::invokeMethod(q, "slotDataReqFromDevice", Qt::QueuedConnection); } } else { - q->connect(slave, SIGNAL(dataReq()), - SLOT(slotDataReqFromDevice())); + q->connect(slave, &SlaveInterface::dataReq, q, [this]() { + slotDataReqFromDevice(); + }); } } else q->connect(slave, &SlaveInterface::dataReq, q, &TransferJob::slotDataReq); q->connect(slave, &SlaveInterface::redirection, q, &TransferJob::slotRedirection); q->connect(slave, &SlaveInterface::mimeType, q, &TransferJob::slotMimetype); - q->connect(slave, SIGNAL(errorPage()), - SLOT(slotErrorPage())); + q->connect(slave, &SlaveInterface::errorPage, q, [this]() { + m_errorPage = true; + }); - q->connect(slave, SIGNAL(needSubUrlData()), - SLOT(slotNeedSubUrlData())); + q->connect(slave, &SlaveInterface::needSubUrlData, q, [this]() { + slotNeedSubUrlData(); + }); - q->connect(slave, SIGNAL(canResume(KIO::filesize_t)), - SLOT(slotCanResume(KIO::filesize_t))); + q->connect(slave, &SlaveInterface::canResume, q, [q](KIO::filesize_t offset) { + emit q->canResume(q, offset); + }); if (slave->suspended()) { m_mimetype = QStringLiteral("unknown"); // WABA: The slave was put on hold. Resume operation. slave->resume(); } SimpleJobPrivate::start(slave); if (m_internalSuspended) { slave->suspend(); } } void TransferJobPrivate::slotNeedSubUrlData() { Q_Q(TransferJob); // Job needs data from subURL. m_subJob = KIO::get(m_subUrl, NoReload, HideProgressInfo); internalSuspend(); // Put job on hold until we have some data. - q->connect(m_subJob, SIGNAL(data(KIO::Job*,QByteArray)), - SLOT(slotSubUrlData(KIO::Job*,QByteArray))); + q->connect(m_subJob, &TransferJob::data, q, [this](KIO::Job *job, const QByteArray &data) { + slotSubUrlData(job, data); + }); q->addSubjob(m_subJob); } void TransferJobPrivate::slotSubUrlData(KIO::Job *, const QByteArray &data) { // The Alternating Bitburg protocol in action again. staticData = data; m_subJob->d_func()->internalSuspend(); // Put job on hold until we have delivered the data. internalResume(); // Activate ourselves again. } void TransferJob::slotMetaData(const KIO::MetaData &_metaData) { Q_D(TransferJob); SimpleJob::slotMetaData(_metaData); storeSSLSessionFromJob(d->m_redirectionURL); } -void TransferJobPrivate::slotErrorPage() -{ - m_errorPage = true; -} - -void TransferJobPrivate::slotCanResume(KIO::filesize_t offset) -{ - Q_Q(TransferJob); - emit q->canResume(q, offset); -} - void TransferJobPrivate::slotDataReqFromDevice() { Q_Q(TransferJob); bool done = false; QByteArray dataForSlave; m_extraFlags |= JobPrivate::EF_TransferJobNeedData; if (m_outgoingDataSource) { dataForSlave.resize(MAX_READ_BUF_SIZE); //Code inspired in QNonContiguousByteDevice qint64 bytesRead = m_outgoingDataSource->read(dataForSlave.data(), MAX_READ_BUF_SIZE); if (bytesRead >= 0) { dataForSlave.resize(bytesRead); } else { dataForSlave.clear(); } done = ((bytesRead == -1) || (bytesRead == 0 && m_outgoingDataSource->atEnd() && !m_outgoingDataSource->isSequential())); } if (dataForSlave.isEmpty()) { emit q->dataReq(q, dataForSlave); if (!done && (m_extraFlags & JobPrivate::EF_TransferJobAsync)) { return; } } q->sendAsyncData(dataForSlave); if (m_subJob) { // Bitburger protocol in action internalSuspend(); // Wait for more data from subJob. m_subJob->d_func()->internalResume(); // Ask for more! } } void TransferJobPrivate::slotIODeviceClosedBeforeStart() { m_closedBeforeStart = true; } void TransferJobPrivate::slotIODeviceClosed() { Q_Q(TransferJob); const QByteArray remainder = m_outgoingDataSource->readAll(); if (!remainder.isEmpty()) { m_extraFlags |= JobPrivate::EF_TransferJobNeedData; q->sendAsyncData(remainder); } m_extraFlags |= JobPrivate::EF_TransferJobNeedData; //We send an empty data array to indicate the stream is over q->sendAsyncData(QByteArray()); if (m_subJob) { // Bitburger protocol in action internalSuspend(); // Wait for more data from subJob. m_subJob->d_func()->internalResume(); // Ask for more! } } void TransferJob::slotResult(KJob *job) { Q_D(TransferJob); // This can only be our suburl. Q_ASSERT(job == d->m_subJob); SimpleJob::slotResult(job); if (!error() && job == d->m_subJob) { d->m_subJob = nullptr; // No action required d->internalResume(); // Make sure we get the remaining data. } } void TransferJob::setModificationTime(const QDateTime &mtime) { addMetaData(QStringLiteral("modified"), mtime.toString(Qt::ISODate)); } TransferJob *KIO::get(const QUrl &url, LoadType reload, JobFlags flags) { // Send decoded path and encoded query KIO_ARGS << url; TransferJob *job = TransferJobPrivate::newJob(url, CMD_GET, packedArgs, QByteArray(), flags); if (reload == Reload) { job->addMetaData(QStringLiteral("cache"), QStringLiteral("reload")); } return job; } #include "moc_transferjob.cpp" diff --git a/src/core/transferjob.h b/src/core/transferjob.h index 5825e189..fd4543c9 100644 --- a/src/core/transferjob.h +++ b/src/core/transferjob.h @@ -1,322 +1,317 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2013 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIO_TRANSFERJOB_H #define KIO_TRANSFERJOB_H #include "simplejob.h" namespace KIO { class TransferJobPrivate; /** * @class KIO::TransferJob transferjob.h * * The transfer job pumps data into and/or out of a Slave. * Data is sent to the slave on request of the slave ( dataReq). * If data coming from the slave can not be handled, the * reading of data from the slave should be suspended. */ class KIOCORE_EXPORT TransferJob : public SimpleJob { Q_OBJECT public: ~TransferJob() override; /** * Sets the modification time of the file to be created (by KIO::put) * Note that some kioslaves might ignore this. */ void setModificationTime(const QDateTime &mtime); /** * Checks whether we got an error page. This currently only happens * with HTTP urls. Call this from your slot connected to result(). * * @return true if we got an (HTML) error page from the server * instead of what we asked for. */ bool isErrorPage() const; /** * Enable the async data mode. * When async data is enabled, data should be provided to the job by * calling sendAsyncData() instead of returning data in the * dataReq() signal. */ void setAsyncDataEnabled(bool enabled); /** * Provide data to the job when async data is enabled. * Should be called exactly once after receiving a dataReq signal * Sending an empty block indicates end of data. */ void sendAsyncData(const QByteArray &data); /** * When enabled, the job reports the amount of data that has been sent, * instead of the amount of data that has been received. * @see slotProcessedSize * @see slotSpeed * @deprecated since 4.2.1, this is unnecessary (it is always false for * KIO::get and true for KIO::put) */ #ifndef KIOCORE_NO_DEPRECATED KIOCORE_DEPRECATED void setReportDataSent(bool enabled); #endif /** * Returns whether the job reports the amount of data that has been * sent (true), or whether the job reports the amount of data that * has been received (false) * @deprecated since 4.2.1, this is unnecessary (it is always false for * KIO::get and true for KIO::put) */ #ifndef KIOCORE_NO_DEPRECATED KIOCORE_DEPRECATED bool reportDataSent() const; #endif /** * Call this in the slot connected to result, * and only after making sure no error happened. * @return the mimetype of the URL */ QString mimetype() const; /** * After the job has finished, it will return the final url in case a redirection * has happened. * @return the final url that can be empty in case no redirection has happened. * @since 5.0 */ QUrl redirectUrl() const; /** * Set the total size of data that we are going to send * in a put job. Helps getting proper progress information. * @since 4.2.1 */ void setTotalSize(KIO::filesize_t bytes); protected: /** * Called when m_subJob finishes. * @param job the job that finished */ void slotResult(KJob *job) override; /** * Reimplemented for internal reasons */ bool doResume() override; Q_SIGNALS: /** * Data from the slave has arrived. * @param job the job that emitted this signal * @param data data received from the slave. * * End of data (EOD) has been reached if data.size() == 0, however, you * should not be certain of data.size() == 0 ever happening (e.g. in case * of an error), so you should rely on result() instead. */ void data(KIO::Job *job, const QByteArray &data); /** * Request for data. * Please note, that you shouldn't put too large chunks * of data in it as this requires copies within the frame * work, so you should rather split the data you want * to pass here in reasonable chunks (about 1MB maximum) * * @param job the job that emitted this signal * @param data buffer to fill with data to send to the * slave. An empty buffer indicates end of data. (EOD) */ void dataReq(KIO::Job *job, QByteArray &data); /** * Signals a redirection. * Use to update the URL shown to the user. * The redirection itself is handled internally. * @param job the job that emitted this signal * @param url the new URL */ void redirection(KIO::Job *job, const QUrl &url); /** * Signals a permanent redirection. * The redirection itself is handled internally. * @param job the job that emitted this signal * @param fromUrl the original URL * @param toUrl the new URL */ void permanentRedirection(KIO::Job *job, const QUrl &fromUrl, const QUrl &toUrl); /** * Mimetype determined. * @param job the job that emitted this signal * @param type the mime type */ void mimetype(KIO::Job *job, const QString &type); /** * @internal * Emitted if the "put" job found an existing partial file * (in which case offset is the size of that file) * and emitted by the "get" job if it supports resuming to * the given offset - in this case @p offset is unused) */ void canResume(KIO::Job *job, KIO::filesize_t offset); protected Q_SLOTS: virtual void slotRedirection(const QUrl &url); void slotFinished() override; virtual void slotData(const QByteArray &data); virtual void slotDataReq(); virtual void slotMimetype(const QString &mimetype); void slotMetaData(const KIO::MetaData &_metaData) override; protected: TransferJob(TransferJobPrivate &dd); private: - Q_PRIVATE_SLOT(d_func(), void slotErrorPage()) - Q_PRIVATE_SLOT(d_func(), void slotCanResume(KIO::filesize_t offset)) Q_PRIVATE_SLOT(d_func(), void slotPostRedirection()) - Q_PRIVATE_SLOT(d_func(), void slotNeedSubUrlData()) - Q_PRIVATE_SLOT(d_func(), void slotSubUrlData(KIO::Job *, const QByteArray &)) - Q_PRIVATE_SLOT(d_func(), void slotDataReqFromDevice()) Q_PRIVATE_SLOT(d_func(), void slotIODeviceClosed()) Q_PRIVATE_SLOT(d_func(), void slotIODeviceClosedBeforeStart()) Q_DECLARE_PRIVATE(TransferJob) // A FileCopyJob may control one or more TransferJobs friend class FileCopyJob; friend class FileCopyJobPrivate; }; /** * Get (means: read). * This is the job to use in order to "download" a file into memory. * The slave emits the data through the data() signal. * * Special case: if you want to determine the mimetype of the file first, * and then read it with the appropriate component, you can still use * a KIO::get() directly. When that job emits the mimeType signal, (which is * guaranteed to happen before it emits any data), put the job on hold: * * @code * job->putOnHold(); * KIO::Scheduler::publishSlaveOnHold(); * @endcode * * and forget about the job. The next time someone does a KIO::get() on the * same URL (even in another process) this job will be resumed. This saves KIO * from doing two requests to the server. * * @param url the URL of the file * @param reload Reload to reload the file, NoReload if it can be taken from the cache * @param flags Can be HideProgressInfo here * @return the job handling the operation. */ KIOCORE_EXPORT TransferJob *get(const QUrl &url, LoadType reload = NoReload, JobFlags flags = DefaultFlags); /** * Put (means: write) * * @param url Where to write data. * @param permissions May be -1. In this case no special permission mode is set. * @param flags Can be HideProgressInfo, Overwrite and Resume here. WARNING: * Setting Resume means that the data will be appended to @p dest if @p dest exists. * @return the job handling the operation. * @see multi_get() */ KIOCORE_EXPORT TransferJob *put(const QUrl &url, int permissions, JobFlags flags = DefaultFlags); /** * HTTP POST (for form data). * * Example: * \code * job = KIO::http_post( url, postData, KIO::HideProgressInfo ); * job->addMetaData("content-type", contentType ); * job->addMetaData("referrer", referrerURL); * \endcode * * @p postData is the data that you want to send and * @p contentType is the complete HTTP header line that * specifies the content's MIME type, for example * "Content-Type: text/xml". * * You MUST specify content-type! * * Often @p contentType is * "Content-Type: application/x-www-form-urlencoded" and * the @p postData is then an ASCII string (without null-termination!) * with characters like space, linefeed and percent escaped like %20, * %0A and %25. * * @param url Where to write the data. * @param postData Encoded data to post. * @param flags Can be HideProgressInfo here * @return the job handling the operation. */ KIOCORE_EXPORT TransferJob *http_post(const QUrl &url, const QByteArray &postData, JobFlags flags = DefaultFlags); /** * HTTP POST. * * This function, unlike the one that accepts a QByteArray, accepts an IO device * from which to read the encoded data to be posted to the server in order to * to avoid holding the content of very large post requests, e.g. multimedia file * uploads, in memory. * * @param url Where to write the data. * @param device the device to read from * @param size Size of the encoded post data. * @param flags Can be HideProgressInfo here * @return the job handling the operation. * * @since 4.7 */ KIOCORE_EXPORT TransferJob *http_post(const QUrl &url, QIODevice *device, qint64 size = -1, JobFlags flags = DefaultFlags); /** * HTTP DELETE. * * Though this function servers the same purpose as KIO::file_delete, unlike * file_delete it accommodates HTTP specific actions such as redirections. * * @param url url resource to delete. * @param flags Can be HideProgressInfo here * @return the job handling the operation. * * @since 4.7.3 */ KIOCORE_EXPORT TransferJob *http_delete(const QUrl &url, JobFlags flags = DefaultFlags); } #endif