Device support: Let devices create project list model.

Now each device gets to decide in what way a process list
is set up and how a process gets killed. This enables
e.g. non-SSH based devices to provide process lists.
The default implementation provides an empty list.

Change-Id: Ibb352cd8a5ea556951b02ba512208daeb3b1e1a6
Reviewed-by: hjk <qthjk@ovi.com>
This commit is contained in:
Christian Kandeler
2012-08-02 14:45:27 +02:00
committed by hjk
parent 64651be53e
commit ed920f446c
12 changed files with 356 additions and 156 deletions

View File

@@ -210,7 +210,7 @@ void StartGdbServerDialog::attachToDevice()
if (!device)
return;
delete d->processList;
d->processList = new DeviceProcessList(device);
d->processList = device->createProcessListModel();
d->proxyModel.setSourceModel(d->processList);
connect(d->processList, SIGNAL(error(QString)),
SLOT(handleRemoteError(QString)));

View File

@@ -111,12 +111,12 @@ void MaddeDevice::executeAction(Core::Id actionId, QWidget *parent) const
if (actionId == Core::Id(MaddeDeviceTestActionId))
d = new LinuxDeviceTestDialog(device, new MaddeDeviceTester, parent);
else if (actionId == Core::Id(MaddeRemoteProcessesActionId))
d = new DeviceProcessesDialog(new DeviceProcessList(device), parent);
d = new DeviceProcessesDialog(createProcessListModel(parent));
else if (actionId == Core::Id(Constants::GenericDeployKeyToDeviceActionId))
d = PublicKeyDeploymentDialog::createDialog(device, parent);
// FIXME: Leak?
if (d)
d->exec();
delete d;
}
QString MaddeDevice::maddeDisplayType(Core::Id type)

View File

