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