Commit 1ff13868 authored by ck's avatar ck
Browse files

Maemo: Add deployment via remote mount.

Only tested on Linux, #ifdef'ed for now.
parent 104f8495
......@@ -35,7 +35,9 @@
#include "maemodeviceconfiglistmodel.h"
#include "maemoglobal.h"
#include "maemopackagecreationstep.h"
#include "maemoremotemounter.h"
#include "maemorunconfiguration.h"
#include "maemotoolchain.h"
#include <coreplugin/ssh/sftpchannel.h>
#include <coreplugin/ssh/sshconnection.h>
......@@ -45,7 +47,10 @@
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/target.h>
#include <qt4projectmanager/qt4buildconfiguration.h>
#include <QtCore/QCryptographicHash>
#include <QtCore/QDir>
#include <QtCore/QEventLoop>
#include <QtCore/QFileInfo>
#include <QtCore/QTimer>
......@@ -79,7 +84,28 @@ MaemoDeployStep::~MaemoDeployStep()
void MaemoDeployStep::ctor()
{
m_connecting = false;
m_needsInstall = false;
m_stopped = false;
m_deviceConfigModel = new MaemoDeviceConfigListModel(this);
#ifdef DEPLOY_VIA_MOUNT
m_canStart = true;
m_cleanupTimer = new QTimer(this);
m_cleanupTimer->setSingleShot(true);
connect(m_cleanupTimer, SIGNAL(timeout()), this,
SLOT(handleCleanupTimeout()));
const Qt4BuildConfiguration * const buildConfig
= qobject_cast<Qt4BuildConfiguration *>(buildConfiguration());
const MaemoToolChain * const toolchain
= dynamic_cast<MaemoToolChain *>(buildConfig->toolChain());
m_mounter = new MaemoRemoteMounter(this,toolchain);
connect(m_mounter, SIGNAL(mounted()), this, SLOT(handleMounted()));
connect(m_mounter, SIGNAL(unmounted()), this, SLOT(handleUnmounted()));
connect(m_mounter, SIGNAL(error(QString)), this,
SLOT(handleMountError(QString)));
connect(m_mounter, SIGNAL(reportProgress(QString)), this,
SLOT(handleProgressReport(QString)));
#endif
}
bool MaemoDeployStep::init()
......@@ -168,21 +194,49 @@ void MaemoDeployStep::raiseError(const QString &errorString)
{
emit addTask(Task(Task::Error, errorString, QString(), -1,
Constants::TASK_CATEGORY_BUILDSYSTEM));
stop();
emit error();
}
void MaemoDeployStep::writeOutput(const QString &text,
ProjectExplorer::BuildStep::OutputFormat format)
BuildStep::OutputFormat format)
{
emit addOutput(text, format);
}
void MaemoDeployStep::stop()
{
#ifdef DEPLOY_VIA_MOUNT
if (m_stopped)
return;
if (m_connecting || m_needsInstall || !m_filesToCopy.isEmpty()
|| (m_installer && m_installer->isRunning())
|| m_currentDeployAction) {
if (m_connection && m_connection->state() == SshConnection::Connected
&& ((m_installer && m_installer->isRunning())
|| m_currentDeployAction)) {
const QByteArray programToKill
= m_currentDeployAction ? "cp" : "dpkg";
const QByteArray cmdLine = "pkill -x " + programToKill
+ "; sleep 1; pkill -x -9 " + programToKill;
SshRemoteProcess::Ptr killProc
= m_connection->createRemoteProcess(cmdLine);
killProc->start();
}
m_stopped = true;
m_canStart = false;
m_needsInstall = false;
m_filesToCopy.clear();
m_connecting = false;
m_cleanupTimer->start(5000);
m_mounter->stop();
}
#else
m_stopped = true;
if (m_installer && m_installer->isRunning()) {
disconnect(m_installer.data(), 0, this, 0);
} else if (!m_uploadsInProgress.isEmpty() || !m_linksInProgress.isEmpty()) {
}
else if (!m_uploadsInProgress.isEmpty() || !m_linksInProgress.isEmpty()) {
m_uploadsInProgress.clear();
m_linksInProgress.clear();
disconnect(m_uploader.data(), 0, this, 0);
......@@ -190,13 +244,15 @@ void MaemoDeployStep::stop()
}
if (m_connection)
disconnect(m_connection.data(), 0, this, 0);
m_stopped = true;
#endif
}
#ifndef DEPLOY_VIA_MOUNT
QString MaemoDeployStep::uploadDir() const
{
return MaemoGlobal::homeDirOnDevice(m_connection->connectionParameters().uname);
}
#endif
bool MaemoDeployStep::currentlyNeedsDeployment(const QString &host,
const MaemoDeployable &deployable) const
......@@ -229,9 +285,14 @@ MaemoDeviceConfigListModel *MaemoDeployStep::deviceConfigModel() const
void MaemoDeployStep::start()
{
m_stopped = false;
if (m_stopped)
#ifdef DEPLOY_VIA_MOUNT
if (!m_canStart) {
raiseError(tr("Can't start deployment, haven't cleaned up from last time yet."));
return;
}
m_cleanupTimer->stop();
#endif
m_stopped = false;
// TODO: Re-use if possible (disconnect + reconnect).
if (m_connection)
......@@ -248,13 +309,46 @@ void MaemoDeployStep::start()
connect(m_connection.data(), SIGNAL(error(SshError)), this,
SLOT(handleConnectionFailure()));
m_connection->connectToHost(devConfig.server);
m_connecting = true;
}
void MaemoDeployStep::handleConnected()
{
if (m_stopped)
m_connecting = false;
if (m_stopped) {
#ifdef DEPLOY_VIA_MOUNT
m_canStart = true;
#endif
return;
}
#ifdef DEPLOY_VIA_MOUNT
Q_ASSERT(!m_currentDeployAction);
Q_ASSERT(!m_needsInstall);
Q_ASSERT(m_filesToCopy.isEmpty());
const MaemoPackageCreationStep * const pStep = packagingStep();
const QString hostName = m_connection->connectionParameters().host;
if (pStep->isPackagingEnabled()) {
const MaemoDeployable d(pStep->packageFilePath(), QString());
if (currentlyNeedsDeployment(hostName, d))
m_needsInstall = true;
} else {
const int deployableCount = m_deployables->deployableCount();
for (int i = 0; i < deployableCount; ++i) {
const MaemoDeployable &d = m_deployables->deployableAt(i);
if (currentlyNeedsDeployment(hostName, d))
m_filesToCopy << d;
}
}
if (m_needsInstall || !m_filesToCopy.isEmpty()) {
m_mounter->setConnection(m_connection);
m_mounter->unmount(); // Clean up potential remains.
} else {
writeOutput(tr("All files up to date, no installation necessary."));
emit done();
}
#else
// TODO: If nothing to deploy, skip this step.
m_uploader = m_connection->createSftpChannel();
connect(m_uploader.data(), SIGNAL(initialized()), this,
......@@ -264,17 +358,24 @@ void MaemoDeployStep::handleConnected()
connect(m_uploader.data(), SIGNAL(finished(Core::SftpJobId, QString)),
this, SLOT(handleSftpJobFinished(Core::SftpJobId, QString)));
m_uploader->initialize();
#endif
}
void MaemoDeployStep::handleConnectionFailure()
{
if (m_stopped)
m_connecting = false;
if (m_stopped) {
#ifdef DEPLOY_VIA_MOUNT
m_canStart = true;
#endif
return;
}
raiseError(tr("Could not connect to host: %1")
.arg(m_connection->errorString()));
}
#ifndef DEPLOY_VIA_MOUNT
void MaemoDeployStep::handleSftpChannelInitialized()
{
if (m_stopped)
......@@ -420,9 +521,199 @@ void MaemoDeployStep::handleLinkProcessFinished(int exitStatus)
}
}
}
#endif
#ifdef DEPLOY_VIA_MOUNT
void MaemoDeployStep::handleMounted()
{
if (m_stopped) {
m_mounter->unmount();
return;
}
if (m_needsInstall) {
writeOutput(tr("Installing package ..."));
const QString packageFileName
= QFileInfo(packagingStep()->packageFilePath()).fileName();
const QByteArray cmd = MaemoGlobal::remoteSudo().toUtf8() + " dpkg -i "
+ deployMountPoint().toUtf8() + '/' + packageFileName.toUtf8();
m_installer = m_connection->createRemoteProcess(cmd);
connect(m_installer.data(), SIGNAL(closed(int)), this,
SLOT(handleInstallationFinished(int)));
connect(m_installer.data(), SIGNAL(outputAvailable(QByteArray)),
this, SLOT(handleInstallerOutput(QByteArray)));
connect(m_installer.data(),
SIGNAL(errorOutputAvailable(QByteArray)), this,
SLOT(handleInstallerErrorOutput(QByteArray)));
m_installer->start();
} else {
deployNextFile();
}
}
void MaemoDeployStep::handleUnmounted()
{
if (m_stopped) {
m_canStart = true;
return;
}
// TODO: port must come from user setting. (Call it "base port" on windows!)
if (m_needsInstall || !m_filesToCopy.isEmpty()) {
if (m_needsInstall) {
const QString localDir = QFileInfo(packagingStep()->packageFilePath())
.absolutePath();
const MaemoMountSpecification mountSpec(localDir,
deployMountPoint(), 11000);
m_mounter->addMountSpecification(mountSpec, true);
} else {
#ifdef Q_OS_WIN
int port = 11000;
bool drivesToMount[26];
for (int i = 0; i < sizeof drivesToMount / drivesToMount[0]; ++i)
drivesToMount[i] = false;
for (int i = 0; i < m_filesToCopy.count(); ++i) {
const QString localDir = QFileInfo(m_filesToCopy.at(i).localFilePath)
.canonicalPath();
const char driveLetter = localDir.at(0).toLower().toLatin1();
if (driveLetter < 'a' || driveLetter > 'z') {
qWarning("Weird: drive letter is '%c'.", driveLetter);
continue;
}
const int index = driveLetter - 'a';
if (drivesToMount[index])
continue;
const QString mountPoint = deployMountPoint()
+ QLatin1Char('/') + QLatin1Char(driveLetter);
const MaemoMountSpecification mountSpec(localDir.left(3),
mountPoint, port++);
m_mounter->addMountSpecification(mountSpec, true);
drivesToMount[index] = true;
}
#else
m_mounter->addMountSpecification(MaemoMountSpecification(QLatin1String("/"),
deployMountPoint(), 11000), true);
#endif
}
m_mounter->mount();
} else {
writeOutput(tr("Deployment finished."));
emit done();
}
}
void MaemoDeployStep::handleMountError(const QString &errorMsg)
{
if (m_stopped)
m_canStart = true;
else
raiseError(errorMsg);
}
void MaemoDeployStep::handleProgressReport(const QString &progressMsg)
{
writeOutput(progressMsg);
}
void MaemoDeployStep::deployNextFile()
{
Q_ASSERT(!m_filesToCopy.isEmpty());
Q_ASSERT(!m_currentDeployAction);
const MaemoDeployable d = m_filesToCopy.takeFirst();
QString sourceFilePath = deployMountPoint();
#ifdef Q_OS_WIN
const QString localFilePath = QDir::fromNativeSeparators(d.localFilePath);
sourceFilePath += QLatin1Char('/') + localFilePath.at(0).toLower()
+ localFilePath.mid(2);
#else
sourceFilePath += d.localFilePath;
#endif
QString command = QString::fromLatin1("%1 cp -r %2 %3")
.arg(MaemoGlobal::remoteSudo(), sourceFilePath,
d.remoteDir + QLatin1Char('/'));
SshRemoteProcess::Ptr copyProcess
= m_connection->createRemoteProcess(command.toUtf8());
connect(copyProcess.data(), SIGNAL(errorOutputAvailable(QByteArray)),
this, SLOT(handleInstallerErrorOutput(QByteArray)));
connect(copyProcess.data(), SIGNAL(closed(int)), this,
SLOT(handleCopyProcessFinished(int)));
m_currentDeployAction.reset(new DeployAction(d, copyProcess));
writeOutput(tr("Copying file '%1' to path '%2' on the device...")
.arg(d.localFilePath, d.remoteDir));
copyProcess->start();
}
void MaemoDeployStep::handleCopyProcessFinished(int exitStatus)
{
if (m_stopped) {
m_mounter->unmount();
return;
}
Q_ASSERT(m_currentDeployAction);
const QString localFilePath = m_currentDeployAction->first.localFilePath;
if (exitStatus != SshRemoteProcess::ExitedNormally
|| m_currentDeployAction->second->exitCode() != 0) {
raiseError(tr("Copying file '%1' failed.").arg(localFilePath));
m_mounter->unmount();
m_currentDeployAction.reset(0);
} else {
writeOutput(tr("Successfully copied file '%1'.").arg(localFilePath));
setDeployed(m_connection->connectionParameters().host,
m_currentDeployAction->first);
m_currentDeployAction.reset(0);
if (m_filesToCopy.isEmpty()) {
writeOutput(tr("All files copied."));
m_mounter->unmount();
} else {
deployNextFile();
}
}
}
void MaemoDeployStep::handleCleanupTimeout()
{
if (!m_canStart) {
qWarning("%s: Deployment cleanup failed apparently, "
"explicitly enabling re-start.", Q_FUNC_INFO);
m_canStart = true;
disconnect(m_connection.data(), 0, this, 0);
if (m_installer)
disconnect(m_installer.data(), 0, this, 0);
if (m_currentDeployAction)
disconnect(m_currentDeployAction->second.data(), 0, this, 0);
}
}
QString MaemoDeployStep::deployMountPoint() const
{
return MaemoGlobal::homeDirOnDevice(deviceConfig().server.uname)
+ QLatin1String("/deployMountPoint");
}
#endif
void MaemoDeployStep::handleInstallationFinished(int exitStatus)
{
#ifdef DEPLOY_VIA_MOUNT
if (m_stopped) {
m_mounter->unmount();
return;
}
if (exitStatus != SshRemoteProcess::ExitedNormally
|| m_installer->exitCode() != 0) {
raiseError(tr("Installing package failed."));
} else {
m_needsInstall = false;
setDeployed(m_connection->connectionParameters().host,
MaemoDeployable(packagingStep()->packageFilePath(), QString()));
writeOutput(tr("Package installed."));
}
m_mounter->unmount();
#else
if (m_stopped)
return;
......@@ -433,6 +724,7 @@ void MaemoDeployStep::handleInstallationFinished(int exitStatus)
writeOutput(tr("Package installation finished."));
emit done();
}
#endif
}
void MaemoDeployStep::handleInstallerOutput(const QByteArray &output)
......
......@@ -42,18 +42,28 @@
#include <QtCore/QPair>
#include <QtCore/QSharedPointer>
// #define DEPLOY_VIA_MOUNT
QT_BEGIN_NAMESPACE
class QEventLoop;
#ifdef DEPLOY_VIA_MOUNT
class QTimer;
#endif
QT_END_NAMESPACE
namespace Core {
#ifndef DEPLOY_VIA_MOUNT
class SftpChannel;
#endif
class SshConnection;
class SshRemoteProcess;
}
namespace Qt4ProjectManager {
namespace Internal {
#ifdef DEPLOY_VIA_MOUNT
class MaemoRemoteMounter;
#endif
class MaemoDeployables;
class MaemoDeviceConfigListModel;
class MaemoPackageCreationStep;
......@@ -81,10 +91,19 @@ private slots:
void stop();
void handleConnected();
void handleConnectionFailure();
#ifdef DEPLOY_VIA_MOUNT
void handleMounted();
void handleUnmounted();
void handleMountError(const QString &errorMsg);
void handleProgressReport(const QString &progressMsg);
void handleCopyProcessFinished(int exitStatus);
void handleCleanupTimeout();
#else
void handleSftpChannelInitialized();
void handleSftpChannelInitializationFailed(const QString &error);
void handleSftpJobFinished(Core::SftpJobId job, const QString &error);
void handleLinkProcessFinished(int exitStatus);
#endif
void handleInstallationFinished(int exitStatus);
void handleInstallerOutput(const QByteArray &output);
void handleInstallerErrorOutput(const QByteArray &output);
......@@ -105,20 +124,39 @@ private:
void addDeployTimesToMap(QVariantMap &map) const;
void getDeployTimesFromMap(const QVariantMap &map);
const MaemoPackageCreationStep *packagingStep() const;
#ifdef DEPLOY_VIA_MOUNT
QString deployMountPoint() const;
void deployNextFile();
#else
bool deploy(const MaemoDeployable &deployable);
#endif
#ifndef DEPLOY_VIA_MOUNT
QString uploadDir() const;
#endif
static const QLatin1String Id;
MaemoDeployables * const m_deployables;
QSharedPointer<Core::SshConnection> m_connection;
#ifdef DEPLOY_VIA_MOUNT
typedef QPair<MaemoDeployable, QSharedPointer<Core::SshRemoteProcess> > DeployAction;
QScopedPointer<DeployAction> m_currentDeployAction;
QList<MaemoDeployable> m_filesToCopy;
MaemoRemoteMounter *m_mounter;
QTimer *m_cleanupTimer;
bool m_canStart;
#else
QSharedPointer<Core::SftpChannel> m_uploader;
QSharedPointer<Core::SshRemoteProcess> m_installer;
typedef QPair<MaemoDeployable, QString> DeployInfo;
QMap<Core::SftpJobId, DeployInfo> m_uploadsInProgress;
QMap<QSharedPointer<Core::SshRemoteProcess>, MaemoDeployable> m_linksInProgress;
bool m_needsInstall;
#endif
QSharedPointer<Core::SshRemoteProcess> m_installer;
bool m_stopped;
bool m_needsInstall;
bool m_connecting;
typedef QPair<MaemoDeployable, QString> DeployablePerHost;
QHash<DeployablePerHost, QDateTime> m_lastDeployed;
MaemoDeviceConfigListModel *m_deviceConfigModel;
......
......@@ -57,15 +57,15 @@ void MaemoRemoteMounter::setConnection(const Core::SshConnection::Ptr &connectio
m_connection = connection;
}
void MaemoRemoteMounter::addMountSpecification(const MaemoMountSpecification &mountSpec)
void MaemoRemoteMounter::addMountSpecification(const MaemoMountSpecification &mountSpec,
bool mountAsRoot)
{
if (mountSpec.isValid())
m_mountSpecs << mountSpec;
m_mountSpecs << MountInfo(mountSpec, mountAsRoot);
}
void MaemoRemoteMounter::mount(const MaemoDeviceConfig &devConfig)
void MaemoRemoteMounter::mount()
{
m_devConfig = devConfig;
m_stop = false;
Q_ASSERT(m_utfsServers.isEmpty());
......@@ -87,7 +87,7 @@ void MaemoRemoteMounter::unmount()
for (int i = 0; i < m_mountSpecs.count(); ++i) {
remoteCall += QString::fromLocal8Bit("%1 umount %2;")
.arg(MaemoGlobal::remoteSudo(),
m_mountSpecs.at(i).remoteMountPoint);
m_mountSpecs.at(i).mountSpec.remoteMountPoint);
}
emit reportProgress(tr("Unmounting remote mount points..."));
......@@ -127,6 +127,7 @@ void MaemoRemoteMounter::handleUnmountProcessFinished(int exitStatus)
utfsServer->kill();
}
m_mountSpecs.clear();
m_utfsServers.clear();
if (errorMsg.isEmpty()) {
emit reportProgress(tr("Finished unmounting."));
......@@ -222,15 +223,17 @@ void MaemoRemoteMounter::startUtfsClients()
const QLatin1String andOp(" && ");
QString remoteCall = chmodFuse + andOp + chmodUtfsClient;
for (int i = 0; i < m_mountSpecs.count(); ++i) {
const MaemoMountSpecification &mountSpec = m_mountSpecs.at(i);
const MaemoMountSpecification &mountSpec = m_mountSpecs.at(i).mountSpec;
const QString mkdir = QString::fromLocal8Bit("%1 mkdir -p %2")
.arg(MaemoGlobal::remoteSudo(), mountSpec.remoteMountPoint);
const QString chmod = QString::fromLocal8Bit("%1 chmod a+r+w+x %2")
.arg(MaemoGlobal::remoteSudo(), mountSpec.remoteMountPoint);
const QString utfsClient
QString utfsClient
= QString::fromLocal8Bit("%1 -l %2 -r %2 -b %2 %4")
.arg(utfsClientOnDevice()).arg(mountSpec.remotePort)
.arg(mountSpec.remoteMountPoint);
if (m_mountSpecs.at(i).mountAsRoot)
utfsClient.prepend(MaemoGlobal::remoteSudo() + QLatin1Char(' '));
remoteCall += andOp + mkdir + andOp + chmod + andOp + utfsClient;
}
......@@ -287,14 +290,15 @@ void MaemoRemoteMounter::startUtfsServers()
{
emit reportProgress(tr("Starting UTFS servers..."));
for (int i = 0; i < m_mountSpecs.count(); ++i) {
const MaemoMountSpecification &mountSpec = m_mountSpecs.at(i);
const MaemoMountSpecification &mountSpec = m_mountSpecs.at(i).mountSpec;
const ProcPtr utfsServerProc(new QProcess);
const QString port = QString::number(mountSpec.remotePort);
const QString localSecretOpt = QLatin1String("-l");
const QString remoteSecretOpt = QLatin1String("-r");
const QStringList utfsServerArgs = QStringList() << localSecretOpt
<< port << remoteSecretOpt << port << QLatin1String("-c")
<< (m_devConfig.server.host + QLatin1Char(':') + port)
const QStringList utfsServerArgs = QStringList()
<< QLatin1String("--detach") << localSecretOpt << port
<< remoteSecretOpt << port << QLatin1String("-c")
<< (m_connection->connectionParameters().host + QLatin1Char(':') + port)
<< mountSpec.localDir;
utfsServerProc->start(utfsServer(), utfsServerArgs);
if (!utfsServerProc->waitForStarted()) {
......@@ -309,6 +313,7 @@ void MaemoRemoteMounter::startUtfsServers()
emit error(errorMsg);
return;
}
utfsServerProc->waitForFinished();
m_utfsServers << utfsServerProc;
}
......