dbhub: Add remote dock with directory browsing support

This adds a new dock to the main window that contains all the remote
functionality (or is supposed to contain it all in the future).

It also adds a directory browsing feature which allows you to browse
through the folders and files on the dbhub server.

By double clicking a database you can download and open it. The Open
Remote menu action isn't needed anymore and has been removed.

This also fixes an issue with pushing databases where, after sending the
file is completed, the save dialog was opened.

Note that this is still WIP and is far from polished.
This commit is contained in:
Martin Kleusberg
2017-03-18 20:40:59 +01:00
parent dc3550ee6a
commit 3e4f3fc3d2
16 changed files with 758 additions and 49 deletions
+5
View File
@@ -115,6 +115,8 @@ set(SQLB_MOC_HDR
src/RemoteDatabase.h
src/ForeignKeyEditorDelegate.h
src/PlotDock.h
src/RemoteDock.h
src/RemoteModel.h
)
set(SQLB_SRC
@@ -150,6 +152,8 @@ set(SQLB_SRC
src/RemoteDatabase.cpp
src/ForeignKeyEditorDelegate.cpp
src/PlotDock.cpp
src/RemoteDock.cpp
src/RemoteModel.cpp
)
set(SQLB_FORMS
@@ -167,6 +171,7 @@ set(SQLB_FORMS
src/ExportSqlDialog.ui
src/ColumnDisplayFormatDialog.ui
src/PlotDock.ui
src/RemoteDock.ui
)
set(SQLB_RESOURCES
+22 -17
View File
@@ -21,6 +21,7 @@
#include "FileDialog.h"
#include "ColumnDisplayFormatDialog.h"
#include "FilterTableHeader.h"
#include "RemoteDock.h"
#include <QFile>
#include <QApplication>
@@ -55,8 +56,10 @@ MainWindow::MainWindow(QWidget* parent)
ui(new Ui::MainWindow),
m_browseTableModel(new SqliteTableModel(db, this, Settings::getSettingsValue("db", "prefetchsize").toInt())),
m_currentTabTableModel(m_browseTableModel),
m_remoteDb(new RemoteDatabase),
editDock(new EditDialog(this)),
plotDock(new PlotDock(this)),
remoteDock(new RemoteDock(this)),
gotoValidator(new QIntValidator(0, 0, this))
{
ui->setupUi(this);
@@ -68,6 +71,7 @@ MainWindow::MainWindow(QWidget* parent)
MainWindow::~MainWindow()
{
delete m_remoteDb;
delete gotoValidator;
delete ui;
}
@@ -77,6 +81,7 @@ void MainWindow::init()
// Load window settings
tabifyDockWidget(ui->dockLog, ui->dockPlot);
tabifyDockWidget(ui->dockLog, ui->dockSchema);
tabifyDockWidget(ui->dockLog, ui->dockRemote);
// Connect SQL logging and database state setting to main window
connect(&db, SIGNAL(dbChanged(bool)), this, SLOT(dbState(bool)));
@@ -107,6 +112,7 @@ void MainWindow::init()
// Create docks
ui->dockEdit->setWidget(editDock);
ui->dockPlot->setWidget(plotDock);
ui->dockRemote->setWidget(remoteDock);
// Restore window geometry
restoreGeometry(Settings::getSettingsValue("MainWindow", "geometry").toByteArray());
@@ -183,10 +189,12 @@ void MainWindow::init()
// Add menu item for edit dock
ui->viewMenu->insertAction(ui->viewDBToolbarAction, ui->dockEdit->toggleViewAction());
ui->viewMenu->actions().at(3)->setShortcut(QKeySequence(tr("Ctrl+E")));
ui->viewMenu->actions().at(3)->setIcon(QIcon(":/icons/log_dock"));
// Add keyboard shortcut for "Edit Cell" dock
ui->viewMenu->actions().at(3)->setShortcut(QKeySequence(tr("Ctrl+E")));
// Add menu item for plot dock
ui->viewMenu->insertAction(ui->viewDBToolbarAction, ui->dockRemote->toggleViewAction());
ui->viewMenu->actions().at(4)->setIcon(QIcon(":/icons/log_dock"));
// If we're not compiling in SQLCipher, hide its FAQ link in the help menu
#ifndef ENABLE_SQLCIPHER
@@ -223,7 +231,7 @@ void MainWindow::init()
connect(ui->dataTable->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showDataColumnPopupMenu(QPoint)));
connect(ui->dataTable->verticalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showRecordPopupMenu(QPoint)));
connect(ui->dockEdit, SIGNAL(visibilityChanged(bool)), this, SLOT(toggleEditDock(bool)));
connect(&m_remoteDb, SIGNAL(openFile(QString)), this, SLOT(fileOpen(QString)));
connect(m_remoteDb, SIGNAL(openFile(QString)), this, SLOT(fileOpen(QString)));
// Lambda function for keyboard shortcuts for selecting next/previous table in Browse Data tab
connect(ui->dataTable, &ExtendedTableWidget::switchTable, [this](bool next) {
@@ -274,6 +282,7 @@ void MainWindow::init()
ui->dockLog->setWindowTitle(ui->dockLog->windowTitle().remove('&'));
ui->dockPlot->setWindowTitle(ui->dockPlot->windowTitle().remove('&'));
ui->dockSchema->setWindowTitle(ui->dockSchema->windowTitle().remove('&'));
ui->dockRemote->setWindowTitle(ui->dockRemote->windowTitle().remove('&'));
}
bool MainWindow::fileOpen(const QString& fileName, bool dontAddToRecentFiles, bool readOnly)
@@ -1717,11 +1726,17 @@ void MainWindow::reloadSettings()
populateTable();
// Hide or show the File → Remote menu as needed
QAction *remoteMenuAction = ui->menuRemote->menuAction();
remoteMenuAction->setVisible(Settings::getSettingsValue("remote", "active").toBool());
bool showRemoteActions = Settings::getSettingsValue("remote", "active").toBool();
ui->menuRemote->menuAction()->setVisible(showRemoteActions);
ui->viewMenu->actions().at(4)->setVisible(showRemoteActions);
if(!showRemoteActions)
ui->dockRemote->setHidden(true);
// Update the remote database connection settings
m_remoteDb.reloadSettings();
m_remoteDb->reloadSettings();
// Reload remote dock settings
remoteDock->reloadSettings();
}
void MainWindow::httpresponse(QNetworkReply *reply)
@@ -1801,23 +1816,13 @@ void MainWindow::httpresponse(QNetworkReply *reply)
reply->deleteLater();
}
void MainWindow::on_actionOpen_Remote_triggered()
{
QString url = QInputDialog::getText(this, qApp->applicationName(), tr("Please enter the URL of the database file to open."));
if(!url.isEmpty())
{
QStringList certs = Settings::getSettingsValue("remote", "client_certificates").toStringList();
m_remoteDb.fetchDatabase(url, (certs.size() ? certs.at(0) : ""));
}
}
void MainWindow::on_actionSave_Remote_triggered()
{
QString url = QInputDialog::getText(this, qApp->applicationName(), tr("Please enter the URL of the database file to save."));
if(!url.isEmpty())
{
QStringList certs = Settings::getSettingsValue("remote", "client_certificates").toStringList();
m_remoteDb.pushDatabase(db.currentFile(), url, (certs.size() ? certs.at(0) : ""));
m_remoteDb->push(db.currentFile(), url, (certs.size() ? certs.at(0) : ""));
}
}
+6 -3
View File
@@ -18,6 +18,7 @@ class SqliteTableModel;
class DbStructureModel;
class QNetworkReply;
class QNetworkAccessManager;
class RemoteDock;
namespace Ui {
class MainWindow;
@@ -83,7 +84,7 @@ public:
~MainWindow();
DBBrowserDB& getDb() { return db; }
const RemoteDatabase& getRemote() const { return m_remoteDb; }
RemoteDatabase& getRemote() { return *m_remoteDb; }
enum Tabs
{
@@ -137,13 +138,16 @@ private:
QMap<QString, BrowseDataTableSettings> browseTableSettings;
RemoteDatabase* m_remoteDb;
EditDialog* editDock;
PlotDock* plotDock;
RemoteDock* remoteDock;
QIntValidator* gotoValidator;
DBBrowserDB db;
QString defaultBrowseTableEncoding;
RemoteDatabase m_remoteDb;
QNetworkAccessManager* m_NetworkManager;
@@ -205,7 +209,6 @@ private slots:
void exportTableToJson();
void fileSave();
void fileRevert();
void on_actionOpen_Remote_triggered();
void on_actionSave_Remote_triggered();
void exportDatabaseToSQL();
void importDatabaseFromSQL();
+9 -6
View File
@@ -883,7 +883,6 @@
<property name="title">
<string>Remote</string>
</property>
<addaction name="actionOpen_Remote"/>
<addaction name="actionSave_Remote"/>
</widget>
<addaction name="fileNewAction"/>
@@ -1120,6 +1119,15 @@
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="dockRemote">
<property name="windowTitle">
<string>&amp;Remote</string>
</property>
<attribute name="dockWidgetArea">
<number>2</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_5"/>
</widget>
<action name="fileNewAction">
<property name="icon">
<iconset resource="icons/icons.qrc">
@@ -1737,11 +1745,6 @@
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionOpen_Remote">
<property name="text">
<string>Open from Remote</string>
</property>
</action>
<action name="actionSave_Remote">
<property name="text">
<string>Save to Remote</string>
+36 -18
View File
@@ -86,28 +86,38 @@ void RemoteDatabase::gotReply(QNetworkReply* reply)
if(reply->error() != QNetworkReply::NoError)
{
QMessageBox::warning(0, qApp->applicationName(),
tr("Error opening remote database file from %1.\n%2").arg(reply->url().toString()).arg(reply->errorString()));
tr("Error when connecting to %1.\n%2").arg(reply->url().toString()).arg(reply->errorString()));
reply->deleteLater();
return;
}
// Ask user where to store the database file
QString saveFileAs = FileDialog::getSaveFileName(0, qApp->applicationName(), FileDialog::getSqlDatabaseFileFilter(), reply->url().fileName());
if(!saveFileAs.isEmpty())
{
// Save the downloaded data under the selected file name
QFile file(saveFileAs);
file.open(QIODevice::WriteOnly);
file.write(reply->readAll());
file.close();
// What type of data is this?
QString type = reply->property("type").toString();
// Tell the application to open this file
emit openFile(saveFileAs);
// Handle the reply data
if(type == "database")
{
// It's a database file. Ask user where to store the database file.
QString saveFileAs = FileDialog::getSaveFileName(0, qApp->applicationName(), FileDialog::getSqlDatabaseFileFilter(), reply->url().fileName());
if(!saveFileAs.isEmpty())
{
// Save the downloaded data under the selected file name
QFile file(saveFileAs);
file.open(QIODevice::WriteOnly);
file.write(reply->readAll());
file.close();
// Tell the application to open this file
emit openFile(saveFileAs);
}
} else if(type == "dir") {
emit gotDirList(reply->readAll(), reply->property("userdata"));
}
// Delete reply later, i.e. after returning from this slot function
m_currentReply = nullptr;
m_progress->hide();
if(type == "database" || type == "push")
m_progress->hide();
reply->deleteLater();
}
@@ -133,7 +143,7 @@ void RemoteDatabase::gotError(QNetworkReply* reply, const QList<QSslError>& erro
}
// Build an error message and short it to the user
QString message = tr("Error opening remote database file from %1.\n%2").arg(reply->url().toString()).arg(errors.at(0).errorString());
QString message = tr("Error opening remote file at %1.\n%2").arg(reply->url().toString()).arg(errors.at(0).errorString());
QMessageBox::warning(0, qApp->applicationName(), message);
// Delete reply later, i.e. after returning from this slot function
@@ -235,7 +245,7 @@ void RemoteDatabase::prepareProgressDialog(bool upload, const QString& url)
connect(m_currentReply, &QNetworkReply::downloadProgress, this, &RemoteDatabase::updateProgress);
}
void RemoteDatabase::fetchDatabase(const QString& url, const QString& clientCert)
void RemoteDatabase::fetch(const QString& url, bool isDatabase, const QString& clientCert, QVariant userdata)
{
// Check if network is accessible. If not, abort right here
if(m_manager->networkAccessible() == QNetworkAccessManager::NotAccessible)
@@ -260,12 +270,19 @@ void RemoteDatabase::fetchDatabase(const QString& url, const QString& clientCert
// Fetch database and save pending reply. Note that we're only supporting one active download here at the moment.
m_currentReply = m_manager->get(request);
if(isDatabase)
m_currentReply->setProperty("type", "database");
else
m_currentReply->setProperty("type", "dir");
m_currentReply->setProperty("userdata", userdata);
// Initialise the progress dialog for this request
prepareProgressDialog(false, url);
// Initialise the progress dialog for this request, but only if this is a database file. Directory listing are small enough to be loaded
// without progress dialog.
if(isDatabase)
prepareProgressDialog(false, url);
}
void RemoteDatabase::pushDatabase(const QString& filename, const QString& url, const QString& clientCert)
void RemoteDatabase::push(const QString& filename, const QString& url, const QString& clientCert)
{
// Check if network is accessible. If not, abort right here
if(m_manager->networkAccessible() == QNetworkAccessManager::NotAccessible)
@@ -304,6 +321,7 @@ void RemoteDatabase::pushDatabase(const QString& filename, const QString& url, c
// Fetch database and save pending reply. Note that we're only supporting one active download here at the moment.
m_currentReply = m_manager->put(request, file_data);
m_currentReply->setProperty("type", "push");
// Initialise the progress dialog for this request
prepareProgressDialog(true, url);
+3 -2
View File
@@ -24,10 +24,11 @@ public:
const QList<QSslCertificate>& caCertificates() const;
const QMap<QString, QSslCertificate>& clientCertificates() const { return m_clientCertFiles; }
void fetchDatabase(const QString& url, const QString& clientCert);
void pushDatabase(const QString& filename, const QString& url, const QString& clientCert);
void fetch(const QString& url, bool isDatabase, const QString& clientCert, QVariant userdata = QVariant());
void push(const QString& filename, const QString& url, const QString& clientCert);
signals:
void gotDirList(QString json, QVariant userdata);
void openFile(QString path);
private:
+74
View File
@@ -0,0 +1,74 @@
#include <QSslCertificate>
#include "RemoteDock.h"
#include "ui_RemoteDock.h"
#include "Settings.h"
#include "RemoteDatabase.h"
#include "RemoteModel.h"
#include "MainWindow.h"
RemoteDock::RemoteDock(MainWindow* parent)
: QDialog(parent),
ui(new Ui::RemoteDock),
remoteDatabase(parent->getRemote()),
remoteModel(new RemoteModel(this, parent->getRemote()))
{
ui->setupUi(this);
// Set up model
ui->treeStructure->setModel(remoteModel);
// Initial setup
reloadSettings();
}
RemoteDock::~RemoteDock()
{
delete ui;
}
void RemoteDock::reloadSettings()
{
// Load list of client certs
ui->comboUser->clear();
QStringList client_certs = Settings::getSettingsValue("remote", "client_certificates").toStringList();
foreach(const QString& file, client_certs)
{
auto certs = QSslCertificate::fromPath(file);
foreach(const QSslCertificate& cert, certs)
ui->comboUser->addItem(cert.subjectInfo(QSslCertificate::CommonName).at(0), file);
}
}
void RemoteDock::setNewIdentity()
{
// Get identity
QString identity = ui->comboUser->currentText();
if(identity.isEmpty())
return;
// Get certificate file name
QString cert = ui->comboUser->itemData(ui->comboUser->findText(identity), Qt::UserRole).toString();
if(cert.isEmpty())
return;
// Open root directory. Get host name from client cert
QString cn = remoteDatabase.clientCertificates()[cert].subjectInfo(QSslCertificate::CommonName).at(0);
QStringList cn_parts = cn.split("@");
if(cn_parts.size() < 2)
return;
remoteModel->setNewRootDir(QString("https://%1:5550/").arg(cn_parts.last()), cert);
}
void RemoteDock::fetchDatabase(const QModelIndex& idx)
{
if(!idx.isValid())
return;
// Get item
const RemoteModelItem* item = remoteModel->modelIndexToItem(idx);
// Only open database file
if(item->value(RemoteModelColumnType).toString() == "database")
remoteDatabase.fetch(item->value(RemoteModelColumnUrl).toString(), true, remoteModel->currentClientCertificate());
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef REMOTEDOCK_H
#define REMOTEDOCK_H
#include <QDialog>
class RemoteDatabase;
class RemoteModel;
class MainWindow;
namespace Ui {
class RemoteDock;
}
class RemoteDock : public QDialog
{
Q_OBJECT
public:
explicit RemoteDock(MainWindow* parent);
~RemoteDock();
void reloadSettings();
private slots:
void setNewIdentity();
void fetchDatabase(const QModelIndex& idx);
private:
Ui::RemoteDock* ui;
RemoteDatabase& remoteDatabase;
RemoteModel* remoteModel;
};
#endif
+132
View File
@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RemoteDock</class>
<widget class="QDialog" name="RemoteDock">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>575</width>
<height>310</height>
</rect>
</property>
<property name="windowTitle">
<string>Remote</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>B&amp;rowse</string>
</property>
<property name="buddy">
<cstring>comboBrowseMode</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBrowseMode">
<item>
<property name="text">
<string>Remote</string>
</property>
</item>
<item>
<property name="text">
<string>Local</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Identity</string>
</property>
<property name="buddy">
<cstring>comboUser</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboUser">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="buttonLogin">
<property name="text">
<string>Go</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/cog_go.png</normaloff>:/icons/cog_go.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeView" name="treeStructure"/>
</item>
</layout>
</widget>
<resources>
<include location="icons/icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonLogin</sender>
<signal>clicked()</signal>
<receiver>RemoteDock</receiver>
<slot>setNewIdentity()</slot>
<hints>
<hint type="sourcelabel">
<x>551</x>
<y>22</y>
</hint>
<hint type="destinationlabel">
<x>419</x>
<y>24</y>
</hint>
</hints>
</connection>
<connection>
<sender>treeStructure</sender>
<signal>doubleClicked(QModelIndex)</signal>
<receiver>RemoteDock</receiver>
<slot>fetchDatabase(QModelIndex)</slot>
<hints>
<hint type="sourcelabel">
<x>211</x>
<y>75</y>
</hint>
<hint type="destinationlabel">
<x>204</x>
<y>37</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>setNewIdentity()</slot>
<slot>fetchDatabase(QModelIndex)</slot>
</slots>
</ui>
+315
View File
@@ -0,0 +1,315 @@
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QImage>
#include "RemoteModel.h"
#include "RemoteDatabase.h"
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.append(item);
}
RemoteModelItem* RemoteModelItem::child(int row) const
{
return m_children.value(row);
}
RemoteModelItem* RemoteModelItem::parent() const
{
return m_parent;
}
int RemoteModelItem::childCount() const
{
return m_children.count();
}
int RemoteModelItem::row() const
{
if(m_parent)
return m_parent->m_children.indexOf(const_cast<RemoteModelItem*>(this));
return 0;
}
bool RemoteModelItem::fetchedDirectoryList() const
{
return m_fetchedDirectoryList;
}
void RemoteModelItem::setFetchedDirectoryList(bool fetched)
{
m_fetchedDirectoryList = fetched;
}
QList<RemoteModelItem*> RemoteModelItem::loadArray(const QJsonValue& value, RemoteModelItem* parent)
{
QList<RemoteModelItem*> items;
// Loop through all directory items
QJsonArray array = value.toArray();
for(int i=0;i<array.size();i++)
{
// Create a new model item with the specified parent
RemoteModelItem* item = new RemoteModelItem(parent);
// Save all relevant values. If one of the values isn't set in the JSON document, an empty string
// will be stored
item->setValue(RemoteModelColumnName, array.at(i).toObject().value("name"));
item->setValue(RemoteModelColumnType, array.at(i).toObject().value("type"));
item->setValue(RemoteModelColumnUrl, array.at(i).toObject().value("url"));
item->setValue(RemoteModelColumnVersion, array.at(i).toObject().value("version"));
item->setValue(RemoteModelColumnSize, array.at(i).toObject().value("size"));
item->setValue(RemoteModelColumnLastModified, array.at(i).toObject().value("last_modified"));
items.push_back(item);
}
return items;
}
RemoteModel::RemoteModel(QObject* parent, RemoteDatabase& remote) :
QAbstractItemModel(parent),
rootItem(new RemoteModelItem()),
remoteDatabase(remote)
{
// Initialise list of column names
headerList << tr("Name") << tr("Version") << tr("Last modified") << tr("Size");
// Set up signals
connect(&remoteDatabase, &RemoteDatabase::gotDirList, this, &RemoteModel::parseDirectoryListing);
}
RemoteModel::~RemoteModel()
{
delete rootItem;
}
void RemoteModel::setNewRootDir(const QString& url, const QString& cert)
{
// Save settings
currentRootDirectory = url;
currentClientCert = cert;
// Fetch root directory and put the reply data under the root item
remoteDatabase.fetch(currentRootDirectory, false, currentClientCert, QModelIndex());
}
void RemoteModel::parseDirectoryListing(const QString& json, const QVariant& userdata)
{
// Load new JSON root document assuming it's an array
QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8());
if(doc.isNull() || !doc.isArray())
return;
QJsonArray array = doc.array();
// Get model index to store the new data under
QModelIndex parent = userdata.toModelIndex();
RemoteModelItem* parentItem = const_cast<RemoteModelItem*>(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
beginInsertRows(parent, 0, array.size());
QList<RemoteModelItem*> items = RemoteModelItem::loadArray(QJsonValue(array), parentItem);
foreach(RemoteModelItem* item, items)
parentItem->appendChild(item);
endInsertRows();
}
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")
return QImage(":/icons/folder");
else if(type == "database")
return QImage(":/icons/database");
} else if(role == Qt::DisplayRole) {
// Display role?
// Return different value depending on column
switch(index.column())
{
case 0:
{
return item->value(RemoteModelColumnName);
}
case 1:
{
if(type == "folder")
return QVariant();
return QString::number(item->value(RemoteModelColumnVersion).toInt());
}
case 2:
{
return item->value(RemoteModelColumnLastModified);
}
case 3:
{
// Folders don't have a size
if(type == "folder")
return QVariant();
// Convert size to human readable format
float size = item->value(RemoteModelColumnSize).toInt();
QStringList list;
list << "KiB" << "MiB" << "GiB" << "TiB";
QStringListIterator it(list);
QString unit(tr("bytes"));
while(size >= 1024.0f && it.hasNext())
{
unit = it.next();
size /= 1024.0;
}
return QString().setNum(size, 'f', 2) + " " + unit;
}
}
}
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(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 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<RemoteModelItem*>(parent.internalPointer());
// Fetch item URL
item->setFetchedDirectoryList(true);
remoteDatabase.fetch(item->value(RemoteModelColumnUrl).toString(), false, currentClientCert, 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<const RemoteModelItem*>(idx.internalPointer());
}
+110
View File
@@ -0,0 +1,110 @@
#ifndef REMOTEMODEL_H
#define REMOTEMODEL_H
#include <QAbstractItemModel>
#include <QStringList>
class RemoteDatabase;
// List of fields stored in the JSON data
enum RemoteModelColumns
{
RemoteModelColumnName,
RemoteModelColumnType,
RemoteModelColumnUrl,
RemoteModelColumnVersion,
RemoteModelColumnSize,
RemoteModelColumnLastModified,
RemoteModelColumnCount
};
class RemoteModelItem
{
public:
RemoteModelItem(RemoteModelItem* parent = nullptr);
~RemoteModelItem();
QVariant value(RemoteModelColumns column) const;
void setValue(RemoteModelColumns column, QVariant value);
bool fetchedDirectoryList() const;
void setFetchedDirectoryList(bool fetched);
void appendChild(RemoteModelItem* item);
RemoteModelItem* child(int row) const;
RemoteModelItem* parent() const;
int childCount() const;
int row() const;
// This function assumes the JSON value it's getting passed is an array ("[{...}, {...}, {...}, ...]"). It returns a list of model items, one
// per array entry and each with the specified parent set.
static QList<RemoteModelItem*> loadArray(const QJsonValue& value, RemoteModelItem* parent = nullptr);
private:
// These are just the fields from the json objects returned by the dbhub.io server
QVariant m_values[RemoteModelColumnCount];
// Child items and parent item
QList<RemoteModelItem*> m_children;
RemoteModelItem* m_parent;
// Indicates whether we already tried fetching a directory listing for this item. This serves two purposes:
// 1) When having an empty directory this allows us to remove the expandable flag for this item.
// 2) Between sending a network request and getting the reply this flag is already set, avoiding a second or third request being sent in the meantime.
bool m_fetchedDirectoryList;
};
class RemoteModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit RemoteModel(QObject* parent, RemoteDatabase& remote);
virtual ~RemoteModel();
void setNewRootDir(const QString& url, const QString& cert);
QModelIndex index(int row, int column,const QModelIndex& parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex& index) const;
QVariant data(const QModelIndex& index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
int rowCount(const QModelIndex& parent = QModelIndex()) const;
int columnCount(const QModelIndex& parent = QModelIndex()) const;
bool hasChildren(const QModelIndex& parent) const;
bool canFetchMore(const QModelIndex& parent) const;
void fetchMore(const QModelIndex& parent);
// This helper function takes a model index and returns the according model item. An invalid model index is used to indicate the
// root item, so if the index is invalid the root item is returned. This means that if you need to check for actual invalid indices
// this needs to be done prior to calling this function.
const RemoteModelItem* modelIndexToItem(const QModelIndex& idx) const;
// Returns the current client certificate
const QString& currentClientCertificate() const;
private slots:
// This is called whenever a network reply containing a directory listing arrives. json contains the reply data, userdata
// contains some custom data passed to the request. In this case we expect this to be the model index of the parent tree item.
void parseDirectoryListing(const QString& json, const QVariant& userdata);
private:
// Pointer to the root item. This contains all the actual item data.
RemoteModelItem* rootItem;
// Thr header list is a list of column titles. It's a static list that's getting filled in the constructor.
QStringList headerList;
// Reference to the remote database object which is stored somewhere in the main window.
RemoteDatabase& remoteDatabase;
// This stores the currently used network identity so it can be used for further requests, e.g. for
// lazy population.
QString currentRootDirectory;
QString currentClientCert;
};
#endif
Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

+3
View File
@@ -51,5 +51,8 @@
<file alias="view_modify">picture_edit.png</file>
<file alias="trigger_modify">script_edit.png</file>
<file alias="index_modify">tag_blue_edit.png</file>
<file>folder.png</file>
<file>database.png</file>
<file>cog_go.png</file>
</qresource>
</RCC>
+8 -3
View File
@@ -54,7 +54,9 @@ HEADERS += \
FilterLineEdit.h \
RemoteDatabase.h \
ForeignKeyEditorDelegate.h \
PlotDock.h
PlotDock.h \
RemoteDock.h \
RemoteModel.h
SOURCES += \
sqlitedb.cpp \
@@ -87,7 +89,9 @@ SOURCES += \
FilterLineEdit.cpp \
RemoteDatabase.cpp \
ForeignKeyEditorDelegate.cpp \
PlotDock.cpp
PlotDock.cpp \
RemoteDock.cpp \
RemoteModel.cpp
RESOURCES += icons/icons.qrc \
translations/flags/flags.qrc \
@@ -108,7 +112,8 @@ FORMS += \
CipherDialog.ui \
ExportSqlDialog.ui \
ColumnDisplayFormatDialog.ui \
PlotDock.ui
PlotDock.ui \
RemoteDock.ui
TRANSLATIONS += \
translations/sqlb_ar_SA.ts \