@@ -30,9 +30,6 @@
#include "deviceprocesslist.h"
#include <utils/qtcassert.h>
#include <ssh/sshremoteprocessrunner.h>
using namespace QSsh;
namespace ProjectExplorer {
namespace Internal {
@@ -48,9 +45,7 @@ public:
{ }
const IDevice::ConstPtr device;
SshRemoteProcessRunner process;
QList<DeviceProcess> remoteProcesses;
QString errorMsg;
State state;
};
@@ -71,7 +66,7 @@ DeviceProcessList::~DeviceProcessList()
void DeviceProcessList::update()
{
QTC_ASSERT(d->state == Inactive, return);
QTC_ASSERT(d->device && d->device->processSupport(), return);
QTC_ASSERT(device(), return);
if (!d->remoteProcesses.isEmpty()) {
beginRemoveRows(QModelIndex(), 0, d->remoteProcesses.count() - 1);
@@ -79,17 +74,46 @@ void DeviceProcessList::update()
endRemoveRows();
}
d->state = Listing;
startProcess(d->device->processSupport()->listProcessesCommandLine());
doUpdate();
}
void DeviceProcessList::doUpdate()
{
reportProcessListUpdated(QList<DeviceProcess>());
}
void DeviceProcessList::reportProcessListUpdated(const QList<DeviceProcess> &processes)
{
QTC_ASSERT(d->state == Listing, return);
setFinished();
if (!processes.isEmpty()) {
beginInsertRows(QModelIndex(), 0, processes.count() - 1);
d->remoteProcesses = processes;
endInsertRows();
}
emit processListUpdated();
}
void DeviceProcessList::killProcess(int row)
{
QTC_ASSERT(row >= 0 && row < d->remoteProcesses.count(), return);
QTC_ASSERT(d->state == Inactive, return);
QTC_ASSERT(device(), return);
d->state = Killing;
const int pid = d->remoteProcesses.at(row).pid;
startProcess(d->device->processSupport()->killProcessByPidCommandLine(pid));
doKillProcess(d->remoteProcesses.at(row));
}
void DeviceProcessList::doKillProcess(const DeviceProcess &)
{
QTC_ASSERT(false, qDebug("Process list should be empty"); return);
}
void DeviceProcessList::reportProcessKilled()
{
QTC_ASSERT(d->state == Killing, return);
setFinished();
emit processKilled();
}
DeviceProcess DeviceProcessList::at(int row) const
@@ -97,11 +121,6 @@ DeviceProcess DeviceProcessList::at(int row) const
return d->remoteProcesses.at(row);
}
IDevice::ConstPtr DeviceProcessList::device() const
{
return d->device;
}
int DeviceProcessList::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : d->remoteProcesses.count();
@@ -134,82 +153,23 @@ QVariant DeviceProcessList::data(const QModelIndex &index, int role) const
return QVariant();
}
void DeviceProcessList::handleConnectionError()
{
QTC_ASSERT(d->state != Inactive, return);
emit error(tr("Connection failure: %1").arg(d->process.lastConnectionErrorString()));
beginResetModel();
d->remoteProcesses.clear();
endResetModel();
setFinished();
}
void DeviceProcessList::handleRemoteProcessFinished(int exitStatus)
{
QTC_ASSERT(d->state != Inactive, return);
switch (exitStatus) {
case SshRemoteProcess::FailedToStart:
d->errorMsg = tr("Error: Remote process failed to start: %1")
.arg(d->process.processErrorString());
break;
case SshRemoteProcess::CrashExit:
d->errorMsg = tr("Error: Remote process crashed: %1")
.arg(d->process.processErrorString());
break;
case SshRemoteProcess::NormalExit:
if (d->process.processExitCode() == 0) {
if (d->state == Listing) {
const QByteArray remoteStdout = d->process.readAllStandardOutput();
const QString stdoutString
= QString::fromUtf8(remoteStdout.data(), remoteStdout.count());
QList<DeviceProcess> processes
= d->device->processSupport()->buildProcessList(stdoutString);
if (!processes.isEmpty()) {
beginInsertRows(QModelIndex(), 0, processes.count()-1);
d->remoteProcesses = processes;
endInsertRows();
}
}
} else {
d->errorMsg = tr("Remote process failed.");
}
break;
default:
Q_ASSERT_X(false, Q_FUNC_INFO, "Invalid exit status");
}
if (d->state == Listing)
emit processListUpdated();
if (!d->errorMsg.isEmpty()) {
const QByteArray remoteStderr = d->process.readAllStandardError();
if (!remoteStderr.isEmpty())
d->errorMsg += tr("\nRemote stderr was: %1").arg(QString::fromUtf8(remoteStderr));
emit error(d->errorMsg);
} else if (d->state == Killing) {
emit processKilled();
}
setFinished();
}
void DeviceProcessList::startProcess(const QString &cmdLine)
{
connect(&d->process, SIGNAL(connectionError()), SLOT(handleConnectionError()));
connect(&d->process, SIGNAL(processClosed(int)),
SLOT(handleRemoteProcessFinished(int)));
d->errorMsg.clear();
d->process.run(cmdLine.toUtf8(), d->device->sshParameters());
}
void DeviceProcessList::setFinished()
{
disconnect(&d->process, 0, this, 0);
d->state = Inactive;
}
IDevice::ConstPtr DeviceProcessList::device() const
{
return d->device;
}
void DeviceProcessList::reportError(const QString &message)
{
QTC_ASSERT(d->state != Inactive, return);
setFinished();
emit error(message);
}
bool DeviceProcess::operator <(const DeviceProcess &other) const
{

View File

@@ -40,6 +40,17 @@ namespace ProjectExplorer {
namespace Internal { class DeviceProcessListPrivate; }
class PROJECTEXPLORER_EXPORT DeviceProcess
{
public:
DeviceProcess() : pid(0) {}
bool operator<(const DeviceProcess &other) const;
int pid;
QString cmdLine;
QString exe;
};
class PROJECTEXPLORER_EXPORT DeviceProcessList : public QAbstractTableModel
{
Q_OBJECT
@@ -51,16 +62,18 @@ public:
void update();
void killProcess(int row);
DeviceProcess at(int row) const;
IDevice::ConstPtr device() const;
signals:
void processListUpdated();
void error(const QString &errorMsg);
void processKilled();
private slots:
void handleConnectionError();
void handleRemoteProcessFinished(int exitStatus);
protected:
void reportError(const QString &message);
void reportProcessKilled();
void reportProcessListUpdated(const QList<DeviceProcess> &processes);
IDevice::ConstPtr device() const;
private:
int rowCount(const QModelIndex &parent = QModelIndex()) const;
@@ -69,7 +82,10 @@ private:
int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
void startProcess(const QString &cmdLine);
// No-op implementations for devices without process listing abilities.
virtual void doUpdate();
virtual void doKillProcess(const DeviceProcess &process);
void setFinished();
Internal::DeviceProcessListPrivate * const d;

View File

@@ -30,6 +30,7 @@
#include "idevice.h"
#include "devicemanager.h"
#include "deviceprocesslist.h"
#include <coreplugin/id.h>
#include <ssh/sshconnection.h>
@@ -259,6 +260,11 @@ PortsGatheringMethod::Ptr IDevice::portsGatheringMethod() const
return PortsGatheringMethod::Ptr();
}
DeviceProcessList *IDevice::createProcessListModel(QObject *parent) const
{
return new DeviceProcessList(sharedFromThis(), parent);
}
IDevice::DeviceState IDevice::deviceState() const
{
return d->deviceState;

View File

@@ -40,6 +40,7 @@
#include <QVariantMap>
QT_BEGIN_NAMESPACE
class QObject;
class QWidget;
QT_END_NAMESPACE
@@ -47,30 +48,18 @@ namespace QSsh { class SshConnectionParameters; }
namespace Utils { class PortList; }
namespace ProjectExplorer {
class DeviceProcessList;
namespace Internal { class IDevicePrivate; }
class IDeviceWidget;
class PROJECTEXPLORER_EXPORT DeviceProcess
{
public:
DeviceProcess() : pid(0) {}
bool operator<(const DeviceProcess &other) const;
int pid;
QString cmdLine;
QString exe;
};
class PROJECTEXPLORER_EXPORT DeviceProcessSupport
{
public:
typedef QSharedPointer<const DeviceProcessSupport> Ptr;
virtual ~DeviceProcessSupport();
virtual QString listProcessesCommandLine() const = 0;
virtual QList<DeviceProcess> buildProcessList(const QString &listProcessesReply) const = 0;
virtual QString killProcessByPidCommandLine(int pid) const = 0;
virtual QString killProcessByNameCommandLine(const QString &filePath) const = 0;
};
@@ -125,6 +114,7 @@ public:
virtual DeviceProcessSupport::Ptr processSupport() const;
virtual PortsGatheringMethod::Ptr portsGatheringMethod() const;
virtual DeviceProcessList *createProcessListModel(QObject *parent = 0) const;
enum DeviceState { DeviceReadyToUse, DeviceConnected, DeviceDisconnected, DeviceStateUnknown };
DeviceState deviceState() const;

View File

@@ -0,0 +1,144 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: http://www.qt-project.org/
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**************************************************************************/
#include "sshdeviceprocesslist.h"
#include <ssh/sshremoteprocessrunner.h>
#include <utils/qtcassert.h>
using namespace QSsh;
namespace ProjectExplorer {
class SshDeviceProcessList::SshDeviceProcessListPrivate
{
public:
SshRemoteProcessRunner process;
};
SshDeviceProcessList::SshDeviceProcessList(const IDevice::ConstPtr &device, QObject *parent) :
DeviceProcessList(device, parent), d(new SshDeviceProcessListPrivate)
{
}
SshDeviceProcessList::~SshDeviceProcessList()
{
delete d;
}
void SshDeviceProcessList::doUpdate()
{
QTC_ASSERT(device()->processSupport(), return);
connect(&d->process, SIGNAL(connectionError()), SLOT(handleConnectionError()));
connect(&d->process, SIGNAL(processClosed(int)), SLOT(handleListProcessFinished(int)));
d->process.run(listProcessesCommandLine().toUtf8(), device()->sshParameters());
}
void SshDeviceProcessList::doKillProcess(const DeviceProcess &process)
{
QTC_ASSERT(device()->processSupport(), return);
connect(&d->process, SIGNAL(connectionError()), SLOT(handleConnectionError()));
connect(&d->process, SIGNAL(processClosed(int)), SLOT(handleKillProcessFinished(int)));
d->process.run(device()->processSupport()->killProcessByPidCommandLine(process.pid).toUtf8(),
device()->sshParameters());
}
void SshDeviceProcessList::handleConnectionError()
{
setFinished();
reportError(tr("Connection failure: %1").arg(d->process.lastConnectionErrorString()));
}
void SshDeviceProcessList::handleListProcessFinished(int exitStatus)
{
setFinished();
switch (exitStatus) {
case SshRemoteProcess::FailedToStart:
handleProcessError(tr("Error: Process listing command failed to start: %1")
.arg(d->process.processErrorString()));
break;
case SshRemoteProcess::CrashExit:
handleProcessError(tr("Error: Process listing command crashed: %1")
.arg(d->process.processErrorString()));
break;
case SshRemoteProcess::NormalExit:
if (d->process.processExitCode() == 0) {
const QByteArray remoteStdout = d->process.readAllStandardOutput();
const QString stdoutString
= QString::fromUtf8(remoteStdout.data(), remoteStdout.count());
reportProcessListUpdated(buildProcessList(stdoutString));
} else {
handleProcessError(tr("Process listing command failed with exit code %1.")
.arg(d->process.processExitCode()));
}
break;
default:
Q_ASSERT_X(false, Q_FUNC_INFO, "Invalid exit status");
}
}
void SshDeviceProcessList::handleKillProcessFinished(int exitStatus)
{
setFinished();
switch (exitStatus) {
case SshRemoteProcess::FailedToStart:
handleProcessError(tr("Error: Kill process failed to start: %1")
.arg(d->process.processErrorString()));
break;
case SshRemoteProcess::CrashExit:
handleProcessError(tr("Error: Kill process crashed: %1")
.arg(d->process.processErrorString()));
break;
case SshRemoteProcess::NormalExit: {
const int exitCode = d->process.processExitCode();
if (exitCode == 0)
reportProcessKilled();
else
handleProcessError(tr("Kill process failed with exit code %1.").arg(exitCode));
break;
}
default:
Q_ASSERT_X(false, Q_FUNC_INFO, "Invalid exit status");
}
}
void SshDeviceProcessList::handleProcessError(const QString &errorMessage)
{
QString fullMessage = errorMessage;
const QByteArray remoteStderr = d->process.readAllStandardError();
if (!remoteStderr.isEmpty())
fullMessage += tr("\nRemote stderr was: %1").arg(QString::fromUtf8(remoteStderr));
reportError(fullMessage);
}
void SshDeviceProcessList::setFinished()
{
d->process.disconnect(this);
}
} // namespace ProjectExplorer

View File

@@ -0,0 +1,64 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: http://www.qt-project.org/
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**************************************************************************/
#ifndef SSHDEVICEPROCESSLIST_H
#define SSHDEVICEPROCESSLIST_H
#include "deviceprocesslist.h"
namespace ProjectExplorer {
class PROJECTEXPLORER_EXPORT SshDeviceProcessList : public DeviceProcessList
{
Q_OBJECT
public:
explicit SshDeviceProcessList(const IDevice::ConstPtr &device, QObject *parent = 0);
~SshDeviceProcessList();
private slots:
void handleConnectionError();
void handleListProcessFinished(int exitStatus);
void handleKillProcessFinished(int exitStatus);
private:
virtual QString listProcessesCommandLine() const = 0;
virtual QList<DeviceProcess> buildProcessList(const QString &listProcessesReply) const = 0;
void doUpdate();
void doKillProcess(const DeviceProcess &process);
void handleProcessError(const QString &errorMessage);
void setFinished();
class SshDeviceProcessListPrivate;
SshDeviceProcessListPrivate * const d;
};
} // namespace ProjectExplorer
#endif // SSHDEVICEPROCESSLIST_H

View File

@@ -126,7 +126,8 @@ HEADERS += projectexplorer.h \
devicesupport/devicesettingswidget.h \
devicesupport/devicesettingspage.h \
devicesupport/deviceusedportsgatherer.h \
devicesupport/deviceapplicationrunner.h
devicesupport/deviceapplicationrunner.h \
devicesupport/sshdeviceprocesslist.h
SOURCES += projectexplorer.cpp \
abi.cpp \
@@ -229,7 +230,8 @@ SOURCES += projectexplorer.cpp \
devicesupport/devicesettingswidget.cpp \
devicesupport/devicesettingspage.cpp \
devicesupport/deviceusedportsgatherer.cpp \
devicesupport/deviceapplicationrunner.cpp
devicesupport/deviceapplicationrunner.cpp \
devicesupport/sshdeviceprocesslist.cpp
FORMS += processstep.ui \
editorsettingspropertiespage.ui \

View File

@@ -298,6 +298,8 @@ QtcPlugin {
"devicesupport/devicefactoryselectiondialog.ui",
"devicesupport/deviceprocesslist.cpp",
"devicesupport/deviceprocesslist.h",
"devicesupport/sshdeviceprocesslist.cpp",
"devicesupport/sshdeviceprocesslist.h",
"devicesupport/deviceprocessesdialog.cpp",
"devicesupport/deviceprocessesdialog.h",
"devicesupport/deviceprocessesdialog.ui",

View File

@@ -36,7 +36,7 @@
#include "remotelinux_constants.h"
#include <coreplugin/id.h>
#include <projectexplorer/devicesupport/deviceprocesslist.h>
#include <projectexplorer/devicesupport/sshdeviceprocesslist.h>
#include <projectexplorer/devicesupport/deviceprocessesdialog.h>
#include <ssh/sshconnection.h>
#include <utils/portlist.h>
@@ -54,8 +54,17 @@ static QString visualizeNull(QString s)
return s.replace(QLatin1Char('\0'), QLatin1String("<null>"));
}
QString LinuxDeviceProcessSupport::listProcessesCommandLine() const
class LinuxDeviceProcessList : public SshDeviceProcessList
{
public:
LinuxDeviceProcessList(const IDevice::ConstPtr &device, QObject *parent)
: SshDeviceProcessList(device, parent)
{
}
private:
QString listProcessesCommandLine() const
{
return QString::fromLatin1(
"for dir in `ls -d /proc/[0123456789]*`; do "
"test -d $dir || continue;" // Decrease the likelihood of a race condition.
@@ -65,10 +74,10 @@ QString LinuxDeviceProcessSupport::listProcessesCommandLine() const
"readlink $dir/exe;"
"printf '%1''%2';"
"done").arg(Delimiter0).arg(Delimiter1);
}
}
QList<DeviceProcess> LinuxDeviceProcessSupport::buildProcessList(const QString &listProcessesReply) const
{
QList<DeviceProcess> buildProcessList(const QString &listProcessesReply) const
{
QList<DeviceProcess> processes;
const QStringList lines = listProcessesReply.split(QString::fromLatin1(Delimiter0)
+ QString::fromLatin1(Delimiter1), QString::SkipEmptyParts);
@@ -108,7 +117,9 @@ QList<DeviceProcess> LinuxDeviceProcessSupport::buildProcessList(const QString &
qSort(processes);
return processes;
}
}
};
QString LinuxDeviceProcessSupport::killProcessByPidCommandLine(int pid) const
{
@@ -212,11 +223,12 @@ void LinuxDevice::executeAction(Core::Id actionId, QWidget *parent) const
if (actionId == Core::Id(Constants::GenericTestDeviceActionId))
d = new LinuxDeviceTestDialog(device, new GenericLinuxDeviceTester, parent);
else if (actionId == Core::Id(Constants::GenericRemoteProcessesActionId))
d = new DeviceProcessesDialog(new DeviceProcessList(device, parent));
d = new DeviceProcessesDialog(createProcessListModel(parent));
else if (actionId == Core::Id(Constants::GenericDeployKeyToDeviceActionId))
d = PublicKeyDeploymentDialog::createDialog(device, parent);
if (d)
d->exec();
delete d;
}
LinuxDevice::LinuxDevice(const QString &name, Core::Id type, MachineType machineType,
@@ -251,4 +263,9 @@ PortsGatheringMethod::Ptr LinuxDevice::portsGatheringMethod() const
return LinuxPortsGatheringMethod::Ptr(new LinuxPortsGatheringMethod);
}
DeviceProcessList *LinuxDevice::createProcessListModel(QObject *parent) const
{
return new LinuxDeviceProcessList(sharedFromThis(), parent);
}
} // namespace RemoteLinux

View File

@@ -46,8 +46,6 @@ namespace Internal { class LinuxDevicePrivate; }
class REMOTELINUX_EXPORT LinuxDeviceProcessSupport : public ProjectExplorer::DeviceProcessSupport
{
public:
QString listProcessesCommandLine() const;
QList<ProjectExplorer::DeviceProcess> buildProcessList(const QString &listProcessesReply) const;
QString killProcessByPidCommandLine(int pid) const;
QString killProcessByNameCommandLine(const QString &filePath) const;
};
@@ -73,6 +71,7 @@ public:
ProjectExplorer::DeviceProcessSupport::Ptr processSupport() const;
ProjectExplorer::PortsGatheringMethod::Ptr portsGatheringMethod() const;
ProjectExplorer::DeviceProcessList *createProcessListModel(QObject *parent) const;
protected:
LinuxDevice() {}