From 6e666b3bfd57a67730d01416799c246d63be56fe Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Fri, 25 Mar 2022 10:35:17 +0100 Subject: [PATCH] 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: Reviewed-by: Qt CI Bot Reviewed-by: hjk --- src/plugins/debugger/loadcoredialog.cpp | 1 + src/plugins/projectexplorer/CMakeLists.txt | 1 + .../devicesupport/devicefilesystemmodel.cpp | 320 ++++++++++++++++++ .../devicesupport/devicefilesystemmodel.h | 69 ++++ .../projectexplorer/projectexplorer.qbs | 1 + src/plugins/remotelinux/linuxdevice.cpp | 6 +- 6 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 src/plugins/projectexplorer/devicesupport/devicefilesystemmodel.cpp create mode 100644 src/plugins/projectexplorer/devicesupport/devicefilesystemmodel.h diff --git a/src/plugins/debugger/loadcoredialog.cpp b/src/plugins/debugger/loadcoredialog.cpp index b5d04ead15c..af7ce863e1b 100644 --- a/src/plugins/debugger/loadcoredialog.cpp +++ b/src/plugins/debugger/loadcoredialog.cpp @@ -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); } diff --git a/src/plugins/projectexplorer/CMakeLists.txt b/src/plugins/projectexplorer/CMakeLists.txt index a48c08452b4..2ec3d6434e3 100644 --- a/src/plugins/projectexplorer/CMakeLists.txt +++ b/src/plugins/projectexplorer/CMakeLists.txt @@ -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 diff --git a/src/plugins/projectexplorer/devicesupport/devicefilesystemmodel.cpp b/src/plugins/projectexplorer/devicesupport/devicefilesystemmodel.cpp new file mode 100644 index 00000000000..bca93342dd3 --- /dev/null +++ b/src/plugins/projectexplorer/devicesupport/devicefilesystemmodel.cpp @@ -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 +#include +#include +#include + +#include +#include +#include +#include +#include + +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 m_children; +}; + +static RemoteFileNode *indexToFileNode(const QModelIndex &index) +{ + return static_cast(index.internalPointer()); +} + +static RemoteDirNode *indexToDirNode(const QModelIndex &index) +{ + RemoteFileNode * const fileNode = indexToFileNode(index); + QTC_CHECK(fileNode); + return dynamic_cast(fileNode); +} + +using ResultType = QList>; + +class DeviceFileSystemModelPrivate +{ +public: + IDevice::ConstPtr m_device; + std::unique_ptr m_rootNode; + QSet *> 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 &futureInterface, const FilePath &dir) +{ + const QList 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 *watcher = new QFutureWatcher(this); + auto future = runAsync(dirEntries, filePath); + d->m_futureSynchronizer.addFuture(future); + connect(watcher, &QFutureWatcher::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 &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 diff --git a/src/plugins/projectexplorer/devicesupport/devicefilesystemmodel.h b/src/plugins/projectexplorer/devicesupport/devicefilesystemmodel.h new file mode 100644 index 00000000000..e77d6113a0e --- /dev/null +++ b/src/plugins/projectexplorer/devicesupport/devicefilesystemmodel.h @@ -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 + +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; diff --git a/src/plugins/projectexplorer/projectexplorer.qbs b/src/plugins/projectexplorer/projectexplorer.qbs index 1afca928404..a497d1e38c0 100644 --- a/src/plugins/projectexplorer/projectexplorer.qbs +++ b/src/plugins/projectexplorer/projectexplorer.qbs @@ -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", diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index f1099ed5087..9a351441eb2 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -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,