Introduce DeviceFileSystemModel

This is going to replace SftpFileSystemModel.

That's needed before planed removal of SshConnection in order
to not to use SftpSession class.

Change-Id: I97f0ffd3418f7e71819ac95048a75e4a17901c71
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2022-03-25 10:35:17 +01:00
parent d0e854e390
commit 6e666b3bfd
6 changed files with 397 additions and 1 deletions

View File

@@ -139,6 +139,7 @@ void SelectRemoteFileDialog::attachToDevice(Kit *k)
IDevice::ConstPtr device = DeviceKitAspect::device(k);
QTC_ASSERT(device, return);
SshConnectionParameters sshParams = device->sshParameters();
// TODO: change into setDevice()
m_fileSystemModel.setSshConnection(sshParams);
}

View File

@@ -52,6 +52,7 @@ add_qtc_plugin(ProjectExplorer
devicesupport/desktopprocesssignaloperation.cpp devicesupport/desktopprocesssignaloperation.h
devicesupport/devicecheckbuildstep.cpp devicesupport/devicecheckbuildstep.h
devicesupport/devicefactoryselectiondialog.cpp devicesupport/devicefactoryselectiondialog.h devicesupport/devicefactoryselectiondialog.ui
devicesupport/devicefilesystemmodel.cpp devicesupport/devicefilesystemmodel.h
devicesupport/devicemanager.cpp devicesupport/devicemanager.h
devicesupport/devicemanagermodel.cpp devicesupport/devicemanagermodel.h
devicesupport/deviceprocessesdialog.cpp devicesupport/deviceprocessesdialog.h

View File

@@ -0,0 +1,320 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "devicefilesystemmodel.h"
#include <utils/futuresynchronizer.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include <utils/utilsicons.h>
#include <QFutureWatcher>
#include <QIcon>
#include <QList>
#include <QScopeGuard>
#include <QSet>
using namespace Utils;
namespace ProjectExplorer {
namespace Internal {
enum class FileType {
File,
Dir,
// Link,
Other
};
class RemoteDirNode;
class RemoteFileNode
{
public:
virtual ~RemoteFileNode() = default;
FilePath m_filePath;
FileType m_fileType = FileType::File;
RemoteDirNode *m_parent = nullptr;
};
class RemoteDirNode : public RemoteFileNode
{
public:
RemoteDirNode() { m_fileType = FileType::Dir; }
~RemoteDirNode() { qDeleteAll(m_children); }
enum { Initial, Fetching, Finished } m_state = Initial;
QList<RemoteFileNode *> m_children;
};
static RemoteFileNode *indexToFileNode(const QModelIndex &index)
{
return static_cast<RemoteFileNode *>(index.internalPointer());
}
static RemoteDirNode *indexToDirNode(const QModelIndex &index)
{
RemoteFileNode * const fileNode = indexToFileNode(index);
QTC_CHECK(fileNode);
return dynamic_cast<RemoteDirNode *>(fileNode);
}
using ResultType = QList<QPair<FilePath, FileType>>;
class DeviceFileSystemModelPrivate
{
public:
IDevice::ConstPtr m_device;
std::unique_ptr<RemoteDirNode> m_rootNode;
QSet<QFutureWatcher<ResultType> *> m_watchers;
FutureSynchronizer m_futureSynchronizer;
};
} // namespace Internal
using namespace Internal;
DeviceFileSystemModel::DeviceFileSystemModel(QObject *parent)
: QAbstractItemModel(parent), d(new DeviceFileSystemModelPrivate)
{
d->m_futureSynchronizer.setCancelOnWait(true);
}
DeviceFileSystemModel::~DeviceFileSystemModel()
{
qDeleteAll(d->m_watchers);
delete d;
}
void DeviceFileSystemModel::setDevice(const IDevice::ConstPtr &device)
{
d->m_device = device;
}
bool DeviceFileSystemModel::canFetchMore(const QModelIndex &parent) const
{
if (!parent.isValid()) {
return !d->m_rootNode.get();
}
RemoteDirNode * const dirNode = indexToDirNode(parent);
if (!dirNode)
return false;
if (dirNode->m_state == RemoteDirNode::Initial)
return true;
return false;
}
void DeviceFileSystemModel::fetchMore(const QModelIndex &parent)
{
if (!parent.isValid()) {
beginInsertRows(QModelIndex(), 0, 0);
QTC_CHECK(!d->m_rootNode);
d->m_rootNode.reset(new RemoteDirNode);
d->m_rootNode->m_filePath = d->m_device->rootPath();
endInsertRows();
return;
}
RemoteDirNode * const dirNode = indexToDirNode(parent);
if (!dirNode)
return;
if (dirNode->m_state != RemoteDirNode::Initial)
return;
collectEntries(dirNode->m_filePath, dirNode);
dirNode->m_state = RemoteDirNode::Fetching;
}
bool DeviceFileSystemModel::hasChildren(const QModelIndex &parent) const
{
if (!parent.isValid())
return true;
RemoteDirNode * const dirNode = indexToDirNode(parent);
if (!dirNode)
return false;
if (dirNode->m_state == RemoteDirNode::Initial)
return true;
return dirNode->m_children.size();
}
int DeviceFileSystemModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 2; // type + name
}
QVariant DeviceFileSystemModel::data(const QModelIndex &index, int role) const
{
const RemoteFileNode * const node = indexToFileNode(index);
QTC_ASSERT(node, return QVariant());
if (index.column() == 0 && role == Qt::DecorationRole) {
if (node->m_fileType == FileType::File)
return Utils::Icons::UNKNOWN_FILE.icon();
if (node->m_fileType == FileType::Dir)
return Utils::Icons::DIR.icon();
return QIcon(":/ssh/images/help.png"); // Shows a question mark.
}
if (index.column() == 1) {
if (role == Qt::DisplayRole) {
if (node->m_filePath == d->m_device->rootPath())
return QString("/");
return node->m_filePath.fileName();
}
if (role == PathRole)
return node->m_filePath.toString();
}
return QVariant();
}
Qt::ItemFlags DeviceFileSystemModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
QVariant DeviceFileSystemModel::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 DeviceFileSystemModel::index(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || row >= rowCount(parent) || column < 0 || column >= columnCount(parent))
return QModelIndex();
if (!d->m_rootNode)
return QModelIndex();
if (!parent.isValid())
return createIndex(row, column, d->m_rootNode.get());
const RemoteDirNode * const parentNode = indexToDirNode(parent);
QTC_ASSERT(parentNode, return QModelIndex());
QTC_ASSERT(row < parentNode->m_children.count(), return QModelIndex());
RemoteFileNode * const childNode = parentNode->m_children.at(row);
return createIndex(row, column, childNode);
}
QModelIndex DeviceFileSystemModel::parent(const QModelIndex &child) const
{
if (!child.isValid()) // Don't assert on this, since the model tester tries it.
return QModelIndex();
const RemoteFileNode * const childNode = indexToFileNode(child);
QTC_ASSERT(childNode, return QModelIndex());
if (childNode == d->m_rootNode.get())
return QModelIndex();
RemoteDirNode * const parentNode = childNode->m_parent;
if (parentNode == d->m_rootNode.get())
return createIndex(0, 0, d->m_rootNode.get());
const RemoteDirNode * const grandParentNode = parentNode->m_parent;
QTC_ASSERT(grandParentNode, return QModelIndex());
return createIndex(grandParentNode->m_children.indexOf(parentNode), 0, parentNode);
}
static FileType fileType(const FilePath &path)
{
if (path.isDir())
return FileType::Dir;
if (path.isFile())
return FileType::File;
return FileType::Other;
}
static void dirEntries(QFutureInterface<ResultType> &futureInterface, const FilePath &dir)
{
const QList<FilePath> entries = dir.dirEntries(QDir::NoFilter);
ResultType result;
for (const FilePath &entry : entries) {
if (futureInterface.isCanceled())
return;
result.append(qMakePair(entry, fileType(entry)));
}
futureInterface.reportResult(result);
}
int DeviceFileSystemModel::rowCount(const QModelIndex &parent) const
{
if (!d->m_rootNode)
return 0;
if (!parent.isValid())
return 1;
if (parent.column() != 0)
return 0;
RemoteDirNode * const dirNode = indexToDirNode(parent);
if (!dirNode)
return 0;
return dirNode->m_children.count();
}
void DeviceFileSystemModel::collectEntries(const FilePath &filePath, RemoteDirNode *parentNode)
{
// Destructor of this will delete working watchers, as they are children of this.
QFutureWatcher<ResultType> *watcher = new QFutureWatcher<ResultType>(this);
auto future = runAsync(dirEntries, filePath);
d->m_futureSynchronizer.addFuture(future);
connect(watcher, &QFutureWatcher<ResultType>::finished, this, [this, watcher, parentNode] {
auto cleanup = qScopeGuard([watcher, this] {
d->m_watchers.remove(watcher);
watcher->deleteLater();
});
QTC_ASSERT(parentNode->m_state == RemoteDirNode::Fetching, return);
parentNode->m_state = RemoteDirNode::Finished;
const ResultType entries = watcher->result();
if (entries.isEmpty())
return;
const int row = parentNode->m_parent
? parentNode->m_parent->m_children.indexOf(parentNode) : 0;
const QModelIndex parentIndex = createIndex(row, 0, parentNode);
beginInsertRows(parentIndex, 0, entries.count() - 1);
for (const QPair<FilePath, FileType> &entry : entries) {
RemoteFileNode *childNode = nullptr;
if (entry.second == FileType::Dir)
childNode = new RemoteDirNode;
else
childNode = new RemoteFileNode;
childNode->m_filePath = entry.first;
childNode->m_fileType = entry.second;
childNode->m_parent = parentNode;
parentNode->m_children.append(childNode);
}
endInsertRows();
});
d->m_watchers.insert(watcher);
watcher->setFuture(future);
}
} // namespace ProjectExplorer

