diff options
author | Christian Kandeler <christian.kandeler@nokia.com> | 2012-05-18 10:49:35 +0200 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@nokia.com> | 2012-05-22 10:51:53 +0200 |
commit | 53a1087d1321758ab1dc0a6ad737c135249bd5e6 (patch) | |
tree | ab0f9596da64a1907690b324bd05416c585eaf8c /src/libs/ssh/sftpfilesystemmodel.cpp | |
parent | b9d9bb7ba85e3b3ea0e06f0876272c0e3bebd144 (diff) | |
download | qt-creator-53a1087d1321758ab1dc0a6ad737c135249bd5e6.tar.gz |
Move SSH support into a dedicated library.
It does not belong into libUtils, which is a collection of small
unrelated utility classes.
Task-number: QTCREATORBUG-7218
Change-Id: Id92b9f28678afec93e6f07166adfde6550f38072
Reviewed-by: Eike Ziller <eike.ziller@nokia.com>
Diffstat (limited to 'src/libs/ssh/sftpfilesystemmodel.cpp')
-rw-r--r-- | src/libs/ssh/sftpfilesystemmodel.cpp | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/src/libs/ssh/sftpfilesystemmodel.cpp b/src/libs/ssh/sftpfilesystemmodel.cpp new file mode 100644 index 0000000000..6d24c1b8c6 --- /dev/null +++ b/src/libs/ssh/sftpfilesystemmodel.cpp @@ -0,0 +1,386 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "sftpfilesystemmodel.h" + +#include "sftpchannel.h" +#include "sshconnection.h" +#include "sshconnectionmanager.h" + +#include <QFileInfo> +#include <QHash> +#include <QIcon> +#include <QList> +#include <QString> + +namespace QSsh { +namespace Internal { +namespace { + +class SftpDirNode; +class SftpFileNode +{ +public: + SftpFileNode() : parent(0) { } + virtual ~SftpFileNode() { } + + QString path; + SftpFileInfo fileInfo; + SftpDirNode *parent; +}; + +class SftpDirNode : public SftpFileNode +{ +public: + SftpDirNode() : lsState(LsNotYetCalled) { } + ~SftpDirNode() { qDeleteAll(children); } + + enum { LsNotYetCalled, LsRunning, LsFinished } lsState; + QList<SftpFileNode *> children; +}; + +typedef QHash<SftpJobId, SftpDirNode *> DirNodeHash; + +SftpFileNode *indexToFileNode(const QModelIndex &index) +{ + return static_cast<SftpFileNode *>(index.internalPointer()); +} + +SftpDirNode *indexToDirNode(const QModelIndex &index) +{ + SftpFileNode * const fileNode = indexToFileNode(index); + QSSH_ASSERT(fileNode); + return dynamic_cast<SftpDirNode *>(fileNode); +} + +} // anonymous namespace + +class SftpFileSystemModelPrivate +{ +public: + SshConnection::Ptr sshConnection; + SftpChannel::Ptr sftpChannel; + QString rootDirectory; + SftpFileNode *rootNode; + SftpJobId statJobId; + DirNodeHash lsOps; + QList<SftpJobId> externalJobs; +}; +} // namespace Internal + +using namespace Internal; + +SftpFileSystemModel::SftpFileSystemModel(QObject *parent) + : QAbstractItemModel(parent), d(new SftpFileSystemModelPrivate) +{ + d->rootDirectory = QLatin1String("/"); + d->rootNode = 0; + d->statJobId = SftpInvalidJob; +} + +SftpFileSystemModel::~SftpFileSystemModel() +{ + shutDown(); + delete d; +} + +void SftpFileSystemModel::setSshConnection(const SshConnectionParameters &sshParams) +{ + QSSH_ASSERT_AND_RETURN(!d->sshConnection); + d->sshConnection = SshConnectionManager::instance().acquireConnection(sshParams); + connect(d->sshConnection.data(), SIGNAL(error(QSsh::SshError)), + SLOT(handleSshConnectionFailure())); + if (d->sshConnection->state() == SshConnection::Connected) { + handleSshConnectionEstablished(); + return; + } + connect(d->sshConnection.data(), SIGNAL(connected()), SLOT(handleSshConnectionEstablished())); + if (d->sshConnection->state() == SshConnection::Unconnected) + d->sshConnection->connectToHost(); +} + +void SftpFileSystemModel::setRootDirectory(const QString &path) +{ + beginResetModel(); + d->rootDirectory = path; + delete d->rootNode; + d->rootNode = 0; + d->lsOps.clear(); + d->statJobId = SftpInvalidJob; + endResetModel(); + statRootDirectory(); +} + +QString SftpFileSystemModel::rootDirectory() const +{ + return d->rootDirectory; +} + +SftpJobId SftpFileSystemModel::downloadFile(const QModelIndex &index, const QString &targetFilePath) +{ + QSSH_ASSERT_AND_RETURN_VALUE(d->rootNode, SftpInvalidJob); + const SftpFileNode * const fileNode = indexToFileNode(index); + QSSH_ASSERT_AND_RETURN_VALUE(fileNode, SftpInvalidJob); + QSSH_ASSERT_AND_RETURN_VALUE(fileNode->fileInfo.type == FileTypeRegular, SftpInvalidJob); + const SftpJobId jobId = d->sftpChannel->downloadFile(fileNode->path, targetFilePath, + SftpOverwriteExisting); + if (jobId != SftpInvalidJob) + d->externalJobs << jobId; + return jobId; +} + +int SftpFileSystemModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 2; // type + name +} + +QVariant SftpFileSystemModel::data(const QModelIndex &index, int role) const +{ + const SftpFileNode * const node = indexToFileNode(index); + if (index.column() == 0 && role == Qt::DecorationRole) { + switch (node->fileInfo.type) { + case FileTypeRegular: + case FileTypeOther: + return QIcon(QLatin1String(":/core/images/unknownfile.png")); + case FileTypeDirectory: + return QIcon(QLatin1String(":/core/images/dir.png")); + case FileTypeUnknown: + return QIcon(QLatin1String(":/core/images/help.png")); // Shows a question mark. + } + } + if (index.column() == 1) { + if (role == Qt::DisplayRole) + return node->fileInfo.name; + if (role == PathRole) + return node->path; + } + return QVariant(); +} + +Qt::ItemFlags SftpFileSystemModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +QVariant SftpFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal) + return QVariant(); + if (role != Qt::DisplayRole) + return QVariant(); + if (section == 0) + return tr("File Type"); + if (section == 1) + return tr("File Name"); + return QVariant(); +} + +QModelIndex SftpFileSystemModel::index(int row, int column, const QModelIndex &parent) const +{ + if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent)) + return QModelIndex(); + if (!d->rootNode) + return QModelIndex(); + if (!parent.isValid()) + return createIndex(row, column, d->rootNode); + const SftpDirNode * const parentNode = indexToDirNode(parent); + QSSH_ASSERT_AND_RETURN_VALUE(parentNode, QModelIndex()); + QSSH_ASSERT_AND_RETURN_VALUE(row < parentNode->children.count(), QModelIndex()); + SftpFileNode * const childNode = parentNode->children.at(row); + return createIndex(row, column, childNode); +} + +QModelIndex SftpFileSystemModel::parent(const QModelIndex &child) const +{ + if (!child.isValid()) // Don't assert on this, since the model tester tries it. + return QModelIndex(); + + const SftpFileNode * const childNode = indexToFileNode(child); + QSSH_ASSERT_AND_RETURN_VALUE(childNode, QModelIndex()); + if (childNode == d->rootNode) + return QModelIndex(); + SftpDirNode * const parentNode = childNode->parent; + if (parentNode == d->rootNode) + return createIndex(0, 0, d->rootNode); + const SftpDirNode * const grandParentNode = parentNode->parent; + QSSH_ASSERT_AND_RETURN_VALUE(grandParentNode, QModelIndex()); + return createIndex(grandParentNode->children.indexOf(parentNode), 0, parentNode); +} + +int SftpFileSystemModel::rowCount(const QModelIndex &parent) const +{ + if (!d->rootNode) + return 0; + if (!parent.isValid()) + return 1; + if (parent.column() != 0) + return 0; + SftpDirNode * const dirNode = indexToDirNode(parent); + if (!dirNode) + return 0; + if (dirNode->lsState != SftpDirNode::LsNotYetCalled) + return dirNode->children.count(); + d->lsOps.insert(d->sftpChannel->listDirectory(dirNode->path), dirNode); + dirNode->lsState = SftpDirNode::LsRunning; + return 0; +} + +void SftpFileSystemModel::statRootDirectory() +{ + d->statJobId = d->sftpChannel->statFile(d->rootDirectory); +} + +void SftpFileSystemModel::shutDown() +{ + if (d->sftpChannel) { + disconnect(d->sftpChannel.data(), 0, this, 0); + d->sftpChannel->closeChannel(); + d->sftpChannel.clear(); + } + if (d->sshConnection) { + disconnect(d->sshConnection.data(), 0, this, 0); + SshConnectionManager::instance().releaseConnection(d->sshConnection); + d->sshConnection.clear(); + } + delete d->rootNode; + d->rootNode = 0; +} + +void SftpFileSystemModel::handleSshConnectionFailure() +{ + emit connectionError(d->sshConnection->errorString()); + beginResetModel(); + shutDown(); + endResetModel(); +} + +void SftpFileSystemModel::handleSftpChannelInitialized() +{ + connect(d->sftpChannel.data(), + SIGNAL(fileInfoAvailable(QSsh::SftpJobId,QList<QSsh::SftpFileInfo>)), + SLOT(handleFileInfo(QSsh::SftpJobId,QList<QSsh::SftpFileInfo>))); + connect(d->sftpChannel.data(), SIGNAL(finished(QSsh::SftpJobId,QString)), + SLOT(handleSftpJobFinished(QSsh::SftpJobId,QString))); + statRootDirectory(); +} + +void SftpFileSystemModel::handleSshConnectionEstablished() +{ + d->sftpChannel = d->sshConnection->createSftpChannel(); + connect(d->sftpChannel.data(), SIGNAL(initialized()), SLOT(handleSftpChannelInitialized())); + connect(d->sftpChannel.data(), SIGNAL(initializationFailed(QString)), + SLOT(handleSftpChannelInitializationFailed(QString))); + d->sftpChannel->initialize(); +} + +void SftpFileSystemModel::handleSftpChannelInitializationFailed(const QString &reason) +{ + emit connectionError(reason); + beginResetModel(); + shutDown(); + endResetModel(); +} + +void SftpFileSystemModel::handleFileInfo(SftpJobId jobId, const QList<SftpFileInfo> &fileInfoList) +{ + if (jobId == d->statJobId) { + QSSH_ASSERT_AND_RETURN(!d->rootNode); + beginInsertRows(QModelIndex(), 0, 0); + d->rootNode = new SftpDirNode; + d->rootNode->path = d->rootDirectory; + d->rootNode->fileInfo = fileInfoList.first(); + d->rootNode->fileInfo.name = d->rootDirectory == QLatin1String("/") + ? d->rootDirectory : QFileInfo(d->rootDirectory).fileName(); + endInsertRows(); + return; + } + SftpDirNode * const parentNode = d->lsOps.value(jobId); + QSSH_ASSERT_AND_RETURN(parentNode); + QList<SftpFileInfo> filteredList; + foreach (const SftpFileInfo &fi, fileInfoList) { + if (fi.name != QLatin1String(".") && fi.name != QLatin1String("..")) + filteredList << fi; + } + if (filteredList.isEmpty()) + return; + + // In theory beginInsertRows() should suffice, but that fails to have an effect + // if rowCount() returned 0 earlier. + emit layoutAboutToBeChanged(); + + foreach (const SftpFileInfo &fileInfo, filteredList) { + SftpFileNode *childNode; + if (fileInfo.type == FileTypeDirectory) + childNode = new SftpDirNode; + else + childNode = new SftpFileNode; + childNode->path = parentNode->path; + if (!childNode->path.endsWith(QLatin1Char('/'))) + childNode->path += QLatin1Char('/'); + childNode->path += fileInfo.name; + childNode->fileInfo = fileInfo; + childNode->parent = parentNode; + parentNode->children << childNode; + } + emit layoutChanged(); // Should be endInsertRows(), see above. +} + +void SftpFileSystemModel::handleSftpJobFinished(SftpJobId jobId, const QString &errorMessage) +{ + if (jobId == d->statJobId) { + d->statJobId = SftpInvalidJob; + if (!errorMessage.isEmpty()) + emit sftpOperationFailed(tr("Error getting 'stat' info about '%1': %2") + .arg(rootDirectory(), errorMessage)); + return; + } + + DirNodeHash::Iterator it = d->lsOps.find(jobId); + if (it != d->lsOps.end()) { + QSSH_ASSERT(it.value()->lsState == SftpDirNode::LsRunning); + it.value()->lsState = SftpDirNode::LsFinished; + if (!errorMessage.isEmpty()) + emit sftpOperationFailed(tr("Error listing contents of directory '%1': %2") + .arg(it.value()->path, errorMessage)); + d->lsOps.erase(it); + return; + } + + const int jobIndex = d->externalJobs.indexOf(jobId); + QSSH_ASSERT_AND_RETURN(jobIndex != -1); + d->externalJobs.removeAt(jobIndex); + emit sftpOperationFinished(jobId, errorMessage); +} + +} // namespace QSsh |