forked from qt-creator/qt-creator
FileTransfer: Make the usage more convenient
Prepare the API for rsync implementation. Change-Id: I13b7def31c2e2b1460d18340f6bd7cbd8e0e9434 Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
@@ -7,6 +7,7 @@ add_qtc_plugin(RemoteLinux
|
|||||||
abstractremotelinuxdeploystep.cpp abstractremotelinuxdeploystep.h
|
abstractremotelinuxdeploystep.cpp abstractremotelinuxdeploystep.h
|
||||||
abstractuploadandinstallpackageservice.cpp abstractuploadandinstallpackageservice.h
|
abstractuploadandinstallpackageservice.cpp abstractuploadandinstallpackageservice.h
|
||||||
deploymenttimeinfo.cpp deploymenttimeinfo.h
|
deploymenttimeinfo.cpp deploymenttimeinfo.h
|
||||||
|
filetransfer.h
|
||||||
genericdirectuploadservice.cpp genericdirectuploadservice.h
|
genericdirectuploadservice.cpp genericdirectuploadservice.h
|
||||||
genericdirectuploadstep.cpp genericdirectuploadstep.h
|
genericdirectuploadstep.cpp genericdirectuploadstep.h
|
||||||
genericlinuxdeviceconfigurationwidget.cpp genericlinuxdeviceconfigurationwidget.h genericlinuxdeviceconfigurationwidget.ui
|
genericlinuxdeviceconfigurationwidget.cpp genericlinuxdeviceconfigurationwidget.h genericlinuxdeviceconfigurationwidget.ui
|
||||||
|
@@ -25,10 +25,11 @@
|
|||||||
|
|
||||||
#include "abstractuploadandinstallpackageservice.h"
|
#include "abstractuploadandinstallpackageservice.h"
|
||||||
|
|
||||||
#include "linuxdevice.h"
|
#include "filetransfer.h"
|
||||||
#include "remotelinuxpackageinstaller.h"
|
#include "remotelinuxpackageinstaller.h"
|
||||||
|
|
||||||
#include <projectexplorer/deployablefile.h>
|
#include <projectexplorer/deployablefile.h>
|
||||||
|
#include <projectexplorer/devicesupport/idevice.h>
|
||||||
#include <utils/processinterface.h>
|
#include <utils/processinterface.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ class AbstractUploadAndInstallPackageServicePrivate
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
State state = Inactive;
|
State state = Inactive;
|
||||||
std::unique_ptr<FileTransfer> uploader;
|
FileTransfer uploader;
|
||||||
Utils::FilePath packageFilePath;
|
Utils::FilePath packageFilePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,6 +59,10 @@ using namespace Internal;
|
|||||||
AbstractUploadAndInstallPackageService::AbstractUploadAndInstallPackageService()
|
AbstractUploadAndInstallPackageService::AbstractUploadAndInstallPackageService()
|
||||||
: d(new AbstractUploadAndInstallPackageServicePrivate)
|
: d(new AbstractUploadAndInstallPackageServicePrivate)
|
||||||
{
|
{
|
||||||
|
connect(&d->uploader, &FileTransfer::done, this,
|
||||||
|
&AbstractUploadAndInstallPackageService::handleUploadFinished);
|
||||||
|
connect(&d->uploader, &FileTransfer::progress, this,
|
||||||
|
&AbstractUploadAndInstallPackageService::progressMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractUploadAndInstallPackageService::~AbstractUploadAndInstallPackageService()
|
AbstractUploadAndInstallPackageService::~AbstractUploadAndInstallPackageService()
|
||||||
@@ -98,17 +103,12 @@ void AbstractUploadAndInstallPackageService::doDeploy()
|
|||||||
|
|
||||||
d->state = Uploading;
|
d->state = Uploading;
|
||||||
|
|
||||||
LinuxDevice::ConstPtr linuxDevice = deviceConfiguration().dynamicCast<const LinuxDevice>();
|
|
||||||
QTC_ASSERT(linuxDevice, return);
|
|
||||||
const QString remoteFilePath = uploadDir() + QLatin1Char('/') + d->packageFilePath.fileName();
|
const QString remoteFilePath = uploadDir() + QLatin1Char('/') + d->packageFilePath.fileName();
|
||||||
const FilesToTransfer files {{d->packageFilePath,
|
const FilesToTransfer files {{d->packageFilePath,
|
||||||
deviceConfiguration()->filePath(remoteFilePath)}};
|
deviceConfiguration()->filePath(remoteFilePath)}};
|
||||||
d->uploader.reset(linuxDevice->createFileTransfer(files));
|
d->uploader.setDevice(deviceConfiguration());
|
||||||
connect(d->uploader.get(), &FileTransfer::done, this,
|
d->uploader.setFilesToTransfer(files);
|
||||||
&AbstractUploadAndInstallPackageService::handleUploadFinished);
|
d->uploader.start();
|
||||||
connect(d->uploader.get(), &FileTransfer::progress, this,
|
|
||||||
&AbstractUploadAndInstallPackageService::progressMessage);
|
|
||||||
d->uploader->start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractUploadAndInstallPackageService::stopDeployment()
|
void AbstractUploadAndInstallPackageService::stopDeployment()
|
||||||
@@ -118,7 +118,7 @@ void AbstractUploadAndInstallPackageService::stopDeployment()
|
|||||||
qWarning("%s: Unexpected state 'Inactive'.", Q_FUNC_INFO);
|
qWarning("%s: Unexpected state 'Inactive'.", Q_FUNC_INFO);
|
||||||
break;
|
break;
|
||||||
case Uploading:
|
case Uploading:
|
||||||
d->uploader->stop();
|
d->uploader.stop();
|
||||||
setFinished();
|
setFinished();
|
||||||
break;
|
break;
|
||||||
case Installing:
|
case Installing:
|
||||||
@@ -167,7 +167,7 @@ void AbstractUploadAndInstallPackageService::handleInstallationFinished(const QS
|
|||||||
void AbstractUploadAndInstallPackageService::setFinished()
|
void AbstractUploadAndInstallPackageService::setFinished()
|
||||||
{
|
{
|
||||||
d->state = Inactive;
|
d->state = Inactive;
|
||||||
d->uploader->stop();
|
d->uploader.stop();
|
||||||
disconnect(packageInstaller(), nullptr, this, nullptr);
|
disconnect(packageInstaller(), nullptr, this, nullptr);
|
||||||
handleDeploymentDone();
|
handleDeploymentDone();
|
||||||
}
|
}
|
||||||
|
85
src/plugins/remotelinux/filetransfer.h
Normal file
85
src/plugins/remotelinux/filetransfer.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2022 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 "remotelinux_export.h"
|
||||||
|
|
||||||
|
#include <projectexplorer/devicesupport/idevicefwd.h>
|
||||||
|
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
|
||||||
|
namespace Utils { class ProcessResultData; }
|
||||||
|
|
||||||
|
namespace RemoteLinux {
|
||||||
|
|
||||||
|
enum class TransferDirection {
|
||||||
|
Upload,
|
||||||
|
Download,
|
||||||
|
Invalid
|
||||||
|
};
|
||||||
|
|
||||||
|
class REMOTELINUX_EXPORT FileToTransfer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Utils::FilePath m_source;
|
||||||
|
Utils::FilePath m_target;
|
||||||
|
TransferDirection transferDirection() const;
|
||||||
|
};
|
||||||
|
using FilesToTransfer = QList<FileToTransfer>;
|
||||||
|
|
||||||
|
enum class FileTransferMethod {
|
||||||
|
Sftp,
|
||||||
|
Rsync,
|
||||||
|
Default = Sftp
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileTransferPrivate;
|
||||||
|
|
||||||
|
class REMOTELINUX_EXPORT FileTransfer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FileTransfer();
|
||||||
|
~FileTransfer();
|
||||||
|
|
||||||
|
void setDevice(const ProjectExplorer::IDeviceConstPtr &device);
|
||||||
|
void setTransferMethod(FileTransferMethod method);
|
||||||
|
void setFilesToTransfer(const FilesToTransfer &files);
|
||||||
|
void setRsyncFlags(const QString &flags);
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void progress(const QString &progressMessage);
|
||||||
|
void done(const Utils::ProcessResultData &resultData);
|
||||||
|
|
||||||
|
private:
|
||||||
|
FileTransferPrivate *d;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace RemoteLinux
|
@@ -24,9 +24,11 @@
|
|||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
#include "genericdirectuploadservice.h"
|
#include "genericdirectuploadservice.h"
|
||||||
#include "linuxdevice.h"
|
|
||||||
|
#include "filetransfer.h"
|
||||||
|
|
||||||
#include <projectexplorer/deployablefile.h>
|
#include <projectexplorer/deployablefile.h>
|
||||||
|
#include <projectexplorer/devicesupport/idevice.h>
|
||||||
#include <utils/hostosinfo.h>
|
#include <utils/hostosinfo.h>
|
||||||
#include <utils/processinterface.h>
|
#include <utils/processinterface.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
@@ -69,7 +71,7 @@ public:
|
|||||||
QQueue<DeployableFile> filesToStat;
|
QQueue<DeployableFile> filesToStat;
|
||||||
State state = Inactive;
|
State state = Inactive;
|
||||||
QList<DeployableFile> filesToUpload;
|
QList<DeployableFile> filesToUpload;
|
||||||
std::unique_ptr<FileTransfer> uploader;
|
FileTransfer uploader;
|
||||||
QList<DeployableFile> deployableFiles;
|
QList<DeployableFile> deployableFiles;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,6 +82,20 @@ using namespace Internal;
|
|||||||
GenericDirectUploadService::GenericDirectUploadService(QObject *parent)
|
GenericDirectUploadService::GenericDirectUploadService(QObject *parent)
|
||||||
: AbstractRemoteLinuxDeployService(parent), d(new GenericDirectUploadServicePrivate)
|
: AbstractRemoteLinuxDeployService(parent), d(new GenericDirectUploadServicePrivate)
|
||||||
{
|
{
|
||||||
|
connect(&d->uploader, &FileTransfer::done, this, [this](const ProcessResultData &result) {
|
||||||
|
QTC_ASSERT(d->state == Uploading, return);
|
||||||
|
if (result.m_error != QProcess::UnknownError) {
|
||||||
|
emit errorMessage(result.m_errorString);
|
||||||
|
setFinished();
|
||||||
|
handleDeploymentDone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
d->state = PostProcessing;
|
||||||
|
chmod();
|
||||||
|
queryFiles();
|
||||||
|
});
|
||||||
|
connect(&d->uploader, &FileTransfer::progress,
|
||||||
|
this, &GenericDirectUploadService::progressMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
GenericDirectUploadService::~GenericDirectUploadService()
|
GenericDirectUploadService::~GenericDirectUploadService()
|
||||||
@@ -250,7 +266,7 @@ void GenericDirectUploadService::setFinished()
|
|||||||
it.key()->terminate();
|
it.key()->terminate();
|
||||||
}
|
}
|
||||||
d->remoteProcs.clear();
|
d->remoteProcs.clear();
|
||||||
d->uploader->stop();
|
d->uploader.stop();
|
||||||
d->filesToUpload.clear();
|
d->filesToUpload.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,25 +322,10 @@ void GenericDirectUploadService::uploadFiles()
|
|||||||
files.append({file.localFilePath(),
|
files.append({file.localFilePath(),
|
||||||
deviceConfiguration()->filePath(file.remoteFilePath())});
|
deviceConfiguration()->filePath(file.remoteFilePath())});
|
||||||
}
|
}
|
||||||
LinuxDevice::ConstPtr linuxDevice = deviceConfiguration().dynamicCast<const LinuxDevice>();
|
|
||||||
QTC_ASSERT(linuxDevice, return);
|
|
||||||
|
|
||||||
d->uploader.reset(linuxDevice->createFileTransfer(files));
|
d->uploader.setDevice(deviceConfiguration());
|
||||||
connect(d->uploader.get(), &FileTransfer::done, this, [this](const ProcessResultData &result) {
|
d->uploader.setFilesToTransfer(files);
|
||||||
QTC_ASSERT(d->state == Uploading, return);
|
d->uploader.start();
|
||||||
if (result.m_error != QProcess::UnknownError) {
|
|
||||||
emit errorMessage(result.m_errorString);
|
|
||||||
setFinished();
|
|
||||||
handleDeploymentDone();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
d->state = PostProcessing;
|
|
||||||
chmod();
|
|
||||||
queryFiles();
|
|
||||||
});
|
|
||||||
connect(d->uploader.get(), &FileTransfer::progress,
|
|
||||||
this, &GenericDirectUploadService::progressMessage);
|
|
||||||
d->uploader->start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericDirectUploadService::chmod()
|
void GenericDirectUploadService::chmod()
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include "linuxdevice.h"
|
#include "linuxdevice.h"
|
||||||
|
|
||||||
|
#include "filetransfer.h"
|
||||||
#include "genericlinuxdeviceconfigurationwidget.h"
|
#include "genericlinuxdeviceconfigurationwidget.h"
|
||||||
#include "genericlinuxdeviceconfigurationwizard.h"
|
#include "genericlinuxdeviceconfigurationwizard.h"
|
||||||
#include "linuxdevicetester.h"
|
#include "linuxdevicetester.h"
|
||||||
@@ -1458,60 +1459,196 @@ static QByteArray transferCommand(const TransferDirection transferDirection, boo
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Provide 2 subclasses that handle sftp and rsync respectively
|
static QString methodName(FileTransferMethod method)
|
||||||
|
{
|
||||||
|
switch (method) {
|
||||||
|
case FileTransferMethod::Sftp: return FileTransfer::tr("sftp"); break;
|
||||||
|
case FileTransferMethod::Rsync: return FileTransfer::tr("rsync"); break;
|
||||||
|
}
|
||||||
|
QTC_CHECK(false);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
class FileTransferInterface : public QObject
|
class FileTransferInterface : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
void setDevice(const ProjectExplorer::IDeviceConstPtr &device) { m_device = device; }
|
||||||
|
void setFilesToTransfer(const FilesToTransfer &files, TransferDirection direction)
|
||||||
|
{
|
||||||
|
m_files = files;
|
||||||
|
m_direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() { startImpl(); }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void progress(const QString &progressMessage);
|
||||||
|
void done(const Utils::ProcessResultData &resultData);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FileTransferInterface(FileTransferMethod method)
|
||||||
|
: m_method(method)
|
||||||
|
, 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, &FileTransferInterface::doneImpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void startFailed(const QString &errorString)
|
||||||
|
{
|
||||||
|
emit done({0, QProcess::NormalExit, QProcess::FailedToStart, errorString});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handleError()
|
||||||
|
{
|
||||||
|
ProcessResultData resultData = m_process.resultData();
|
||||||
|
if (resultData.m_error == QProcess::FailedToStart) {
|
||||||
|
resultData.m_errorString = tr("\"%1\" failed to start: %2")
|
||||||
|
.arg(methodName(m_method), resultData.m_errorString);
|
||||||
|
} else if (resultData.m_exitStatus != QProcess::NormalExit) {
|
||||||
|
resultData.m_errorString = tr("\"%1\" crashed.").arg(methodName(m_method));
|
||||||
|
} else if (resultData.m_exitCode != 0) {
|
||||||
|
resultData.m_errorString = QString::fromLocal8Bit(m_process.readAllStandardError());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleDone()
|
||||||
|
{
|
||||||
|
if (!handleError())
|
||||||
|
emit done(m_process.resultData());
|
||||||
|
}
|
||||||
|
|
||||||
|
FileTransferMethod m_method = FileTransferMethod::Default;
|
||||||
|
IDevice::ConstPtr m_device;
|
||||||
|
FilesToTransfer m_files;
|
||||||
|
QtcProcess m_process;
|
||||||
|
TransferDirection m_direction;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void startImpl() = 0;
|
||||||
|
virtual void doneImpl() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SftpTransferImpl : public FileTransferInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SftpTransferImpl() : FileTransferInterface(FileTransferMethod::Sftp) { }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void startImpl()
|
||||||
|
{
|
||||||
|
const FilePath sftpBinary = SshSettings::sftpFilePath();
|
||||||
|
if (!sftpBinary.exists()) {
|
||||||
|
startFailed(tr("\"sftp\" binary \"%1\" does not exist.").arg(sftpBinary.toUserOutput()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_batchFile.reset(new QTemporaryFile(this));
|
||||||
|
if (!m_batchFile->isOpen() && !m_batchFile->open()) {
|
||||||
|
startFailed(tr("Could not create temporary file: %1").arg(m_batchFile->errorString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilePaths dirs = dirsToCreate(m_files);
|
||||||
|
for (const FilePath &dir : dirs) {
|
||||||
|
if (m_direction == TransferDirection::Upload) {
|
||||||
|
m_batchFile->write("-mkdir " + ProcessArgs::quoteArgUnix(dir.path()).toLocal8Bit()
|
||||||
|
+ '\n');
|
||||||
|
} else if (m_direction == TransferDirection::Download) {
|
||||||
|
if (!QDir::root().mkpath(dir.path())) {
|
||||||
|
startFailed(tr("Failed to create local directory \"%1\".")
|
||||||
|
.arg(QDir::toNativeSeparators(dir.path())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const FileToTransfer &file : m_files) {
|
||||||
|
FilePath sourceFileOrLinkTarget = file.m_source;
|
||||||
|
bool link = false;
|
||||||
|
if (m_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(m_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();
|
||||||
|
}
|
||||||
|
|
||||||
|
void doneImpl() final { handleDone(); }
|
||||||
|
|
||||||
|
std::unique_ptr<QTemporaryFile> m_batchFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RsyncTransferImpl : public FileTransferInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RsyncTransferImpl(const QString &flags)
|
||||||
|
: FileTransferInterface(FileTransferMethod::Sftp)
|
||||||
|
, m_flags(flags)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void startImpl() {}
|
||||||
|
void doneImpl() {}
|
||||||
|
|
||||||
|
QString m_flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileTransferPrivate : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FileTransferInterface(const LinuxDevice::ConstPtr &device, const FilesToTransfer &files);
|
void setDevice(const ProjectExplorer::IDeviceConstPtr &device) { m_device = device; }
|
||||||
|
void setTransferMethod(FileTransferMethod method) { m_method = method; }
|
||||||
|
void setFilesToTransfer(const FilesToTransfer &files) { m_files = files; }
|
||||||
|
void setRsyncFlags(const QString &flags) { m_rsyncFlags = flags; }
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void stop() { m_process.close(); }
|
void stop();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void progress(const QString &progressMessage);
|
void progress(const QString &progressMessage);
|
||||||
void done(const Utils::ProcessResultData &resultData);
|
void done(const Utils::ProcessResultData &resultData);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LinuxDevice::ConstPtr m_device;
|
void startFailed(const QString &errorString);
|
||||||
FilesToTransfer m_files;
|
|
||||||
|
|
||||||
QTemporaryFile m_batchFile;
|
FileTransferMethod m_method = FileTransferMethod::Default;
|
||||||
QtcProcess m_process;
|
IDevice::ConstPtr m_device;
|
||||||
|
FilesToTransfer m_files;
|
||||||
|
QString m_rsyncFlags = {"-av"}; // TODO: see RsyncDeployStep::defaultFlags(), reuse it
|
||||||
|
|
||||||
|
std::unique_ptr<FileTransferInterface> m_transfer;
|
||||||
};
|
};
|
||||||
|
|
||||||
FileTransferInterface::FileTransferInterface(const LinuxDevice::ConstPtr &device, const FilesToTransfer &files)
|
void FileTransferPrivate::start()
|
||||||
: 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();
|
stop();
|
||||||
|
|
||||||
auto startFailed = [this](const QString &errorString) {
|
|
||||||
emit done({0, QProcess::NormalExit, QProcess::FailedToStart, errorString});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (m_files.isEmpty())
|
if (m_files.isEmpty())
|
||||||
return startFailed(tr("No files to transfer."));
|
return startFailed(tr("No files to transfer."));
|
||||||
|
|
||||||
@@ -1522,60 +1659,40 @@ void FileTransferInterface::start()
|
|||||||
if (!isDeviceMatched(m_files, m_device->id().toString()))
|
if (!isDeviceMatched(m_files, m_device->id().toString()))
|
||||||
return startFailed(tr("Trying to transfer into / from not matching device."));
|
return startFailed(tr("Trying to transfer into / from not matching device."));
|
||||||
|
|
||||||
const FilePath sftpBinary = SshSettings::sftpFilePath();
|
switch (m_method) {
|
||||||
if (!sftpBinary.exists())
|
case FileTransferMethod::Sftp:
|
||||||
return startFailed(tr("\"sftp\" binary \"%1\" does not exist.").arg(sftpBinary.toUserOutput()));
|
m_transfer.reset(new SftpTransferImpl());
|
||||||
|
break;
|
||||||
if (!m_batchFile.isOpen() && !m_batchFile.open())
|
case FileTransferMethod::Rsync:
|
||||||
return startFailed(tr("Could not create temporary file: %1").arg(m_batchFile.errorString()));
|
m_transfer.reset(new RsyncTransferImpl(m_rsyncFlags));
|
||||||
|
break;
|
||||||
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()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
QTC_ASSERT(m_transfer, startFailed(tr("Missing transfer implementation.")));
|
||||||
|
m_transfer->setDevice(m_device);
|
||||||
|
m_transfer->setFilesToTransfer(m_files, direction);
|
||||||
|
connect(m_transfer.get(), &FileTransferInterface::progress,
|
||||||
|
this, &FileTransferPrivate::progress);
|
||||||
|
connect(m_transfer.get(), &FileTransferInterface::done,
|
||||||
|
this, &FileTransferPrivate::done);
|
||||||
|
m_transfer->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const FileToTransfer &file : m_files) {
|
void FileTransferPrivate::stop()
|
||||||
FilePath sourceFileOrLinkTarget = file.m_source;
|
{
|
||||||
bool link = false;
|
m_transfer.reset();
|
||||||
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)
|
void FileTransferPrivate::startFailed(const QString &errorString)
|
||||||
: d(transferInterface)
|
{
|
||||||
|
emit done({0, QProcess::NormalExit, QProcess::FailedToStart, errorString});
|
||||||
|
}
|
||||||
|
|
||||||
|
FileTransfer::FileTransfer()
|
||||||
|
: d(new FileTransferPrivate)
|
||||||
{
|
{
|
||||||
d->setParent(this);
|
d->setParent(this);
|
||||||
connect(d, &FileTransferInterface::progress, this, &FileTransfer::progress);
|
connect(d, &FileTransferPrivate::progress, this, &FileTransfer::progress);
|
||||||
connect(d, &FileTransferInterface::done, this, &FileTransfer::done);
|
connect(d, &FileTransferPrivate::done, this, &FileTransfer::done);
|
||||||
}
|
}
|
||||||
|
|
||||||
FileTransfer::~FileTransfer()
|
FileTransfer::~FileTransfer()
|
||||||
@@ -1584,6 +1701,26 @@ FileTransfer::~FileTransfer()
|
|||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FileTransfer::setDevice(const ProjectExplorer::IDeviceConstPtr &device)
|
||||||
|
{
|
||||||
|
d->setDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileTransfer::setTransferMethod(FileTransferMethod method)
|
||||||
|
{
|
||||||
|
d->setTransferMethod(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileTransfer::setFilesToTransfer(const FilesToTransfer &files)
|
||||||
|
{
|
||||||
|
d->setFilesToTransfer(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileTransfer::setRsyncFlags(const QString &flags)
|
||||||
|
{
|
||||||
|
d->setRsyncFlags(flags);
|
||||||
|
}
|
||||||
|
|
||||||
void FileTransfer::start()
|
void FileTransfer::start()
|
||||||
{
|
{
|
||||||
d->start();
|
d->start();
|
||||||
@@ -1594,12 +1731,6 @@ void FileTransfer::stop()
|
|||||||
d->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
|
||||||
|
@@ -30,48 +30,8 @@
|
|||||||
#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)
|
||||||
@@ -127,8 +87,6 @@ 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();
|
||||||
|
|
||||||
|
@@ -24,6 +24,7 @@ Project {
|
|||||||
"abstractuploadandinstallpackageservice.h",
|
"abstractuploadandinstallpackageservice.h",
|
||||||
"deploymenttimeinfo.cpp",
|
"deploymenttimeinfo.cpp",
|
||||||
"deploymenttimeinfo.h",
|
"deploymenttimeinfo.h",
|
||||||
|
"filetransfer.h",
|
||||||
"genericdirectuploadservice.cpp",
|
"genericdirectuploadservice.cpp",
|
||||||
"genericdirectuploadservice.h",
|
"genericdirectuploadservice.h",
|
||||||
"genericdirectuploadstep.cpp",
|
"genericdirectuploadstep.cpp",
|
||||||
|
@@ -161,6 +161,7 @@ void RsyncDeployService::deployNextFile()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: consider adding "--progress" option for reporting the progress.
|
||||||
const QStringList args = QStringList(cmdLine.options)
|
const QStringList args = QStringList(cmdLine.options)
|
||||||
<< (localFilePath + (file.localFilePath().isDir() ? "/" : QString()))
|
<< (localFilePath + (file.localFilePath().isDir() ? "/" : QString()))
|
||||||
<< (cmdLine.remoteHostSpec + ':' + file.remoteFilePath());
|
<< (cmdLine.remoteHostSpec + ':' + file.remoteFilePath());
|
||||||
|
Reference in New Issue
Block a user