diff --git a/CMakeLists.txt b/CMakeLists.txt index 46236dea..ff9690ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,7 @@ set(SQLB_MOC_HDR src/TableBrowser.h src/ImageViewer.h src/RemoteLocalFilesModel.h + src/RemoteCommitsModel.h ) set(SQLB_SRC @@ -232,6 +233,7 @@ set(SQLB_SRC src/sql/parser/sqlite3_parser.cpp src/ImageViewer.cpp src/RemoteLocalFilesModel.cpp + src/RemoteCommitsModel.cpp ) set(SQLB_FORMS diff --git a/src/RemoteCommitsModel.cpp b/src/RemoteCommitsModel.cpp new file mode 100644 index 00000000..13f44b16 --- /dev/null +++ b/src/RemoteCommitsModel.cpp @@ -0,0 +1,140 @@ +#include + +#include "RemoteCommitsModel.h" +#include "Settings.h" + +using json = nlohmann::json; + +RemoteCommitsModel::RemoteCommitsModel(QObject* parent) : + QAbstractItemModel(parent) +{ + QStringList header; + header << tr("Commit ID") << tr("Message") << tr("Date") << tr("Author"); + rootItem = new QTreeWidgetItem(header); +} + +RemoteCommitsModel::~RemoteCommitsModel() +{ + delete rootItem; +} + +void RemoteCommitsModel::clear() +{ + beginResetModel(); + + // Remove all data except for the root item + while(rootItem->childCount()) + delete rootItem->child(0); + + endResetModel(); +} + +void RemoteCommitsModel::refresh(const std::string& json_data, const std::string& last_commit_id) +{ + // Clear previous items + clear(); + beginResetModel(); + + // Read in all commits + json j = json::parse(json_data, nullptr, false); + std::unordered_map commit_to_iterator; + for(auto it=j.begin();it!=j.end();++it) + commit_to_iterator.insert({it.value()["id"], it}); + + // Starting from the last commit add follow the chain up to the first commit + std::string parent_id = last_commit_id; + while(true) + { + if(commit_to_iterator.find(parent_id) == commit_to_iterator.end()) + break; + + json commit = commit_to_iterator[parent_id].value(); + + QTreeWidgetItem* item = new QTreeWidgetItem(rootItem); + item->setText(ColumnCommitId, QString::fromStdString(commit["id"])); + item->setText(ColumnMessage, QString::fromStdString(commit["message"])); + item->setText(ColumnDate, QString::fromStdString(commit["timestamp"])); + item->setText(ColumnAuthor, QString::fromStdString(commit["author_name"]) + " <" + QString::fromStdString(commit["author_email"]) + ">"); + + parent_id = commit["parent"]; + } + + // Refresh the view + endResetModel(); +} + +QModelIndex RemoteCommitsModel::index(int row, int column, const QModelIndex& parent) const +{ + if(!hasIndex(row, column, parent)) + return QModelIndex(); + + QTreeWidgetItem *parentItem; + if(!parent.isValid()) + parentItem = rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + QTreeWidgetItem* childItem = parentItem->child(row); + if(childItem) + return createIndex(row, column, childItem); + else + return QModelIndex(); +} + +QModelIndex RemoteCommitsModel::parent(const QModelIndex& index) const +{ + if(!index.isValid()) + return QModelIndex(); + + QTreeWidgetItem* childItem = static_cast(index.internalPointer()); + QTreeWidgetItem* parentItem = childItem->parent(); + + if(parentItem == rootItem) + return QModelIndex(); + else + return createIndex(0, 0, parentItem); +} + +QVariant RemoteCommitsModel::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + return QVariant(); + + // Get the item the index points at + QTreeWidgetItem* item = static_cast(index.internalPointer()); + + // Return data depending on the role + switch(role) + { + case Qt::DisplayRole: + case Qt::EditRole: + return item->text(index.column()); + default: + return QVariant(); + } +} + +QVariant RemoteCommitsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + // Get the header string from the root item + if(orientation == Qt::Horizontal && role == Qt::DisplayRole) + return rootItem->data(section, role); + + return QVariant(); +} + +int RemoteCommitsModel::rowCount(const QModelIndex& parent) const +{ + if(parent.column() > 0) + return 0; + + if(!parent.isValid()) + return rootItem->childCount(); + else + return static_cast(parent.internalPointer())->childCount(); +} + +int RemoteCommitsModel::columnCount(const QModelIndex& /*parent*/) const +{ + return rootItem->columnCount(); +} diff --git a/src/RemoteCommitsModel.h b/src/RemoteCommitsModel.h new file mode 100644 index 00000000..faa0f733 --- /dev/null +++ b/src/RemoteCommitsModel.h @@ -0,0 +1,43 @@ +#ifndef REMOTECOMMITSMODEL_H +#define REMOTECOMMITSMODEL_H + +#include + +#include + +class QTreeWidgetItem; + +class RemoteCommitsModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit RemoteCommitsModel(QObject* parent); + ~RemoteCommitsModel() override; + + void clear(); + void refresh(const std::string& json_data, const std::string& last_commit_id); + + QModelIndex index(int row, int column,const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; + + QVariant data(const QModelIndex& index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + enum Columns + { + ColumnCommitId, + ColumnMessage, + ColumnDate, + ColumnAuthor, + }; + +private: + // Pointer to the root item. This contains all the actual item data. + QTreeWidgetItem* rootItem; +}; + +#endif diff --git a/src/RemoteDatabase.cpp b/src/RemoteDatabase.cpp index 38037d17..666bed7d 100644 --- a/src/RemoteDatabase.cpp +++ b/src/RemoteDatabase.cpp @@ -309,6 +309,41 @@ void RemoteDatabase::gotReply(QNetworkReply* reply) emit uploadFinished(obj["url"]); break; } + case RequestTypeMetadata: + { + // Read and check results + json obj = json::parse(reply->readAll(), nullptr, false); + if(obj.is_discarded() || !obj.is_object()) + break; + + // Extract and convert data + json obj_branches = obj["branches"]; + json obj_commits = obj["commits"]; + json obj_releases = obj["releases"]; + json obj_tags = obj["tags"]; + std::string default_branch = (obj.contains("default_branch") && !obj["default_branch"].empty()) ? obj["default_branch"] : "master"; + std::vector branches; + for(auto it=obj_branches.cbegin();it!=obj_branches.cend();++it) + branches.emplace_back(it.key(), it.value()["commit"], it.value()["description"], it.value()["commit_count"]); + std::vector releases; + for(auto it=obj_releases.cbegin();it!=obj_releases.cend();++it) + { + releases.emplace_back(it.key(), it.value()["commit"], it.value()["date"], + it.value()["description"], it.value()["email"], + it.value()["name"], it.value()["size"]); + } + std::vector tags; + for(auto it=obj_tags.cbegin();it!=obj_tags.cend();++it) + { + tags.emplace_back(it.key(), it.value()["commit"], it.value()["date"], + it.value()["description"], it.value()["email"], + it.value()["name"], 0); + } + + // Send data list to anyone who is interested + emit gotMetadata(branches, obj_commits.dump(), releases, tags, default_branch); + break; + } } // Delete reply later, i.e. after returning from this slot function diff --git a/src/RemoteDatabase.h b/src/RemoteDatabase.h index 86662844..424262aa 100644 --- a/src/RemoteDatabase.h +++ b/src/RemoteDatabase.h @@ -15,6 +15,52 @@ class QHttpMultiPart; class QFile; struct sqlite3; +class RemoteMetadataBranchInfo +{ +public: + RemoteMetadataBranchInfo(const std::string& _name, const std::string& _commit_id, const std::string& _description, unsigned int _commit_count) : + name(_name), + commit_id(_commit_id), + description(_description), + commit_count(_commit_count) + {} + RemoteMetadataBranchInfo() : + commit_count(0) + {} + + std::string name; + std::string commit_id; + std::string description; + unsigned int commit_count; +}; + +class RemoteMetadataReleaseInfo +{ +public: + RemoteMetadataReleaseInfo(const std::string& _name, const std::string& _commit_id, const std::string& _date, + const std::string& _description, const std::string& _email, + const std::string& _user_name, unsigned int _size) : + name(_name), + commit_id(_commit_id), + date(_date), + description(_description), + email(_email), + user_name(_user_name), + size(_size) + {} + RemoteMetadataReleaseInfo() : + size(0) + {} + + std::string name; + std::string commit_id; + std::string date; + std::string description; + std::string email; + std::string user_name; + unsigned long size; +}; + class RemoteDatabase : public QObject { Q_OBJECT @@ -43,6 +89,7 @@ public: RequestTypePush, RequestTypeLicenceList, RequestTypeBranchList, + RequestTypeMetadata, }; void fetch(const QString& url, RequestType type, const QString& clientCert = QString(), QVariant userdata = QVariant()); @@ -98,6 +145,9 @@ signals: void gotCurrentVersion(QString version, QString url); void gotLicenceList(std::vector> licences); void gotBranchList(std::vector branches, std::string default_branch); + void gotMetadata(std::vector branches, std::string commits, + std::vector releases, std::vector tags, + std::string default_branch); // The uploadFinished() signal is emitted when a push() call is finished, i.e. a database upload has completed. void uploadFinished(std::string url); diff --git a/src/RemoteDock.cpp b/src/RemoteDock.cpp index 91564299..c0ac203b 100644 --- a/src/RemoteDock.cpp +++ b/src/RemoteDock.cpp @@ -1,10 +1,12 @@ #include #include #include +#include #include "RemoteDock.h" #include "ui_RemoteDock.h" #include "Settings.h" +#include "RemoteCommitsModel.h" #include "RemoteDatabase.h" #include "RemoteLocalFilesModel.h" #include "RemoteModel.h" @@ -18,13 +20,15 @@ RemoteDock::RemoteDock(MainWindow* parent) mainWindow(parent), remoteDatabase(parent->getRemote()), remoteModel(new RemoteModel(this, parent->getRemote())), - remoteLocalFilesModel(new RemoteLocalFilesModel(this, parent->getRemote())) + remoteLocalFilesModel(new RemoteLocalFilesModel(this, parent->getRemote())), + remoteCommitsModel(new RemoteCommitsModel(this)) { ui->setupUi(this); // Set models ui->treeRemote->setModel(remoteModel); ui->treeLocal->setModel(remoteLocalFilesModel); + ui->treeDatabaseCommits->setModel(remoteCommitsModel); // When a database has been downloaded and must be opened, notify users of this class connect(&remoteDatabase, &RemoteDatabase::openFile, this, &RemoteDock::openFile); @@ -37,6 +41,9 @@ RemoteDock::RemoteDock(MainWindow* parent) // Whenever a new directory listing has been parsed, check if it was a new root dir and, if so, open the user's directory connect(remoteModel, &RemoteModel::directoryListingParsed, this, &RemoteDock::newDirectoryNode); + // Show metadata for a database when we get it + connect(&remoteDatabase, &RemoteDatabase::gotMetadata, this, &RemoteDock::showMetadata); + // When the Preferences link is clicked in the no-certificates-label, open the preferences dialog. For other links than the ones we know, // just open them in a web browser connect(ui->labelNoCert, &QLabel::linkActivated, [this](const QString& link) { @@ -50,6 +57,12 @@ RemoteDock::RemoteDock(MainWindow* parent) } }); + // When changing the current branch in the branches combo box, update the tree view accordingly + connect(ui->comboDatabaseBranch, static_cast(&QComboBox::currentIndexChanged), [this](int /*index*/) { + remoteCommitsModel->refresh(current_commit_json, ui->comboDatabaseBranch->currentData().toString().toStdString()); + ui->treeDatabaseCommits->expandAll(); + }); + // Initial setup reloadSettings(); } @@ -227,6 +240,7 @@ void RemoteDock::fileOpened(const QString& filename) info = remoteDatabase.localGetLocalFileInfo(filename); // Copy information to view + remoteCommitsModel->clear(); ui->labelDatabaseUser->setText(info.user_name()); ui->labelDatabaseFile->setText(QString::fromStdString(info.name)); ui->labelDatabaseBranch->setText(QString::fromStdString(info.branch)); @@ -239,7 +253,33 @@ void RemoteDock::fileOpened(const QString& filename) if(QString::fromStdString(info.identity) != QFileInfo(remoteModel->currentClientCertificate()).fileName()) ui->comboUser->setCurrentIndex(ui->comboUser->findData("/" + QString::fromStdString(info.identity), Qt::UserRole, Qt::MatchEndsWith)); + // Query more information on database from server + QUrl url(remoteDatabase.getInfoFromClientCert(remoteModel->currentClientCertificate(), RemoteDatabase::CertInfoServer) + "/metadata/get"); + QUrlQuery query; + query.addQueryItem("username", info.user_name()); + query.addQueryItem("folder", "/"); + query.addQueryItem("dbname", QString::fromStdString(info.name)); + url.setQuery(query); + remoteDatabase.fetch(url.toString(), RemoteDatabase::RequestTypeMetadata, remoteModel->currentClientCertificate()); + // Switch to "Current Database" tab ui->tabs->setCurrentIndex(2); } } + +void RemoteDock::showMetadata(const std::vector& branches, const std::string& commits, + const std::vector& releases, const std::vector& tags, + const std::string& /*default_branch*/) +{ + current_commit_json = commits; + + // Fill branches combo box + ui->comboDatabaseBranch->clear(); + for(const auto& branch : branches) + ui->comboDatabaseBranch->addItem(QString::fromStdString(branch.name), QString::fromStdString(branch.commit_id)); + for(const auto& release : releases) + ui->comboDatabaseBranch->addItem(QString::fromStdString(release.name) + " (" + tr("release") + ")", QString::fromStdString(release.commit_id)); + for(const auto& tag : tags) + ui->comboDatabaseBranch->addItem(QString::fromStdString(tag.name) + " (" + tr("tag") + ")", QString::fromStdString(tag.commit_id)); + ui->comboDatabaseBranch->setCurrentIndex(ui->comboDatabaseBranch->findText(ui->labelDatabaseBranch->text())); +} diff --git a/src/RemoteDock.h b/src/RemoteDock.h index c0a7d8e3..5f1f6feb 100644 --- a/src/RemoteDock.h +++ b/src/RemoteDock.h @@ -3,7 +3,10 @@ #include +class RemoteCommitsModel; class RemoteDatabase; +class RemoteMetadataBranchInfo; +class RemoteMetadataReleaseInfo; class RemoteLocalFilesModel; class RemoteModel; class MainWindow; @@ -39,6 +42,9 @@ private slots: void newDirectoryNode(const QModelIndex& parent); void switchToMainView(); void openLocalFile(const QModelIndex& idx); + void showMetadata(const std::vector& branches, const std::string& commits, + const std::vector& releases, const std::vector& tags, + const std::string& default_branch); signals: void openFile(QString file); @@ -51,6 +57,9 @@ private: RemoteDatabase& remoteDatabase; RemoteModel* remoteModel; RemoteLocalFilesModel* remoteLocalFilesModel; + RemoteCommitsModel* remoteCommitsModel; + + std::string current_commit_json; void refreshLocalFileList(); }; diff --git a/src/RemoteDock.ui b/src/RemoteDock.ui index 6594f86e..9298877c 100644 --- a/src/RemoteDock.ui +++ b/src/RemoteDock.ui @@ -7,7 +7,7 @@ 0 0 534 - 298 + 357 @@ -129,56 +129,91 @@ Current Database - - - - - User + + + + + Clone + + + + + User + + + + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Database + + + + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Branch + + + + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + - - - - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Database - - - - - - - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Branch - - - - - - - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + Commits + + + + + + + Commits for + + + + + + + + + + + + diff --git a/src/src.pro b/src/src.pro index 782aafd0..c42b5d30 100644 --- a/src/src.pro +++ b/src/src.pro @@ -22,6 +22,7 @@ CONFIG(unittest) { HEADERS += \ ImageViewer.h \ + RemoteCommitsModel.h \ RemoteLocalFilesModel.h \ sqlitedb.h \ MainWindow.h \ @@ -84,6 +85,7 @@ HEADERS += \ SOURCES += \ ImageViewer.cpp \ + RemoteCommitsModel.cpp \ RemoteLocalFilesModel.cpp \ sqlitedb.cpp \ MainWindow.cpp \