forked from qt-creator/qt-creator
Get rid of SftpSession
Should be substituted by FilePath actions using remote paths. Change-Id: Ib1e3913cc94d417045cbe6b922284a2f8ab6d71f Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -2,7 +2,6 @@ add_qtc_library(QtcSsh
|
||||
DEPENDS Qt5::Core Qt5::Network Qt5::Widgets Utils
|
||||
SOURCES
|
||||
sftpdefs.cpp sftpdefs.h
|
||||
sftpsession.cpp sftpsession.h
|
||||
sftptransfer.cpp sftptransfer.h
|
||||
ssh.qrc
|
||||
ssh_global.h
|
||||
|
@@ -37,8 +37,6 @@ namespace QSsh {
|
||||
|
||||
class SftpTransfer;
|
||||
using SftpTransferPtr = std::unique_ptr<SftpTransfer>;
|
||||
class SftpSession;
|
||||
using SftpSessionPtr = std::unique_ptr<SftpSession>;
|
||||
|
||||
enum class FileTransferErrorHandling { Abort, Ignore };
|
||||
|
||||
|
@@ -1,337 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sftpsession.h"
|
||||
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshremoteprocess.h"
|
||||
#include "sshsettings.h"
|
||||
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/commandline.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QByteArrayList>
|
||||
#include <QFileInfo>
|
||||
#include <QQueue>
|
||||
#include <QTimer>
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace QSsh {
|
||||
using namespace Internal;
|
||||
|
||||
enum class CommandType { Ls, Mkdir, Rmdir, Rm, Rename, Ln, Put, Get, None };
|
||||
|
||||
struct Command
|
||||
{
|
||||
Command() = default;
|
||||
Command(CommandType t, const QStringList &p, SftpJobId id) : type(t), paths(p), jobId(id) {}
|
||||
bool isValid() const { return type != CommandType::None; }
|
||||
|
||||
CommandType type = CommandType::None;
|
||||
QStringList paths;
|
||||
SftpJobId jobId = SftpInvalidJob;
|
||||
};
|
||||
|
||||
struct SftpSession::SftpSessionPrivate
|
||||
{
|
||||
QtcProcess sftpProc;
|
||||
QStringList connectionArgs;
|
||||
QByteArray output;
|
||||
QQueue<Command> pendingCommands;
|
||||
Command activeCommand;
|
||||
SftpJobId nextJobId = 1;
|
||||
SftpSession::State state = SftpSession::State::Inactive;
|
||||
|
||||
QByteArray commandString(CommandType command) const
|
||||
{
|
||||
switch (command) {
|
||||
case CommandType::Ls: return "ls -n";
|
||||
case CommandType::Mkdir: return "mkdir";
|
||||
case CommandType::Rmdir: return "rmdir";
|
||||
case CommandType::Rm: return "rm";
|
||||
case CommandType::Rename: return "rename";
|
||||
case CommandType::Ln: return "ln -s";
|
||||
case CommandType::Put: return "put";
|
||||
case CommandType::Get: return "get";
|
||||
default: QTC_ASSERT(false, return QByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
SftpJobId queueCommand(CommandType command, const QStringList &paths)
|
||||
{
|
||||
qCDebug(sshLog) << "queueing command" << int(command) << paths;
|
||||
|
||||
const SftpJobId jobId = nextJobId++;
|
||||
pendingCommands.enqueue(Command(command, paths, jobId));
|
||||
runNextCommand();
|
||||
return jobId;
|
||||
}
|
||||
|
||||
void runNextCommand()
|
||||
{
|
||||
if (activeCommand.isValid())
|
||||
return;
|
||||
if (pendingCommands.empty())
|
||||
return;
|
||||
QTC_ASSERT(sftpProc.isRunning(), return);
|
||||
activeCommand = pendingCommands.dequeue();
|
||||
|
||||
// The second newline forces the prompt to appear after the command has finished.
|
||||
sftpProc.write(commandString(activeCommand.type) + ' '
|
||||
+ ProcessArgs::createUnixArgs(activeCommand.paths)
|
||||
.toString().toLocal8Bit() + "\n\n");
|
||||
}
|
||||
};
|
||||
|
||||
static QByteArray prompt() { return "sftp> "; }
|
||||
|
||||
SftpSession::SftpSession(const QStringList &connectionArgs) : d(new SftpSessionPrivate)
|
||||
{
|
||||
SshRemoteProcess::setupSshEnvironment(&d->sftpProc);
|
||||
d->sftpProc.setProcessMode(ProcessMode::Writer);
|
||||
d->connectionArgs = connectionArgs;
|
||||
connect(&d->sftpProc, &QtcProcess::started, [this] {
|
||||
qCDebug(sshLog) << "sftp process started";
|
||||
d->sftpProc.write("\n"); // Force initial prompt.
|
||||
});
|
||||
connect(&d->sftpProc, &QtcProcess::done, [this] {
|
||||
qCDebug(sshLog) << "sftp process finished";
|
||||
|
||||
d->state = State::Inactive;
|
||||
const QString processMessage = [this] {
|
||||
if (d->sftpProc.error() == QProcess::FailedToStart)
|
||||
return tr("sftp failed to start: %1").arg(d->sftpProc.errorString());
|
||||
if (d->sftpProc.exitStatus() != QProcess::NormalExit)
|
||||
return tr("sftp crashed.");
|
||||
if (d->sftpProc.exitCode() != 0)
|
||||
return QString::fromLocal8Bit(d->sftpProc.readAllStandardError());
|
||||
return QString();
|
||||
}();
|
||||
emit done(processMessage);
|
||||
});
|
||||
connect(&d->sftpProc, &QtcProcess::readyReadStandardOutput, this, &SftpSession::handleStdout);
|
||||
}
|
||||
|
||||
void SftpSession::doStart()
|
||||
{
|
||||
if (d->state != State::Starting)
|
||||
return;
|
||||
const FilePath sftpBinary = SshSettings::sftpFilePath();
|
||||
if (!sftpBinary.exists()) {
|
||||
d->state = State::Inactive;
|
||||
emit done(tr("Cannot establish SFTP session: sftp binary \"%1\" does not exist.")
|
||||
.arg(sftpBinary.toUserOutput()));
|
||||
return;
|
||||
}
|
||||
d->activeCommand = Command();
|
||||
const QStringList args = QStringList{"-q"} << d->connectionArgs;
|
||||
qCDebug(sshLog) << "starting sftp session:" << sftpBinary.toUserOutput() << args;
|
||||
d->sftpProc.setCommand(CommandLine(sftpBinary, args));
|
||||
d->sftpProc.start();
|
||||
}
|
||||
|
||||
void SftpSession::handleStdout()
|
||||
{
|
||||
if (state() == State::Running && !d->activeCommand.isValid()) {
|
||||
qCWarning(sshLog) << "ignoring unexpected sftp output:"
|
||||
<< d->sftpProc.readAllStandardOutput();
|
||||
return;
|
||||
}
|
||||
|
||||
d->output += d->sftpProc.readAllStandardOutput();
|
||||
qCDebug(sshLog) << "accumulated sftp output:" << d->output;
|
||||
const int firstPromptOffset = d->output.indexOf(prompt());
|
||||
if (firstPromptOffset == -1)
|
||||
return;
|
||||
if (state() == State::Starting) {
|
||||
d->state = State::Running;
|
||||
d->output.clear();
|
||||
d->sftpProc.readAllStandardError(); // The "connected" message goes to stderr.
|
||||
emit started();
|
||||
return;
|
||||
}
|
||||
const int secondPromptOffset = d->output.indexOf(prompt(), firstPromptOffset + prompt().size());
|
||||
if (secondPromptOffset == -1)
|
||||
return;
|
||||
const Command command = d->activeCommand;
|
||||
d->activeCommand = Command();
|
||||
const QByteArray commandOutput = d->output.mid(
|
||||
firstPromptOffset + prompt().size(),
|
||||
secondPromptOffset - firstPromptOffset - prompt().size());
|
||||
d->output = d->output.mid(secondPromptOffset + prompt().size());
|
||||
if (command.type == CommandType::Ls)
|
||||
handleLsOutput(command.jobId, commandOutput);
|
||||
const QByteArray stdErr = d->sftpProc.readAllStandardError();
|
||||
emit commandFinished(command.jobId, QString::fromLocal8Bit(stdErr));
|
||||
d->runNextCommand();
|
||||
}
|
||||
|
||||
static SftpFileType typeFromLsOutput(char c)
|
||||
{
|
||||
if (c == '-')
|
||||
return FileTypeRegular;
|
||||
if (c == 'd')
|
||||
return FileTypeDirectory;
|
||||
return FileTypeOther;
|
||||
}
|
||||
|
||||
static QFile::Permissions permissionsFromLsOutput(const QByteArray &output)
|
||||
{
|
||||
QFile::Permissions perms;
|
||||
if (output.at(0) == 'r')
|
||||
perms |= QFile::ReadOwner;
|
||||
if (output.at(1) == 'w')
|
||||
perms |= QFile::WriteOwner;
|
||||
if (output.at(2) == 'x')
|
||||
perms |= QFile::ExeOwner;
|
||||
if (output.at(3) == 'r')
|
||||
perms |= QFile::ReadGroup;
|
||||
if (output.at(4) == 'w')
|
||||
perms |= QFile::WriteGroup;
|
||||
if (output.at(5) == 'x')
|
||||
perms |= QFile::ExeGroup;
|
||||
if (output.at(6) == 'r')
|
||||
perms |= QFile::ReadOther;
|
||||
if (output.at(7) == 'w')
|
||||
perms |= QFile::WriteOther;
|
||||
if (output.at(8) == 'x')
|
||||
perms |= QFile::ExeOther;
|
||||
return perms;
|
||||
}
|
||||
|
||||
void SftpSession::handleLsOutput(SftpJobId jobId, const QByteArray &output)
|
||||
{
|
||||
QList<SftpFileInfo> allFileInfo;
|
||||
for (const QByteArray &line : output.split('\n')) {
|
||||
if (line.startsWith("ls") || line.isEmpty())
|
||||
continue;
|
||||
const QByteArrayList components = line.simplified().split(' ');
|
||||
if (components.size() < 9) {
|
||||
qCWarning(sshLog) << "Don't know how to parse sftp ls output:" << line;
|
||||
continue;
|
||||
}
|
||||
const QByteArray typeAndPermissions = components.first();
|
||||
if (typeAndPermissions.size() != 10) {
|
||||
qCWarning(sshLog) << "Don't know how to parse sftp ls output:" << line;
|
||||
continue;
|
||||
}
|
||||
SftpFileInfo fileInfo;
|
||||
fileInfo.type = typeFromLsOutput(typeAndPermissions.at(0));
|
||||
fileInfo.permissions = permissionsFromLsOutput(QByteArray::fromRawData(
|
||||
typeAndPermissions.constData() + 1,
|
||||
typeAndPermissions.size() - 1));
|
||||
bool isNumber;
|
||||
fileInfo.size = components.at(4).toULongLong(&isNumber);
|
||||
if (!isNumber) {
|
||||
qCWarning(sshLog) << "Don't know how to parse sftp ls output:" << line;
|
||||
continue;
|
||||
}
|
||||
// TODO: This will not work for file names with weird whitespace combinations
|
||||
fileInfo.name = QFileInfo(QString::fromUtf8(components.mid(8).join(' '))).fileName();
|
||||
allFileInfo << fileInfo;
|
||||
}
|
||||
emit fileInfoAvailable(jobId, allFileInfo);
|
||||
}
|
||||
|
||||
SftpSession::~SftpSession()
|
||||
{
|
||||
quit();
|
||||
delete d;
|
||||
}
|
||||
|
||||
void SftpSession::start()
|
||||
{
|
||||
QTC_ASSERT(d->state == State::Inactive, return);
|
||||
d->state = State::Starting;
|
||||
QTimer::singleShot(0, this, &SftpSession::doStart);
|
||||
}
|
||||
|
||||
void SftpSession::quit()
|
||||
{
|
||||
qCDebug(sshLog) << "quitting sftp session, current state is" << int(state());
|
||||
|
||||
switch (state()) {
|
||||
case State::Starting:
|
||||
case State::Closing:
|
||||
d->state = State::Closing;
|
||||
d->sftpProc.kill();
|
||||
break;
|
||||
case State::Running:
|
||||
d->state = State::Closing;
|
||||
d->sftpProc.write("bye\n");
|
||||
break;
|
||||
case State::Inactive:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SftpJobId SftpSession::ls(const QString &path)
|
||||
{
|
||||
return d->queueCommand(CommandType::Ls, QStringList(path));
|
||||
}
|
||||
|
||||
SftpJobId SftpSession::createDirectory(const QString &path)
|
||||
{
|
||||
return d->queueCommand(CommandType::Mkdir, QStringList(path));
|
||||
}
|
||||
|
||||
SftpJobId SftpSession::removeDirectory(const QString &path)
|
||||
{
|
||||
return d->queueCommand(CommandType::Rmdir, QStringList(path));
|
||||
}
|
||||
|
||||
SftpJobId SftpSession::removeFile(const QString &path)
|
||||
{
|
||||
return d->queueCommand(CommandType::Rm, QStringList(path));
|
||||
}
|
||||
|
||||
SftpJobId SftpSession::rename(const QString &oldPath, const QString &newPath)
|
||||
{
|
||||
return d->queueCommand(CommandType::Rename, QStringList{oldPath, newPath});
|
||||
}
|
||||
|
||||
SftpJobId SftpSession::createSoftLink(const QString &filePath, const QString &target)
|
||||
{
|
||||
return d->queueCommand(CommandType::Ln, QStringList{filePath, target});
|
||||
}
|
||||
|
||||
SftpJobId SftpSession::uploadFile(const QString &localFilePath, const QString &remoteFilePath)
|
||||
{
|
||||
return d->queueCommand(CommandType::Put, QStringList{localFilePath, remoteFilePath});
|
||||
}
|
||||
|
||||
SftpJobId SftpSession::downloadFile(const QString &remoteFilePath, const QString &localFilePath)
|
||||
{
|
||||
return d->queueCommand(CommandType::Get, QStringList{remoteFilePath, localFilePath});
|
||||
}
|
||||
|
||||
SftpSession::State SftpSession::state() const
|
||||
{
|
||||
return d->state;
|
||||
}
|
||||
|
||||
} // namespace QSsh
|
@@ -1,73 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sftpdefs.h"
|
||||
#include "ssh_global.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace QSsh {
|
||||
class SshConnection;
|
||||
|
||||
class QSSH_EXPORT SftpSession : public QObject
|
||||
{
|
||||
friend class SshConnection;
|
||||
Q_OBJECT
|
||||
public:
|
||||
~SftpSession() override;
|
||||
void start();
|
||||
void quit();
|
||||
|
||||
SftpJobId ls(const QString &path);
|
||||
SftpJobId createDirectory(const QString &path);
|
||||
SftpJobId removeDirectory(const QString &path);
|
||||
SftpJobId removeFile(const QString &path);
|
||||
SftpJobId rename(const QString &oldPath, const QString &newPath);
|
||||
SftpJobId createSoftLink(const QString &filePath, const QString &target);
|
||||
SftpJobId uploadFile(const QString &localFilePath, const QString &remoteFilePath);
|
||||
SftpJobId downloadFile(const QString &remoteFilePath, const QString &localFilePath);
|
||||
|
||||
enum class State { Inactive, Starting, Running, Closing };
|
||||
State state() const;
|
||||
|
||||
signals:
|
||||
void started();
|
||||
void done(const QString &error);
|
||||
void commandFinished(SftpJobId job, const QString &error);
|
||||
void fileInfoAvailable(SftpJobId job, const QList<SftpFileInfo> &fileInfoList);
|
||||
|
||||
private:
|
||||
SftpSession(const QStringList &connectionArgs);
|
||||
void doStart();
|
||||
void handleStdout();
|
||||
void handleLsOutput(SftpJobId jobId, const QByteArray &output);
|
||||
|
||||
struct SftpSessionPrivate;
|
||||
SftpSessionPrivate * const d;
|
||||
};
|
||||
|
||||
} // namespace QSsh
|
@@ -19,8 +19,6 @@ Project {
|
||||
files: [
|
||||
"sftpdefs.cpp",
|
||||
"sftpdefs.h",
|
||||
"sftpsession.cpp",
|
||||
"sftpsession.h",
|
||||
"sftptransfer.cpp",
|
||||
"sftptransfer.h",
|
||||
"ssh.qrc",
|
||||
|
@@ -25,7 +25,6 @@
|
||||
|
||||
#include "sshconnection.h"
|
||||
|
||||
#include "sftpsession.h"
|
||||
#include "sftptransfer.h"
|
||||
#include "sshlogging_p.h"
|
||||
#include "sshremoteprocess.h"
|
||||
@@ -324,12 +323,6 @@ SftpTransferPtr SshConnection::createDownload(const FilesToTransfer &files,
|
||||
return setupTransfer(files, Internal::FileTransferType::Download, errorHandlingMode);
|
||||
}
|
||||
|
||||
SftpSessionPtr SshConnection::createSftpSession()
|
||||
{
|
||||
QTC_ASSERT(state() == Connected, return SftpSessionPtr());
|
||||
return SftpSessionPtr(new SftpSession(d->connectionArgs(SshSettings::sftpFilePath())));
|
||||
}
|
||||
|
||||
void SshConnection::doConnectToHost()
|
||||
{
|
||||
if (d->state != Connecting)
|
||||
|
@@ -120,7 +120,6 @@ public:
|
||||
FileTransferErrorHandling errorHandlingMode);
|
||||
SftpTransferPtr createDownload(const FilesToTransfer &files,
|
||||
FileTransferErrorHandling errorHandlingMode);
|
||||
SftpSessionPtr createSftpSession();
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
|
@@ -23,7 +23,6 @@
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <ssh/sftpsession.h>
|
||||
#include <ssh/sftptransfer.h>
|
||||
#include <ssh/sshconnection.h>
|
||||
#include <ssh/sshremoteprocessrunner.h>
|
||||
@@ -150,7 +149,6 @@ void tst_Ssh::pristineConnectionObject()
|
||||
QTest::ignoreMessage(QtDebugMsg, assertToIgnore);
|
||||
QVERIFY(!connection.createRemoteProcess(""));
|
||||
QTest::ignoreMessage(QtDebugMsg, assertToIgnore);
|
||||
QVERIFY(!connection.createSftpSession());
|
||||
}
|
||||
|
||||
void tst_Ssh::remoteProcess_data()
|
||||
@@ -332,10 +330,6 @@ void tst_Ssh::sftp()
|
||||
static const auto getRemoteFilePath = [](const QString &localFileName) {
|
||||
return QString("/tmp/").append(localFileName).append(".upload");
|
||||
};
|
||||
const auto getDownloadFilePath = [](const QTemporaryDir &dirForFilesToDownload,
|
||||
const QString &localFileName) {
|
||||
return QString(dirForFilesToDownload.path()).append('/').append(localFileName);
|
||||
};
|
||||
FilesToTransfer filesToUpload;
|
||||
std::srand(QDateTime::currentDateTime().toSecsSinceEpoch());
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
@@ -384,193 +378,6 @@ void tst_Ssh::sftp()
|
||||
QVERIFY(timer.isActive());
|
||||
timer.stop();
|
||||
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
||||
|
||||
// Establish interactive SFTP session
|
||||
SftpSessionPtr sftpChannel = connection.createSftpSession();
|
||||
QList<SftpJobId> jobs;
|
||||
bool invalidFinishedSignal = false;
|
||||
connect(sftpChannel.get(), &SftpSession::started, &loop, &QEventLoop::quit);
|
||||
connect(sftpChannel.get(), &SftpSession::done, &loop, &QEventLoop::quit);
|
||||
connect(sftpChannel.get(), &SftpSession::commandFinished,
|
||||
[&loop, &jobs, &invalidFinishedSignal, &jobError](SftpJobId job, const QString &error) {
|
||||
if (!jobs.removeOne(job)) {
|
||||
invalidFinishedSignal = true;
|
||||
loop.quit();
|
||||
return;
|
||||
}
|
||||
if (!error.isEmpty()) {
|
||||
jobError = error;
|
||||
loop.quit();
|
||||
return;
|
||||
}
|
||||
if (jobs.empty())
|
||||
loop.quit();
|
||||
});
|
||||
timer.start();
|
||||
sftpChannel->start();
|
||||
loop.exec();
|
||||
QVERIFY(timer.isActive());
|
||||
timer.stop();
|
||||
QVERIFY(!invalidFinishedSignal);
|
||||
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
||||
|
||||
// Download the uploaded files to a different location
|
||||
const QStringList allUploadedFileNames
|
||||
= QDir(dirForFilesToUpload.path()).entryList(QDir::Files);
|
||||
QCOMPARE(allUploadedFileNames.size(), 101);
|
||||
for (const QString &fileName : allUploadedFileNames) {
|
||||
const QString localFilePath = dirForFilesToUpload.path() + '/' + fileName;
|
||||
const QString remoteFilePath = getRemoteFilePath(fileName);
|
||||
const QString downloadFilePath = getDownloadFilePath(dirForFilesToDownload, fileName);
|
||||
const SftpJobId downloadJob = sftpChannel->downloadFile(remoteFilePath, downloadFilePath);
|
||||
QVERIFY(downloadJob != SftpInvalidJob);
|
||||
jobs << downloadJob;
|
||||
}
|
||||
QCOMPARE(jobs.size(), 101);
|
||||
loop.exec();
|
||||
QVERIFY(!invalidFinishedSignal);
|
||||
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
||||
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
||||
QVERIFY(jobs.empty());
|
||||
|
||||
// Compare contents of uploaded and downloaded files
|
||||
bool success;
|
||||
const auto compareFiles = [&](const QTemporaryDir &downloadDir) {
|
||||
success = false;
|
||||
for (const QString &fileName : allUploadedFileNames) {
|
||||
QFile originalFile(dirForFilesToUpload.path() + '/' + fileName);
|
||||
QVERIFY2(originalFile.open(QIODevice::ReadOnly), qPrintable(originalFile.errorString()));
|
||||
QFile downloadedFile(getDownloadFilePath(downloadDir, fileName));
|
||||
QVERIFY2(downloadedFile.open(QIODevice::ReadOnly),
|
||||
qPrintable(downloadedFile.errorString()));
|
||||
QVERIFY(originalFile.fileName() != downloadedFile.fileName());
|
||||
QCOMPARE(originalFile.size(), downloadedFile.size());
|
||||
qint64 bytesLeft = originalFile.size();
|
||||
while (bytesLeft > 0) {
|
||||
const qint64 bytesToRead = qMin(bytesLeft, Q_INT64_C(1024 * 1024));
|
||||
const QByteArray origBlock = originalFile.read(bytesToRead);
|
||||
const QByteArray copyBlock = downloadedFile.read(bytesToRead);
|
||||
QCOMPARE(origBlock.size(), bytesToRead);
|
||||
QCOMPARE(origBlock, copyBlock);
|
||||
bytesLeft -= bytesToRead;
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
};
|
||||
compareFiles(dirForFilesToDownload);
|
||||
QVERIFY(success);
|
||||
|
||||
// The same again, with a non-interactive download.
|
||||
const FilesToTransfer filesToDownload = transform(filesToUpload, [&](const FileToTransfer &fileToUpload) {
|
||||
return FileToTransfer(fileToUpload.targetFile,
|
||||
getDownloadFilePath(dir2ForFilesToDownload,
|
||||
QFileInfo(fileToUpload.sourceFile).fileName()));
|
||||
});
|
||||
const SftpTransferPtr download = connection.createDownload(filesToDownload,
|
||||
FileTransferErrorHandling::Abort);
|
||||
connect(download.get(), &SftpTransfer::done, [&jobError, &loop](const QString &error) {
|
||||
jobError = error;
|
||||
loop.quit();
|
||||
});
|
||||
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
timer.setSingleShot(true);
|
||||
timer.setInterval(30 * 1000);
|
||||
timer.start();
|
||||
download->start();
|
||||
loop.exec();
|
||||
QVERIFY(timer.isActive());
|
||||
timer.stop();
|
||||
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
||||
compareFiles(dir2ForFilesToDownload);
|
||||
QVERIFY(success);
|
||||
|
||||
// Remove the uploaded files on the remote system
|
||||
timer.setInterval((params.timeout + 5) * 1000);
|
||||
for (const QString &fileName : allUploadedFileNames) {
|
||||
const QString remoteFilePath = getRemoteFilePath(fileName);
|
||||
const SftpJobId removeJob = sftpChannel->removeFile(remoteFilePath);
|
||||
QVERIFY(removeJob != SftpInvalidJob);
|
||||
jobs << removeJob;
|
||||
}
|
||||
loop.exec();
|
||||
QVERIFY(!invalidFinishedSignal);
|
||||
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
||||
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
||||
QVERIFY(jobs.empty());
|
||||
|
||||
// Create a directory on the remote system
|
||||
const QString remoteDirPath = "/tmp/sftptest-" + QDateTime::currentDateTime().toString();
|
||||
const SftpJobId mkdirJob = sftpChannel->createDirectory(remoteDirPath);
|
||||
QVERIFY(mkdirJob != SftpInvalidJob);
|
||||
jobs << mkdirJob;
|
||||
loop.exec();
|
||||
QVERIFY(!invalidFinishedSignal);
|
||||
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
||||
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
||||
QVERIFY(jobs.empty());
|
||||
|
||||
// Retrieve and check the attributes of the remote directory
|
||||
QList<SftpFileInfo> remoteFileInfo;
|
||||
const auto fileInfoHandler
|
||||
= [&remoteFileInfo](SftpJobId, const QList<SftpFileInfo> &fileInfoList) {
|
||||
remoteFileInfo << fileInfoList;
|
||||
};
|
||||
connect(sftpChannel.get(), &SftpSession::fileInfoAvailable, fileInfoHandler);
|
||||
const SftpJobId statDirJob = sftpChannel->ls(remoteDirPath + "/..");
|
||||
QVERIFY(statDirJob != SftpInvalidJob);
|
||||
jobs << statDirJob;
|
||||
loop.exec();
|
||||
QVERIFY(!invalidFinishedSignal);
|
||||
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
||||
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
||||
QVERIFY(jobs.empty());
|
||||
QVERIFY(!remoteFileInfo.empty());
|
||||
SftpFileInfo remoteDirInfo;
|
||||
for (const SftpFileInfo &fi : qAsConst(remoteFileInfo)) {
|
||||
if (fi.name == QFileInfo(remoteDirPath).fileName()) {
|
||||
remoteDirInfo = fi;
|
||||
break;
|
||||
}
|
||||
}
|
||||
QCOMPARE(remoteDirInfo.type, FileTypeDirectory);
|
||||
QCOMPARE(remoteDirInfo.name, QFileInfo(remoteDirPath).fileName());
|
||||
|
||||
// Retrieve and check the contents of the remote directory
|
||||
remoteFileInfo.clear();
|
||||
const SftpJobId lsDirJob = sftpChannel->ls(remoteDirPath);
|
||||
QVERIFY(lsDirJob != SftpInvalidJob);
|
||||
jobs << lsDirJob;
|
||||
loop.exec();
|
||||
QVERIFY(!invalidFinishedSignal);
|
||||
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
||||
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
||||
QVERIFY(jobs.empty());
|
||||
QCOMPARE(remoteFileInfo.size(), 0);
|
||||
|
||||
// Remove the remote directory.
|
||||
const SftpJobId rmDirJob = sftpChannel->removeDirectory(remoteDirPath);
|
||||
QVERIFY(rmDirJob != SftpInvalidJob);
|
||||
jobs << rmDirJob;
|
||||
loop.exec();
|
||||
QVERIFY(!invalidFinishedSignal);
|
||||
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
||||
QCOMPARE(sftpChannel->state(), SftpSession::State::Running);
|
||||
QVERIFY(jobs.empty());
|
||||
|
||||
// Closing down
|
||||
sftpChannel->quit();
|
||||
QCOMPARE(sftpChannel->state(), SftpSession::State::Closing);
|
||||
loop.exec();
|
||||
QVERIFY(!invalidFinishedSignal);
|
||||
QVERIFY2(jobError.isEmpty(), qPrintable(jobError));
|
||||
QCOMPARE(sftpChannel->state(), SftpSession::State::Inactive);
|
||||
connect(&connection, &SshConnection::disconnected, &loop, &QEventLoop::quit);
|
||||
timer.start();
|
||||
connection.disconnectFromHost();
|
||||
loop.exec();
|
||||
QVERIFY(timer.isActive());
|
||||
QCOMPARE(connection.state(), SshConnection::Unconnected);
|
||||
QVERIFY2(connection.errorString().isEmpty(), qPrintable(connection.errorString()));
|
||||
}
|
||||
|
||||
void tst_Ssh::cleanupTestCase()
|
||||
|
Reference in New Issue
Block a user