LinuxDevice: Add createFileTransfer() method

The method returns FileTransfer object. This should substitue
SftpTransfer. It is going to handle rsync transfer, too.

Change-Id: I082cf21581ac387e42bb3594604facafe32d7492
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
Jarek Kobus
2022-05-10 10:36:34 +02:00
parent d9005be8a2
commit f8d637dc71
2 changed files with 244 additions and 0 deletions

View File

@@ -1415,6 +1415,208 @@ bool LinuxDevice::writeFileContents(const FilePath &filePath, const QByteArray &
return d->runInShell({"dd", {"of=" + filePath.path()}}, data); return d->runInShell({"dd", {"of=" + filePath.path()}}, data);
} }
TransferDirection FileToTransfer::transferDirection() const
{
if (m_source.needsDevice() == m_target.needsDevice())
return TransferDirection::Invalid;
return m_source.needsDevice() ? TransferDirection::Download : TransferDirection::Upload;
}
static TransferDirection transferDirection(const FilesToTransfer &files)
{
if (files.isEmpty())
return TransferDirection::Invalid;
const TransferDirection transferDirection = files.first().transferDirection();
for (const FileToTransfer &file : files) {
if (file.transferDirection() != transferDirection)
return TransferDirection::Invalid;
}
return transferDirection;
}
static bool isDeviceMatched(const FilePath &file, const QString &id)
{
return (file.scheme() == "device") && (file.host() == id);
}
static bool isDeviceMatched(const FilesToTransfer &files, const QString &id)
{
for (const FileToTransfer &file : files) {
if (file.transferDirection() == TransferDirection::Upload && !isDeviceMatched(file.m_target, id))
return false;
if (file.transferDirection() == TransferDirection::Download && !isDeviceMatched(file.m_source, id))
return false;
}
return true;
}
static FilePaths dirsToCreate(const FilesToTransfer &files)
{
FilePaths dirs;
for (const FileToTransfer &file : files) {
FilePath parentDir = file.m_target.parentDir();
while (true) {
if (dirs.contains(parentDir) || QDir(parentDir.path()).isRoot())
break;
dirs << parentDir;
parentDir = parentDir.parentDir();
}
}
sort(dirs);
return dirs;
}
static QByteArray transferCommand(const TransferDirection transferDirection, bool link)
{
if (transferDirection == TransferDirection::Upload)
return link ? "ln -s" : "put";
if (transferDirection == TransferDirection::Download)
return "get";
return {};
}
// TODO: Provide 2 subclasses that handle sftp and rsync respectively
class FileTransferInterface : public QObject
{
Q_OBJECT
public:
FileTransferInterface(const LinuxDevice::ConstPtr &device, const FilesToTransfer &files);
void start();
void stop() { m_process.close(); }
signals:
void progress(const QString &progressMessage);
void done(const Utils::ProcessResultData &resultData);
private:
LinuxDevice::ConstPtr m_device;
FilesToTransfer m_files;
QTemporaryFile m_batchFile;
QtcProcess m_process;
};
FileTransferInterface::FileTransferInterface(const LinuxDevice::ConstPtr &device, const FilesToTransfer &files)
: m_device(device)
, m_files(files)
, m_batchFile(this)
, m_process(this)
{
SshConnectionParameters::setupSshEnvironment(&m_process);
connect(&m_process, &QtcProcess::readyReadStandardOutput, this, [this] {
emit progress(QString::fromLocal8Bit(m_process.readAllStandardOutput()));
});
connect(&m_process, &QtcProcess::done, this, [this] {
ProcessResultData resultData = m_process.resultData();
if (resultData.m_error == QProcess::FailedToStart)
resultData.m_errorString = tr("\"sftp\" failed to start: %1").arg(resultData.m_errorString);
else if (resultData.m_exitStatus != QProcess::NormalExit)
resultData.m_errorString = tr("\"sftp\" crashed.");
else if (resultData.m_exitCode != 0)
resultData.m_errorString = QString::fromLocal8Bit(m_process.readAllStandardError());
emit done(resultData);
});
}
void FileTransferInterface::start()
{
stop();
auto startFailed = [this](const QString &errorString) {
emit done({0, QProcess::NormalExit, QProcess::FailedToStart, errorString});
};
if (m_files.isEmpty())
return startFailed(tr("No files to transfer."));
const TransferDirection direction = transferDirection(m_files);
if (direction == TransferDirection::Invalid)
return startFailed(tr("Mixing different types on transfer in one go."));
if (!isDeviceMatched(m_files, m_device->id().toString()))
return startFailed(tr("Trying to transfer into / from not matching device."));
const FilePath sftpBinary = SshSettings::sftpFilePath();
if (!sftpBinary.exists())
return startFailed(tr("\"sftp\" binary \"%1\" does not exist.").arg(sftpBinary.toUserOutput()));
if (!m_batchFile.isOpen() && !m_batchFile.open())
return startFailed(tr("Could not create temporary file: %1").arg(m_batchFile.errorString()));
const FilePaths dirs = dirsToCreate(m_files);
for (const FilePath &dir : dirs) {
if (direction == TransferDirection::Upload) {
m_batchFile.write("-mkdir " + ProcessArgs::quoteArgUnix(dir.path()).toLocal8Bit()
+ '\n');
} else if (direction == TransferDirection::Download) {
if (!QDir::root().mkpath(dir.path())) {
return startFailed(tr("Failed to create local directory \"%1\".")
.arg(dir.toUserOutput()));
}
}
}
for (const FileToTransfer &file : m_files) {
FilePath sourceFileOrLinkTarget = file.m_source;
bool link = false;
if (direction == TransferDirection::Upload) {
const QFileInfo fi(file.m_source.toFileInfo());
if (fi.isSymLink()) {
link = true;
m_batchFile.write("-rm " + ProcessArgs::quoteArgUnix(
file.m_target.path()).toLocal8Bit() + '\n');
// see QTBUG-5817.
sourceFileOrLinkTarget.setPath(fi.dir().relativeFilePath(fi.symLinkTarget()));
}
}
m_batchFile.write(transferCommand(direction, link) + ' '
+ ProcessArgs::quoteArgUnix(sourceFileOrLinkTarget.path()).toLocal8Bit() + ' '
+ ProcessArgs::quoteArgUnix(file.m_target.path()).toLocal8Bit() + '\n');
}
m_batchFile.close();
m_process.setStandardInputFile(m_batchFile.fileName());
// TODO: Add support for shared ssh connection
const SshConnectionParameters params = displayless(m_device->sshParameters());
m_process.setCommand(CommandLine(sftpBinary,
params.connectionOptions(sftpBinary) << params.host()));
m_process.start();
}
FileTransfer::FileTransfer(FileTransferInterface *transferInterface)
: d(transferInterface)
{
d->setParent(this);
connect(d, &FileTransferInterface::progress, this, &FileTransfer::progress);
connect(d, &FileTransferInterface::done, this, &FileTransfer::done);
}
FileTransfer::~FileTransfer()
{
stop();
delete d;
}
void FileTransfer::start()
{
d->start();
}
void FileTransfer::stop()
{
d->stop();
}
FileTransfer *LinuxDevice::createFileTransfer(const FilesToTransfer &files) const
{
return new FileTransfer(new FileTransferInterface(
sharedFromThis().dynamicCast<const LinuxDevice>(), files));
}
namespace Internal { namespace Internal {
// Factory // Factory

View File

@@ -30,8 +30,48 @@
#include <projectexplorer/devicesupport/idevice.h> #include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/devicesupport/idevicefactory.h> #include <projectexplorer/devicesupport/idevicefactory.h>
namespace Utils { class ProcessResultData; }
namespace RemoteLinux { namespace RemoteLinux {
enum class TransferDirection {
Upload,
Download,
Invalid
};
class FileToTransfer
{
public:
Utils::FilePath m_source;
Utils::FilePath m_target;
TransferDirection transferDirection() const;
};
using FilesToTransfer = QList<FileToTransfer>;
class FileTransferInterface;
class REMOTELINUX_EXPORT FileTransfer : public QObject
{
Q_OBJECT
public:
~FileTransfer();
void start();
void stop();
signals:
void progress(const QString &progressMessage);
void done(const Utils::ProcessResultData &resultData);
private:
FileTransfer(FileTransferInterface *transferInterface);
FileTransferInterface *d;
friend class LinuxDevice;
};
class REMOTELINUX_EXPORT LinuxDevice : public ProjectExplorer::IDevice class REMOTELINUX_EXPORT LinuxDevice : public ProjectExplorer::IDevice
{ {
Q_DECLARE_TR_FUNCTIONS(RemoteLinux::Internal::LinuxDevice) Q_DECLARE_TR_FUNCTIONS(RemoteLinux::Internal::LinuxDevice)
@@ -87,6 +127,8 @@ public:
QFileDevice::Permissions permissions(const Utils::FilePath &filePath) const override; QFileDevice::Permissions permissions(const Utils::FilePath &filePath) const override;
bool setPermissions(const Utils::FilePath &filePath, QFileDevice::Permissions permissions) const override; bool setPermissions(const Utils::FilePath &filePath, QFileDevice::Permissions permissions) const override;
FileTransfer *createFileTransfer(const FilesToTransfer &files) const;
protected: protected:
LinuxDevice(); LinuxDevice();