diff --git a/src/core/batchrenamejob.cpp b/src/core/batchrenamejob.cpp index 92c73209..fbeda4a8 100644 --- a/src/core/batchrenamejob.cpp +++ b/src/core/batchrenamejob.cpp @@ -1,209 +1,214 @@ /* This file is part of the KDE libraries Copyright (C) 2017 by Chinmoy Ranjan Pradhan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "batchrenamejob.h" #include "job_p.h" #include "copyjob.h" #include #include #include #include using namespace KIO; class KIO::BatchRenameJobPrivate : public KIO::JobPrivate { public: BatchRenameJobPrivate(const QList &src, const QString &newName, int index, QChar placeHolder, JobFlags flags) : JobPrivate(), m_srcList(src), m_newName(newName), m_index(index), m_placeHolder(placeHolder), m_listIterator(m_srcList.constBegin()), m_allExtensionsDifferent(true), m_useIndex(true), m_appendIndex(false), m_flags(flags) { // There occur four cases when renaming multiple files, // 1. All files have different extension and $newName contains a valid placeholder. // 2. At least two files have same extension and $newName contains a valid placeholder. // In these two cases the placeholder character will be replaced by an integer($index). // 3. All files have different extension and new name contains an invalid placeholder // (this means either $newName doesn't contain the placeholder or the placeholders // are not in a connected sequence). // In this case nothing is substituted and all files have the same $newName. // 4. At least two files have same extension and $newName contains an invalid placeholder. // In this case $index is appended to $newName. // Check for extensions. QSet extensions; QMimeDatabase db; foreach (const QUrl &url, m_srcList) { const QString extension = db.suffixForFileName(url.toDisplayString().toLower()); if (extensions.contains(extension)) { m_allExtensionsDifferent = false; break; } extensions.insert(extension); } // Check for exactly one placeholder character or exactly one sequence of placeholders. int pos = newName.indexOf(placeHolder); if (pos != -1) { while (pos < newName.size() && newName.at(pos) == placeHolder) { pos++; } } const bool validPlaceholder = (newName.indexOf(placeHolder, pos) == -1); if (!validPlaceholder) { if (!m_allExtensionsDifferent) { m_appendIndex = true; } else { m_useIndex = false; } } } QList m_srcList; QString m_newName; int m_index; QChar m_placeHolder; QList::const_iterator m_listIterator; bool m_allExtensionsDifferent; bool m_useIndex; bool m_appendIndex; QUrl m_newUrl; // for fileRenamed signal const JobFlags m_flags; Q_DECLARE_PUBLIC(BatchRenameJob) void slotStart(); QString indexedName(const QString& name, int index, QChar placeHolder) const; static inline BatchRenameJob *newJob(const QList &src, const QString &newName, int index, QChar placeHolder, JobFlags flags) { BatchRenameJob *job = new BatchRenameJob(*new BatchRenameJobPrivate(src, newName, index, placeHolder, flags)); 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 = Rename; + } return job; } }; BatchRenameJob::BatchRenameJob(BatchRenameJobPrivate &dd) : Job(dd) { QTimer::singleShot(0, this, SLOT(slotStart())); } BatchRenameJob::~BatchRenameJob() { } QString BatchRenameJobPrivate::indexedName(const QString& name, int index, QChar placeHolder) const { if (!m_useIndex) { return name; } QString newName = name; QString indexString = QString::number(index); if (m_appendIndex) { newName.append(indexString); return newName; } // Insert leading zeros if necessary const int minIndexLength = name.count(placeHolder); indexString.prepend(QString(minIndexLength - indexString.length(), QLatin1Char('0'))); // Replace the index placeholders by the indexString const int placeHolderStart = newName.indexOf(placeHolder); newName.replace(placeHolderStart, minIndexLength, indexString); return newName; } void BatchRenameJobPrivate::slotStart() { Q_Q(BatchRenameJob); if (m_listIterator == m_srcList.constBegin()) { // emit total q->setTotalAmount(KJob::Files, m_srcList.count()); } if (m_listIterator != m_srcList.constEnd()) { QString newName = indexedName(m_newName, m_index, m_placeHolder); const QUrl oldUrl = *m_listIterator; QMimeDatabase db; const QString extension = db.suffixForFileName(oldUrl.path().toLower()); if (!extension.isEmpty()) { newName.append(QLatin1Char('.')); newName.append(extension); } m_newUrl = oldUrl.adjusted(QUrl::RemoveFilename); m_newUrl.setPath(m_newUrl.path() + KIO::encodeFileName(newName)); KIO::Job * job = KIO::moveAs(oldUrl, m_newUrl, KIO::HideProgressInfo); + job->setParentJob(q); q->addSubjob(job); q->setProcessedAmount(KJob::Files, q->processedAmount(KJob::Files) + 1); } else { q->emitResult(); } } void BatchRenameJob::slotResult(KJob *job) { Q_D(BatchRenameJob); if (job->error()) { KIO::Job::slotResult(job); return; } removeSubjob(job); emit fileRenamed(*d->m_listIterator, d->m_newUrl); ++d->m_listIterator; ++d->m_index; emitPercent(d->m_listIterator - d->m_srcList.constBegin(), d->m_srcList.count()); d->slotStart(); } BatchRenameJob * KIO::batchRename(const QList &src, const QString &newName, int index, QChar placeHolder, KIO::JobFlags flags) { return BatchRenameJobPrivate::newJob(src, newName, index, placeHolder, flags); } #include "moc_batchrenamejob.cpp" diff --git a/src/core/chmodjob.cpp b/src/core/chmodjob.cpp index 91be534b..886a059b 100644 --- a/src/core/chmodjob.cpp +++ b/src/core/chmodjob.cpp @@ -1,289 +1,294 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow David Faure 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 "chmodjob.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include "listjob.h" #include "job_p.h" #include "jobuidelegatefactory.h" #include "kioglobal_p.h" namespace KIO { struct ChmodInfo { QUrl url; int permissions; }; enum ChmodJobState { CHMODJOB_STATE_LISTING, CHMODJOB_STATE_CHMODING }; class ChmodJobPrivate: public KIO::JobPrivate { public: ChmodJobPrivate(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive) : state(CHMODJOB_STATE_LISTING) , m_permissions(permissions) , m_mask(mask) , m_newOwner(newOwner) , m_newGroup(newGroup) , m_recursive(recursive) , m_bAutoSkipFiles(false) , m_lstItems(lstItems) { } ChmodJobState state; int m_permissions; int m_mask; KUserId m_newOwner; KGroupId m_newGroup; bool m_recursive; bool m_bAutoSkipFiles; KFileItemList m_lstItems; QLinkedList m_infos; // linkedlist since we keep removing the first item void _k_chmodNextFile(); void _k_slotEntries(KIO::Job *, const KIO::UDSEntryList &); void _k_processList(); Q_DECLARE_PUBLIC(ChmodJob) static inline ChmodJob *newJob(const KFileItemList &lstItems, int permissions, int mask, KUserId newOwner, KGroupId newGroup, bool recursive, JobFlags flags) { ChmodJob *job = new ChmodJob(*new ChmodJobPrivate(lstItems, permissions, mask, newOwner, newGroup, recursive)); 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 = ChangeAttr; + } return job; } }; } // namespace KIO using namespace KIO; ChmodJob::ChmodJob(ChmodJobPrivate &dd) : KIO::Job(dd) { QMetaObject::invokeMethod(this, "_k_processList", Qt::QueuedConnection); } ChmodJob::~ChmodJob() { } void ChmodJobPrivate::_k_processList() { Q_Q(ChmodJob); while (!m_lstItems.isEmpty()) { const KFileItem item = m_lstItems.first(); if (!item.isLink()) { // don't do anything with symlinks // File or directory -> remember to chmod ChmodInfo info; info.url = item.url(); // This is a toplevel file, we apply changes directly (no +X emulation here) const mode_t permissions = item.permissions() & 0777; // get rid of "set gid" and other special flags info.permissions = (m_permissions & m_mask) | (permissions & ~m_mask); /*//qDebug() << "toplevel url:" << info.url << "\n current permissions=" << QString::number(permissions,8) << "\n wanted permission=" << QString::number(m_permissions,8) << "\n with mask=" << QString::number(m_mask,8) << "\n with ~mask (mask bits we keep) =" << QString::number((uint)~m_mask,8) << "\n bits we keep =" << QString::number(permissions & ~m_mask,8) << "\n new permissions = " << QString::number(info.permissions,8);*/ m_infos.prepend(info); //qDebug() << "processList : Adding info for " << info.url; // Directory and recursive -> list if (item.isDir() && m_recursive) { //qDebug() << "ChmodJob::processList dir -> listing"; KIO::ListJob *listJob = KIO::listRecursive(item.url(), KIO::HideProgressInfo); q->connect(listJob, SIGNAL(entries(KIO::Job *, const KIO::UDSEntryList &)), SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList))); q->addSubjob(listJob); return; // we'll come back later, when this one's finished } } m_lstItems.removeFirst(); } //qDebug() << "ChmodJob::processList -> going to STATE_CHMODING"; // We have finished, move on state = CHMODJOB_STATE_CHMODING; _k_chmodNextFile(); } void ChmodJobPrivate::_k_slotEntries(KIO::Job *, const KIO::UDSEntryList &list) { KIO::UDSEntryList::ConstIterator it = list.begin(); KIO::UDSEntryList::ConstIterator end = list.end(); for (; it != end; ++it) { const KIO::UDSEntry &entry = *it; const bool isLink = !entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST).isEmpty(); const QString relativePath = entry.stringValue(KIO::UDSEntry::UDS_NAME); if (!isLink && relativePath != QLatin1String("..")) { const mode_t permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & 0777; // get rid of "set gid" and other special flags ChmodInfo info; info.url = m_lstItems.first().url(); // base directory info.url.setPath(concatPaths(info.url.path(), relativePath)); int mask = m_mask; // Emulate -X: only give +x to files that had a +x bit already // So the check is the opposite : if the file had no x bit, don't touch x bits // For dirs this doesn't apply if (!entry.isDir()) { int newPerms = m_permissions & mask; if ((newPerms & 0111) && !(permissions & 0111)) { // don't interfere with mandatory file locking if (newPerms & 02000) { mask = mask & ~0101; } else { mask = mask & ~0111; } } } info.permissions = (m_permissions & mask) | (permissions & ~mask); /*//qDebug() << info.url << "\n current permissions=" << QString::number(permissions,8) << "\n wanted permission=" << QString::number(m_permissions,8) << "\n with mask=" << QString::number(mask,8) << "\n with ~mask (mask bits we keep) =" << QString::number((uint)~mask,8) << "\n bits we keep =" << QString::number(permissions & ~mask,8) << "\n new permissions = " << QString::number(info.permissions,8);*/ // Prepend this info in our todo list. // This way, the toplevel dirs are done last. m_infos.prepend(info); } } } void ChmodJobPrivate::_k_chmodNextFile() { Q_Q(ChmodJob); if (!m_infos.isEmpty()) { ChmodInfo info = m_infos.takeFirst(); // First update group / owner (if local file) // (permissions have to set after, in case of suid and sgid) if (info.url.isLocalFile() && (m_newOwner.isValid() || m_newGroup.isValid())) { QString path = info.url.toLocalFile(); if (!KIOPrivate::changeOwnership(path, m_newOwner, m_newGroup)) { if (!m_uiDelegateExtension) { emit q->warning(q, i18n("Could not modify the ownership of file %1", path)); } else if (!m_bAutoSkipFiles) { const QString errMsg = i18n("Could not modify the ownership of file %1. You have insufficient access to the file to perform the change.", path); SkipDialog_Options options; if (m_infos.count() > 1) { options |= SkipDialog_MultipleItems; } const SkipDialog_Result skipResult = m_uiDelegateExtension->askSkip(q, options, errMsg); switch (skipResult) { case Result_AutoSkip: m_bAutoSkipFiles = true; // fall through case Result_Skip: QMetaObject::invokeMethod(q, "_k_chmodNextFile", Qt::QueuedConnection); return; case Result_Retry: m_infos.prepend(info); QMetaObject::invokeMethod(q, "_k_chmodNextFile", Qt::QueuedConnection); return; case Result_Cancel: default: q->setError(ERR_USER_CANCELED); q->emitResult(); return; } } } } /*qDebug() << "chmod'ing" << info.url << "to" << QString::number(info.permissions,8);*/ KIO::SimpleJob *job = KIO::chmod(info.url, info.permissions); + job->setParentJob(q); // copy the metadata for acl and default acl const QString aclString = q->queryMetaData(QStringLiteral("ACL_STRING")); const QString defaultAclString = q->queryMetaData(QStringLiteral("DEFAULT_ACL_STRING")); if (!aclString.isEmpty()) { job->addMetaData(QStringLiteral("ACL_STRING"), aclString); } if (!defaultAclString.isEmpty()) { job->addMetaData(QStringLiteral("DEFAULT_ACL_STRING"), defaultAclString); } q->addSubjob(job); } else // We have finished { q->emitResult(); } } void ChmodJob::slotResult(KJob *job) { Q_D(ChmodJob); removeSubjob(job); if (job->error()) { setError(job->error()); setErrorText(job->errorText()); emitResult(); return; } //qDebug() << "d->m_lstItems:" << d->m_lstItems.count(); switch (d->state) { case CHMODJOB_STATE_LISTING: d->m_lstItems.removeFirst(); //qDebug() << "-> processList"; d->_k_processList(); return; case CHMODJOB_STATE_CHMODING: //qDebug() << "-> chmodNextFile"; d->_k_chmodNextFile(); return; default: Q_ASSERT(false); return; } } ChmodJob *KIO::chmod(const KFileItemList &lstItems, int permissions, int mask, const QString &owner, const QString &group, bool recursive, JobFlags flags) { KUserId uid = KUserId::fromName(owner); KGroupId gid = KGroupId::fromName(group); return ChmodJobPrivate::newJob(lstItems, permissions, mask, uid, gid, recursive, flags); } #include "moc_chmodjob.cpp" diff --git a/src/core/copyjob.cpp b/src/core/copyjob.cpp index b33302a0..346b4704 100644 --- a/src/core/copyjob.cpp +++ b/src/core/copyjob.cpp @@ -1,2242 +1,2266 @@ /* 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 #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(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) { setProperty("destUrl", d_func()->m_dest.toString()); QTimer::singleShot(0, this, SLOT(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())); 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); 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() == "data" ? "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); 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())); 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 #if QT_VERSION >= QT_VERSION_CHECK(5,8,0) Q_FALLTHROUGH(); #endif 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)); info.ctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1)); info.size = (KIO::filesize_t) entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1); if (info.size != (KIO::filesize_t) - 1) { m_totalSize += info.size; } // recursive listing, displayName can be a/b/c/d const QString 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('/'); // don't make this a find()! QString path = url.path(); int pos = 0; for (int n = 0; n < numberOfSlashes + 1; ++n) { pos = path.lastIndexOf('/', pos - 1); if (pos == -1) { // error 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("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()) + ".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:"<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->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(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('/')) { oldPath += '/'; } // Change the current one and strip the trailing '/' (*it).uDest = newUrl.adjusted(QUrl::StripTrailingSlash); QString newPath = newUrl.path(); // With trailing slash if (!newPath.endsWith('/')) { newPath += '/'; } 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('/')) { path += '/'; } 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); 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); 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(); // Its modification time: const QDateTime destmtime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1)); const QDateTime destctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1)); const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE); const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); q->removeSubjob(job); 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; } } 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: 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); 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); 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 = dynamic_cast(job); Q_ASSERT(kiojob); m_incomingMetaData += kiojob->metaData(); q->removeSubjob(job); 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(); const QDateTime destmtime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1)); const QDateTime destctime = QDateTime::fromMSecsSinceEpoch(1000 * entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1)); 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; } 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); assert(!q->hasSubjobs()); switch (res) { case Result_Cancel: q->setError(ERR_USER_CANCELED); q->emitResult(); return; case Result_AutoRename: m_bAutoRenameFiles = true; // fall through 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 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: 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 { 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 (bCopyFile) { // any file to create, finally ? qCDebug(KIO_COPYJOB_DEBUG)<<"preparing to copy"<<(*it).uSource<<(*it).size<setError(ERR_DISK_FULL); q->emitResult(); return; } //TODO check if dst mount is msdos and (*it).size exceeds it's limits } 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, SIGNAL(processedSize(KJob*,qulonglong)), SLOT(slotProcessedSize(KJob*,qulonglong))); q->connect(newjob, SIGNAL(totalSize(KJob*,qulonglong)), SLOT(slotTotalSize(KJob*,qulonglong))); } 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); 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); 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 = dynamic_cast(job); Q_ASSERT(kiojob); m_incomingMetaData += kiojob->metaData(); q->removeSubjob(job); 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 + "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 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 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: //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); 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: 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/deletejob.cpp b/src/core/deletejob.cpp index c5cc34a9..cabf40ae 100644 --- a/src/core/deletejob.cpp +++ b/src/core/deletejob.cpp @@ -1,508 +1,514 @@ /* This file is part of the KDE libraries Copyright 2000 Stephan Kulow Copyright 2000-2009 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 "deletejob.h" #include "job.h" // buildErrorString #include "statjob.h" #include "listjob.h" #include "kcoredirlister.h" #include "scheduler.h" #include "kdirwatch.h" #include "kprotocolmanager.h" #include #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include "job_p.h" extern bool kio_resolve_local_urls; // from copyjob.cpp, abused here to save a symbol. static bool isHttpProtocol(const QString &protocol) { return (protocol.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive) || protocol.startsWith(QLatin1String("http"), Qt::CaseInsensitive)); } namespace KIO { enum DeleteJobState { DELETEJOB_STATE_STATING, DELETEJOB_STATE_DELETING_FILES, DELETEJOB_STATE_DELETING_DIRS }; /* static const char* const s_states[] = { "DELETEJOB_STATE_STATING", "DELETEJOB_STATE_DELETING_FILES", "DELETEJOB_STATE_DELETING_DIRS" }; */ class DeleteJobPrivate: public KIO::JobPrivate { public: DeleteJobPrivate(const QList &src) : state(DELETEJOB_STATE_STATING) , m_processedFiles(0) , m_processedDirs(0) , m_totalFilesDirs(0) , m_srcList(src) , m_currentStat(m_srcList.begin()) , m_reportTimer(nullptr) { } DeleteJobState state; int m_processedFiles; int m_processedDirs; int m_totalFilesDirs; QUrl m_currentURL; QList files; QList symlinks; QList dirs; QList m_srcList; QList::iterator m_currentStat; QSet m_parentDirs; QTimer *m_reportTimer; void statNextSrc(); void currentSourceStated(bool isDir, bool isLink); void finishedStatPhase(); void deleteNextFile(); void deleteNextDir(); void restoreDirWatch() const; void slotReport(); void slotStart(); void slotEntries(KIO::Job *, const KIO::UDSEntryList &list); Q_DECLARE_PUBLIC(DeleteJob) static inline DeleteJob *newJob(const QList &src, JobFlags flags) { DeleteJob *job = new DeleteJob(*new DeleteJobPrivate(src)); 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 = Delete; + } return job; } }; } // namespace KIO using namespace KIO; DeleteJob::DeleteJob(DeleteJobPrivate &dd) : Job(dd) { d_func()->m_reportTimer = new QTimer(this); connect(d_func()->m_reportTimer, SIGNAL(timeout()), this, SLOT(slotReport())); //this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX d_func()->m_reportTimer->start(200); QTimer::singleShot(0, this, SLOT(slotStart())); } DeleteJob::~DeleteJob() { } QList DeleteJob::urls() const { return d_func()->m_srcList; } void DeleteJobPrivate::slotStart() { statNextSrc(); } void DeleteJobPrivate::slotReport() { Q_Q(DeleteJob); emit q->deleting(q, m_currentURL); // TODO: maybe we could skip everything else when (flags & HideProgressInfo) ? JobPrivate::emitDeleting(q, m_currentURL); switch (state) { case DELETEJOB_STATE_STATING: q->setTotalAmount(KJob::Files, files.count()); q->setTotalAmount(KJob::Directories, dirs.count()); break; case DELETEJOB_STATE_DELETING_DIRS: q->setProcessedAmount(KJob::Directories, m_processedDirs); q->emitPercent(m_processedFiles + m_processedDirs, m_totalFilesDirs); break; case DELETEJOB_STATE_DELETING_FILES: q->setProcessedAmount(KJob::Files, m_processedFiles); q->emitPercent(m_processedFiles, m_totalFilesDirs); break; } } void DeleteJobPrivate::slotEntries(KIO::Job *job, const UDSEntryList &list) { UDSEntryList::ConstIterator it = list.begin(); const UDSEntryList::ConstIterator end = list.end(); for (; it != end; ++it) { const UDSEntry &entry = *it; const QString displayName = entry.stringValue(KIO::UDSEntry::UDS_NAME); Q_ASSERT(!displayName.isEmpty()); if (displayName != QLatin1String("..") && displayName != QLatin1String(".")) { QUrl url; const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL); if (!urlStr.isEmpty()) { url = QUrl(urlStr); } else { url = static_cast(job)->url(); // assumed to be a dir url.setPath(concatPaths(url.path(), displayName)); } //qDebug() << displayName << "(" << url << ")"; if (entry.isLink()) { symlinks.append(url); } else if (entry.isDir()) { dirs.append(url); } else { files.append(url); } } } } void DeleteJobPrivate::statNextSrc() { Q_Q(DeleteJob); //qDebug(); if (m_currentStat != m_srcList.end()) { m_currentURL = (*m_currentStat); // if the file system doesn't support deleting, we do not even stat if (!KProtocolManager::supportsDeleting(m_currentURL)) { QPointer that = q; ++m_currentStat; emit q->warning(q, buildErrorString(ERR_CANNOT_DELETE, m_currentURL.toDisplayString())); if (that) { statNextSrc(); } return; } // Stat it state = DELETEJOB_STATE_STATING; // Fast path for KFileItems in directory views while (m_currentStat != m_srcList.end()) { m_currentURL = (*m_currentStat); const KFileItem cachedItem = KCoreDirLister::cachedItemForUrl(m_currentURL); if (cachedItem.isNull()) { break; } //qDebug() << "Found cached info about" << m_currentURL << "isDir=" << cachedItem.isDir() << "isLink=" << cachedItem.isLink(); currentSourceStated(cachedItem.isDir(), cachedItem.isLink()); ++m_currentStat; } // Hook for unit test to disable the fast path. if (!kio_resolve_local_urls) { // Fast path for local files // (using a loop, instead of a huge recursion) while (m_currentStat != m_srcList.end() && (*m_currentStat).isLocalFile()) { m_currentURL = (*m_currentStat); QFileInfo fileInfo(m_currentURL.toLocalFile()); currentSourceStated(fileInfo.isDir(), fileInfo.isSymLink()); ++m_currentStat; } } if (m_currentStat == m_srcList.end()) { // Done, jump to the last else of this method statNextSrc(); } else { KIO::SimpleJob *job = KIO::stat(m_currentURL, StatJob::SourceSide, 0, KIO::HideProgressInfo); Scheduler::setJobPriority(job, 1); //qDebug() << "stat'ing" << m_currentURL; q->addSubjob(job); } } else { if (!q->hasSubjobs()) { // don't go there yet if we're still listing some subdirs finishedStatPhase(); } } } void DeleteJobPrivate::finishedStatPhase() { m_totalFilesDirs = files.count() + symlinks.count() + dirs.count(); slotReport(); // 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 instance // used by e.g. kdirlister). const QSet::const_iterator itEnd = m_parentDirs.constEnd(); for (QSet::const_iterator it = m_parentDirs.constBegin(); it != itEnd; ++it) { KDirWatch::self()->stopDirScan(*it); } state = DELETEJOB_STATE_DELETING_FILES; deleteNextFile(); } void DeleteJobPrivate::deleteNextFile() { Q_Q(DeleteJob); //qDebug(); if (!files.isEmpty() || !symlinks.isEmpty()) { SimpleJob *job; do { // Take first file to delete out of list QList::iterator it = files.begin(); bool isLink = false; if (it == files.end()) { // No more files it = symlinks.begin(); // Pick up a symlink to delete isLink = true; } // Normal deletion // If local file, try do it directly if ((*it).isLocalFile() && QFile::remove((*it).toLocalFile())) { //kdDebug(7007) << "DeleteJob deleted" << (*it).toLocalFile(); job = nullptr; m_processedFiles++; if (m_processedFiles % 300 == 1 || m_totalFilesDirs < 300) { // update progress info every 300 files m_currentURL = *it; slotReport(); } } else { // if remote - or if unlink() failed (we'll use the job's error handling in that case) //qDebug() << "calling file_delete on" << *it; if (isHttpProtocol(it->scheme())) { job = KIO::http_delete(*it, KIO::HideProgressInfo); } else { job = KIO::file_delete(*it, KIO::HideProgressInfo); + job->setParentJob(q); } Scheduler::setJobPriority(job, 1); m_currentURL = (*it); } if (isLink) { symlinks.erase(it); } else { files.erase(it); } if (job) { q->addSubjob(job); return; } // loop only if direct deletion worked (job=0) and there is something else to delete } while (!job && (!files.isEmpty() || !symlinks.isEmpty())); } state = DELETEJOB_STATE_DELETING_DIRS; deleteNextDir(); } void DeleteJobPrivate::deleteNextDir() { Q_Q(DeleteJob); if (!dirs.isEmpty()) { // some dirs to delete ? do { // Take first dir to delete out of list - last ones first ! QList::iterator it = --dirs.end(); // If local dir, try to rmdir it directly if ((*it).isLocalFile() && QDir().rmdir((*it).toLocalFile())) { m_processedDirs++; if (m_processedDirs % 100 == 1) { // update progress info every 100 dirs m_currentURL = *it; slotReport(); } } else { // Call rmdir - works for kioslaves with canDeleteRecursive too, // CMD_DEL will trigger the recursive deletion in the slave. SimpleJob *job = KIO::rmdir(*it); + job->setParentJob(q); job->addMetaData(QStringLiteral("recurse"), QStringLiteral("true")); Scheduler::setJobPriority(job, 1); dirs.erase(it); q->addSubjob(job); return; } dirs.erase(it); } while (!dirs.isEmpty()); } // Re-enable watching on the dirs that held the deleted files restoreDirWatch(); // Finished - tell the world if (!m_srcList.isEmpty()) { //qDebug() << "KDirNotify'ing FilesRemoved" << m_srcList; org::kde::KDirNotify::emitFilesRemoved(m_srcList); } if (m_reportTimer != nullptr) { m_reportTimer->stop(); } q->emitResult(); } void DeleteJobPrivate::restoreDirWatch() const { const auto itEnd = m_parentDirs.constEnd(); for (auto it = m_parentDirs.constBegin(); it != itEnd; ++it) { KDirWatch::self()->restartDirScan(*it); } } void DeleteJobPrivate::currentSourceStated(bool isDir, bool isLink) { Q_Q(DeleteJob); const QUrl url = (*m_currentStat); if (isDir && !isLink) { // Add toplevel dir in list of dirs dirs.append(url); if (url.isLocalFile()) { // We are about to delete this dir, no need to watch it // Maybe we should ask kdirwatch to remove all watches recursively? // But then there would be no feedback (things disappearing progressively) during huge deletions KDirWatch::self()->stopDirScan(url.adjusted(QUrl::StripTrailingSlash).toLocalFile()); } if (!KProtocolManager::canDeleteRecursive(url)) { //qDebug() << url << "is a directory, let's list it"; ListJob *newjob = KIO::listRecursive(url, KIO::HideProgressInfo); newjob->addMetaData(QStringLiteral("details"), QStringLiteral("0")); newjob->setUnrestricted(true); // No KIOSK restrictions Scheduler::setJobPriority(newjob, 1); QObject::connect(newjob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), q, SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); q->addSubjob(newjob); // Note that this listing job will happen in parallel with other stat jobs. } } else { if (isLink) { //qDebug() << "Target is a symlink"; symlinks.append(url); } else { //qDebug() << "Target is a file"; files.append(url); } } if (url.isLocalFile()) { const QString parentDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); m_parentDirs.insert(parentDir); } } void DeleteJob::slotResult(KJob *job) { Q_D(DeleteJob); switch (d->state) { case DELETEJOB_STATE_STATING: removeSubjob(job); // Was this a stat job or a list job? We do both in parallel. if (StatJob *statJob = qobject_cast(job)) { // Was there an error while stating ? if (job->error()) { // Probably : doesn't exist Job::slotResult(job); // will set the error and emit result(this) d->restoreDirWatch(); return; } const UDSEntry entry = statJob->statResult(); // Is it a file or a dir ? const bool isLink = entry.isLink(); const bool isDir = entry.isDir(); d->currentSourceStated(isDir, isLink); ++d->m_currentStat; d->statNextSrc(); } else { if (job->error()) { // Try deleting nonetheless, it may be empty (and non-listable) } if (!hasSubjobs()) { d->finishedStatPhase(); } } break; case DELETEJOB_STATE_DELETING_FILES: // Propagate the subjob's metadata (a SimpleJob) to the real DeleteJob // FIXME: setMetaData() in the KIO API only allows access to outgoing metadata, // but we need to alter the incoming one d->m_incomingMetaData = dynamic_cast(job)->metaData(); if (job->error()) { Job::slotResult(job); // will set the error and emit result(this) d->restoreDirWatch(); return; } removeSubjob(job); Q_ASSERT(!hasSubjobs()); d->m_processedFiles++; d->deleteNextFile(); break; case DELETEJOB_STATE_DELETING_DIRS: if (job->error()) { Job::slotResult(job); // will set the error and emit result(this) d->restoreDirWatch(); return; } removeSubjob(job); Q_ASSERT(!hasSubjobs()); d->m_processedDirs++; //emit processedAmount( this, KJob::Directories, d->m_processedDirs ); //emitPercent( d->m_processedFiles + d->m_processedDirs, d->m_totalFilesDirs ); d->deleteNextDir(); break; default: Q_ASSERT(0); } } DeleteJob *KIO::del(const QUrl &src, JobFlags flags) { QList srcList; srcList.append(src); DeleteJob *job = DeleteJobPrivate::newJob(srcList, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent); } return job; } DeleteJob *KIO::del(const QList &src, JobFlags flags) { DeleteJob *job = DeleteJobPrivate::newJob(src, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent); } return job; } #include "moc_deletejob.cpp" diff --git a/src/core/filecopyjob.cpp b/src/core/filecopyjob.cpp index 301b7039..1b99de85 100644 --- a/src/core/filecopyjob.cpp +++ b/src/core/filecopyjob.cpp @@ -1,585 +1,593 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 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 "filecopyjob.h" #include "job_p.h" #include #include "kprotocolmanager.h" #include "scheduler.h" #include "slave.h" #include using namespace KIO; static inline Slave *jobSlave(SimpleJob *job) { return SimpleJobPrivate::get(job)->m_slave; } /** @internal */ class KIO::FileCopyJobPrivate: public KIO::JobPrivate { public: FileCopyJobPrivate(const QUrl &src, const QUrl &dest, int permissions, bool move, JobFlags flags) : m_sourceSize(filesize_t(-1)), m_src(src), m_dest(dest), m_moveJob(nullptr), m_copyJob(nullptr), m_delJob(nullptr), m_chmodJob(nullptr), m_getJob(nullptr), m_putJob(nullptr), m_permissions(permissions), m_move(move), m_mustChmod(0), m_flags(flags) { } KIO::filesize_t m_sourceSize; QDateTime m_modificationTime; QUrl m_src; QUrl m_dest; QByteArray m_buffer; SimpleJob *m_moveJob; SimpleJob *m_copyJob; SimpleJob *m_delJob; SimpleJob *m_chmodJob; TransferJob *m_getJob; TransferJob *m_putJob; int m_permissions; bool m_move: 1; bool m_canResume: 1; bool m_resumeAnswerSent: 1; bool m_mustChmod: 1; JobFlags m_flags; void startBestCopyMethod(); void startCopyJob(); void startCopyJob(const QUrl &slave_url); void startRenameJob(const QUrl &slave_url); void startDataPump(); void connectSubjob(SimpleJob *job); void slotStart(); void slotData(KIO::Job *, const QByteArray &data); void slotDataReq(KIO::Job *, QByteArray &data); void slotMimetype(KIO::Job *, const QString &type); /** * Forward signal from subjob * @param job the job that emitted this signal * @param size the processed size in bytes */ void slotProcessedSize(KJob *job, qulonglong size); /** * Forward signal from subjob * @param job the job that emitted this signal * @param size the total size */ void slotTotalSize(KJob *job, qulonglong size); /** * Forward signal from subjob * @param job the job that emitted this signal * @param pct the percentage */ void slotPercent(KJob *job, unsigned long pct); /** * Forward signal from subjob * @param job the job that emitted this signal * @param offset the offset to resume from */ void slotCanResume(KIO::Job *job, KIO::filesize_t offset); Q_DECLARE_PUBLIC(FileCopyJob) static inline FileCopyJob *newJob(const QUrl &src, const QUrl &dest, int permissions, bool move, JobFlags flags) { //qDebug() << src << "->" << dest; FileCopyJob *job = new FileCopyJob( *new FileCopyJobPrivate(src, dest, permissions, move, flags)); job->setProperty("destUrl", dest.toString()); 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 = move ? Move : Copy; + } return job; } }; /* * The FileCopyJob works according to the famous Bavarian * 'Alternating Bitburger Protocol': we either drink a beer or we * we order a beer, but never both at the same time. * Translated to io-slaves: We alternate between receiving a block of data * and sending it away. */ FileCopyJob::FileCopyJob(FileCopyJobPrivate &dd) : Job(dd) { //qDebug(); QTimer::singleShot(0, this, SLOT(slotStart())); } void FileCopyJobPrivate::slotStart() { Q_Q(FileCopyJob); if (!m_move) { JobPrivate::emitCopying(q, m_src, m_dest); } else { JobPrivate::emitMoving(q, m_src, m_dest); } if (m_move) { // The if() below must be the same as the one in startBestCopyMethod if ((m_src.scheme() == m_dest.scheme()) && (m_src.host() == m_dest.host()) && (m_src.port() == m_dest.port()) && (m_src.userName() == m_dest.userName()) && (m_src.password() == m_dest.password())) { startRenameJob(m_src); return; } else if (m_src.isLocalFile() && KProtocolManager::canRenameFromFile(m_dest)) { startRenameJob(m_dest); return; } else if (m_dest.isLocalFile() && KProtocolManager::canRenameToFile(m_src)) { startRenameJob(m_src); return; } // No fast-move available, use copy + del. } startBestCopyMethod(); } void FileCopyJobPrivate::startBestCopyMethod() { if ((m_src.scheme() == m_dest.scheme()) && (m_src.host() == m_dest.host()) && (m_src.port() == m_dest.port()) && (m_src.userName() == m_dest.userName()) && (m_src.password() == m_dest.password())) { startCopyJob(); } else if (m_src.isLocalFile() && KProtocolManager::canCopyFromFile(m_dest)) { startCopyJob(m_dest); } else if (m_dest.isLocalFile() && KProtocolManager::canCopyToFile(m_src) && !KIO::Scheduler::isSlaveOnHoldFor(m_src)) { startCopyJob(m_src); } else { startDataPump(); } } FileCopyJob::~FileCopyJob() { } void FileCopyJob::setSourceSize(KIO::filesize_t size) { Q_D(FileCopyJob); d->m_sourceSize = size; if (size != (KIO::filesize_t) - 1) { setTotalAmount(KJob::Bytes, size); } } void FileCopyJob::setModificationTime(const QDateTime &mtime) { Q_D(FileCopyJob); d->m_modificationTime = mtime; } QUrl FileCopyJob::srcUrl() const { return d_func()->m_src; } QUrl FileCopyJob::destUrl() const { return d_func()->m_dest; } void FileCopyJobPrivate::startCopyJob() { startCopyJob(m_src); } void FileCopyJobPrivate::startCopyJob(const QUrl &slave_url) { Q_Q(FileCopyJob); //qDebug(); KIO_ARGS << m_src << m_dest << m_permissions << (qint8)(m_flags & Overwrite); m_copyJob = new DirectCopyJob(slave_url, packedArgs); + m_copyJob->setParentJob(q); if (m_modificationTime.isValid()) { m_copyJob->addMetaData(QStringLiteral("modified"), m_modificationTime.toString(Qt::ISODate)); // #55804 } q->addSubjob(m_copyJob); connectSubjob(m_copyJob); q->connect(m_copyJob, SIGNAL(canResume(KIO::Job*,KIO::filesize_t)), SLOT(slotCanResume(KIO::Job*,KIO::filesize_t))); } void FileCopyJobPrivate::startRenameJob(const QUrl &slave_url) { Q_Q(FileCopyJob); m_mustChmod = true; // CMD_RENAME by itself doesn't change permissions KIO_ARGS << m_src << m_dest << (qint8)(m_flags & Overwrite); m_moveJob = SimpleJobPrivate::newJobNoUi(slave_url, CMD_RENAME, packedArgs); + m_moveJob->setParentJob(q); if (m_modificationTime.isValid()) { m_moveJob->addMetaData(QStringLiteral("modified"), m_modificationTime.toString(Qt::ISODate)); // #55804 } q->addSubjob(m_moveJob); connectSubjob(m_moveJob); } void FileCopyJobPrivate::connectSubjob(SimpleJob *job) { Q_Q(FileCopyJob); q->connect(job, SIGNAL(totalSize(KJob*,qulonglong)), SLOT(slotTotalSize(KJob*,qulonglong))); q->connect(job, SIGNAL(processedSize(KJob*,qulonglong)), SLOT(slotProcessedSize(KJob*,qulonglong))); q->connect(job, SIGNAL(percent(KJob*,ulong)), SLOT(slotPercent(KJob*,ulong))); if (q->isSuspended()) { job->suspend(); } } bool FileCopyJob::doSuspend() { Q_D(FileCopyJob); if (d->m_moveJob) { d->m_moveJob->suspend(); } if (d->m_copyJob) { d->m_copyJob->suspend(); } if (d->m_getJob) { d->m_getJob->suspend(); } if (d->m_putJob) { d->m_putJob->suspend(); } Job::doSuspend(); return true; } bool FileCopyJob::doResume() { Q_D(FileCopyJob); if (d->m_moveJob) { d->m_moveJob->resume(); } if (d->m_copyJob) { d->m_copyJob->resume(); } if (d->m_getJob) { d->m_getJob->resume(); } if (d->m_putJob) { d->m_putJob->resume(); } Job::doResume(); return true; } void FileCopyJobPrivate::slotProcessedSize(KJob *, qulonglong size) { Q_Q(FileCopyJob); q->setProcessedAmount(KJob::Bytes, size); } void FileCopyJobPrivate::slotTotalSize(KJob *, qulonglong size) { Q_Q(FileCopyJob); if (size != q->totalAmount(KJob::Bytes)) { q->setTotalAmount(KJob::Bytes, size); } } void FileCopyJobPrivate::slotPercent(KJob *, unsigned long pct) { Q_Q(FileCopyJob); if (pct > q->percent()) { q->setPercent(pct); } } void FileCopyJobPrivate::startDataPump() { Q_Q(FileCopyJob); //qDebug(); m_canResume = false; m_resumeAnswerSent = false; m_getJob = nullptr; // for now m_putJob = put(m_dest, m_permissions, (m_flags | HideProgressInfo) /* no GUI */); + m_putJob->setParentJob(q); //qDebug() << "m_putJob=" << m_putJob << "m_dest=" << m_dest; if (m_modificationTime.isValid()) { m_putJob->setModificationTime(m_modificationTime); } // The first thing the put job will tell us is whether we can // resume or not (this is always emitted) q->connect(m_putJob, SIGNAL(canResume(KIO::Job*,KIO::filesize_t)), SLOT(slotCanResume(KIO::Job*,KIO::filesize_t))); q->connect(m_putJob, SIGNAL(dataReq(KIO::Job*,QByteArray&)), SLOT(slotDataReq(KIO::Job*,QByteArray&))); q->addSubjob(m_putJob); } void FileCopyJobPrivate::slotCanResume(KIO::Job *job, KIO::filesize_t offset) { Q_Q(FileCopyJob); if (job == m_putJob || job == m_copyJob) { //qDebug() << "'can resume' from PUT job. offset=" << KIO::number(offset); if (offset) { RenameDialog_Result res = R_RESUME; if (!KProtocolManager::autoResume() && !(m_flags & Overwrite) && m_uiDelegateExtension) { QString newPath; KIO::Job *job = (q->parentJob()) ? q->parentJob() : q; // Ask confirmation about resuming previous transfer res = m_uiDelegateExtension->askFileRename( job, i18n("File Already Exists"), m_src, m_dest, RenameDialog_Options(RenameDialog_Overwrite | RenameDialog_Resume | RenameDialog_NoRename), newPath, m_sourceSize, offset); } if (res == R_OVERWRITE || (m_flags & Overwrite)) { offset = 0; } else if (res == R_CANCEL) { if (job == m_putJob) { m_putJob->kill(FileCopyJob::Quietly); q->removeSubjob(m_putJob); m_putJob = nullptr; } else { m_copyJob->kill(FileCopyJob::Quietly); q->removeSubjob(m_copyJob); m_copyJob = nullptr; } q->setError(ERR_USER_CANCELED); q->emitResult(); return; } } else { m_resumeAnswerSent = true; // No need for an answer } if (job == m_putJob) { m_getJob = KIO::get(m_src, NoReload, HideProgressInfo /* no GUI */); + m_getJob->setParentJob(q); //qDebug() << "m_getJob=" << m_getJob << m_src; m_getJob->addMetaData(QStringLiteral("errorPage"), QStringLiteral("false")); m_getJob->addMetaData(QStringLiteral("AllowCompressedPage"), QStringLiteral("false")); // Set size in subjob. This helps if the slave doesn't emit totalSize. if (m_sourceSize != (KIO::filesize_t) - 1) { m_getJob->setTotalAmount(KJob::Bytes, m_sourceSize); } if (offset) { //qDebug() << "Setting metadata for resume to" << (unsigned long) offset; m_getJob->addMetaData(QStringLiteral("range-start"), KIO::number(offset)); // Might or might not get emitted q->connect(m_getJob, SIGNAL(canResume(KIO::Job*,KIO::filesize_t)), SLOT(slotCanResume(KIO::Job*,KIO::filesize_t))); } jobSlave(m_putJob)->setOffset(offset); m_putJob->d_func()->internalSuspend(); q->addSubjob(m_getJob); connectSubjob(m_getJob); // Progress info depends on get m_getJob->d_func()->internalResume(); // Order a beer q->connect(m_getJob, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotData(KIO::Job*,QByteArray))); q->connect(m_getJob, SIGNAL(mimetype(KIO::Job*,QString)), SLOT(slotMimetype(KIO::Job*,QString))); } else { // copyjob jobSlave(m_copyJob)->sendResumeAnswer(offset != 0); } } else if (job == m_getJob) { // Cool, the get job said ok, we can resume m_canResume = true; //qDebug() << "'can resume' from the GET job -> we can resume"; jobSlave(m_getJob)->setOffset(jobSlave(m_putJob)->offset()); } else { qCWarning(KIO_CORE) << "unknown job=" << job << "m_getJob=" << m_getJob << "m_putJob=" << m_putJob; } } void FileCopyJobPrivate::slotData(KIO::Job *, const QByteArray &data) { //qDebug() << "data size:" << data.size(); Q_ASSERT(m_putJob); if (!m_putJob) { return; // Don't crash } m_getJob->d_func()->internalSuspend(); m_putJob->d_func()->internalResume(); // Drink the beer m_buffer += data; // On the first set of data incoming, we tell the "put" slave about our // decision about resuming if (!m_resumeAnswerSent) { m_resumeAnswerSent = true; //qDebug() << "(first time) -> send resume answer " << m_canResume; jobSlave(m_putJob)->sendResumeAnswer(m_canResume); } } void FileCopyJobPrivate::slotDataReq(KIO::Job *, QByteArray &data) { Q_Q(FileCopyJob); //qDebug(); if (!m_resumeAnswerSent && !m_getJob) { // This can't happen q->setError(ERR_INTERNAL); q->setErrorText(QStringLiteral("'Put' job did not send canResume or 'Get' job did not send data!")); m_putJob->kill(FileCopyJob::Quietly); q->removeSubjob(m_putJob); m_putJob = nullptr; q->emitResult(); return; } if (m_getJob) { m_getJob->d_func()->internalResume(); // Order more beer m_putJob->d_func()->internalSuspend(); } data = m_buffer; m_buffer = QByteArray(); } void FileCopyJobPrivate::slotMimetype(KIO::Job *, const QString &type) { Q_Q(FileCopyJob); emit q->mimetype(q, type); } void FileCopyJob::slotResult(KJob *job) { Q_D(FileCopyJob); //qDebug() << "this=" << this << "job=" << job; removeSubjob(job); // Did job have an error ? if (job->error()) { if ((job == d->m_moveJob) && (job->error() == ERR_UNSUPPORTED_ACTION)) { d->m_moveJob = nullptr; d->startBestCopyMethod(); return; } else if ((job == d->m_copyJob) && (job->error() == ERR_UNSUPPORTED_ACTION)) { d->m_copyJob = nullptr; d->startDataPump(); return; } else if (job == d->m_getJob) { d->m_getJob = nullptr; if (d->m_putJob) { d->m_putJob->kill(Quietly); removeSubjob(d->m_putJob); } } else if (job == d->m_putJob) { d->m_putJob = nullptr; if (d->m_getJob) { d->m_getJob->kill(Quietly); removeSubjob(d->m_getJob); } } setError(job->error()); setErrorText(job->errorText()); emitResult(); return; } if (d->m_mustChmod) { // If d->m_permissions == -1, keep the default permissions if (d->m_permissions != -1) { d->m_chmodJob = chmod(d->m_dest, d->m_permissions); addSubjob(d->m_chmodJob); } d->m_mustChmod = false; } if (job == d->m_moveJob) { d->m_moveJob = nullptr; // Finished } if (job == d->m_copyJob) { d->m_copyJob = nullptr; if (d->m_move) { d->m_delJob = file_delete(d->m_src, HideProgressInfo/*no GUI*/); // Delete source addSubjob(d->m_delJob); } } if (job == d->m_getJob) { //qDebug() << "m_getJob finished"; d->m_getJob = nullptr; // No action required if (d->m_putJob) { d->m_putJob->d_func()->internalResume(); } } if (job == d->m_putJob) { //qDebug() << "m_putJob finished"; d->m_putJob = nullptr; if (d->m_getJob) { // The get job is still running, probably after emitting data(QByteArray()) // and before we receive its finished(). d->m_getJob->d_func()->internalResume(); } if (d->m_move) { d->m_delJob = file_delete(d->m_src, HideProgressInfo/*no GUI*/); // Delete source addSubjob(d->m_delJob); } } if (job == d->m_delJob) { d->m_delJob = nullptr; // Finished } if (job == d->m_chmodJob) { d->m_chmodJob = nullptr; // Finished } if (!hasSubjobs()) { emitResult(); } } FileCopyJob *KIO::file_copy(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) { return FileCopyJobPrivate::newJob(src, dest, permissions, false, flags); } FileCopyJob *KIO::file_move(const QUrl &src, const QUrl &dest, int permissions, JobFlags flags) { FileCopyJob *job = FileCopyJobPrivate::newJob(src, dest, permissions, true, flags); if (job->uiDelegateExtension()) { job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::UpdateContent); } return job; } #include "moc_filecopyjob.cpp" diff --git a/src/core/job_p.h b/src/core/job_p.h index a9308942..ff83d5f0 100644 --- a/src/core/job_p.h +++ b/src/core/job_p.h @@ -1,374 +1,399 @@ /* 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), m_confirmationAsked(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; bool m_confirmationAsked; QString m_caption, m_message; FileOperationType m_operationType; PrivilegeOperationStatus tryAskPrivilegeOpConfirmation(); 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) Q_DECL_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/mkpathjob.cpp b/src/core/mkpathjob.cpp index 604188b9..cff2d2b2 100644 --- a/src/core/mkpathjob.cpp +++ b/src/core/mkpathjob.cpp @@ -1,154 +1,159 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 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 "mkpathjob.h" #include "job_p.h" #include "mkdirjob.h" #include "../pathhelpers_p.h" #include #include #include #include using namespace KIO; class KIO::MkpathJobPrivate : public KIO::JobPrivate { public: MkpathJobPrivate(const QUrl &url, const QUrl &baseUrl, JobFlags flags) : JobPrivate(), m_url(url), m_pathComponents(url.path().split('/', QString::SkipEmptyParts)), m_pathIterator(), m_flags(flags) { const QStringList basePathComponents = baseUrl.path().split('/', QString::SkipEmptyParts); m_url.setPath(QStringLiteral("/")); int i = 0; for (; i < basePathComponents.count() && i < m_pathComponents.count(); ++i) { const QString pathComponent = m_pathComponents.at(i); if (pathComponent == basePathComponents.at(i)) { m_url.setPath(concatPaths(m_url.path(), pathComponent)); } else { break; } } if (i > 0) { m_pathComponents.erase(m_pathComponents.begin(), m_pathComponents.begin() + i); } // fast path for local files using QFileInfo::isDir if (m_url.isLocalFile()) { i = 0; for (; i < m_pathComponents.count(); ++i) { const QString localFile = m_url.toLocalFile(); QString testDir; if (localFile == QLatin1String("/")) { testDir = localFile + m_pathComponents.at(i); } else { testDir = localFile + '/' + m_pathComponents.at(i); } if (QFileInfo(testDir).isDir()) { m_url.setPath(testDir); } else { break; } } if (i > 0) { m_pathComponents.erase(m_pathComponents.begin(), m_pathComponents.begin() + i); } } m_pathIterator = m_pathComponents.constBegin(); } QUrl m_url; QUrl m_baseUrl; QStringList m_pathComponents; QStringList::const_iterator m_pathIterator; const JobFlags m_flags; Q_DECLARE_PUBLIC(MkpathJob) void slotStart(); static inline MkpathJob *newJob(const QUrl &url, const QUrl &baseUrl, JobFlags flags) { MkpathJob *job = new MkpathJob(*new MkpathJobPrivate(url, baseUrl, flags)); 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 = MkDir; + } return job; } }; MkpathJob::MkpathJob(MkpathJobPrivate &dd) : Job(dd) { QTimer::singleShot(0, this, SLOT(slotStart())); } MkpathJob::~MkpathJob() { } void MkpathJobPrivate::slotStart() { Q_Q(MkpathJob); if (m_pathIterator == m_pathComponents.constBegin()) { // first time: emit total q->setTotalAmount(KJob::Directories, m_pathComponents.count()); } if (m_pathIterator != m_pathComponents.constEnd()) { m_url.setPath(concatPaths(m_url.path(), *m_pathIterator)); KIO::Job* job = KIO::mkdir(m_url); + job->setParentJob(q); q->addSubjob(job); q->setProcessedAmount(KJob::Directories, q->processedAmount(KJob::Directories) + 1); } else { q->emitResult(); } } void MkpathJob::slotResult(KJob *job) { Q_D(MkpathJob); if (job->error() && job->error() != KIO::ERR_DIR_ALREADY_EXIST) { KIO::Job::slotResult(job); // will set the error and emit result(this) return; } removeSubjob(job); emit directoryCreated(d->m_url); // Move on to next one ++d->m_pathIterator; emitPercent(d->m_pathIterator - d->m_pathComponents.constBegin(), d->m_pathComponents.count()); d->slotStart(); } MkpathJob * KIO::mkpath(const QUrl &url, const QUrl &baseUrl, KIO::JobFlags flags) { return MkpathJobPrivate::newJob(url, baseUrl, flags); } #include "moc_mkpathjob.cpp" diff --git a/src/core/simplejob.cpp b/src/core/simplejob.cpp index 37ff44ad..f0161f8f 100644 --- a/src/core/simplejob.cpp +++ b/src/core/simplejob.cpp @@ -1,429 +1,429 @@ /* 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 "simplejob.h" #include "job_p.h" #include "scheduler.h" #include "slave.h" #include "kprotocolinfo.h" #include #include #include using namespace KIO; SimpleJob::SimpleJob(SimpleJobPrivate &dd) : Job(dd) { d_func()->simpleJobInit(); } void SimpleJobPrivate::simpleJobInit() { Q_Q(SimpleJob); if (!m_url.isValid() || m_url.scheme().isEmpty()) { qCWarning(KIO_CORE) << "Invalid URL:" << m_url; q->setError(ERR_MALFORMED_URL); q->setErrorText(m_url.toString()); QTimer::singleShot(0, q, SLOT(slotFinished())); return; } Scheduler::doJob(q); } bool SimpleJob::doKill() { Q_D(SimpleJob); if ((d->m_extraFlags & JobPrivate::EF_KillCalled) == 0) { d->m_extraFlags |= JobPrivate::EF_KillCalled; Scheduler::cancelJob(this); // deletes the slave if not 0 } else { qCWarning(KIO_CORE) << this << "This is overkill."; } return Job::doKill(); } bool SimpleJob::doSuspend() { Q_D(SimpleJob); if (d->m_slave) { d->m_slave->suspend(); } return Job::doSuspend(); } bool SimpleJob::doResume() { Q_D(SimpleJob); if (d->m_slave) { d->m_slave->resume(); } return Job::doResume(); } const QUrl &SimpleJob::url() const { return d_func()->m_url; } void SimpleJob::putOnHold() { Q_D(SimpleJob); Q_ASSERT(d->m_slave); if (d->m_slave) { Scheduler::putSlaveOnHold(this, d->m_url); } // we should now be disassociated from the slave Q_ASSERT(!d->m_slave); kill(Quietly); } void SimpleJob::removeOnHold() { Scheduler::removeSlaveOnHold(); } bool SimpleJob::isRedirectionHandlingEnabled() const { return d_func()->m_redirectionHandlingEnabled; } void SimpleJob::setRedirectionHandlingEnabled(bool handle) { Q_D(SimpleJob); d->m_redirectionHandlingEnabled = handle; } SimpleJob::~SimpleJob() { Q_D(SimpleJob); // last chance to remove this job from the scheduler! if (d->m_schedSerial) { //qDebug() << "Killing job" << this << "in destructor!"/* << qBacktrace()*/; Scheduler::cancelJob(this); } } void SimpleJobPrivate::start(Slave *slave) { Q_Q(SimpleJob); m_slave = slave; // Slave::setJob can send us SSL metadata if there is a persistent connection q->connect(slave, SIGNAL(metaData(KIO::MetaData)), SLOT(slotMetaData(KIO::MetaData))); slave->setJob(q); q->connect(slave, SIGNAL(error(int,QString)), SLOT(slotError(int,QString))); q->connect(slave, SIGNAL(warning(QString)), SLOT(slotWarning(QString))); q->connect(slave, SIGNAL(infoMessage(QString)), SLOT(_k_slotSlaveInfoMessage(QString))); q->connect(slave, SIGNAL(connected()), SLOT(slotConnected())); q->connect(slave, SIGNAL(finished()), SLOT(slotFinished())); q->connect(slave, SIGNAL(privilegeOperationRequested()), SLOT(slotPrivilegeOperationRequested())); if ((m_extraFlags & EF_TransferJobDataSent) == 0) { // this is a "get" job q->connect(slave, SIGNAL(totalSize(KIO::filesize_t)), SLOT(slotTotalSize(KIO::filesize_t))); q->connect(slave, SIGNAL(processedSize(KIO::filesize_t)), SLOT(slotProcessedSize(KIO::filesize_t))); q->connect(slave, SIGNAL(speed(ulong)), SLOT(slotSpeed(ulong))); } const QVariant windowIdProp = q->property("window-id"); // see KJobWidgets::setWindow if (windowIdProp.isValid()) { m_outgoingMetaData.insert(QStringLiteral("window-id"), QString::number(windowIdProp.toULongLong())); } const QVariant userTimestampProp = q->property("userTimestamp"); // see KJobWidgets::updateUserTimestamp if (userTimestampProp.isValid()) { m_outgoingMetaData.insert(QStringLiteral("user-timestamp"), QString::number(userTimestampProp.toULongLong())); } if (q->uiDelegate() == nullptr) { // not interactive m_outgoingMetaData.insert(QStringLiteral("no-auth-prompt"), QStringLiteral("true")); } if (!m_outgoingMetaData.isEmpty()) { KIO_ARGS << m_outgoingMetaData; slave->send(CMD_META_DATA, packedArgs); } if (!m_subUrl.isEmpty()) { KIO_ARGS << m_subUrl; slave->send(CMD_SUBURL, packedArgs); } slave->send(m_command, m_packedArgs); if (q->isSuspended()) { slave->suspend(); } } void SimpleJobPrivate::slaveDone() { Q_Q(SimpleJob); if (m_slave) { if (m_command == CMD_OPEN) { m_slave->send(CMD_CLOSE); } q->disconnect(m_slave); // Remove all signals between slave and job } // only finish a job once; Scheduler::jobFinished() resets schedSerial to zero. if (m_schedSerial) { Scheduler::jobFinished(q, m_slave); } } void SimpleJob::slotFinished() { Q_D(SimpleJob); // Return slave to the scheduler d->slaveDone(); if (!hasSubjobs()) { if (!error() && (d->m_command == CMD_MKDIR || d->m_command == CMD_RENAME)) { if (d->m_command == CMD_MKDIR) { const QUrl urlDir = url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); org::kde::KDirNotify::emitFilesAdded(urlDir); } else { /*if ( m_command == CMD_RENAME )*/ QUrl src, dst; QDataStream str(d->m_packedArgs); str >> src >> dst; if (src.adjusted(QUrl::RemoveFilename) == dst.adjusted(QUrl::RemoveFilename) // For the user, moving isn't renaming. Only renaming is. && !KProtocolInfo::slaveHandlesNotify(dst.scheme()).contains(QLatin1String("Rename"))) { org::kde::KDirNotify::emitFileRenamed(src, dst); } org::kde::KDirNotify::emitFileMoved(src, dst); if (d->m_uiDelegateExtension) { d->m_uiDelegateExtension->updateUrlInClipboard(src, dst); } } } emitResult(); } } void SimpleJob::slotError(int err, const QString &errorText) { Q_D(SimpleJob); setError(err); setErrorText(errorText); if ((error() == ERR_UNKNOWN_HOST) && d->m_url.host().isEmpty()) { setErrorText(QString()); } // error terminates the job slotFinished(); } void SimpleJob::slotWarning(const QString &errorText) { emit warning(this, errorText); } void SimpleJobPrivate::_k_slotSlaveInfoMessage(const QString &msg) { emit q_func()->infoMessage(q_func(), msg); } void SimpleJobPrivate::slotConnected() { emit q_func()->connected(q_func()); } void SimpleJobPrivate::slotTotalSize(KIO::filesize_t size) { Q_Q(SimpleJob); if (size != q->totalAmount(KJob::Bytes)) { q->setTotalAmount(KJob::Bytes, size); } } void SimpleJobPrivate::slotProcessedSize(KIO::filesize_t size) { Q_Q(SimpleJob); //qDebug() << KIO::number(size); q->setProcessedAmount(KJob::Bytes, size); } void SimpleJobPrivate::slotSpeed(unsigned long speed) { //qDebug() << speed; q_func()->emitSpeed(speed); } void SimpleJobPrivate::restartAfterRedirection(QUrl *redirectionUrl) { Q_Q(SimpleJob); // Return slave to the scheduler while we still have the old URL in place; the scheduler // requires a job URL to stay invariant while the job is running. slaveDone(); m_url = *redirectionUrl; redirectionUrl->clear(); if ((m_extraFlags & EF_KillCalled) == 0) { Scheduler::doJob(q); } } int SimpleJobPrivate::requestMessageBox(int _type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo, const QString &iconYes, const QString &iconNo, const QString &dontAskAgainName, const KIO::MetaData &sslMetaData) { if (m_uiDelegateExtension) { const JobUiDelegateExtension::MessageBoxType type = static_cast(_type); return m_uiDelegateExtension->requestMessageBox(type, text, caption, buttonYes, buttonNo, iconYes, iconNo, dontAskAgainName, sslMetaData); } qCWarning(KIO_CORE) << "JobUiDelegate not set! Returing -1"; return -1; } void SimpleJob::slotMetaData(const KIO::MetaData &_metaData) { Q_D(SimpleJob); QMapIterator it(_metaData); while (it.hasNext()) { it.next(); if (it.key().startsWith(QLatin1String("{internal~"), Qt::CaseInsensitive)) { d->m_internalMetaData.insert(it.key(), it.value()); } else { d->m_incomingMetaData.insert(it.key(), it.value()); } } // Update the internal meta-data values as soon as possible. Waiting until // the ioslave is finished has unintended consequences if the client starts // a new connection without waiting for the ioslave to finish. if (!d->m_internalMetaData.isEmpty()) { Scheduler::updateInternalMetaData(this); } } void SimpleJob::storeSSLSessionFromJob(const QUrl &redirectionURL) { Q_UNUSED(redirectionURL); } void SimpleJobPrivate::slotPrivilegeOperationRequested() { m_slave->send(MSG_PRIVILEGE_EXEC, QByteArray::number(tryAskPrivilegeOpConfirmation())); } ////////// SimpleJob *KIO::rmdir(const QUrl &url) { //qDebug() << "rmdir " << url; KIO_ARGS << url << qint8(false); // isFile is false return SimpleJobPrivate::newJob(url, CMD_DEL, packedArgs); } SimpleJob *KIO::chmod(const QUrl &url, int permissions) { //qDebug() << "chmod " << url; KIO_ARGS << url << permissions; return SimpleJobPrivate::newJob(url, CMD_CHMOD, packedArgs); } SimpleJob *KIO::chown(const QUrl &url, const QString &owner, const QString &group) { KIO_ARGS << url << owner << group; return SimpleJobPrivate::newJob(url, CMD_CHOWN, packedArgs); } SimpleJob *KIO::setModificationTime(const QUrl &url, const QDateTime &mtime) { //qDebug() << "setModificationTime " << url << " " << mtime; KIO_ARGS << url << mtime; return SimpleJobPrivate::newJobNoUi(url, CMD_SETMODIFICATIONTIME, packedArgs); } SimpleJob *KIO::rename(const QUrl &src, const QUrl &dest, JobFlags flags) { //qDebug() << "rename " << src << " " << dest; KIO_ARGS << src << dest << (qint8)(flags & Overwrite); - return SimpleJobPrivate::newJob(src, CMD_RENAME, packedArgs); + return SimpleJobPrivate::newJob(src, CMD_RENAME, packedArgs, flags); } SimpleJob *KIO::symlink(const QString &target, const QUrl &dest, JobFlags flags) { //qDebug() << "symlink target=" << target << " " << dest; KIO_ARGS << target << dest << (qint8)(flags & Overwrite); return SimpleJobPrivate::newJob(dest, CMD_SYMLINK, packedArgs, flags); } SimpleJob *KIO::special(const QUrl &url, const QByteArray &data, JobFlags flags) { //qDebug() << "special " << url; return SimpleJobPrivate::newJob(url, CMD_SPECIAL, data, flags); } SimpleJob *KIO::mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags) { KIO_ARGS << int(1) << qint8(ro ? 1 : 0) << QString::fromLatin1(fstype) << dev << point; SimpleJob *job = special(QUrl(QStringLiteral("file:///")), packedArgs, flags); if (!(flags & HideProgressInfo)) { KIO::JobPrivate::emitMounting(job, dev, point); } return job; } SimpleJob *KIO::unmount(const QString &point, JobFlags flags) { KIO_ARGS << int(2) << point; SimpleJob *job = special(QUrl(QStringLiteral("file:///")), packedArgs, flags); if (!(flags & HideProgressInfo)) { KIO::JobPrivate::emitUnmounting(job, point); } return job; } ////////// SimpleJob *KIO::http_update_cache(const QUrl &url, bool no_cache, const QDateTime &expireDate) { Q_ASSERT(url.scheme() == QLatin1String("http") || url.scheme() == QLatin1String("https")); // Send http update_cache command (2) KIO_ARGS << (int)2 << url << no_cache << qlonglong(expireDate.toMSecsSinceEpoch() / 1000); SimpleJob *job = SimpleJobPrivate::newJob(url, CMD_SPECIAL, packedArgs); Scheduler::setJobPriority(job, 1); return job; } #include "moc_simplejob.cpp" diff --git a/src/core/storedtransferjob.cpp b/src/core/storedtransferjob.cpp index 6ead638c..6bd49964 100644 --- a/src/core/storedtransferjob.cpp +++ b/src/core/storedtransferjob.cpp @@ -1,467 +1,475 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow 2000-2009 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 "storedtransferjob.h" #include "job_p.h" #include #include #include #include using namespace KIO; class KIO::StoredTransferJobPrivate: public TransferJobPrivate { public: StoredTransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &_staticData) : TransferJobPrivate(url, command, packedArgs, _staticData), m_uploadOffset(0) {} StoredTransferJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice) : TransferJobPrivate(url, command, packedArgs, ioDevice), m_uploadOffset(0) {} QByteArray m_data; int m_uploadOffset; void slotStoredData(KIO::Job *job, const QByteArray &data); void slotStoredDataReq(KIO::Job *job, QByteArray &data); Q_DECLARE_PUBLIC(StoredTransferJob) static inline StoredTransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, const QByteArray &staticData, JobFlags flags) { StoredTransferJob *job = new StoredTransferJob( *new StoredTransferJobPrivate(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 StoredTransferJob *newJob(const QUrl &url, int command, const QByteArray &packedArgs, QIODevice *ioDevice, JobFlags flags) { StoredTransferJob *job = new StoredTransferJob( *new StoredTransferJobPrivate(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; } }; StoredTransferJob::StoredTransferJob(StoredTransferJobPrivate &dd) : TransferJob(dd) { connect(this, SIGNAL(data(KIO::Job*,QByteArray)), SLOT(slotStoredData(KIO::Job*,QByteArray))); connect(this, SIGNAL(dataReq(KIO::Job*,QByteArray&)), SLOT(slotStoredDataReq(KIO::Job*,QByteArray&))); } StoredTransferJob::~StoredTransferJob() { } void StoredTransferJob::setData(const QByteArray &arr) { Q_D(StoredTransferJob); Q_ASSERT(d->m_data.isNull()); // check that we're only called once Q_ASSERT(d->m_uploadOffset == 0); // no upload started yet d->m_data = arr; setTotalSize(d->m_data.size()); } QByteArray StoredTransferJob::data() const { return d_func()->m_data; } void StoredTransferJobPrivate::slotStoredData(KIO::Job *, const QByteArray &data) { // check for end-of-data marker: if (data.size() == 0) { return; } unsigned int oldSize = m_data.size(); m_data.resize(oldSize + data.size()); memcpy(m_data.data() + oldSize, data.data(), data.size()); } void StoredTransferJobPrivate::slotStoredDataReq(KIO::Job *, QByteArray &data) { // Inspired from kmail's KMKernel::byteArrayToRemoteFile // send the data in 64 KB chunks const int MAX_CHUNK_SIZE = 64 * 1024; int remainingBytes = m_data.size() - m_uploadOffset; if (remainingBytes > MAX_CHUNK_SIZE) { // send MAX_CHUNK_SIZE bytes to the receiver (deep copy) data = QByteArray(m_data.data() + m_uploadOffset, MAX_CHUNK_SIZE); m_uploadOffset += MAX_CHUNK_SIZE; //qDebug() << "Sending " << MAX_CHUNK_SIZE << " bytes (" // << remainingBytes - MAX_CHUNK_SIZE << " bytes remain)\n"; } else { // send the remaining bytes to the receiver (deep copy) data = QByteArray(m_data.data() + m_uploadOffset, remainingBytes); m_data = QByteArray(); m_uploadOffset = 0; //qDebug() << "Sending " << remainingBytes << " bytes\n"; } } StoredTransferJob *KIO::storedGet(const QUrl &url, LoadType reload, JobFlags flags) { // Send decoded path and encoded query KIO_ARGS << url; StoredTransferJob *job = StoredTransferJobPrivate::newJob(url, CMD_GET, packedArgs, QByteArray(), flags); if (reload == Reload) { job->addMetaData(QStringLiteral("cache"), QStringLiteral("reload")); } return job; } StoredTransferJob *KIO::storedPut(const QByteArray &arr, const QUrl &url, int permissions, JobFlags flags) { KIO_ARGS << url << qint8((flags & Overwrite) ? 1 : 0) << qint8((flags & Resume) ? 1 : 0) << permissions; StoredTransferJob *job = StoredTransferJobPrivate::newJob(url, CMD_PUT, packedArgs, QByteArray(), flags); job->setData(arr); return job; } StoredTransferJob *KIO::storedPut(QIODevice* input, const QUrl &url, int permissions, JobFlags flags) { Q_ASSERT(input && input->isReadable()); KIO_ARGS << url << qint8((flags & Overwrite) ? 1 : 0) << qint8((flags & Resume) ? 1 : 0) << permissions; StoredTransferJob *job = StoredTransferJobPrivate::newJob(url, CMD_PUT, packedArgs, input, flags); if (!input->isSequential()) { job->setTotalSize(input->size()); } return job; } namespace KIO { class PostErrorJob : public StoredTransferJob { Q_OBJECT public: PostErrorJob(int _error, const QString &url, const QByteArray &packedArgs, const QByteArray &postData) : StoredTransferJob(*new StoredTransferJobPrivate(QUrl(), CMD_SPECIAL, packedArgs, postData)) { setError(_error); setErrorText(url); } PostErrorJob(int _error, const QString &url, const QByteArray &packedArgs, QIODevice *ioDevice) : StoredTransferJob(*new StoredTransferJobPrivate(QUrl(), CMD_SPECIAL, packedArgs, ioDevice)) { setError(_error); setErrorText(url); } }; } static int isUrlPortBad(const QUrl &url) { int _error = 0; // filter out some malicious ports static const int bad_ports[] = { 1, // tcpmux 7, // echo 9, // discard 11, // systat 13, // daytime 15, // netstat 17, // qotd 19, // chargen 20, // ftp-data 21, // ftp-cntl 22, // ssh 23, // telnet 25, // smtp 37, // time 42, // name 43, // nicname 53, // domain 77, // priv-rjs 79, // finger 87, // ttylink 95, // supdup 101, // hostriame 102, // iso-tsap 103, // gppitnp 104, // acr-nema 109, // pop2 110, // pop3 111, // sunrpc 113, // auth 115, // sftp 117, // uucp-path 119, // nntp 123, // NTP 135, // loc-srv / epmap 139, // netbios 143, // imap2 179, // BGP 389, // ldap 512, // print / exec 513, // login 514, // shell 515, // printer 526, // tempo 530, // courier 531, // Chat 532, // netnews 540, // uucp 556, // remotefs 587, // sendmail 601, // 989, // ftps data 990, // ftps 992, // telnets 993, // imap/SSL 995, // pop3/SSL 1080, // SOCKS 2049, // nfs 4045, // lockd 6000, // x11 6667, // irc 0 }; if (url.port() != 80) { const int port = url.port(); for (int cnt = 0; bad_ports[cnt] && bad_ports[cnt] <= port; ++cnt) if (port == bad_ports[cnt]) { _error = KIO::ERR_POST_DENIED; break; } } if (_error) { static bool override_loaded = false; static QList< int > *overriden_ports = nullptr; if (!override_loaded) { KConfig cfg(QStringLiteral("kio_httprc")); overriden_ports = new QList< int >; *overriden_ports = cfg.group(QString()).readEntry("OverriddenPorts", QList()); override_loaded = true; } for (QList< int >::ConstIterator it = overriden_ports->constBegin(); it != overriden_ports->constEnd(); ++it) { if (overriden_ports->contains(url.port())) { _error = 0; } } } // filter out non https? protocols if ((url.scheme() != QLatin1String("http")) && (url.scheme() != QLatin1String("https"))) { _error = KIO::ERR_POST_DENIED; } if (!_error && !KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), url)) { _error = KIO::ERR_ACCESS_DENIED; } return _error; } static KIO::PostErrorJob *precheckHttpPost(const QUrl &url, QIODevice *ioDevice, JobFlags flags) { // if request is not valid, return an invalid transfer job const int _error = isUrlPortBad(url); if (_error) { KIO_ARGS << (int)1 << url; PostErrorJob *job = new PostErrorJob(_error, url.toString(), packedArgs, ioDevice); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } return job; } // all is ok, return 0 return nullptr; } static KIO::PostErrorJob *precheckHttpPost(const QUrl &url, const QByteArray &postData, JobFlags flags) { // if request is not valid, return an invalid transfer job const int _error = isUrlPortBad(url); if (_error) { KIO_ARGS << (int)1 << url; PostErrorJob *job = new PostErrorJob(_error, url.toString(), packedArgs, postData); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); } return job; } // all is ok, return 0 return nullptr; } TransferJob *KIO::http_post(const QUrl &url, const QByteArray &postData, JobFlags flags) { bool redirection = false; QUrl _url(url); if (_url.path().isEmpty()) { redirection = true; _url.setPath(QStringLiteral("/")); } TransferJob *job = precheckHttpPost(_url, postData, flags); if (job) { return job; } // Send http post command (1), decoded path and encoded query KIO_ARGS << (int)1 << _url << static_cast(postData.size()); job = TransferJobPrivate::newJob(_url, CMD_SPECIAL, packedArgs, postData, flags); if (redirection) { QTimer::singleShot(0, job, SLOT(slotPostRedirection())); } return job; } TransferJob *KIO::http_post(const QUrl &url, QIODevice *ioDevice, qint64 size, JobFlags flags) { bool redirection = false; QUrl _url(url); if (_url.path().isEmpty()) { redirection = true; _url.setPath(QStringLiteral("/")); } TransferJob *job = precheckHttpPost(_url, ioDevice, flags); if (job) { return job; } // If no size is specified and the QIODevice is not a sequential one, // attempt to obtain the size information from it. Q_ASSERT(ioDevice); if (size < 0) { size = ((ioDevice && !ioDevice->isSequential()) ? ioDevice->size() : -1); } // Send http post command (1), decoded path and encoded query KIO_ARGS << (int)1 << _url << size; job = TransferJobPrivate::newJob(_url, CMD_SPECIAL, packedArgs, ioDevice, flags); if (redirection) { QTimer::singleShot(0, job, SLOT(slotPostRedirection())); } return job; } TransferJob *KIO::http_delete(const QUrl &url, JobFlags flags) { // Send decoded path and encoded query KIO_ARGS << url; TransferJob *job = TransferJobPrivate::newJob(url, CMD_DEL, packedArgs, QByteArray(), flags); return job; } StoredTransferJob *KIO::storedHttpPost(const QByteArray &postData, const QUrl &url, JobFlags flags) { QUrl _url(url); if (_url.path().isEmpty()) { _url.setPath(QStringLiteral("/")); } StoredTransferJob *job = precheckHttpPost(_url, postData, flags); if (job) { return job; } // Send http post command (1), decoded path and encoded query KIO_ARGS << (int)1 << _url << static_cast(postData.size()); job = StoredTransferJobPrivate::newJob(_url, CMD_SPECIAL, packedArgs, postData, flags); return job; } StoredTransferJob *KIO::storedHttpPost(QIODevice *ioDevice, const QUrl &url, qint64 size, JobFlags flags) { QUrl _url(url); if (_url.path().isEmpty()) { _url.setPath(QStringLiteral("/")); } StoredTransferJob *job = precheckHttpPost(_url, ioDevice, flags); if (job) { return job; } // If no size is specified and the QIODevice is not a sequential one, // attempt to obtain the size information from it. Q_ASSERT(ioDevice); if (size < 0) { size = ((ioDevice && !ioDevice->isSequential()) ? ioDevice->size() : -1); } // Send http post command (1), decoded path and encoded query KIO_ARGS << (int)1 << _url << size; job = StoredTransferJobPrivate::newJob(_url, CMD_SPECIAL, packedArgs, ioDevice, flags); return job; } // http post got redirected from http://host to http://host/ by TransferJob // We must do this redirection ourselves because redirections by the // slave change post jobs into get jobs. void TransferJobPrivate::slotPostRedirection() { Q_Q(TransferJob); //qDebug() << m_url; // Tell the user about the new url. emit q->redirection(q, m_url); } TransferJob *KIO::put(const QUrl &url, int permissions, JobFlags flags) { KIO_ARGS << url << qint8((flags & Overwrite) ? 1 : 0) << qint8((flags & Resume) ? 1 : 0) << permissions; return TransferJobPrivate::newJob(url, CMD_PUT, packedArgs, QByteArray(), flags); } #include "moc_storedtransferjob.cpp" #include "storedtransferjob.moc"