From 89e1b2d85a932ef70256e111b5952f2c5add3c96 Mon Sep 17 00:00:00 2001 From: ck <qt-info@nokia.com> Date: Wed, 21 Jul 2010 11:43:29 +0200 Subject: [PATCH] SSH: Implement recursive upload. --- src/plugins/coreplugin/ssh/sftpchannel.cpp | 193 ++++++++++++++++--- src/plugins/coreplugin/ssh/sftpchannel.h | 2 + src/plugins/coreplugin/ssh/sftpchannel_p.h | 2 + src/plugins/coreplugin/ssh/sftpoperation.cpp | 21 +- src/plugins/coreplugin/ssh/sftpoperation_p.h | 49 ++++- 5 files changed, 228 insertions(+), 39 deletions(-) diff --git a/src/plugins/coreplugin/ssh/sftpchannel.cpp b/src/plugins/coreplugin/ssh/sftpchannel.cpp index 2cb31a3bcec..4c94c4ac3a5 100644 --- a/src/plugins/coreplugin/ssh/sftpchannel.cpp +++ b/src/plugins/coreplugin/ssh/sftpchannel.cpp @@ -34,6 +34,7 @@ #include "sshexception_p.h" #include "sshsendfacility_p.h" +#include <QtCore/QDir> #include <QtCore/QFile> #include <QtCore/QWeakPointer> @@ -138,8 +139,8 @@ SftpJobId SftpChannel::uploadFile(const QString &localFilePath, QSharedPointer<QFile> localFile(new QFile(localFilePath)); if (!localFile->open(QIODevice::ReadOnly)) return SftpInvalidJob; - return d->createJob(Internal::SftpUpload::Ptr( - new Internal::SftpUpload(++d->m_nextJobId, remoteFilePath, localFile, mode))); + return d->createJob(Internal::SftpUploadFile::Ptr( + new Internal::SftpUploadFile(++d->m_nextJobId, remoteFilePath, localFile, mode))); } SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath, @@ -159,6 +160,26 @@ SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath, new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, localFile))); } +SftpJobId SftpChannel::uploadDir(const QString &localDirPath, + const QString &remoteParentDirPath) +{ + if (state() != Initialized) + return SftpInvalidJob; + const QDir localDir(localDirPath); + if (!localDir.exists() || !localDir.isReadable()) + return SftpInvalidJob; + const Internal::SftpUploadDir::Ptr uploadDirOp( + new Internal::SftpUploadDir(++d->m_nextJobId)); + const QString remoteDirPath + = remoteParentDirPath + QLatin1Char('/') + localDir.dirName(); + const Internal::SftpMakeDir::Ptr mkdirOp( + new Internal::SftpMakeDir(++d->m_nextJobId, remoteDirPath, uploadDirOp)); + uploadDirOp->mkdirsInProgress.insert(mkdirOp, + Internal::SftpUploadDir::Dir(localDirPath, remoteDirPath)); + d->createJob(mkdirOp); + return uploadDirOp->jobId; +} + SftpChannel::~SftpChannel() { delete d; @@ -300,7 +321,7 @@ void SftpChannelPrivate::handleHandle() case AbstractSftpOperation::Download: handleGetHandle(it); break; - case AbstractSftpOperation::Upload: + case AbstractSftpOperation::UploadFile: handlePutHandle(it); break; default: @@ -332,7 +353,9 @@ void SftpChannelPrivate::handleGetHandle(const JobMap::Iterator &it) void SftpChannelPrivate::handlePutHandle(const JobMap::Iterator &it) { - SftpUpload::Ptr op = it.value().staticCast<SftpUpload>(); + SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>(); + if (op->parentJob && op->parentJob->hasError) + sendTransferCloseHandle(op, it.key()); // OpenSSH does not implement the RFC's append functionality, so we // have to emulate it. @@ -359,10 +382,12 @@ void SftpChannelPrivate::handleStatus() case AbstractSftpOperation::Download: handleGetStatus(it, response); break; - case AbstractSftpOperation::Upload: + case AbstractSftpOperation::UploadFile: handlePutStatus(it, response); break; case AbstractSftpOperation::MakeDir: + handleMkdirStatus(it, response); + break; case AbstractSftpOperation::RmDir: case AbstractSftpOperation::Rm: case AbstractSftpOperation::Rename: @@ -381,6 +406,76 @@ void SftpChannelPrivate::handleStatusGeneric(const JobMap::Iterator &it, m_jobs.erase(it); } +void SftpChannelPrivate::handleMkdirStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response) +{ + SftpMakeDir::Ptr op = it.value().staticCast<SftpMakeDir>(); + if (op->parentJob == SftpUploadDir::Ptr()) { + handleStatusGeneric(it, response); + return; + } + if (op->parentJob->hasError) { + m_jobs.erase(it); + return; + } + + typedef QMap<SftpMakeDir::Ptr, SftpUploadDir::Dir>::Iterator DirIt; + DirIt dirIt = op->parentJob->mkdirsInProgress.find(op); + Q_ASSERT(dirIt != op->parentJob->mkdirsInProgress.end()); + const QString &remoteDir = dirIt.value().remoteDir; + if (response.status == SSH_FX_OK) { + createDelayedDataAvailableSignal(op->parentJob->jobId, + SSH_TR("Created remote directory '%1'.").arg(remoteDir)); + } else if (response.status == SSH_FX_FAILURE) { + createDelayedDataAvailableSignal(op->parentJob->jobId, + SSH_TR("Remote directory '%1' already exists.").arg(remoteDir)); + } else { + op->parentJob->setError(); + createDelayedJobFinishedSignal(op->parentJob->jobId, + SSH_TR("Error creating directory '%1': %2") + .arg(remoteDir, response.errorString)); + m_jobs.erase(it); + return; + } + + QDir localDir(dirIt.value().localDir); + const QFileInfoList &dirInfos + = localDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + foreach (const QFileInfo &dirInfo, dirInfos) { + const QString remoteSubDir = remoteDir + '/' + dirInfo.fileName(); + const SftpMakeDir::Ptr mkdirOp( + new SftpMakeDir(++m_nextJobId, remoteSubDir, op->parentJob)); + op->parentJob->mkdirsInProgress.insert(mkdirOp, + SftpUploadDir::Dir(dirInfo.absoluteFilePath(), remoteSubDir)); + createJob(mkdirOp); + } + + const QFileInfoList &fileInfos = localDir.entryInfoList(QDir::Files); + foreach (const QFileInfo &fileInfo, fileInfos) { + QSharedPointer<QFile> localFile(new QFile(fileInfo.absoluteFilePath())); + if (!localFile->open(QIODevice::ReadOnly)) { + op->parentJob->setError(); + createDelayedJobFinishedSignal(op->parentJob->jobId, + SSH_TR("Could not open local file '%1': %2") + .arg(fileInfo.absoluteFilePath(), localFile->error())); + m_jobs.erase(it); + return; + } + + const QString remoteFilePath = remoteDir + '/' + fileInfo.fileName(); + SftpUploadFile::Ptr uploadFileOp(new SftpUploadFile(++m_nextJobId, + remoteFilePath, localFile, SftpOverwriteExisting, op->parentJob)); + createJob(uploadFileOp); + op->parentJob->uploadsInProgress.append(uploadFileOp); + } + + op->parentJob->mkdirsInProgress.erase(dirIt); + if (op->parentJob->mkdirsInProgress.isEmpty() + && op->parentJob->uploadsInProgress.isEmpty()) + createDelayedJobFinishedSignal(op->parentJob->jobId); + m_jobs.erase(it); +} + void SftpChannelPrivate::handleLsStatus(const JobMap::Iterator &it, const SftpStatusResponse &response) { @@ -457,29 +552,69 @@ void SftpChannelPrivate::handleGetStatus(const JobMap::Iterator &it, void SftpChannelPrivate::handlePutStatus(const JobMap::Iterator &it, const SftpStatusResponse &response) { - SftpUpload::Ptr job = it.value().staticCast<SftpUpload>(); + SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>(); switch (job->state) { - case SftpUpload::OpenRequested: - createDelayedJobFinishedSignal(job->jobId, - errorMessage(response.errorString, - SSH_TR("Failed to open remote file for writing."))); + case SftpUploadFile::OpenRequested: { + bool emitError = false; + if (job->parentJob) { + if (!job->parentJob->hasError) { + job->parentJob->setError(); + emitError = true; + } + } else { + emitError = true; + } + + if (emitError) { + createDelayedJobFinishedSignal(job->jobId, + errorMessage(response.errorString, + SSH_TR("Failed to open remote file for writing."))); + } m_jobs.erase(it); break; - case SftpUpload::Open: + } + case SftpUploadFile::Open: + if (job->hasError || (job->parentJob && job->parentJob->hasError)) { + job->hasError = true; + finishTransferRequest(it); + return; + } + if (response.status == SSH_FX_OK) { sendWriteRequest(it); - } else if(!job->hasError) { + } else { + if (job->parentJob) + job->parentJob->setError(); reportRequestError(job, errorMessage(response.errorString, SSH_TR("Failed to write remote file."))); finishTransferRequest(it); } break; - case SftpUpload::CloseRequested: + case SftpUploadFile::CloseRequested: Q_ASSERT(job->inFlightCount == 1); - if (!job->hasError) { - const QString error = errorMessage(response, + if (job->hasError || (job->parentJob && job->parentJob->hasError)) { + m_jobs.erase(it); + return; + } + + if (response.status == SSH_FX_OK) { + if (job->parentJob) { + job->parentJob->uploadsInProgress.removeOne(job); + if (job->parentJob->mkdirsInProgress.isEmpty() + && job->parentJob->uploadsInProgress.isEmpty()) + createDelayedJobFinishedSignal(job->parentJob->jobId); + } else { + createDelayedJobFinishedSignal(job->jobId); + } + } else { + const QString error = errorMessage(response.errorString, SSH_TR("Failed to close remote file.")); - createDelayedJobFinishedSignal(job->jobId, error); + if (job->parentJob) { + job->parentJob->setError(); + createDelayedJobFinishedSignal(job->parentJob->jobId, error); + } else { + createDelayedJobFinishedSignal(job->jobId, error); + } } m_jobs.erase(it); break; @@ -559,7 +694,7 @@ void SftpChannelPrivate::handleAttrs() throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, "Unexpected SSH_FXP_ATTRS packet."); } - Q_ASSERT(transfer->type() == AbstractSftpOperation::Upload + Q_ASSERT(transfer->type() == AbstractSftpOperation::UploadFile || transfer->type() == AbstractSftpOperation::Download); if (transfer->type() == AbstractSftpOperation::Download) { @@ -573,11 +708,19 @@ void SftpChannelPrivate::handleAttrs() op->statRequested = false; spawnReadRequests(op); } else { - SftpUpload::Ptr op = transfer.staticCast<SftpUpload>(); + SftpUploadFile::Ptr op = transfer.staticCast<SftpUploadFile>(); + if (op->parentJob && op->parentJob->hasError) { + op->hasError = true; + sendTransferCloseHandle(op, op->jobId); + return; + } + if (response.attrs.sizePresent) { op->offset = response.attrs.size; spawnWriteRequests(it); } else { + if (op->parentJob) + op->parentJob->setError(); reportRequestError(op, SSH_TR("Cannot append to remote file: " "Server does not support file size attribute.")); sendTransferCloseHandle(op, op->jobId); @@ -662,13 +805,13 @@ void SftpChannelPrivate::removeTransferRequest(const JobMap::Iterator &it) void SftpChannelPrivate::sendWriteRequest(const JobMap::Iterator &it) { - SftpUpload::Ptr job = it.value().staticCast<SftpUpload>(); + SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>(); QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize); if (job->localFile->error() != QFile::NoError) { - if (!job->hasError) { - reportRequestError(job, SSH_TR("Error reading local file: %1") - .arg(job->localFile->errorString())); - } + if (job->parentJob) + job->parentJob->setError(); + reportRequestError(job, SSH_TR("Error reading local file: %1") + .arg(job->localFile->errorString())); finishTransferRequest(it); } else if (data.isEmpty()) { finishTransferRequest(it); @@ -681,10 +824,10 @@ void SftpChannelPrivate::sendWriteRequest(const JobMap::Iterator &it) void SftpChannelPrivate::spawnWriteRequests(const JobMap::Iterator &it) { - SftpUpload::Ptr op = it.value().staticCast<SftpUpload>(); + SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>(); op->calculateInFlightCount(AbstractSftpPacket::MaxDataSize); sendWriteRequest(it); - for (int i = 1; i < op->inFlightCount; ++i) + for (int i = 1; !op->hasError && i < op->inFlightCount; ++i) sendWriteRequest(m_jobs.insert(++m_nextJobId, op)); } diff --git a/src/plugins/coreplugin/ssh/sftpchannel.h b/src/plugins/coreplugin/ssh/sftpchannel.h index 6b4ffea7e50..cbdc072283e 100644 --- a/src/plugins/coreplugin/ssh/sftpchannel.h +++ b/src/plugins/coreplugin/ssh/sftpchannel.h @@ -92,6 +92,8 @@ public: const QString &remoteFilePath, SftpOverwriteMode mode); SftpJobId downloadFile(const QString &remoteFilePath, const QString &localFilePath, SftpOverwriteMode mode); + SftpJobId uploadDir(const QString &localDirPath, + const QString &remoteParentDirPath); ~SftpChannel(); diff --git a/src/plugins/coreplugin/ssh/sftpchannel_p.h b/src/plugins/coreplugin/ssh/sftpchannel_p.h index 8d27a2c01dc..ec9c0fcfe87 100644 --- a/src/plugins/coreplugin/ssh/sftpchannel_p.h +++ b/src/plugins/coreplugin/ssh/sftpchannel_p.h @@ -84,6 +84,8 @@ private: void handleStatusGeneric(const JobMap::Iterator &it, const SftpStatusResponse &response); + void handleMkdirStatus(const JobMap::Iterator &it, + const SftpStatusResponse &response); void handleLsStatus(const JobMap::Iterator &it, const SftpStatusResponse &response); void handleGetStatus(const JobMap::Iterator &it, diff --git a/src/plugins/coreplugin/ssh/sftpoperation.cpp b/src/plugins/coreplugin/ssh/sftpoperation.cpp index 8acb126db1f..0acdd1dc033 100644 --- a/src/plugins/coreplugin/ssh/sftpoperation.cpp +++ b/src/plugins/coreplugin/ssh/sftpoperation.cpp @@ -31,7 +31,6 @@ #include "sftpoutgoingpacket_p.h" -#include <QtCore/QTime> #include <QtCore/QFile> namespace Core { @@ -44,8 +43,9 @@ AbstractSftpOperation::AbstractSftpOperation(SftpJobId jobId) : jobId(jobId) AbstractSftpOperation::~AbstractSftpOperation() { } -SftpMakeDir::SftpMakeDir(SftpJobId jobId, const QString &path) - : AbstractSftpOperation(jobId), remoteDir(path) +SftpMakeDir::SftpMakeDir(SftpJobId jobId, const QString &path, + const SftpUploadDir::Ptr &parentJob) + : AbstractSftpOperation(jobId), parentJob(parentJob), remoteDir(path) { } @@ -132,6 +132,8 @@ AbstractSftpTransfer::AbstractSftpTransfer(SftpJobId jobId, const QString &remot { } +AbstractSftpTransfer::~AbstractSftpTransfer() {} + void AbstractSftpTransfer::calculateInFlightCount(quint32 chunkSize) { if (fileSize == 0) { @@ -159,18 +161,23 @@ SftpOutgoingPacket &SftpDownload::initialPacket(SftpOutgoingPacket &packet) } -SftpUpload::SftpUpload(SftpJobId jobId, const QString &remotePath, - const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode) - : AbstractSftpTransfer(jobId, remotePath, localFile), mode(mode) +SftpUploadFile::SftpUploadFile(SftpJobId jobId, const QString &remotePath, + const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode, + const SftpUploadDir::Ptr &parentJob) + : AbstractSftpTransfer(jobId, remotePath, localFile), + parentJob(parentJob), mode(mode) { fileSize = localFile->size(); } -SftpOutgoingPacket &SftpUpload::initialPacket(SftpOutgoingPacket &packet) +SftpOutgoingPacket &SftpUploadFile::initialPacket(SftpOutgoingPacket &packet) { state = OpenRequested; return packet.generateOpenFileForWriting(remotePath, mode, jobId); } + +SftpUploadDir::~SftpUploadDir() {} + } // namespace Internal } // namespace Core diff --git a/src/plugins/coreplugin/ssh/sftpoperation_p.h b/src/plugins/coreplugin/ssh/sftpoperation_p.h index 65987813322..c64d081b9d8 100644 --- a/src/plugins/coreplugin/ssh/sftpoperation_p.h +++ b/src/plugins/coreplugin/ssh/sftpoperation_p.h @@ -33,6 +33,7 @@ #include "sftpdefs.h" #include <QtCore/QByteArray> +#include <QtCore/QList> #include <QtCore/QMap> #include <QtCore/QSharedPointer> @@ -49,7 +50,7 @@ struct AbstractSftpOperation { typedef QSharedPointer<AbstractSftpOperation> Ptr; enum Type { - ListDir, MakeDir, RmDir, Rm, Rename, CreateFile, Download, Upload + ListDir, MakeDir, RmDir, Rm, Rename, CreateFile, Download, UploadFile }; AbstractSftpOperation(SftpJobId jobId); @@ -64,14 +65,18 @@ private: AbstractSftpOperation &operator=(const AbstractSftpOperation &); }; +class SftpUploadDir; + struct SftpMakeDir : public AbstractSftpOperation { typedef QSharedPointer<SftpMakeDir> Ptr; - SftpMakeDir(SftpJobId jobId, const QString &path); + SftpMakeDir(SftpJobId jobId, const QString &path, + const QSharedPointer<SftpUploadDir> &parentJob = QSharedPointer<SftpUploadDir>()); virtual Type type() const { return MakeDir; } virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); + const QSharedPointer<SftpUploadDir> parentJob; const QString remoteDir; }; @@ -152,6 +157,7 @@ struct AbstractSftpTransfer : public AbstractSftpOperationWithHandle AbstractSftpTransfer(SftpJobId jobId, const QString &remotePath, const QSharedPointer<QFile> &localFile); + ~AbstractSftpTransfer(); void calculateInFlightCount(quint32 chunkSize); static const int MaxInFlightCount; @@ -175,18 +181,47 @@ struct SftpDownload : public AbstractSftpTransfer SftpJobId eofId; }; -struct SftpUpload : public AbstractSftpTransfer +struct SftpUploadFile : public AbstractSftpTransfer { - typedef QSharedPointer<SftpUpload> Ptr; + typedef QSharedPointer<SftpUploadFile> Ptr; - SftpUpload(SftpJobId jobId, const QString &remotePath, - const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode); - virtual Type type() const { return Upload; } + SftpUploadFile(SftpJobId jobId, const QString &remotePath, + const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode, + const QSharedPointer<SftpUploadDir> &parentJob = QSharedPointer<SftpUploadDir>()); + virtual Type type() const { return UploadFile; } virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); + const QSharedPointer<SftpUploadDir> parentJob; SftpOverwriteMode mode; }; +// Composite operation. +struct SftpUploadDir +{ + typedef QSharedPointer<SftpUploadDir> Ptr; + + struct Dir { + Dir(const QString &l, const QString &r) : localDir(l), remoteDir(r) {} + QString localDir; + QString remoteDir; + }; + + SftpUploadDir(SftpJobId jobId) : jobId(jobId), hasError(false) {} + ~SftpUploadDir(); + + void setError() + { + hasError = true; + uploadsInProgress.clear(); + mkdirsInProgress.clear(); + } + + const SftpJobId jobId; + bool hasError; + QList<SftpUploadFile::Ptr> uploadsInProgress; + QMap<SftpMakeDir::Ptr, Dir> mkdirsInProgress; +}; + } // namespace Internal } // namespace Core -- GitLab