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();
|
||||
|
||||
Reference in New Issue
Block a user