See More

#include #include "Data.h" #include "RemoteModel.h" #include "RemoteNetwork.h" using json = nlohmann::json; RemoteModelItem::RemoteModelItem(RemoteModelItem* parent) : m_parent(parent), m_fetchedDirectoryList(false) { } RemoteModelItem::~RemoteModelItem() { qDeleteAll(m_children); } QVariant RemoteModelItem::value(RemoteModelColumns column) const { return m_values[column]; } void RemoteModelItem::setValue(RemoteModelColumns column, QVariant value) { m_values[column] = value; } void RemoteModelItem::appendChild(RemoteModelItem *item) { m_children.push_back(item); } RemoteModelItem* RemoteModelItem::child(int row) const { return m_children[static_cast(row)]; } RemoteModelItem* RemoteModelItem::parent() const { return m_parent; } int RemoteModelItem::childCount() const { return static_cast(m_children.size()); } int RemoteModelItem::row() const { if(m_parent) { auto f = std::find( m_parent->m_children.begin(), m_parent->m_children.end(), const_cast(this)); if(f == m_parent->m_children.end()) return -1; else return static_cast(std::distance(m_parent->m_children.begin(), f)); } return 0; } bool RemoteModelItem::fetchedDirectoryList() const { return m_fetchedDirectoryList; } void RemoteModelItem::setFetchedDirectoryList(bool fetched) { m_fetchedDirectoryList = fetched; } std::vector RemoteModelItem::loadArray(const json& array, RemoteModelItem* parent) { std::vector items; // Loop through all directory items for(const auto& elem : array) { // Create a new model item with the specified parent RemoteModelItem* item = new RemoteModelItem(parent); // Save all relevant values. Some of the values are only available for databases. item->setValue(RemoteModelColumnName, QString::fromStdString(elem["name"])); item->setValue(RemoteModelColumnType, QString::fromStdString(elem["type"])); item->setValue(RemoteModelColumnUrl, QString::fromStdString(elem["url"])); item->setValue(RemoteModelColumnLastModified, isoDateTimeStringToLocalDateTimeString(QString::fromStdString(elem["last_modified"]))); if(item->value(RemoteModelColumnType).toString() == "database") { item->setValue(RemoteModelColumnCommitId, QString::fromStdString(elem["commit_id"])); item->setValue(RemoteModelColumnSize, QString::number(static_cast(elem["size"]))); item->setValue(RemoteModelColumnDefaultBranch, QString::fromStdString(elem["default_branch"])); item->setValue(RemoteModelColumnLicence, QString::fromStdString(elem["licence"])); item->setValue(RemoteModelColumnOneLineDescription, QString::fromStdString(elem["one_line_description"])); item->setValue(RemoteModelColumnPublic, static_cast(elem["public"])); item->setValue(RemoteModelColumnSha256, QString::fromStdString(elem["sha256"])); item->setValue(RemoteModelColumnRepoModified, isoDateTimeStringToLocalDateTimeString(QString::fromStdString(elem["repo_modified"]))); } items.push_back(item); } return items; } RemoteModel::RemoteModel(QObject* parent) : QAbstractItemModel(parent), headerList({tr("Name"), tr("Last modified"), tr("Size"), tr("Commit")}), rootItem(new RemoteModelItem()) { } RemoteModel::~RemoteModel() { delete rootItem; } void RemoteModel::setNewRootDir(const QString& url, const QString& cert) { // Get user name from client cert currentUserName = RemoteNetwork::get().getInfoFromClientCert(cert, RemoteNetwork::CertInfoUser); // Save settings currentRootDirectory = url; currentClientCert = cert; // Fetch root directory refresh(); } void RemoteModel::refresh() { // Fetch root directory and put the reply data under the root item RemoteNetwork::get().fetch(currentRootDirectory, RemoteNetwork::RequestTypeCustom, currentClientCert, [this](const QByteArray& reply) { parseDirectoryListing(reply, QModelIndex()); }); } void RemoteModel::parseDirectoryListing(const QString& text, QModelIndex parent) { // Load new JSON root document assuming it's an array json array = json::parse(text.toStdString(), nullptr, false); if(array.is_discarded() || !array.is_array()) return; // Get model index to store the new data under RemoteModelItem* parentItem = const_cast(modelIndexToItem(parent)); // An invalid model index indicates that this is a new root item. This means the old one needs to be entirely deleted first. if(!parent.isValid()) { // Clear root item beginResetModel(); delete rootItem; rootItem = new RemoteModelItem(); endResetModel(); // Set parent model index and parent item to the new values parent = QModelIndex(); parentItem = rootItem; } // Insert data std::vector items = RemoteModelItem::loadArray(array, parentItem); beginInsertRows(parent, 0, static_cast(items.size() - 1)); for(RemoteModelItem* item : items) parentItem->appendChild(item); endInsertRows(); // Emit directory listing parsed signal emit directoryListingParsed(parent); } QModelIndex RemoteModel::index(int row, int column, const QModelIndex& parent) const { if(!hasIndex(row, column, parent)) return QModelIndex(); const RemoteModelItem* parentItem = modelIndexToItem(parent); RemoteModelItem* childItem = parentItem->child(row); if(childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex RemoteModel::parent(const QModelIndex& index) const { if(!index.isValid()) return QModelIndex(); const RemoteModelItem* childItem = modelIndexToItem(index); RemoteModelItem* parentItem = childItem->parent(); if(parentItem == rootItem) return QModelIndex(); return createIndex(parentItem->row(), 0, parentItem); } QVariant RemoteModel::data(const QModelIndex& index, int role) const { // Don't return data for invalid indices if(!index.isValid()) return QVariant(); // Type of item const RemoteModelItem* item = modelIndexToItem(index); QString type = item->value(RemoteModelColumnType).toString(); // Decoration role? Only for first column! if(role == Qt::DecorationRole && index.column() == 0) { // Use different icons depending on item type if(type == "folder" && index.parent() == QModelIndex() && item->value(RemoteModelColumnName) == currentUserName) return QImage(":/icons/folder_user"); else if(type == "folder") return QImage(":/icons/folder"); else if(type == "database") return QImage(":/icons/database"); } else if(role == Qt::ToolTipRole) { if(type == "database") { // Use URL to generate user name and database name. This avoids using the name of the parent item which // might not contain the user name when the server sends a different directory structure. QString result = "" + item->value(RemoteModelColumnUrl).toUrl().path().mid(1) + ""; if(!item->value(RemoteModelColumnOneLineDescription).toString().isEmpty()) result += "
" + item->value(RemoteModelColumnOneLineDescription).toString(); result += "
" + tr("Size: ") + humanReadableSize(item->value(RemoteModelColumnSize).toULongLong()); result += "
" + tr("Last Modified: ") + item->value(RemoteModelColumnLastModified).toString(); result += "
" + tr("Licence: ") + item->value(RemoteModelColumnLicence).toString(); result += "
" + tr("Default Branch: ") + item->value(RemoteModelColumnDefaultBranch).toString(); return result; } } else if(role == Qt::DisplayRole) { // Display role? // Return different value depending on column switch(index.column()) { case 0: { return item->value(RemoteModelColumnName); } case 1: { return item->value(RemoteModelColumnLastModified); } case 2: { // Folders don't have a size if(type == "folder") return QVariant(); // Convert size to human readable format unsigned int size = item->value(RemoteModelColumnSize).toUInt(); return humanReadableSize(size); } case 3: { if(type == "folder") return QVariant(); return item->value(RemoteModelColumnCommitId); } } } return QVariant(); } QVariant RemoteModel::headerData(int section, Qt::Orientation orientation, int role) const { // Call default implementation for vertical headers and for non-display roles if(role != Qt::DisplayRole || orientation != Qt::Horizontal) return QAbstractItemModel::headerData(section, orientation, role); // Return header string depending on column return headerList.at(static_cast(section)); } int RemoteModel::rowCount(const QModelIndex& parent) const { if(parent.column() > 0) return 0; const RemoteModelItem* parentItem = modelIndexToItem(parent); return parentItem->childCount(); } int RemoteModel::columnCount(const QModelIndex& /*parent*/) const { return static_cast(headerList.size()); } bool RemoteModel::hasChildren(const QModelIndex& parent) const { if(!parent.isValid()) return true; // If the item actually has children or is of type "folder" (and may have no children yet), we say that it actually has children const RemoteModelItem* item = modelIndexToItem(parent); return item->childCount() || item->value(RemoteModelColumnType) == "folder"; } bool RemoteModel::canFetchMore(const QModelIndex& parent) const { if(!parent.isValid()) return false; // If the item is of type "folder" and we haven't tried fetching a directory listing yet, we indicate that there might be more data to load const RemoteModelItem* item = modelIndexToItem(parent); return item->value(RemoteModelColumnType) == "folder" && !item->fetchedDirectoryList(); } void RemoteModel::fetchMore(const QModelIndex& parent) { // Can we even fetch more data? if(!canFetchMore(parent)) return; // Get parent item RemoteModelItem* item = static_cast(parent.internalPointer()); // Fetch item URL item->setFetchedDirectoryList(true); RemoteNetwork::get().fetch(item->value(RemoteModelColumnUrl).toUrl(), RemoteNetwork::RequestTypeCustom, currentClientCert, [this, parent](const QByteArray& reply) { parseDirectoryListing(reply, parent); }); } const QString& RemoteModel::currentClientCertificate() const { return currentClientCert; } const RemoteModelItem* RemoteModel::modelIndexToItem(const QModelIndex& idx) const { if(!idx.isValid()) return rootItem; else return static_cast(idx.internalPointer()); }