SSH: Implement recursive upload.

This commit is contained in:
ck
2010-07-21 11:43:29 +02:00
parent fbd25fe84e
commit 89e1b2d85a
5 changed files with 228 additions and 39 deletions

View File

@@ -34,6 +34,7 @@
#include "sshexception_p.h" #include "sshexception_p.h"
#include "sshsendfacility_p.h" #include "sshsendfacility_p.h"
#include <QtCore/QDir>
#include <QtCore/QFile> #include <QtCore/QFile>
#include <QtCore/QWeakPointer> #include <QtCore/QWeakPointer>
@@ -138,8 +139,8 @@ SftpJobId SftpChannel::uploadFile(const QString &localFilePath,
QSharedPointer<QFile> localFile(new QFile(localFilePath)); QSharedPointer<QFile> localFile(new QFile(localFilePath));
if (!localFile->open(QIODevice::ReadOnly)) if (!localFile->open(QIODevice::ReadOnly))
return SftpInvalidJob; return SftpInvalidJob;
return d->createJob(Internal::SftpUpload::Ptr( return d->createJob(Internal::SftpUploadFile::Ptr(
new Internal::SftpUpload(++d->m_nextJobId, remoteFilePath, localFile, mode))); new Internal::SftpUploadFile(++d->m_nextJobId, remoteFilePath, localFile, mode)));
} }
SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath, SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath,
@@ -159,6 +160,26 @@ SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath,
new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, localFile))); 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() SftpChannel::~SftpChannel()
{ {
delete d; delete d;
@@ -300,7 +321,7 @@ void SftpChannelPrivate::handleHandle()
case AbstractSftpOperation::Download: case AbstractSftpOperation::Download:
handleGetHandle(it); handleGetHandle(it);
break; break;
case AbstractSftpOperation::Upload: case AbstractSftpOperation::UploadFile:
handlePutHandle(it); handlePutHandle(it);
break; break;
default: default:
@@ -332,7 +353,9 @@ void SftpChannelPrivate::handleGetHandle(const JobMap::Iterator &it)
void SftpChannelPrivate::handlePutHandle(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 // OpenSSH does not implement the RFC's append functionality, so we
// have to emulate it. // have to emulate it.
@@ -359,10 +382,12 @@ void SftpChannelPrivate::handleStatus()
case AbstractSftpOperation::Download: case AbstractSftpOperation::Download:
handleGetStatus(it, response); handleGetStatus(it, response);
break; break;
case AbstractSftpOperation::Upload: case AbstractSftpOperation::UploadFile:
handlePutStatus(it, response); handlePutStatus(it, response);
break; break;
case AbstractSftpOperation::MakeDir: case AbstractSftpOperation::MakeDir:
handleMkdirStatus(it, response);
break;
case AbstractSftpOperation::RmDir: case AbstractSftpOperation::RmDir:
case AbstractSftpOperation::Rm: case AbstractSftpOperation::Rm:
case AbstractSftpOperation::Rename: case AbstractSftpOperation::Rename:
@@ -381,6 +406,76 @@ void SftpChannelPrivate::handleStatusGeneric(const JobMap::Iterator &it,
m_jobs.erase(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, void SftpChannelPrivate::handleLsStatus(const JobMap::Iterator &it,
const SftpStatusResponse &response) const SftpStatusResponse &response)
{ {
@@ -457,30 +552,70 @@ void SftpChannelPrivate::handleGetStatus(const JobMap::Iterator &it,
void SftpChannelPrivate::handlePutStatus(const JobMap::Iterator &it, void SftpChannelPrivate::handlePutStatus(const JobMap::Iterator &it,
const SftpStatusResponse &response) const SftpStatusResponse &response)
{ {
SftpUpload::Ptr job = it.value().staticCast<SftpUpload>(); SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
switch (job->state) { switch (job->state) {
case SftpUpload::OpenRequested: 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, createDelayedJobFinishedSignal(job->jobId,
errorMessage(response.errorString, errorMessage(response.errorString,
SSH_TR("Failed to open remote file for writing."))); SSH_TR("Failed to open remote file for writing.")));
}
m_jobs.erase(it); m_jobs.erase(it);
break; 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) { if (response.status == SSH_FX_OK) {
sendWriteRequest(it); sendWriteRequest(it);
} else if(!job->hasError) { } else {
if (job->parentJob)
job->parentJob->setError();
reportRequestError(job, errorMessage(response.errorString, reportRequestError(job, errorMessage(response.errorString,
SSH_TR("Failed to write remote file."))); SSH_TR("Failed to write remote file.")));
finishTransferRequest(it); finishTransferRequest(it);
} }
break; break;
case SftpUpload::CloseRequested: case SftpUploadFile::CloseRequested:
Q_ASSERT(job->inFlightCount == 1); Q_ASSERT(job->inFlightCount == 1);
if (!job->hasError) { if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
const QString error = errorMessage(response, 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.")); SSH_TR("Failed to close remote file."));
if (job->parentJob) {
job->parentJob->setError();
createDelayedJobFinishedSignal(job->parentJob->jobId, error);
} else {
createDelayedJobFinishedSignal(job->jobId, error); createDelayedJobFinishedSignal(job->jobId, error);
} }
}
m_jobs.erase(it); m_jobs.erase(it);
break; break;
default: default:
@@ -559,7 +694,7 @@ void SftpChannelPrivate::handleAttrs()
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR, throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected SSH_FXP_ATTRS packet."); "Unexpected SSH_FXP_ATTRS packet.");
} }
Q_ASSERT(transfer->type() == AbstractSftpOperation::Upload Q_ASSERT(transfer->type() == AbstractSftpOperation::UploadFile
|| transfer->type() == AbstractSftpOperation::Download); || transfer->type() == AbstractSftpOperation::Download);
if (transfer->type() == AbstractSftpOperation::Download) { if (transfer->type() == AbstractSftpOperation::Download) {
@@ -573,11 +708,19 @@ void SftpChannelPrivate::handleAttrs()
op->statRequested = false; op->statRequested = false;
spawnReadRequests(op); spawnReadRequests(op);
} else { } 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) { if (response.attrs.sizePresent) {
op->offset = response.attrs.size; op->offset = response.attrs.size;
spawnWriteRequests(it); spawnWriteRequests(it);
} else { } else {
if (op->parentJob)
op->parentJob->setError();
reportRequestError(op, SSH_TR("Cannot append to remote file: " reportRequestError(op, SSH_TR("Cannot append to remote file: "
"Server does not support file size attribute.")); "Server does not support file size attribute."));
sendTransferCloseHandle(op, op->jobId); sendTransferCloseHandle(op, op->jobId);
@@ -662,13 +805,13 @@ void SftpChannelPrivate::removeTransferRequest(const JobMap::Iterator &it)
void SftpChannelPrivate::sendWriteRequest(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); QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize);
if (job->localFile->error() != QFile::NoError) { if (job->localFile->error() != QFile::NoError) {
if (!job->hasError) { if (job->parentJob)
job->parentJob->setError();
reportRequestError(job, SSH_TR("Error reading local file: %1") reportRequestError(job, SSH_TR("Error reading local file: %1")
.arg(job->localFile->errorString())); .arg(job->localFile->errorString()));
}
finishTransferRequest(it); finishTransferRequest(it);
} else if (data.isEmpty()) { } else if (data.isEmpty()) {
finishTransferRequest(it); finishTransferRequest(it);
@@ -681,10 +824,10 @@ void SftpChannelPrivate::sendWriteRequest(const JobMap::Iterator &it)
void SftpChannelPrivate::spawnWriteRequests(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); op->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
sendWriteRequest(it); 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)); sendWriteRequest(m_jobs.insert(++m_nextJobId, op));
} }

View File

@@ -92,6 +92,8 @@ public:
const QString &remoteFilePath, SftpOverwriteMode mode); const QString &remoteFilePath, SftpOverwriteMode mode);
SftpJobId downloadFile(const QString &remoteFilePath, SftpJobId downloadFile(const QString &remoteFilePath,
const QString &localFilePath, SftpOverwriteMode mode); const QString &localFilePath, SftpOverwriteMode mode);
SftpJobId uploadDir(const QString &localDirPath,
const QString &remoteParentDirPath);
~SftpChannel(); ~SftpChannel();

View File

@@ -84,6 +84,8 @@ private:
void handleStatusGeneric(const JobMap::Iterator &it, void handleStatusGeneric(const JobMap::Iterator &it,
const SftpStatusResponse &response); const SftpStatusResponse &response);
void handleMkdirStatus(const JobMap::Iterator &it,
const SftpStatusResponse &response);
void handleLsStatus(const JobMap::Iterator &it, void handleLsStatus(const JobMap::Iterator &it,
const SftpStatusResponse &response); const SftpStatusResponse &response);
void handleGetStatus(const JobMap::Iterator &it, void handleGetStatus(const JobMap::Iterator &it,

View File

@@ -31,7 +31,6 @@
#include "sftpoutgoingpacket_p.h" #include "sftpoutgoingpacket_p.h"
#include <QtCore/QTime>
#include <QtCore/QFile> #include <QtCore/QFile>
namespace Core { namespace Core {
@@ -44,8 +43,9 @@ AbstractSftpOperation::AbstractSftpOperation(SftpJobId jobId) : jobId(jobId)
AbstractSftpOperation::~AbstractSftpOperation() { } AbstractSftpOperation::~AbstractSftpOperation() { }
SftpMakeDir::SftpMakeDir(SftpJobId jobId, const QString &path) SftpMakeDir::SftpMakeDir(SftpJobId jobId, const QString &path,
: AbstractSftpOperation(jobId), remoteDir(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) void AbstractSftpTransfer::calculateInFlightCount(quint32 chunkSize)
{ {
if (fileSize == 0) { if (fileSize == 0) {
@@ -159,18 +161,23 @@ SftpOutgoingPacket &SftpDownload::initialPacket(SftpOutgoingPacket &packet)
} }
SftpUpload::SftpUpload(SftpJobId jobId, const QString &remotePath, SftpUploadFile::SftpUploadFile(SftpJobId jobId, const QString &remotePath,
const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode) const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode,
: AbstractSftpTransfer(jobId, remotePath, localFile), mode(mode) const SftpUploadDir::Ptr &parentJob)
: AbstractSftpTransfer(jobId, remotePath, localFile),
parentJob(parentJob), mode(mode)
{ {
fileSize = localFile->size(); fileSize = localFile->size();
} }
SftpOutgoingPacket &SftpUpload::initialPacket(SftpOutgoingPacket &packet) SftpOutgoingPacket &SftpUploadFile::initialPacket(SftpOutgoingPacket &packet)
{ {
state = OpenRequested; state = OpenRequested;
return packet.generateOpenFileForWriting(remotePath, mode, jobId); return packet.generateOpenFileForWriting(remotePath, mode, jobId);
} }
SftpUploadDir::~SftpUploadDir() {}
} // namespace Internal } // namespace Internal
} // namespace Core } // namespace Core

View File

@@ -33,6 +33,7 @@
#include "sftpdefs.h" #include "sftpdefs.h"
#include <QtCore/QByteArray> #include <QtCore/QByteArray>
#include <QtCore/QList>
#include <QtCore/QMap> #include <QtCore/QMap>
#include <QtCore/QSharedPointer> #include <QtCore/QSharedPointer>
@@ -49,7 +50,7 @@ struct AbstractSftpOperation
{ {
typedef QSharedPointer<AbstractSftpOperation> Ptr; typedef QSharedPointer<AbstractSftpOperation> Ptr;
enum Type { enum Type {
ListDir, MakeDir, RmDir, Rm, Rename, CreateFile, Download, Upload ListDir, MakeDir, RmDir, Rm, Rename, CreateFile, Download, UploadFile
}; };
AbstractSftpOperation(SftpJobId jobId); AbstractSftpOperation(SftpJobId jobId);
@@ -64,14 +65,18 @@ private:
AbstractSftpOperation &operator=(const AbstractSftpOperation &); AbstractSftpOperation &operator=(const AbstractSftpOperation &);
}; };
class SftpUploadDir;
struct SftpMakeDir : public AbstractSftpOperation struct SftpMakeDir : public AbstractSftpOperation
{ {
typedef QSharedPointer<SftpMakeDir> Ptr; 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 Type type() const { return MakeDir; }
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
const QSharedPointer<SftpUploadDir> parentJob;
const QString remoteDir; const QString remoteDir;
}; };
@@ -152,6 +157,7 @@ struct AbstractSftpTransfer : public AbstractSftpOperationWithHandle
AbstractSftpTransfer(SftpJobId jobId, const QString &remotePath, AbstractSftpTransfer(SftpJobId jobId, const QString &remotePath,
const QSharedPointer<QFile> &localFile); const QSharedPointer<QFile> &localFile);
~AbstractSftpTransfer();
void calculateInFlightCount(quint32 chunkSize); void calculateInFlightCount(quint32 chunkSize);
static const int MaxInFlightCount; static const int MaxInFlightCount;
@@ -175,18 +181,47 @@ struct SftpDownload : public AbstractSftpTransfer
SftpJobId eofId; SftpJobId eofId;
}; };
struct SftpUpload : public AbstractSftpTransfer struct SftpUploadFile : public AbstractSftpTransfer
{ {
typedef QSharedPointer<SftpUpload> Ptr; typedef QSharedPointer<SftpUploadFile> Ptr;
SftpUpload(SftpJobId jobId, const QString &remotePath, SftpUploadFile(SftpJobId jobId, const QString &remotePath,
const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode); const QSharedPointer<QFile> &localFile, SftpOverwriteMode mode,
virtual Type type() const { return Upload; } const QSharedPointer<SftpUploadDir> &parentJob = QSharedPointer<SftpUploadDir>());
virtual Type type() const { return UploadFile; }
virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet); virtual SftpOutgoingPacket &initialPacket(SftpOutgoingPacket &packet);
const QSharedPointer<SftpUploadDir> parentJob;
SftpOverwriteMode mode; 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 Internal
} // namespace Core } // namespace Core