View File

@@ -0,0 +1,69 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "idevice.h"
#include <QAbstractItemModel>
namespace ProjectExplorer {
namespace Internal {
class DeviceFileSystemModelPrivate;
class RemoteDirNode;
}
// Very simple read-only model. Symbolic links are not followed.
class PROJECTEXPLORER_EXPORT DeviceFileSystemModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit DeviceFileSystemModel(QObject *parent = nullptr);
~DeviceFileSystemModel();
void setDevice(const IDevice::ConstPtr &device);
// Use this to get the full path of a file or directory.
static const int PathRole = Qt::UserRole;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:
bool canFetchMore(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
void fetchMore(const QModelIndex &parent) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
void collectEntries(const Utils::FilePath &filePath, Internal::RemoteDirNode *parentNode);
Internal::DeviceFileSystemModelPrivate * const d;
};
} // namespace QSsh;

View File

@@ -209,6 +209,7 @@ Project {
"desktopdevicefactory.cpp", "desktopdevicefactory.h",
"devicecheckbuildstep.cpp", "devicecheckbuildstep.h",
"devicefactoryselectiondialog.cpp", "devicefactoryselectiondialog.h", "devicefactoryselectiondialog.ui",
"devicefilesystemmodel.cpp", "devicefilesystemmodel.h",
"devicemanager.cpp", "devicemanager.h",
"devicemanagermodel.cpp", "devicemanagermodel.h",
"deviceprocessesdialog.cpp", "deviceprocessesdialog.h",

View File

@@ -447,7 +447,11 @@ FilePath LinuxDevice::mapToGlobalPath(const FilePath &pathOnDevice) const
bool LinuxDevice::handlesFile(const FilePath &filePath) const
{
return filePath.scheme() == "ssh" && filePath.host() == userAtHost();
if (filePath.scheme() == "device" && filePath.host() == id().toString())
return true;
if (filePath.scheme() == "ssh" && filePath.host() == userAtHost())
return true;
return false;
}
CommandLine LinuxDevicePrivate::fullLocalCommandLine(const CommandLine &remoteCommand,