forked from qt-creator/qt-creator
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:
@@ -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
|
||||||
|
@@ -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();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user