forked from qt-creator/qt-creator
Move SSH functionality out of the Qt4ProjectManager.
It does not conceptually belong there, and several people have asked for an interface to use the functionality in their own plugins. Task-number: QTCREATORBUG-1204 Reviewed-by: kh1
This commit is contained in:
2
src/libs/3rdparty/botan/botan.pri
vendored
Normal file
2
src/libs/3rdparty/botan/botan.pri
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
INCLUDEPATH += $$PWD/build
|
||||
LIBS *= -l$$qtLibraryTarget(Botan)
|
||||
3
src/libs/3rdparty/net7ssh/net7ssh.pri
vendored
Normal file
3
src/libs/3rdparty/net7ssh/net7ssh.pri
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
include(net7ssh_dependencies.pri)
|
||||
INCLUDEPATH += $$PWD/src
|
||||
LIBS *= -l$$qtLibraryTarget(Net7ssh)
|
||||
1
src/libs/3rdparty/net7ssh/net7ssh_dependencies.pri
vendored
Normal file
1
src/libs/3rdparty/net7ssh/net7ssh_dependencies.pri
vendored
Normal file
@@ -0,0 +1 @@
|
||||
include(../botan/botan.pri)
|
||||
2
src/libs/3rdparty/net7ssh/src/src.pro
vendored
2
src/libs/3rdparty/net7ssh/src/src.pro
vendored
@@ -8,7 +8,7 @@ include(../../../../qtcreatorlibrary.pri)
|
||||
DEPENDPATH += .
|
||||
INCLUDEPATH += $$PWD $$PWD/../../botan $$PWD/../../botan/build
|
||||
|
||||
LIBS += -l$$qtLibraryTarget(Botan)
|
||||
include(../net7ssh_dependencies.pri)
|
||||
|
||||
win32 {
|
||||
LIBS += -lWs2_32
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "modemanager.h"
|
||||
#include "fileiconprovider.h"
|
||||
#include "designmode.h"
|
||||
#include "ssh/ne7sshobject.h"
|
||||
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
|
||||
@@ -88,6 +89,7 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
|
||||
|
||||
m_designMode = new DesignMode(editorManager);
|
||||
addObject(m_designMode);
|
||||
Ne7SshObject::instance();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -111,6 +113,7 @@ void CorePlugin::fileOpenRequest(const QString &f)
|
||||
void CorePlugin::shutdown()
|
||||
{
|
||||
m_mainWindow->shutdown();
|
||||
Ne7SshObject::removeInstance();
|
||||
}
|
||||
|
||||
Q_EXPORT_PLUGIN(CorePlugin)
|
||||
|
||||
@@ -83,7 +83,10 @@ SOURCES += mainwindow.cpp \
|
||||
imode.cpp \
|
||||
editormanager/systemeditor.cpp \
|
||||
designmode.cpp \
|
||||
editortoolbar.cpp
|
||||
editortoolbar.cpp \
|
||||
ssh/ne7sshobject.cpp \
|
||||
ssh/sshconnection.cpp \
|
||||
ssh/sshkeygenerator.cpp
|
||||
|
||||
HEADERS += mainwindow.h \
|
||||
editmode.h \
|
||||
@@ -165,7 +168,10 @@ HEADERS += mainwindow.h \
|
||||
eventfilteringmainwindow.h \
|
||||
editormanager/systemeditor.h \
|
||||
designmode.h \
|
||||
editortoolbar.h
|
||||
editortoolbar.h \
|
||||
ssh/ne7sshobject.h \
|
||||
ssh/sshconnection.h \
|
||||
ssh/sshkeygenerator.h
|
||||
|
||||
FORMS += dialogs/newdialog.ui \
|
||||
actionmanager/commandmappings.ui \
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
include(../../libs/extensionsystem/extensionsystem.pri)
|
||||
include(../../libs/utils/utils.pri)
|
||||
include(../../libs/3rdparty/net7ssh/net7ssh.pri)
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
#include <ne7ssh.h>
|
||||
|
||||
namespace Qt4ProjectManager {
|
||||
namespace Core {
|
||||
namespace Internal {
|
||||
|
||||
Ne7SshObject *Ne7SshObject::instance()
|
||||
@@ -60,7 +60,7 @@ void Ne7SshObject::removeInstance()
|
||||
delete m_instance;
|
||||
}
|
||||
|
||||
QSharedPointer<ne7ssh> Ne7SshObject::get()
|
||||
Ne7SshObject::Ptr Ne7SshObject::get()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
QSharedPointer<ne7ssh> shared = m_weakRef.toStrongRef();
|
||||
@@ -78,4 +78,4 @@ Ne7SshObject::Ne7SshObject()
|
||||
Ne7SshObject *Ne7SshObject::m_instance = 0;
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Qt4ProjectManager
|
||||
} // namespace Core
|
||||
@@ -42,22 +42,26 @@
|
||||
#ifndef NE7SSHOBJECT_H
|
||||
#define NE7SSHOBJECT_H
|
||||
|
||||
#include <coreplugin/core_global.h>
|
||||
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QWeakPointer>
|
||||
|
||||
class ne7ssh;
|
||||
|
||||
namespace Qt4ProjectManager {
|
||||
namespace Core {
|
||||
namespace Internal {
|
||||
|
||||
class Ne7SshObject
|
||||
{
|
||||
public:
|
||||
typedef QSharedPointer<ne7ssh> Ptr;
|
||||
|
||||
static Ne7SshObject *instance();
|
||||
static void removeInstance();
|
||||
|
||||
QSharedPointer<ne7ssh> get();
|
||||
Ptr get();
|
||||
|
||||
private:
|
||||
Ne7SshObject();
|
||||
@@ -71,6 +75,6 @@ private:
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Qt4ProjectManager
|
||||
} // namespace Core
|
||||
|
||||
#endif // NE7SSHOBJECT_H
|
||||
422
src/plugins/coreplugin/ssh/sshconnection.cpp
Normal file
422
src/plugins/coreplugin/ssh/sshconnection.cpp
Normal file
@@ -0,0 +1,422 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** No Commercial Usage
|
||||
** This file contains pre-release code and may not be distributed.
|
||||
** You may use this file in accordance with the terms and conditions
|
||||
** contained in the Technology Preview License Agreement accompanying
|
||||
** this package.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, 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.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at qt-info@nokia.com.
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "sshconnection.h"
|
||||
|
||||
#include "ne7sshobject.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <ne7ssh.h>
|
||||
|
||||
#include <exception>
|
||||
|
||||
namespace Core {
|
||||
|
||||
namespace {
|
||||
|
||||
class GenericSshConnection
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(GenericSshConnection)
|
||||
public:
|
||||
GenericSshConnection(const SshServerInfo &server)
|
||||
: ssh(Internal::Ne7SshObject::instance()->get()),
|
||||
m_server(server),
|
||||
m_channel(-1)
|
||||
{ }
|
||||
|
||||
~GenericSshConnection()
|
||||
{
|
||||
quit();
|
||||
}
|
||||
|
||||
bool start(bool shell)
|
||||
{
|
||||
Q_ASSERT(m_channel == -1);
|
||||
|
||||
try {
|
||||
const QString *authString;
|
||||
int (ne7ssh::*connFunc)(const char *, int, const char *,
|
||||
const char *, bool, int);
|
||||
if (m_server.authType == SshServerInfo::AuthByPwd) {
|
||||
authString = &m_server.pwd;
|
||||
connFunc = &ne7ssh::connectWithPassword;
|
||||
} else {
|
||||
authString = &m_server.privateKeyFile;
|
||||
connFunc = &ne7ssh::connectWithKey;
|
||||
}
|
||||
m_channel = (ssh.data()->*connFunc)(m_server.host.toLatin1(),
|
||||
m_server.port, m_server.uname.toAscii(),
|
||||
authString->toLatin1(), shell, m_server.timeout);
|
||||
if (m_channel == -1) {
|
||||
setError(tr("Could not connect to host."), false);
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
// Should in theory not be necessary, but Net7 leaks Botan exceptions.
|
||||
setError(tr("Error in cryptography backend: %1")
|
||||
.arg(QLatin1String(e.what())), false);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void quit()
|
||||
{
|
||||
const int channel = m_channel;
|
||||
if (channel != -1) {
|
||||
m_channel = -1;
|
||||
if (!ssh->close(channel))
|
||||
qWarning("%s: close() failed.", Q_FUNC_INFO);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasError() const { return !m_error.isEmpty(); }
|
||||
QString error() const { return m_error; }
|
||||
int channel() const { return m_channel; }
|
||||
QString lastNe7Error() { return ssh->errors()->pop(channel()); }
|
||||
const SshServerInfo &server() { return m_server; }
|
||||
|
||||
void setError(const QString error, bool appendNe7ErrMsg)
|
||||
{
|
||||
m_error = error;
|
||||
if (appendNe7ErrMsg)
|
||||
m_error += QLatin1String(": ") + lastNe7Error();
|
||||
}
|
||||
|
||||
QSharedPointer<ne7ssh> ssh;
|
||||
private:
|
||||
const SshServerInfo m_server;
|
||||
QString m_error;
|
||||
int m_channel;
|
||||
};
|
||||
|
||||
|
||||
char *alloc(size_t n)
|
||||
{
|
||||
return new char[n];
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace Internal {
|
||||
|
||||
struct InteractiveSshConnectionPrivate
|
||||
{
|
||||
InteractiveSshConnectionPrivate(const SshServerInfo &server)
|
||||
: conn(server), outputReader(0) {}
|
||||
|
||||
GenericSshConnection conn;
|
||||
ConnectionOutputReader *outputReader;
|
||||
};
|
||||
|
||||
struct NonInteractiveSshConnectionPrivate
|
||||
{
|
||||
NonInteractiveSshConnectionPrivate(const SshServerInfo &server)
|
||||
: conn(server) {}
|
||||
|
||||
GenericSshConnection conn;
|
||||
Ne7SftpSubsystem sftp;
|
||||
};
|
||||
|
||||
class ConnectionOutputReader : public QThread
|
||||
{
|
||||
public:
|
||||
ConnectionOutputReader(InteractiveSshConnection *parent)
|
||||
: QThread(parent), m_conn(parent), m_stopRequested(false)
|
||||
{}
|
||||
|
||||
~ConnectionOutputReader()
|
||||
{
|
||||
stop();
|
||||
wait();
|
||||
}
|
||||
|
||||
// TODO: Use a wakeup mechanism here as soon as we no longer poll for output
|
||||
// from Net7.
|
||||
void stop()
|
||||
{
|
||||
m_stopRequested = true;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual void run()
|
||||
{
|
||||
while (!m_stopRequested) {
|
||||
const int channel = m_conn->d->conn.channel();
|
||||
if (channel != -1) {
|
||||
QScopedPointer<char, QScopedPointerArrayDeleter<char> >
|
||||
output(m_conn->d->conn.ssh->readAndReset(channel, alloc));
|
||||
if (output)
|
||||
emit m_conn->remoteOutput(QByteArray(output.data()));
|
||||
}
|
||||
sleep(1); // TODO: Hack Net7 to enable wait() functionality.
|
||||
}
|
||||
}
|
||||
|
||||
InteractiveSshConnection *m_conn;
|
||||
bool m_stopRequested;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
|
||||
InteractiveSshConnection::InteractiveSshConnection(const SshServerInfo &server)
|
||||
: d(new Internal::InteractiveSshConnectionPrivate(server))
|
||||
{
|
||||
d->outputReader = new Internal::ConnectionOutputReader(this);
|
||||
}
|
||||
|
||||
InteractiveSshConnection::~InteractiveSshConnection()
|
||||
{
|
||||
d->conn.ssh->send("exit\n", d->conn.channel());
|
||||
quit();
|
||||
delete d;
|
||||
}
|
||||
|
||||
bool InteractiveSshConnection::start()
|
||||
{
|
||||
if (!d->conn.start(true))
|
||||
return false;
|
||||
|
||||
d->outputReader->start();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InteractiveSshConnection::sendInput(const QByteArray &input)
|
||||
{
|
||||
if (!d->conn.ssh->send(input.data(), d->conn.channel())) {
|
||||
d->conn.setError(tr("Error sending input"), true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InteractiveSshConnection::quit()
|
||||
{
|
||||
d->outputReader->stop();
|
||||
d->conn.quit();
|
||||
}
|
||||
|
||||
InteractiveSshConnection::Ptr InteractiveSshConnection::create(const SshServerInfo &server)
|
||||
{
|
||||
return Ptr(new InteractiveSshConnection(server));
|
||||
}
|
||||
|
||||
bool InteractiveSshConnection::hasError() const
|
||||
{
|
||||
return d->conn.hasError();
|
||||
}
|
||||
|
||||
QString InteractiveSshConnection::error() const
|
||||
{
|
||||
return d->conn.error();
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
class FileMgr
|
||||
{
|
||||
public:
|
||||
FileMgr(const QString &filePath, const char *mode)
|
||||
: m_file(fopen(filePath.toLatin1().data(), mode)) {}
|
||||
~FileMgr() { if (m_file) fclose(m_file); }
|
||||
FILE *file() const { return m_file; }
|
||||
private:
|
||||
FILE * const m_file;
|
||||
};
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
SftpConnection::SftpConnection(const SshServerInfo &server)
|
||||
: d(new Internal::NonInteractiveSshConnectionPrivate(server))
|
||||
{ }
|
||||
|
||||
SftpConnection::~SftpConnection()
|
||||
{
|
||||
quit();
|
||||
delete d;
|
||||
}
|
||||
|
||||
bool SftpConnection::start()
|
||||
{
|
||||
if (!d->conn.start(false))
|
||||
return false;
|
||||
if (!d->conn.ssh->initSftp(d->sftp, d->conn.channel())
|
||||
|| !d->sftp.setTimeout(d->conn.server().timeout)) {
|
||||
d->conn.setError(tr("Error setting up SFTP subsystem"), true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SftpConnection::transferFiles(const QList<SftpTransferInfo> &transferList)
|
||||
{
|
||||
for (int i = 0; i < transferList.count(); ++i) {
|
||||
const SftpTransferInfo &transfer = transferList.at(i);
|
||||
bool success;
|
||||
if (transfer.type == SftpTransferInfo::Upload) {
|
||||
success = upload(transfer.localFilePath, transfer.remoteFilePath);
|
||||
} else {
|
||||
success = download(transfer.remoteFilePath, transfer.localFilePath);
|
||||
}
|
||||
if (!success)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SftpConnection::upload(const QString &localFilePath,
|
||||
const QByteArray &remoteFilePath)
|
||||
{
|
||||
FileMgr fileMgr(localFilePath, "rb");
|
||||
if (!fileMgr.file()) {
|
||||
d->conn.setError(tr("Could not open file '%1'").arg(localFilePath),
|
||||
false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!d->sftp.put(fileMgr.file(), remoteFilePath.data())) {
|
||||
d->conn.setError(tr("Could not uplodad file '%1'")
|
||||
.arg(localFilePath), true);
|
||||
return false;
|
||||
}
|
||||
|
||||
emit fileCopied(localFilePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SftpConnection::download(const QByteArray &remoteFilePath,
|
||||
const QString &localFilePath)
|
||||
{
|
||||
FileMgr fileMgr(localFilePath, "wb");
|
||||
if (!fileMgr.file()) {
|
||||
d->conn.setError(tr("Could not open file '%1'").arg(localFilePath),
|
||||
false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!d->sftp.get(remoteFilePath.data(), fileMgr.file())) {
|
||||
d->conn.setError(tr("Could not copy remote file '%1' to local file '%2'")
|
||||
.arg(remoteFilePath, localFilePath), false);
|
||||
return false;
|
||||
}
|
||||
|
||||
emit fileCopied(remoteFilePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SftpConnection::createRemoteDir(const QByteArray &remoteDir)
|
||||
{
|
||||
if (!d->sftp.mkdir(remoteDir.data())) {
|
||||
d->conn.setError(tr("Could not create remote directory"), true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SftpConnection::removeRemoteDir(const QByteArray &remoteDir)
|
||||
{
|
||||
if (!d->sftp.rmdir(remoteDir.data())) {
|
||||
d->conn.setError(tr("Could not remove remote directory"), true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray SftpConnection::listRemoteDirContents(const QByteArray &remoteDir,
|
||||
bool withAttributes, bool &ok)
|
||||
{
|
||||
const char * const buffer = d->sftp.ls(remoteDir.data(), withAttributes);
|
||||
if (!buffer) {
|
||||
d->conn.setError(tr("Could not get remote directory contents"), true);
|
||||
ok = false;
|
||||
return QByteArray();
|
||||
}
|
||||
ok = true;
|
||||
return QByteArray(buffer);
|
||||
}
|
||||
|
||||
bool SftpConnection::removeRemoteFile(const QByteArray &remoteFile)
|
||||
{
|
||||
if (!d->sftp.rm(remoteFile.data())) {
|
||||
d->conn.setError(tr("Could not remove remote file"), true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SftpConnection::changeRemoteWorkingDir(const QByteArray &newRemoteDir)
|
||||
{
|
||||
if (!d->sftp.cd(newRemoteDir.data())) {
|
||||
d->conn.setError(tr("Could not change remote working directory"), true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SftpConnection::quit()
|
||||
{
|
||||
d->conn.quit();
|
||||
}
|
||||
|
||||
bool SftpConnection::hasError() const
|
||||
{
|
||||
return d->conn.hasError();
|
||||
}
|
||||
|
||||
QString SftpConnection::error() const
|
||||
{
|
||||
return d->conn.error();
|
||||
}
|
||||
|
||||
SftpConnection::Ptr SftpConnection::create(const SshServerInfo &server)
|
||||
{
|
||||
return Ptr(new SftpConnection(server));
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
150
src/plugins/coreplugin/ssh/sshconnection.h
Normal file
150
src/plugins/coreplugin/ssh/sshconnection.h
Normal file
@@ -0,0 +1,150 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** No Commercial Usage
|
||||
** This file contains pre-release code and may not be distributed.
|
||||
** You may use this file in accordance with the terms and conditions
|
||||
** contained in the Technology Preview License Agreement accompanying
|
||||
** this package.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, 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.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at qt-info@nokia.com.
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef SSHCONNECTION_H
|
||||
#define SSHCONNECTION_H
|
||||
|
||||
#include <coreplugin/core_global.h>
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QString>
|
||||
|
||||
namespace Core {
|
||||
|
||||
namespace Internal {
|
||||
struct InteractiveSshConnectionPrivate;
|
||||
struct NonInteractiveSshConnectionPrivate;
|
||||
class ConnectionOutputReader;
|
||||
}
|
||||
|
||||
struct CORE_EXPORT SshServerInfo
|
||||
{
|
||||
QString host;
|
||||
QString uname;
|
||||
QString pwd;
|
||||
QString privateKeyFile;
|
||||
int timeout;
|
||||
enum AuthType { AuthByPwd, AuthByKey } authType;
|
||||
quint16 port;
|
||||
};
|
||||
|
||||
|
||||
class CORE_EXPORT InteractiveSshConnection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(InteractiveSshConnection)
|
||||
friend class Internal::ConnectionOutputReader;
|
||||
public:
|
||||
typedef QSharedPointer<InteractiveSshConnection> Ptr;
|
||||
|
||||
static Ptr create(const SshServerInfo &server);
|
||||
|
||||
bool start();
|
||||
void quit();
|
||||
bool sendInput(const QByteArray &input); // Should normally end in newline.
|
||||
bool hasError() const;
|
||||
QString error() const;
|
||||
~InteractiveSshConnection();
|
||||
|
||||
signals:
|
||||
void remoteOutput(const QByteArray &output);
|
||||
|
||||
private:
|
||||
InteractiveSshConnection(const SshServerInfo &server);
|
||||
|
||||
struct Internal::InteractiveSshConnectionPrivate *d;
|
||||
};
|
||||
|
||||
|
||||
struct CORE_EXPORT SftpTransferInfo
|
||||
{
|
||||
enum Type { Upload, Download };
|
||||
|
||||
SftpTransferInfo(const QString &localFilePath,
|
||||
const QByteArray &remoteFilePath, Type type)
|
||||
: localFilePath(localFilePath),
|
||||
remoteFilePath(remoteFilePath),
|
||||
type(type)
|
||||
{
|
||||
}
|
||||
|
||||
QString localFilePath;
|
||||
QByteArray remoteFilePath;
|
||||
Type type;
|
||||
};
|
||||
|
||||
class CORE_EXPORT SftpConnection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(SftpConnection)
|
||||
public:
|
||||
typedef QSharedPointer<SftpConnection> Ptr;
|
||||
|
||||
static Ptr create(const SshServerInfo &server);
|
||||
bool start();
|
||||
void quit();
|
||||
bool hasError() const;
|
||||
QString error() const;
|
||||
bool upload(const QString &localFilePath, const QByteArray &remoteFilePath);
|
||||
bool download(const QByteArray &remoteFilePath, const QString &localFilePath);
|
||||
bool transferFiles(const QList<SftpTransferInfo> &transferList);
|
||||
bool createRemoteDir(const QByteArray &remoteDir);
|
||||
bool removeRemoteDir(const QByteArray &remoteDir);
|
||||
bool removeRemoteFile(const QByteArray &remoteFile);
|
||||
bool changeRemoteWorkingDir(const QByteArray &newRemoteDir);
|
||||
QByteArray listRemoteDirContents(const QByteArray &remoteDir,
|
||||
bool withAttributes, bool &ok);
|
||||
~SftpConnection();
|
||||
|
||||
signals:
|
||||
void fileCopied(const QString &filePath);
|
||||
|
||||
private:
|
||||
SftpConnection(const SshServerInfo &server);
|
||||
|
||||
Internal::NonInteractiveSshConnectionPrivate *d;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
#endif // SSHCONNECTION_H
|
||||
56
src/plugins/coreplugin/ssh/sshkeygenerator.cpp
Normal file
56
src/plugins/coreplugin/ssh/sshkeygenerator.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "sshkeygenerator.h"
|
||||
|
||||
#include "ne7sshobject.h"
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QTemporaryFile>
|
||||
|
||||
#include <ne7ssh.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
SshKeyGenerator::SshKeyGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
bool SshKeyGenerator::generateKeys(KeyType type, const QString &id, int keySize)
|
||||
{
|
||||
QTemporaryFile tmpPubKeyFile;
|
||||
QTemporaryFile tmpPrivKeyFile;
|
||||
if (!tmpPubKeyFile.open() || !tmpPrivKeyFile.open()) {
|
||||
m_error = tr("Error creating temporary files.");
|
||||
return false;
|
||||
}
|
||||
tmpPubKeyFile.setAutoRemove(false);
|
||||
tmpPubKeyFile.close();
|
||||
tmpPrivKeyFile.close();
|
||||
const char * const typeStr = type == Rsa ? "rsa" : "dsa";
|
||||
Internal::Ne7SshObject::Ptr ne7Object
|
||||
= Internal::Ne7SshObject::instance()->get();
|
||||
if (!ne7Object->generateKeyPair(typeStr, id.toUtf8(),
|
||||
tmpPrivKeyFile.fileName().toUtf8(),
|
||||
tmpPubKeyFile.fileName().toUtf8(), keySize)) {
|
||||
// TODO: Race condition on pop() call. Perhaps not use Net7 errors? Or hack API
|
||||
m_error = tr("Error generating keys: %1")
|
||||
.arg(ne7Object->errors()->pop());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tmpPubKeyFile.open() || !tmpPrivKeyFile.open()) {
|
||||
m_error = tr("Error reading temporary files.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_publicKey = tmpPubKeyFile.readAll();
|
||||
m_privateKey = tmpPrivKeyFile.readAll();
|
||||
if (tmpPubKeyFile.error() != QFile::NoError
|
||||
|| tmpPrivKeyFile.error() != QFile::NoError) {
|
||||
m_error = tr("Error reading temporary files.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_type = type;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
33
src/plugins/coreplugin/ssh/sshkeygenerator.h
Normal file
33
src/plugins/coreplugin/ssh/sshkeygenerator.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef SSHKEYGENERATOR_H
|
||||
#define SSHKEYGENERATOR_H
|
||||
|
||||
#include <coreplugin/core_global.h>
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QPair>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class CORE_EXPORT SshKeyGenerator
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(SshKeyGenerator)
|
||||
public:
|
||||
enum KeyType { Rsa, Dsa };
|
||||
|
||||
SshKeyGenerator();
|
||||
bool generateKeys(KeyType type, const QString &id, int keySize);
|
||||
QString error() const { return m_error; }
|
||||
QString privateKey() const { return m_privateKey; }
|
||||
QString publicKey() const { return m_publicKey; }
|
||||
KeyType type() const { return m_type; }
|
||||
|
||||
private:
|
||||
QString m_error;
|
||||
QString m_publicKey;
|
||||
QString m_privateKey;
|
||||
KeyType m_type;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
#endif // SSHKEYGENERATOR_H
|
||||
@@ -105,7 +105,7 @@ void MaemoConfigTestDialog::startConfigTest()
|
||||
"|sed 's/[[:space:]][[:space:]]*/ /g' "
|
||||
"|cut -d ' ' -f 2,3 |sed 's/~.*//g'");
|
||||
QString command(sysInfoCmd + " && " + qtInfoCmd);
|
||||
m_deviceTester = new MaemoSshRunner(m_config, command);
|
||||
m_deviceTester = new MaemoSshRunner(m_config.server, command);
|
||||
connect(m_deviceTester, SIGNAL(remoteOutput(QString)),
|
||||
this, SLOT(processSshOutput(QString)));
|
||||
connect(m_deviceTester, SIGNAL(finished()),
|
||||
@@ -126,12 +126,12 @@ void MaemoConfigTestDialog::handleTestThreadFinished()
|
||||
output.append(tr("\nDid you start Qemu?"));
|
||||
} else {
|
||||
output = parseTestOutput();
|
||||
if (!m_qtVersionOk) {
|
||||
m_ui->errorLabel->setText(tr("Qt version mismatch! "
|
||||
" Expected Qt on device: 4.6.2 or later."));
|
||||
}
|
||||
}
|
||||
m_ui->testResultEdit->setPlainText(output);
|
||||
if (!m_qtVersionOk) {
|
||||
m_ui->errorLabel->setText(tr("Qt version mismatch! Expected Qt on device: "
|
||||
"4.6.2 or later."));
|
||||
}
|
||||
stopConfigTest();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
typedef Core::SshServerInfo::AuthType AuthType;
|
||||
|
||||
namespace Qt4ProjectManager {
|
||||
namespace Internal {
|
||||
|
||||
@@ -78,7 +80,7 @@ namespace {
|
||||
const QString DefaultHostNameHW(QLatin1String("192.168.2.15"));
|
||||
const QString DefaultHostNameSim(QLatin1String("localhost"));
|
||||
const QString DefaultUserName(QLatin1String("developer"));
|
||||
const MaemoDeviceConfig::AuthType DefaultAuthType(MaemoDeviceConfig::Key);
|
||||
const AuthType DefaultAuthType(Core::SshServerInfo::AuthByKey);
|
||||
const int DefaultTimeout(30);
|
||||
const MaemoDeviceConfig::DeviceType DefaultDeviceType(MaemoDeviceConfig::Physical);
|
||||
};
|
||||
@@ -98,33 +100,35 @@ private:
|
||||
MaemoDeviceConfig::MaemoDeviceConfig(const QString &name, MaemoDeviceConfig::DeviceType devType)
|
||||
: name(name),
|
||||
type(devType),
|
||||
host(defaultHost(type)),
|
||||
sshPort(defaultSshPort(type)),
|
||||
gdbServerPort(defaultGdbServerPort(type)),
|
||||
uname(DefaultUserName),
|
||||
authentication(DefaultAuthType),
|
||||
keyFile(DefaultKeyFile),
|
||||
timeout(DefaultTimeout),
|
||||
internalId(MaemoDeviceConfigurations::instance().m_nextId++)
|
||||
{
|
||||
server.host = defaultHost(type);
|
||||
server.port = defaultSshPort(type);
|
||||
server.uname = DefaultUserName;
|
||||
server.authType = DefaultAuthType;
|
||||
server.privateKeyFile = DefaultKeyFile;
|
||||
server.timeout = DefaultTimeout;
|
||||
}
|
||||
|
||||
MaemoDeviceConfig::MaemoDeviceConfig(const QSettings &settings,
|
||||
quint64 &nextId)
|
||||
: name(settings.value(NameKey).toString()),
|
||||
type(static_cast<DeviceType>(settings.value(TypeKey, DefaultDeviceType).toInt())),
|
||||
host(settings.value(HostKey, defaultHost(type)).toString()),
|
||||
sshPort(settings.value(SshPortKey, defaultSshPort(type)).toInt()),
|
||||
gdbServerPort(settings.value(GdbServerPortKey, defaultGdbServerPort(type)).toInt()),
|
||||
uname(settings.value(UserNameKey, DefaultUserName).toString()),
|
||||
authentication(static_cast<AuthType>(settings.value(AuthKey, DefaultAuthType).toInt())),
|
||||
pwd(settings.value(PasswordKey).toString()),
|
||||
keyFile(settings.value(KeyFileKey, DefaultKeyFile).toString()),
|
||||
timeout(settings.value(TimeoutKey, DefaultTimeout).toInt()),
|
||||
internalId(settings.value(InternalIdKey, nextId).toInt())
|
||||
{
|
||||
if (internalId == nextId)
|
||||
++nextId;
|
||||
server.host = settings.value(HostKey, defaultHost(type)).toString();
|
||||
server.port = settings.value(SshPortKey, defaultSshPort(type)).toInt();
|
||||
server.uname = settings.value(UserNameKey, DefaultUserName).toString();
|
||||
server.authType
|
||||
= static_cast<AuthType>(settings.value(AuthKey, DefaultAuthType).toInt());
|
||||
server.pwd = settings.value(PasswordKey).toString();
|
||||
server.privateKeyFile
|
||||
= settings.value(KeyFileKey, DefaultKeyFile).toString();
|
||||
server.timeout = settings.value(TimeoutKey, DefaultTimeout).toInt();
|
||||
}
|
||||
|
||||
MaemoDeviceConfig::MaemoDeviceConfig()
|
||||
@@ -156,14 +160,14 @@ void MaemoDeviceConfig::save(QSettings &settings) const
|
||||
{
|
||||
settings.setValue(NameKey, name);
|
||||
settings.setValue(TypeKey, type);
|
||||
settings.setValue(HostKey, host);
|
||||
settings.setValue(SshPortKey, sshPort);
|
||||
settings.setValue(HostKey, server.host);
|
||||
settings.setValue(SshPortKey, server.port);
|
||||
settings.setValue(GdbServerPortKey, gdbServerPort);
|
||||
settings.setValue(UserNameKey, uname);
|
||||
settings.setValue(AuthKey, authentication);
|
||||
settings.setValue(PasswordKey, pwd);
|
||||
settings.setValue(KeyFileKey, keyFile);
|
||||
settings.setValue(TimeoutKey, timeout);
|
||||
settings.setValue(UserNameKey, server.uname);
|
||||
settings.setValue(AuthKey, server.authType);
|
||||
settings.setValue(PasswordKey, server.pwd);
|
||||
settings.setValue(KeyFileKey, server.privateKeyFile);
|
||||
settings.setValue(TimeoutKey, server.timeout);
|
||||
settings.setValue(InternalIdKey, internalId);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
#ifndef MAEMODEVICECONFIGURATIONS_H
|
||||
#define MAEMODEVICECONFIGURATIONS_H
|
||||
|
||||
#include <coreplugin/ssh/sshconnection.h>
|
||||
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
@@ -52,22 +54,16 @@ class MaemoDeviceConfig
|
||||
{
|
||||
public:
|
||||
enum DeviceType { Physical, Simulator };
|
||||
enum AuthType { Password, Key };
|
||||
MaemoDeviceConfig();
|
||||
MaemoDeviceConfig(const QString &name, DeviceType type);
|
||||
MaemoDeviceConfig(const QSettings &settings, quint64 &nextId);
|
||||
void save(QSettings &settings) const;
|
||||
bool isValid() const;
|
||||
|
||||
Core::SshServerInfo server;
|
||||
QString name;
|
||||
DeviceType type;
|
||||
QString host;
|
||||
int sshPort;
|
||||
int gdbServerPort;
|
||||
QString uname;
|
||||
AuthType authentication;
|
||||
QString pwd;
|
||||
QString keyFile;
|
||||
int timeout;
|
||||
quint64 internalId;
|
||||
|
||||
private:
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
#include "maemosettingspage.h"
|
||||
#include "maemotoolchain.h"
|
||||
#include "maemorunconfiguration.h"
|
||||
#include "ne7sshobject.h"
|
||||
|
||||
#include <coreplugin/actionmanager/actionmanager.h>
|
||||
#include <coreplugin/coreconstants.h>
|
||||
@@ -83,8 +82,6 @@ MaemoManager::MaemoManager()
|
||||
pluginManager->addObject(m_runConfigurationFactory);
|
||||
pluginManager->addObject(m_packageCreationFactory);
|
||||
pluginManager->addObject(m_settingsPage);
|
||||
|
||||
Ne7SshObject::instance();
|
||||
}
|
||||
|
||||
MaemoManager::~MaemoManager()
|
||||
@@ -96,7 +93,6 @@ MaemoManager::~MaemoManager()
|
||||
pluginManager->removeObject(m_packageCreationFactory);
|
||||
pluginManager->removeObject(m_settingsPage);
|
||||
|
||||
Ne7SshObject::removeInstance();
|
||||
m_instance = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ void AbstractMaemoRunControl::startDeployment(bool forDebugging)
|
||||
emit finished();
|
||||
} else {
|
||||
m_deployables.clear();
|
||||
if (m_runConfig->currentlyNeedsDeployment(m_devConfig.host)) {
|
||||
if (m_runConfig->currentlyNeedsDeployment(m_devConfig.server.host)) {
|
||||
m_deployables.append(Deployable(packageFileName(),
|
||||
QFileInfo(executableOnHost()).canonicalPath(),
|
||||
&MaemoRunConfiguration::wasDeployed));
|
||||
@@ -132,7 +132,7 @@ void AbstractMaemoRunControl::startDeployment(bool forDebugging)
|
||||
m_needsInstall = false;
|
||||
}
|
||||
if (forDebugging
|
||||
&& m_runConfig->debuggingHelpersNeedDeployment(m_devConfig.host)) {
|
||||
&& m_runConfig->debuggingHelpersNeedDeployment(m_devConfig.server.host)) {
|
||||
const QFileInfo &info(m_runConfig->dumperLib());
|
||||
m_deployables.append(Deployable(info.fileName(), info.canonicalPath(),
|
||||
&MaemoRunConfiguration::debuggingHelpersDeployed));
|
||||
@@ -148,7 +148,7 @@ void AbstractMaemoRunControl::deploy()
|
||||
->addTask(m_progress.future(), tr("Deploying"),
|
||||
QLatin1String("Maemo.Deploy"));
|
||||
if (!m_deployables.isEmpty()) {
|
||||
QList<SshDeploySpec> deploySpecs;
|
||||
QList<Core::SftpTransferInfo> deploySpecs;
|
||||
QStringList files;
|
||||
foreach (const Deployable &deployable, m_deployables) {
|
||||
const QString srcFilePath
|
||||
@@ -156,10 +156,11 @@ void AbstractMaemoRunControl::deploy()
|
||||
const QString tgtFilePath
|
||||
= remoteDir() % QDir::separator() % deployable.fileName;
|
||||
files << srcFilePath;
|
||||
deploySpecs << SshDeploySpec(srcFilePath, tgtFilePath);
|
||||
deploySpecs << Core::SftpTransferInfo(srcFilePath,
|
||||
tgtFilePath.toUtf8(), Core::SftpTransferInfo::Upload);
|
||||
}
|
||||
emit appendMessage(this, tr("Files to deploy: %1.").arg(files.join(" ")), false);
|
||||
m_sshDeployer.reset(new MaemoSshDeployer(m_devConfig, deploySpecs));
|
||||
m_sshDeployer.reset(new MaemoSshDeployer(m_devConfig.server, deploySpecs));
|
||||
connect(m_sshDeployer.data(), SIGNAL(finished()),
|
||||
this, SLOT(handleDeployThreadFinished()));
|
||||
connect(m_sshDeployer.data(), SIGNAL(fileCopied(QString)),
|
||||
@@ -177,7 +178,7 @@ void AbstractMaemoRunControl::deploy()
|
||||
void AbstractMaemoRunControl::handleFileCopied()
|
||||
{
|
||||
Deployable deployable = m_deployables.takeFirst();
|
||||
(m_runConfig->*deployable.updateTimestamp)(m_devConfig.host);
|
||||
(m_runConfig->*deployable.updateTimestamp)(m_devConfig.server.host);
|
||||
m_progress.setProgressValue(m_progress.progressValue() + 1);
|
||||
}
|
||||
|
||||
@@ -208,7 +209,7 @@ bool AbstractMaemoRunControl::isCleaning() const
|
||||
|
||||
void AbstractMaemoRunControl::startExecution()
|
||||
{
|
||||
m_sshRunner.reset(new MaemoSshRunner(m_devConfig, remoteCall()));
|
||||
m_sshRunner.reset(new MaemoSshRunner(m_devConfig.server, remoteCall()));
|
||||
connect(m_sshRunner.data(), SIGNAL(finished()),
|
||||
this, SLOT(handleRunThreadFinished()));
|
||||
connect(m_sshRunner.data(), SIGNAL(remoteOutput(QString)),
|
||||
@@ -246,7 +247,7 @@ void AbstractMaemoRunControl::killRemoteProcesses(const QStringList &apps,
|
||||
remoteCall.remove(remoteCall.count() - 1, 1); // Get rid of trailing semicolon.
|
||||
QScopedPointer<MaemoSshRunner> &runner
|
||||
= initialCleanup ? m_initialCleaner : m_sshStopper;
|
||||
runner.reset(new MaemoSshRunner(m_devConfig, remoteCall));
|
||||
runner.reset(new MaemoSshRunner(m_devConfig.server, remoteCall));
|
||||
if (initialCleanup)
|
||||
connect(runner.data(), SIGNAL(finished()),
|
||||
this, SLOT(handleInitialCleanupFinished()));
|
||||
@@ -296,7 +297,6 @@ void AbstractMaemoRunControl::handleRunThreadFinished()
|
||||
|
||||
const QString AbstractMaemoRunControl::executableOnHost() const
|
||||
{
|
||||
qDebug("runconfig->executable: %s", qPrintable(m_runConfig->executable()));
|
||||
return m_runConfig->executable();
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ const QString AbstractMaemoRunControl::executableFileName() const
|
||||
|
||||
const QString AbstractMaemoRunControl::remoteDir() const
|
||||
{
|
||||
return homeDirOnDevice(m_devConfig.uname);
|
||||
return homeDirOnDevice(m_devConfig.server.uname);
|
||||
}
|
||||
|
||||
QString AbstractMaemoRunControl::remoteSudo() const
|
||||
@@ -384,7 +384,7 @@ MaemoDebugRunControl::MaemoDebugRunControl(RunConfiguration *runConfiguration)
|
||||
m_startParams->startMode = Debugger::StartRemote;
|
||||
m_startParams->executable = executableOnHost();
|
||||
m_startParams->remoteChannel
|
||||
= m_devConfig.host % QLatin1Char(':') % gdbServerPort();
|
||||
= m_devConfig.server.host % QLatin1Char(':') % gdbServerPort();
|
||||
m_startParams->remoteArchitecture = QLatin1String("arm");
|
||||
m_startParams->sysRoot = m_runConfig->sysRoot();
|
||||
m_startParams->toolChainType = ToolChain::GCC_MAEMO;
|
||||
|
||||
@@ -190,31 +190,31 @@ void MaemoSettingsWidget::display(const MaemoDeviceConfig &devConfig)
|
||||
otherConfig = &m_lastConfigHW;
|
||||
m_ui->simulatorButton->setChecked(true);
|
||||
}
|
||||
otherConfig->authentication = devConfig.authentication;
|
||||
otherConfig->timeout = devConfig.timeout;
|
||||
otherConfig->pwd = devConfig.pwd;
|
||||
otherConfig->keyFile = devConfig.keyFile;
|
||||
otherConfig->server.authType = devConfig.server.authType;
|
||||
otherConfig->server.timeout = devConfig.server.timeout;
|
||||
otherConfig->server.pwd = devConfig.server.pwd;
|
||||
otherConfig->server.privateKeyFile = devConfig.server.privateKeyFile;
|
||||
|
||||
if (devConfig.authentication == MaemoDeviceConfig::Password)
|
||||
if (devConfig.server.authType == Core::SshServerInfo::AuthByPwd)
|
||||
m_ui->passwordButton->setChecked(true);
|
||||
else
|
||||
m_ui->keyButton->setChecked(true);
|
||||
m_ui->detailsWidget->setEnabled(true);
|
||||
m_nameValidator->setDisplayName(devConfig.name);
|
||||
m_ui->timeoutSpinBox->setValue(devConfig.timeout);
|
||||
m_ui->timeoutSpinBox->setValue(devConfig.server.timeout);
|
||||
fillInValues();
|
||||
}
|
||||
|
||||
void MaemoSettingsWidget::fillInValues()
|
||||
{
|
||||
m_ui->nameLineEdit->setText(currentConfig().name);
|
||||
m_ui->hostLineEdit->setText(currentConfig().host);
|
||||
m_ui->sshPortSpinBox->setValue(currentConfig().sshPort);
|
||||
m_ui->hostLineEdit->setText(currentConfig().server.host);
|
||||
m_ui->sshPortSpinBox->setValue(currentConfig().server.port);
|
||||
m_ui->gdbServerPortSpinBox->setValue(currentConfig().gdbServerPort);
|
||||
m_ui->timeoutSpinBox->setValue(currentConfig().timeout);
|
||||
m_ui->userLineEdit->setText(currentConfig().uname);
|
||||
m_ui->pwdLineEdit->setText(currentConfig().pwd);
|
||||
m_ui->keyFileLineEdit->setPath(currentConfig().keyFile);
|
||||
m_ui->timeoutSpinBox->setValue(currentConfig().server.timeout);
|
||||
m_ui->userLineEdit->setText(currentConfig().server.uname);
|
||||
m_ui->pwdLineEdit->setText(currentConfig().server.pwd);
|
||||
m_ui->keyFileLineEdit->setPath(currentConfig().server.privateKeyFile);
|
||||
|
||||
const bool isSimulator
|
||||
= currentConfig().type == MaemoDeviceConfig::Simulator;
|
||||
@@ -269,9 +269,8 @@ void MaemoSettingsWidget::deviceTypeChanged()
|
||||
void MaemoSettingsWidget::authenticationTypeChanged()
|
||||
{
|
||||
const bool usePassword = m_ui->passwordButton->isChecked();
|
||||
currentConfig().authentication = usePassword
|
||||
? MaemoDeviceConfig::Password
|
||||
: MaemoDeviceConfig::Key;
|
||||
currentConfig().server.authType
|
||||
= usePassword ? Core::SshServerInfo::AuthByPwd : Core::SshServerInfo::AuthByKey;
|
||||
m_ui->pwdLineEdit->setEnabled(usePassword);
|
||||
m_ui->passwordLabel->setEnabled(usePassword);
|
||||
m_ui->keyFileLineEdit->setEnabled(!usePassword);
|
||||
@@ -280,12 +279,12 @@ void MaemoSettingsWidget::authenticationTypeChanged()
|
||||
|
||||
void MaemoSettingsWidget::hostNameEditingFinished()
|
||||
{
|
||||
currentConfig().host = m_ui->hostLineEdit->text();
|
||||
currentConfig().server.host = m_ui->hostLineEdit->text();
|
||||
}
|
||||
|
||||
void MaemoSettingsWidget::sshPortEditingFinished()
|
||||
{
|
||||
currentConfig().sshPort = m_ui->sshPortSpinBox->value();
|
||||
currentConfig().server.port = m_ui->sshPortSpinBox->value();
|
||||
}
|
||||
|
||||
void MaemoSettingsWidget::gdbServerPortEditingFinished()
|
||||
@@ -295,22 +294,22 @@ void MaemoSettingsWidget::gdbServerPortEditingFinished()
|
||||
|
||||
void MaemoSettingsWidget::timeoutEditingFinished()
|
||||
{
|
||||
currentConfig().timeout = m_ui->timeoutSpinBox->value();
|
||||
currentConfig().server.timeout = m_ui->timeoutSpinBox->value();
|
||||
}
|
||||
|
||||
void MaemoSettingsWidget::userNameEditingFinished()
|
||||
{
|
||||
currentConfig().uname = m_ui->userLineEdit->text();
|
||||
currentConfig().server.uname = m_ui->userLineEdit->text();
|
||||
}
|
||||
|
||||
void MaemoSettingsWidget::passwordEditingFinished()
|
||||
{
|
||||
currentConfig().pwd = m_ui->pwdLineEdit->text();
|
||||
currentConfig().server.pwd = m_ui->pwdLineEdit->text();
|
||||
}
|
||||
|
||||
void MaemoSettingsWidget::keyFileEditingFinished()
|
||||
{
|
||||
currentConfig().keyFile = m_ui->keyFileLineEdit->path();
|
||||
currentConfig().server.privateKeyFile = m_ui->keyFileLineEdit->path();
|
||||
}
|
||||
|
||||
void MaemoSettingsWidget::testConfig()
|
||||
@@ -322,18 +321,9 @@ void MaemoSettingsWidget::testConfig()
|
||||
void MaemoSettingsWidget::showGenerateSshKeyDialog()
|
||||
{
|
||||
MaemoSshConfigDialog dialog(this);
|
||||
connect(&dialog, SIGNAL(publicKeyGenerated(QString)), this,
|
||||
SLOT(setPublicKey(QString)));
|
||||
connect(&dialog, SIGNAL(privateKeyGenerated(QString)), this,
|
||||
SLOT(setPrivateKey(QString)));
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void MaemoSettingsWidget::setPublicKey(const QString &path)
|
||||
{
|
||||
m_publicKeyFileName = path;
|
||||
}
|
||||
|
||||
void MaemoSettingsWidget::setPrivateKey(const QString &path)
|
||||
{
|
||||
m_ui->keyFileLineEdit->setPath(path);
|
||||
@@ -345,30 +335,29 @@ void MaemoSettingsWidget::deployKey()
|
||||
if (m_keyDeployer)
|
||||
return;
|
||||
|
||||
if (!QFileInfo(m_publicKeyFileName).exists()) {
|
||||
const QString &dir = QFileInfo(currentConfig().keyFile).path();
|
||||
m_publicKeyFileName = QFileDialog::getOpenFileName(this,
|
||||
tr("Choose public key file"), dir,
|
||||
tr("Public Key Files(*.pub);;All Files (*)"));
|
||||
}
|
||||
|
||||
if (m_publicKeyFileName.isEmpty())
|
||||
const QString &dir
|
||||
= QFileInfo(currentConfig().server.privateKeyFile).path();
|
||||
QString publicKeyFileName = QFileDialog::getOpenFileName(this,
|
||||
tr("Choose public key file"), dir,
|
||||
tr("Public Key Files(*.pub);;All Files (*)"));
|
||||
if (publicKeyFileName.isEmpty())
|
||||
return;
|
||||
QFile keyFile(m_publicKeyFileName);
|
||||
|
||||
QFile keyFile(publicKeyFileName);
|
||||
QByteArray key;
|
||||
const bool keyFileAccessible = keyFile.open(QIODevice::ReadOnly);
|
||||
if (keyFileAccessible)
|
||||
key = keyFile.readAll();
|
||||
if (!keyFileAccessible || keyFile.error() != QFile::NoError) {
|
||||
QMessageBox::critical(this, tr("Deployment Failed"),
|
||||
tr("Could not read public key file '%1'.").arg(m_publicKeyFileName));
|
||||
tr("Could not read public key file '%1'.").arg(publicKeyFileName));
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui->deployKeyButton->disconnect();
|
||||
const QString command = QLatin1String("test -d .ssh || mkdir .ssh && echo '")
|
||||
+ key + QLatin1String("' >> .ssh/authorized_keys");
|
||||
m_keyDeployer = new MaemoSshRunner(currentConfig(), command);
|
||||
m_keyDeployer = new MaemoSshRunner(currentConfig().server, command);
|
||||
connect(m_keyDeployer, SIGNAL(finished()),
|
||||
this, SLOT(handleDeployThreadFinished()));
|
||||
m_ui->deployKeyButton->setText(tr("Stop deploying"));
|
||||
@@ -399,7 +388,7 @@ void MaemoSettingsWidget::stopDeploying()
|
||||
m_keyDeployer->stop();
|
||||
delete m_keyDeployer;
|
||||
m_keyDeployer = 0;
|
||||
m_ui->deployKeyButton->setText(tr("Deploy Key ..."));
|
||||
m_ui->deployKeyButton->setText(tr("Deploy Public Key ..."));
|
||||
connect(m_ui->deployKeyButton, SIGNAL(clicked()), this, SLOT(deployKey()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ private slots:
|
||||
void testConfig();
|
||||
|
||||
void showGenerateSshKeyDialog();
|
||||
void setPublicKey(const QString &path);
|
||||
void setPrivateKey(const QString &path);
|
||||
|
||||
// For key deploying.
|
||||
@@ -104,7 +103,6 @@ private:
|
||||
MaemoDeviceConfig m_lastConfigSim;
|
||||
NameValidator * const m_nameValidator;
|
||||
MaemoSshRunner *m_keyDeployer;
|
||||
QString m_publicKeyFileName;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>563</width>
|
||||
<width>596</width>
|
||||
<height>336</height>
|
||||
</rect>
|
||||
</property>
|
||||
@@ -335,7 +335,7 @@
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Deploy public Key ...</string>
|
||||
<string>Deploy Public Key ...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -35,23 +35,23 @@
|
||||
#include "maemosshconfigdialog.h"
|
||||
|
||||
#include "maemodeviceconfigurations.h"
|
||||
#include "ne7sshobject.h"
|
||||
|
||||
#include <ne7ssh.h>
|
||||
#include <coreplugin/ssh/sshkeygenerator.h>
|
||||
|
||||
#include <QtCore/QDir>
|
||||
|
||||
#include <QtNetwork/QHostInfo>
|
||||
|
||||
#include <QtGui/QApplication>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtGui/QFileDialog>
|
||||
#include <QtGui/QMessageBox>
|
||||
#include <QtNetwork/QHostInfo>
|
||||
|
||||
|
||||
using namespace Qt4ProjectManager::Internal;
|
||||
|
||||
MaemoSshConfigDialog::MaemoSshConfigDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, home(QDesktopServices::storageLocation(QDesktopServices::HomeLocation))
|
||||
, m_keyGenerator(new Core::SshKeyGenerator)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
@@ -75,26 +75,22 @@ void MaemoSshConfigDialog::slotToggled()
|
||||
|
||||
void MaemoSshConfigDialog::generateSshKey()
|
||||
{
|
||||
algorithm = m_ui.rsa->isChecked() ? "rsa" : "dsa";
|
||||
tmpKey = QDir::tempPath().append(QLatin1Char('/') + algorithm).toUtf8();
|
||||
const Core::SshKeyGenerator::KeyType keyType = m_ui.rsa->isChecked()
|
||||
? Core::SshKeyGenerator::Rsa
|
||||
: Core::SshKeyGenerator::Dsa;
|
||||
|
||||
QByteArray userId = QString(home.mid(home.lastIndexOf(QLatin1Char('/')) + 1)
|
||||
+ QLatin1Char('@') + QHostInfo::localHostName()).toUtf8();
|
||||
|
||||
QFile::remove(tmpKey);
|
||||
QFile::remove(tmpKey + ".pub");
|
||||
|
||||
QApplication::setOverrideCursor(Qt::BusyCursor);
|
||||
|
||||
QSharedPointer<ne7ssh> ssh = Ne7SshObject::instance()->get();
|
||||
if (ssh->generateKeyPair(algorithm, userId, tmpKey, tmpKey + ".pub",
|
||||
m_ui.comboBox->currentText().toUShort())) {
|
||||
QFile file(tmpKey + ".pub");
|
||||
if (file.open(QIODevice::ReadOnly))
|
||||
m_ui.plainTextEdit->setPlainText(file.readAll());
|
||||
if (m_keyGenerator->generateKeys(keyType, userId,
|
||||
m_ui.comboBox->currentText().toUShort())) {
|
||||
m_ui.plainTextEdit->setPlainText(m_keyGenerator->publicKey());
|
||||
m_ui.savePublicKey->setEnabled(true);
|
||||
m_ui.savePrivateKey->setEnabled(true);
|
||||
} else {
|
||||
m_ui.plainTextEdit->setPlainText(tr("Could not create SSH key pair."));
|
||||
m_ui.plainTextEdit->setPlainText(m_keyGenerator->error());
|
||||
}
|
||||
|
||||
QApplication::restoreOverrideCursor();
|
||||
@@ -102,18 +98,12 @@ void MaemoSshConfigDialog::generateSshKey()
|
||||
|
||||
void MaemoSshConfigDialog::savePublicKey()
|
||||
{
|
||||
checkSshDir();
|
||||
copyFile(QFileDialog::getSaveFileName(this, tr("Choose folder to save "
|
||||
"public key file"), home + QString::fromLatin1("/.ssh/id_%1.pub")
|
||||
.arg(algorithm.constData())), true);
|
||||
saveKey(true);
|
||||
}
|
||||
|
||||
void MaemoSshConfigDialog::savePrivateKey()
|
||||
{
|
||||
checkSshDir();
|
||||
copyFile(QFileDialog::getSaveFileName(this, tr("Choose folder to save "
|
||||
"private key file"), home + QString::fromLatin1("/.ssh/id_%1")
|
||||
.arg(algorithm.constData())), false);
|
||||
saveKey(false);
|
||||
}
|
||||
|
||||
void MaemoSshConfigDialog::checkSshDir()
|
||||
@@ -123,15 +113,31 @@ void MaemoSshConfigDialog::checkSshDir()
|
||||
dir.mkpath(home + QString::fromLatin1("/.ssh"));
|
||||
}
|
||||
|
||||
void MaemoSshConfigDialog::copyFile(const QString &file, bool pubKey)
|
||||
void MaemoSshConfigDialog::saveKey(bool publicKey)
|
||||
{
|
||||
if (!file.isEmpty()) {
|
||||
if (!QFile::exists(file) || QFile::remove(file)) {
|
||||
QFile(tmpKey + (pubKey ? ".pub" : "")).copy(file);
|
||||
if (pubKey)
|
||||
emit publicKeyGenerated(file);
|
||||
else
|
||||
emit privateKeyGenerated(file);
|
||||
}
|
||||
checkSshDir();
|
||||
const QString suggestedTypeSuffix =
|
||||
m_keyGenerator->type() == Core::SshKeyGenerator::Rsa ? "rsa" : "dsa";
|
||||
const QString suggestedName = home + QString::fromLatin1("/.ssh/id_%1%2")
|
||||
.arg(suggestedTypeSuffix).arg(publicKey ? ".pub" : "");
|
||||
const QString dlgTitle
|
||||
= publicKey ? tr("Save public key file") : tr("Save private key file");
|
||||
const QString fileName
|
||||
= QFileDialog::getSaveFileName(this, dlgTitle, suggestedName);
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
QFile file(fileName);
|
||||
const bool canOpen = file.open(QIODevice::WriteOnly);
|
||||
if (canOpen)
|
||||
file.write(publicKey
|
||||
? m_keyGenerator->publicKey().toUtf8()
|
||||
: m_keyGenerator->privateKey().toUtf8());
|
||||
if (!canOpen || file.error() != QFile::NoError) {
|
||||
QMessageBox::critical(this, tr("Error writing file"),
|
||||
tr("Could not write file '%1':\n %2")
|
||||
.arg(fileName, file.errorString()));
|
||||
} else if (!publicKey) {
|
||||
emit privateKeyGenerated(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,13 @@
|
||||
|
||||
#include "ui_maemosshconfigdialog.h"
|
||||
|
||||
#include <QtCore/QScopedPointer>
|
||||
#include <QtGui/QDialog>
|
||||
|
||||
namespace Core {
|
||||
class SshKeyGenerator;
|
||||
}
|
||||
|
||||
namespace Qt4ProjectManager {
|
||||
namespace Internal {
|
||||
|
||||
@@ -46,11 +51,10 @@ class MaemoSshConfigDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MaemoSshConfigDialog(QWidget *parent = 0);
|
||||
~MaemoSshConfigDialog();
|
||||
MaemoSshConfigDialog(QWidget *parent = 0);
|
||||
~MaemoSshConfigDialog();
|
||||
|
||||
signals:
|
||||
void publicKeyGenerated(const QString &path);
|
||||
void privateKeyGenerated(const QString &path);
|
||||
|
||||
private slots:
|
||||
@@ -61,12 +65,11 @@ private slots:
|
||||
|
||||
private:
|
||||
void checkSshDir();
|
||||
void copyFile(const QString &file, bool pubKey);
|
||||
void saveKey(bool publicKey);
|
||||
|
||||
private:
|
||||
QString home;
|
||||
QByteArray tmpKey;
|
||||
QByteArray algorithm;
|
||||
QScopedPointer<Core::SshKeyGenerator> m_keyGenerator;
|
||||
Ui::MaemoSshConfigDialog m_ui;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the Qt Creator.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** No Commercial Usage
|
||||
** This file contains pre-release code and may not be distributed.
|
||||
** You may use this file in accordance with the terms and conditions
|
||||
** contained in the Technology Preview License Agreement accompanying
|
||||
** this package.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, 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.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at qt-info@nokia.com.
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "maemosshconnection.h"
|
||||
|
||||
#include "maemodeviceconfigurations.h"
|
||||
#include "ne7sshobject.h"
|
||||
|
||||
#include <ne7ssh.h>
|
||||
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QScopedPointer>
|
||||
#include <QtCore/QStringBuilder>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace Qt4ProjectManager {
|
||||
namespace Internal {
|
||||
namespace {
|
||||
char *alloc(size_t n)
|
||||
{
|
||||
return new char[n];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Which encoding to use for file names? Unicode? Latin1? ASCII?
|
||||
|
||||
MaemoSshConnection::MaemoSshConnection(const MaemoDeviceConfig &devConf,
|
||||
bool shell)
|
||||
: ssh(Ne7SshObject::instance()->get()),
|
||||
m_channel(-1),
|
||||
m_stopRequested(false)
|
||||
{
|
||||
const QString *authString;
|
||||
int (ne7ssh::*connFunc)(const char *, int, const char *, const char *, bool, int);
|
||||
if (devConf.authentication == MaemoDeviceConfig::Password) {
|
||||
authString = &devConf.pwd;
|
||||
connFunc = &ne7ssh::connectWithPassword;
|
||||
} else {
|
||||
authString = &devConf.keyFile;
|
||||
connFunc = &ne7ssh::connectWithKey;
|
||||
}
|
||||
m_channel = (ssh.data()->*connFunc)(devConf.host.toLatin1(), devConf.sshPort,
|
||||
devConf.uname.toAscii(), authString->toLatin1(), shell, devConf.timeout);
|
||||
if (m_channel == -1)
|
||||
throw MaemoSshException(tr("Could not connect to host"));
|
||||
}
|
||||
|
||||
MaemoSshConnection::~MaemoSshConnection()
|
||||
{
|
||||
qDebug("%s", Q_FUNC_INFO);
|
||||
ssh->close(m_channel);
|
||||
}
|
||||
|
||||
const char *MaemoSshConnection::lastError()
|
||||
{
|
||||
return ssh->errors()->pop(channel());
|
||||
}
|
||||
|
||||
void MaemoSshConnection::stop()
|
||||
{
|
||||
m_stopRequested = true;
|
||||
}
|
||||
|
||||
MaemoInteractiveSshConnection::MaemoInteractiveSshConnection(const MaemoDeviceConfig &devConf)
|
||||
: MaemoSshConnection(devConf, true)
|
||||
{
|
||||
strcpy(m_prompt, devConf.uname == QLatin1String("root") ? "# " : "$ ");
|
||||
if (!ssh->waitFor(channel(), m_prompt, devConf.timeout)) {
|
||||
QScopedPointer<char, QScopedPointerArrayDeleter<char> >
|
||||
shellString(ssh->readAndReset(channel(), alloc));
|
||||
if (!shellString.data()) {
|
||||
const QString error
|
||||
= tr("Could not start remote shell: %1").arg(lastError());
|
||||
throw MaemoSshException(error);
|
||||
} else {
|
||||
const int length = strlen(shellString.data());
|
||||
strcpy(m_prompt, shellString.data() + length - qMin(2, length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaemoInteractiveSshConnection::~MaemoInteractiveSshConnection()
|
||||
{
|
||||
ssh->send("exit\n", channel());
|
||||
ssh->waitFor(channel(), m_prompt, 1);
|
||||
}
|
||||
|
||||
void MaemoInteractiveSshConnection::runCommand(const QString &command)
|
||||
{
|
||||
/*
|
||||
* We don't have access to the remote process management, so we
|
||||
* try to track the lifetime of the process by adding a second command
|
||||
* that prints a rare character. When it occurs for the second time (the
|
||||
* first one is the echo from the remote terminal), we assume the
|
||||
* process has finished. If anyone actually prints this special character
|
||||
* in their application, they are out of luck.
|
||||
*/
|
||||
const QString endMarker(QChar(0x13a0));
|
||||
const int endMarkerLen = strlen(endMarker.toUtf8());
|
||||
|
||||
const QString finalCommand
|
||||
= command + QLatin1String(";echo ") + endMarker + QLatin1Char('\n');
|
||||
if (!ssh->send(finalCommand.toUtf8().data(), channel())) {
|
||||
throw MaemoSshException(tr("Error running command: %1")
|
||||
.arg(lastError()));
|
||||
}
|
||||
|
||||
int endMarkerCount = 0;
|
||||
do {
|
||||
ssh->waitFor(channel(), endMarker.toUtf8(), 1); // TODO: Hack net7 to get rid of busy loop.
|
||||
const char * const error = lastError();
|
||||
if (error)
|
||||
throw MaemoSshException(tr("SSH error: %1").arg(error));
|
||||
QScopedPointer<char, QScopedPointerArrayDeleter<char> >
|
||||
output(ssh->readAndReset(channel(), alloc));
|
||||
|
||||
/*
|
||||
* The output the user should see is everything after the first
|
||||
* and before the last occurrence of our marker string.
|
||||
*/
|
||||
if (output.data()) {
|
||||
const char *firstCharToEmit;
|
||||
int charsToEmitCount;
|
||||
const char * const endMarkerPos
|
||||
= strstr(output.data(), endMarker.toUtf8());
|
||||
if (endMarkerPos) {
|
||||
if (endMarkerCount++ == 0) {
|
||||
emit remoteOutput(QLatin1String("========== Remote output starts now. ==========\n"));
|
||||
firstCharToEmit = endMarkerPos + endMarkerLen + 1;
|
||||
const char * const endMarkerPos2
|
||||
= strstr(firstCharToEmit, endMarker.toUtf8());
|
||||
if (endMarkerPos2) {
|
||||
++ endMarkerCount;
|
||||
charsToEmitCount = endMarkerPos2 - firstCharToEmit;
|
||||
} else {
|
||||
charsToEmitCount = -1;
|
||||
}
|
||||
} else {
|
||||
firstCharToEmit = output.data();
|
||||
charsToEmitCount = endMarkerPos - output.data();
|
||||
}
|
||||
} else {
|
||||
if (endMarkerCount == 0) {
|
||||
charsToEmitCount = 0;
|
||||
} else {
|
||||
firstCharToEmit = output.data();
|
||||
charsToEmitCount = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (charsToEmitCount != 0)
|
||||
emit remoteOutput(QString::fromUtf8(firstCharToEmit, charsToEmitCount));
|
||||
}
|
||||
} while (endMarkerCount < 2 && !stopRequested());
|
||||
emit remoteOutput(QLatin1String("========== Remote output ends now. ==========\n"));
|
||||
}
|
||||
|
||||
MaemoInteractiveSshConnection::Ptr MaemoInteractiveSshConnection::create(const MaemoDeviceConfig &devConf)
|
||||
{
|
||||
return Ptr(new MaemoInteractiveSshConnection(devConf));
|
||||
}
|
||||
|
||||
MaemoSftpConnection::MaemoSftpConnection(const MaemoDeviceConfig &devConf)
|
||||
: MaemoSshConnection(devConf, false),
|
||||
sftp(new Ne7SftpSubsystem)
|
||||
{
|
||||
if (!ssh->initSftp(*sftp, channel()) || !sftp->setTimeout(devConf.timeout))
|
||||
throw MaemoSshException(tr("Error setting up SFTP subsystem: %1")
|
||||
.arg(lastError()));
|
||||
}
|
||||
|
||||
MaemoSftpConnection::~MaemoSftpConnection()
|
||||
{
|
||||
}
|
||||
|
||||
class FileManager
|
||||
{
|
||||
public:
|
||||
FileManager(const QString &filePath)
|
||||
: m_file(fopen(filePath.toLatin1().data(), "rb")) {}
|
||||
~FileManager() { if (m_file) fclose(m_file); }
|
||||
FILE *file() const { return m_file; }
|
||||
private:
|
||||
FILE * const m_file;
|
||||
};
|
||||
|
||||
void MaemoSftpConnection::transferFiles(const QList<SshDeploySpec> &deploySpecs)
|
||||
{
|
||||
for (int i = 0; i < deploySpecs.count() && !stopRequested(); ++i) {
|
||||
const SshDeploySpec &deploySpec = deploySpecs.at(i);
|
||||
const QString &curSrcFile = deploySpec.srcFilePath();
|
||||
FileManager fileMgr(curSrcFile);
|
||||
if (!fileMgr.file())
|
||||
throw MaemoSshException(tr("Could not open file '%1'").arg(curSrcFile));
|
||||
const QString &curTgtFile = deploySpec.tgtFilePath();
|
||||
|
||||
// TODO: Is the mkdir() method recursive? If not, we have to
|
||||
// introduce a recursive version ourselves.
|
||||
if (deploySpec.mkdir()) {
|
||||
const QString &dir = QFileInfo(curTgtFile).path();
|
||||
sftp->mkdir(dir.toLatin1().data());
|
||||
}
|
||||
|
||||
qDebug("Deploying file %s to %s.", qPrintable(curSrcFile), qPrintable(curTgtFile));
|
||||
|
||||
if (!sftp->put(fileMgr.file(), curTgtFile.toLatin1().data())) {
|
||||
const QString &error = tr("Could not copy local file '%1' "
|
||||
"to remote file '%2': %3").arg(curSrcFile, curTgtFile)
|
||||
.arg(lastError());
|
||||
throw MaemoSshException(error);
|
||||
}
|
||||
emit fileCopied(curSrcFile);
|
||||
}
|
||||
}
|
||||
|
||||
MaemoSftpConnection::Ptr MaemoSftpConnection::create(const MaemoDeviceConfig &devConf)
|
||||
{
|
||||
return Ptr(new MaemoSftpConnection(devConf));
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Qt4ProjectManager
|
||||
@@ -1,152 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the Qt Creator.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** No Commercial Usage
|
||||
** This file contains pre-release code and may not be distributed.
|
||||
** You may use this file in accordance with the terms and conditions
|
||||
** contained in the Technology Preview License Agreement accompanying
|
||||
** this package.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, 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.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at qt-info@nokia.com.
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef MAEMOSSHCONNECTION_H
|
||||
#define MAEMOSSHCONNECTION_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QScopedPointer>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QString>
|
||||
|
||||
class ne7ssh;
|
||||
class Ne7SftpSubsystem;
|
||||
|
||||
namespace Qt4ProjectManager {
|
||||
namespace Internal {
|
||||
|
||||
class MaemoDeviceConfig;
|
||||
|
||||
class MaemoSshException
|
||||
{
|
||||
public:
|
||||
MaemoSshException(const QString &error) : m_error(error) {}
|
||||
const QString &error() const { return m_error; }
|
||||
private:
|
||||
const QString m_error;
|
||||
};
|
||||
|
||||
class MaemoSshConnection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(MaemoSshConnection)
|
||||
public:
|
||||
typedef QSharedPointer<MaemoSshConnection> Ptr;
|
||||
|
||||
void stop();
|
||||
virtual ~MaemoSshConnection();
|
||||
|
||||
protected:
|
||||
MaemoSshConnection(const MaemoDeviceConfig &devConf, bool shell);
|
||||
int channel() const { return m_channel; }
|
||||
bool stopRequested() const {return m_stopRequested; }
|
||||
const char *lastError();
|
||||
|
||||
QSharedPointer<ne7ssh> ssh;
|
||||
private:
|
||||
int m_channel;
|
||||
volatile bool m_stopRequested;
|
||||
};
|
||||
|
||||
class MaemoInteractiveSshConnection : public MaemoSshConnection
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(MaemoInteractiveSshConnection)
|
||||
public:
|
||||
typedef QSharedPointer<MaemoInteractiveSshConnection> Ptr;
|
||||
|
||||
static Ptr create(const MaemoDeviceConfig &devConf);
|
||||
void runCommand(const QString &command);
|
||||
virtual ~MaemoInteractiveSshConnection();
|
||||
|
||||
signals:
|
||||
void remoteOutput(const QString &output);
|
||||
|
||||
private:
|
||||
MaemoInteractiveSshConnection(const MaemoDeviceConfig &devConf);
|
||||
|
||||
char m_prompt[3];
|
||||
};
|
||||
|
||||
|
||||
class SshDeploySpec
|
||||
{
|
||||
public:
|
||||
SshDeploySpec(const QString &srcFilePath, const QString &tgtFilePath,
|
||||
bool mkdir = false)
|
||||
: m_srcFilePath(srcFilePath), m_tgtFilePath(tgtFilePath), m_mkdir(mkdir)
|
||||
{
|
||||
}
|
||||
|
||||
QString srcFilePath() const { return m_srcFilePath; }
|
||||
QString tgtFilePath() const { return m_tgtFilePath; }
|
||||
bool mkdir() const { return m_mkdir; }
|
||||
|
||||
private:
|
||||
QString m_srcFilePath;
|
||||
QString m_tgtFilePath;
|
||||
bool m_mkdir;
|
||||
};
|
||||
|
||||
class MaemoSftpConnection : public MaemoSshConnection
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(MaemoSftpConnection)
|
||||
public:
|
||||
typedef QSharedPointer<MaemoSftpConnection> Ptr;
|
||||
|
||||
static Ptr create(const MaemoDeviceConfig &devConf);
|
||||
void transferFiles(const QList<SshDeploySpec> &deploySpecs);
|
||||
virtual ~MaemoSftpConnection();
|
||||
|
||||
signals:
|
||||
void fileCopied(const QString &filePath);
|
||||
|
||||
private:
|
||||
MaemoSftpConnection(const MaemoDeviceConfig &devConf);
|
||||
|
||||
QScopedPointer<Ne7SftpSubsystem> sftp;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Qt4ProjectManager
|
||||
|
||||
#endif // MAEMOSSHCONNECTION_H
|
||||
@@ -41,86 +41,164 @@
|
||||
|
||||
#include "maemosshthread.h"
|
||||
|
||||
#include <exception>
|
||||
|
||||
namespace Qt4ProjectManager {
|
||||
namespace Internal {
|
||||
|
||||
MaemoSshThread::MaemoSshThread(const MaemoDeviceConfig &devConf)
|
||||
: m_stopRequested(false), m_devConf(devConf)
|
||||
template <class SshConnection> MaemoSshThread<SshConnection>::MaemoSshThread(const Core::SshServerInfo &server)
|
||||
: m_server(server), m_stopRequested(false)
|
||||
{
|
||||
}
|
||||
|
||||
MaemoSshThread::~MaemoSshThread()
|
||||
template <class SshConnection> MaemoSshThread<SshConnection>::~MaemoSshThread()
|
||||
{
|
||||
stop();
|
||||
wait();
|
||||
}
|
||||
|
||||
void MaemoSshThread::run()
|
||||
template <class SshConnection> void MaemoSshThread<SshConnection>::run()
|
||||
{
|
||||
try {
|
||||
if (!m_stopRequested)
|
||||
runInternal();
|
||||
} catch (const MaemoSshException &e) {
|
||||
m_error = e.error();
|
||||
} catch (const std::exception &e) {
|
||||
// Should in theory not be necessary, but Net7 leaks Botan exceptions.
|
||||
m_error = tr("Error in cryptography backend: %1").arg(QLatin1String(e.what()));
|
||||
}
|
||||
if (m_stopRequested)
|
||||
return;
|
||||
|
||||
if (!runInternal())
|
||||
m_error = m_connection->error();
|
||||
}
|
||||
|
||||
void MaemoSshThread::stop()
|
||||
template<class SshConnection> void MaemoSshThread<SshConnection>::stop()
|
||||
{
|
||||
m_mutex.lock();
|
||||
m_stopRequested = true;
|
||||
m_waitCond.wakeAll();
|
||||
const bool hasConnection = !m_connection.isNull();
|
||||
m_mutex.unlock();
|
||||
if (hasConnection)
|
||||
m_connection->stop();
|
||||
m_connection->quit();
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
template <class Conn> typename Conn::Ptr MaemoSshThread::createConnection()
|
||||
template<class SshConnection> void MaemoSshThread<SshConnection>::waitForStop()
|
||||
{
|
||||
typename Conn::Ptr connection = Conn::create(m_devConf);
|
||||
m_mutex.lock();
|
||||
while (!stopRequested())
|
||||
m_waitCond.wait(&m_mutex);
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
template<class SshConnection> void MaemoSshThread<SshConnection>::createConnection()
|
||||
{
|
||||
typename SshConnection::Ptr connection = SshConnection::create(m_server);
|
||||
m_mutex.lock();
|
||||
m_connection = connection;
|
||||
m_mutex.unlock();
|
||||
return connection;
|
||||
}
|
||||
|
||||
MaemoSshRunner::MaemoSshRunner(const MaemoDeviceConfig &devConf,
|
||||
MaemoSshRunner::MaemoSshRunner(const Core::SshServerInfo &server,
|
||||
const QString &command)
|
||||
: MaemoSshThread(devConf), m_command(command)
|
||||
: MaemoSshThread<Core::InteractiveSshConnection>(server),
|
||||
m_command(command)
|
||||
{
|
||||
m_prompt = server.uname == QLatin1String("root") ? "#" : "$";
|
||||
}
|
||||
|
||||
void MaemoSshRunner::runInternal()
|
||||
bool MaemoSshRunner::runInternal()
|
||||
{
|
||||
MaemoInteractiveSshConnection::Ptr connection
|
||||
= createConnection<MaemoInteractiveSshConnection>();
|
||||
createConnection();
|
||||
connect(m_connection.data(), SIGNAL(remoteOutput(QByteArray)),
|
||||
this, SLOT(handleRemoteOutput(QByteArray)));
|
||||
m_endMarkerCount = 0;
|
||||
m_promptEncountered = false;
|
||||
if (!m_connection->start())
|
||||
return false;
|
||||
if (stopRequested())
|
||||
return true;
|
||||
|
||||
waitForStop();
|
||||
return !m_connection->hasError();
|
||||
}
|
||||
|
||||
void MaemoSshRunner::handleRemoteOutput(const QByteArray &output)
|
||||
{
|
||||
// Wait for a prompt before sending the command.
|
||||
if (!m_promptEncountered) {
|
||||
if (output.indexOf(m_prompt) != -1) {
|
||||
m_promptEncountered = true;
|
||||
|
||||
/*
|
||||
* We don't have access to the remote process management, so we
|
||||
* try to track the lifetime of the process by adding a second command
|
||||
* that prints a rare character. When it occurs for the second time (the
|
||||
* first one is the echo from the remote terminal), we assume the
|
||||
* process has finished. If anyone actually prints this special character
|
||||
* in their application, they are out of luck.
|
||||
*/
|
||||
const QString finalCommand = m_command + QLatin1String(";echo ")
|
||||
+ QString::fromUtf8(EndMarker) + QLatin1Char('\n');
|
||||
if (!m_connection->sendInput(finalCommand.toUtf8()))
|
||||
stop();
|
||||
}
|
||||
return;
|
||||
connect(connection.data(), SIGNAL(remoteOutput(QString)),
|
||||
this, SIGNAL(remoteOutput(QString)));
|
||||
connection->runCommand(m_command);
|
||||
}
|
||||
|
||||
/*
|
||||
* The output the user should see is everything after the first
|
||||
* and before the last occurrence of our marker string.
|
||||
* Note: We don't currently handle the case of an incomplete unicode
|
||||
* character being sent.
|
||||
*/
|
||||
int firstCharToEmit;
|
||||
int charsToEmitCount;
|
||||
const int endMarkerPos = output.indexOf(EndMarker);
|
||||
if (endMarkerPos != -1) {
|
||||
if (m_endMarkerCount++ == 0) {
|
||||
firstCharToEmit = endMarkerPos + EndMarker.count() + 1;
|
||||
int endMarkerPos2
|
||||
= output.indexOf(EndMarker, firstCharToEmit);
|
||||
if (endMarkerPos2 != -1) {
|
||||
++ m_endMarkerCount;
|
||||
charsToEmitCount = endMarkerPos2 - firstCharToEmit;
|
||||
} else {
|
||||
charsToEmitCount = -1;
|
||||
}
|
||||
} else {
|
||||
firstCharToEmit = 0;
|
||||
charsToEmitCount = endMarkerPos;
|
||||
}
|
||||
} else {
|
||||
if (m_endMarkerCount == 0) {
|
||||
charsToEmitCount = 0;
|
||||
} else {
|
||||
firstCharToEmit = 0;
|
||||
charsToEmitCount = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (charsToEmitCount != 0)
|
||||
emit remoteOutput(QString::fromUtf8(output.data() + firstCharToEmit,
|
||||
charsToEmitCount));
|
||||
if (m_endMarkerCount == 2)
|
||||
stop();
|
||||
}
|
||||
|
||||
MaemoSshDeployer::MaemoSshDeployer(const MaemoDeviceConfig &devConf,
|
||||
const QList<SshDeploySpec> &deploySpecs)
|
||||
: MaemoSshThread(devConf), m_deploySpecs(deploySpecs)
|
||||
const QByteArray MaemoSshRunner::EndMarker(QString(QChar(0x13a0)).toUtf8());
|
||||
|
||||
|
||||
MaemoSshDeployer::MaemoSshDeployer(const Core::SshServerInfo &server,
|
||||
const QList<Core::SftpTransferInfo> &deploySpecs)
|
||||
: MaemoSshThread<Core::SftpConnection>(server),
|
||||
m_deploySpecs(deploySpecs)
|
||||
{
|
||||
}
|
||||
|
||||
void MaemoSshDeployer::runInternal()
|
||||
bool MaemoSshDeployer::runInternal()
|
||||
{
|
||||
MaemoSftpConnection::Ptr connection
|
||||
= createConnection<MaemoSftpConnection>();
|
||||
createConnection();
|
||||
if (!m_connection->start())
|
||||
return false;
|
||||
if (stopRequested())
|
||||
return;
|
||||
connect(connection.data(), SIGNAL(fileCopied(QString)),
|
||||
return true;
|
||||
|
||||
connect(m_connection.data(), SIGNAL(fileCopied(QString)),
|
||||
this, SIGNAL(fileCopied(QString)));
|
||||
connection->transferFiles(m_deploySpecs);
|
||||
return m_connection->transferFiles(m_deploySpecs);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -43,18 +43,20 @@
|
||||
#define MAEMOSSHTHREAD_H
|
||||
|
||||
#include "maemodeviceconfigurations.h"
|
||||
#include "maemosshconnection.h"
|
||||
|
||||
#include <coreplugin/ssh/sshconnection.h>
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QWaitCondition>
|
||||
|
||||
namespace Qt4ProjectManager {
|
||||
namespace Internal {
|
||||
|
||||
class MaemoSshThread : public QThread
|
||||
template<class SshConnection> class MaemoSshThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(MaemoSshThread)
|
||||
public:
|
||||
QString error() const { return m_error; }
|
||||
@@ -64,53 +66,62 @@ public:
|
||||
~MaemoSshThread();
|
||||
|
||||
protected:
|
||||
MaemoSshThread(const MaemoDeviceConfig &devConf);
|
||||
template <class Conn> typename Conn::Ptr createConnection();
|
||||
MaemoSshThread(const Core::SshServerInfo &server);
|
||||
void createConnection();
|
||||
bool stopRequested() const { return m_stopRequested; }
|
||||
void waitForStop();
|
||||
|
||||
typename SshConnection::Ptr m_connection;
|
||||
|
||||
private:
|
||||
virtual void runInternal() = 0;
|
||||
virtual bool runInternal() = 0;
|
||||
|
||||
const Core::SshServerInfo m_server;
|
||||
bool m_stopRequested;
|
||||
QString m_error;
|
||||
QMutex m_mutex;
|
||||
const MaemoDeviceConfig m_devConf;
|
||||
MaemoSshConnection::Ptr m_connection;
|
||||
QWaitCondition m_waitCond;
|
||||
};
|
||||
|
||||
|
||||
class MaemoSshRunner : public MaemoSshThread
|
||||
class MaemoSshRunner : public MaemoSshThread<Core::InteractiveSshConnection>
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(MaemoSshRunner)
|
||||
public:
|
||||
MaemoSshRunner(const MaemoDeviceConfig &devConf, const QString &command);
|
||||
MaemoSshRunner(const Core::SshServerInfo &server, const QString &command);
|
||||
|
||||
signals:
|
||||
void remoteOutput(const QString &output);
|
||||
|
||||
private:
|
||||
virtual void runInternal();
|
||||
virtual bool runInternal();
|
||||
Q_SLOT void handleRemoteOutput(const QByteArray &output);
|
||||
|
||||
static const QByteArray EndMarker;
|
||||
|
||||
const QString m_command;
|
||||
const char *m_prompt;
|
||||
int m_endMarkerCount;
|
||||
bool m_promptEncountered;
|
||||
};
|
||||
|
||||
|
||||
class MaemoSshDeployer : public MaemoSshThread
|
||||
class MaemoSshDeployer : public MaemoSshThread<Core::SftpConnection>
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(MaemoSshDeployer)
|
||||
public:
|
||||
MaemoSshDeployer(const MaemoDeviceConfig &devConf,
|
||||
const QList<SshDeploySpec> &deploySpecs);
|
||||
MaemoSshDeployer(const Core::SshServerInfo &server,
|
||||
const QList<Core::SftpTransferInfo> &deploySpecs);
|
||||
|
||||
signals:
|
||||
void fileCopied(const QString &filePath);
|
||||
|
||||
private:
|
||||
virtual void runInternal();
|
||||
virtual bool runInternal();
|
||||
|
||||
const QList<SshDeploySpec> m_deploySpecs;
|
||||
const QList<Core::SftpTransferInfo> m_deploySpecs;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
INCLUDEPATH += $$PWD/../../../libs/3rdparty/botan/build
|
||||
INCLUDEPATH += $$PWD/../../../libs/3rdparty/net7ssh/src
|
||||
LIBS += -l$$qtLibraryTarget(Net7ssh) -l$$qtLibraryTarget(Botan)
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/maemoconfigtestdialog.h \
|
||||
$$PWD/maemoconstants.h \
|
||||
@@ -14,12 +10,10 @@ HEADERS += \
|
||||
$$PWD/maemosettingspage.h \
|
||||
$$PWD/maemosettingswidget.h \
|
||||
$$PWD/maemosshconfigdialog.h \
|
||||
$$PWD/maemosshconnection.h \
|
||||
$$PWD/maemosshthread.h \
|
||||
$$PWD/maemotoolchain.h \
|
||||
$$PWD/maemopackagecreationstep.h \
|
||||
$$PWD/maemopackagecreationfactory.h \
|
||||
$$PWD/ne7sshobject.h \
|
||||
$$PWD/maemopackagecreationwidget.h \
|
||||
$$PWD/maemopackagecontents.h
|
||||
|
||||
@@ -34,12 +28,10 @@ SOURCES += \
|
||||
$$PWD/maemosettingspage.cpp \
|
||||
$$PWD/maemosettingswidget.cpp \
|
||||
$$PWD/maemosshconfigdialog.cpp \
|
||||
$$PWD/maemosshconnection.cpp \
|
||||
$$PWD/maemosshthread.cpp \
|
||||
$$PWD/maemotoolchain.cpp \
|
||||
$$PWD/maemopackagecreationstep.cpp \
|
||||
$$PWD/maemopackagecreationfactory.cpp \
|
||||
$$PWD/ne7sshobject.cpp \
|
||||
$$PWD/maemopackagecreationwidget.cpp \
|
||||
$$PWD/maemopackagecontents.cpp
|
||||
|
||||
|
||||
Reference in New Issue
Block a user