Add author request list with individual key approvals
This commit is contained in:
1 parent
2b7def369d
commit
7cffb15c73
3 files changed
+121
No files matched your search
@@ -6,6 +6,7 @@
|
|||||||
#include "chat_feedback.h"
|
#include "chat_feedback.h"
|
||||||
#include "member_sync.h"
|
#include "member_sync.h"
|
||||||
#include "../utun_instance.h"
|
#include "../utun_instance.h"
|
||||||
|
#include "../transport_layer/etcp_connections.h"
|
||||||
#include "../routing_layer/etcp_router.h"
|
#include "../routing_layer/etcp_router.h"
|
||||||
#include "../routing_layer/topo_node_sqlite.h"
|
#include "../routing_layer/topo_node_sqlite.h"
|
||||||
#include "../media_delivery/media_index.h"
|
#include "../media_delivery/media_index.h"
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ add_executable(vibechat WIN32
|
|||||||
src/messagedelegate.cpp
|
src/messagedelegate.cpp
|
||||||
src/message_links.cpp
|
src/message_links.cpp
|
||||||
src/messagelist.cpp
|
src/messagelist.cpp
|
||||||
|
src/attachment_key_ui.cpp
|
||||||
src/messagefeedback.cpp
|
src/messagefeedback.cpp
|
||||||
src/accountlist.cpp
|
src/accountlist.cpp
|
||||||
src/accountdelegate.cpp
|
src/accountdelegate.cpp
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/* Локальная очередь запросов автора. Чтение — GUI, решения — через uasync. */
|
||||||
|
#include "messagelist.h"
|
||||||
|
#include "debug_ui.h"
|
||||||
|
#include "../db/db_manager.h"
|
||||||
|
#include "../transport/gui_bridge.h"
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
extern "C" {
|
||||||
|
#include "chat/chat_attachment.h"
|
||||||
|
#include "lib/mem.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Счётчик и список строятся из durable таблицы, включая уже принятые решения. */
|
||||||
|
void MessageList::refreshKeyRequests() {
|
||||||
|
const bool group = !m_currentChannelId.isEmpty() && !isDmChannel(m_currentChannelId);
|
||||||
|
m_keyRequestsButton->setVisible(group);
|
||||||
|
if (!group || !m_db || !m_db->isOpen()) return;
|
||||||
|
const QByteArray channel = m_currentChannelId.toUtf8();
|
||||||
|
sqlite3_stmt* st = m_db->prepareOrNull("SELECT count(*) FROM attachment_key_requests WHERE channel=? AND incoming=1 AND state=0");
|
||||||
|
int count = 0;
|
||||||
|
if (st) {
|
||||||
|
sqlite3_bind_text(st, 1, channel.constData(), -1, SQLITE_STATIC);
|
||||||
|
if (sqlite3_step(st) == SQLITE_ROW) count = sqlite3_column_int(st, 0);
|
||||||
|
sqlite3_finalize(st);
|
||||||
|
}
|
||||||
|
QString title = QCoreApplication::translate("ChatGui", "Decryption requests");
|
||||||
|
m_keyRequestsButton->setText(count ? title + QStringLiteral(" · %1").arg(count) : title);
|
||||||
|
m_keyRequestsButton->setAccessibleName(m_keyRequestsButton->text());
|
||||||
|
if (!m_keyRequestsDialog || !m_keyRequestsList) return;
|
||||||
|
m_keyRequestsList->clear();
|
||||||
|
st = m_db->prepareOrNull("SELECT r.message_id,r.peer,r.state,r.created_at,a.info FROM attachment_key_requests r"
|
||||||
|
" JOIN attachment_keys a ON a.channel=r.channel AND a.message_id=r.message_id"
|
||||||
|
" WHERE r.channel=? AND r.incoming=1 ORDER BY r.state<>0,r.created_at DESC,r.peer LIMIT 500");
|
||||||
|
if (!st) return;
|
||||||
|
sqlite3_bind_text(st, 1, channel.constData(), -1, SQLITE_STATIC);
|
||||||
|
int rc;
|
||||||
|
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||||
|
const QByteArray message = DbManager::colBlob(st, 0);
|
||||||
|
const quint64 peer = quint64(sqlite3_column_int64(st, 1));
|
||||||
|
const int state = sqlite3_column_int(st, 2);
|
||||||
|
struct attachment_info info = {};
|
||||||
|
const bool valid = !attachment_decode((const uint8_t*)sqlite3_column_blob(st, 4), sqlite3_column_bytes(st, 4), &info);
|
||||||
|
auto* row = new QTreeWidgetItem(m_keyRequestsList);
|
||||||
|
row->setText(0, authorName(peer));
|
||||||
|
row->setToolTip(0, QStringLiteral("%1").arg(peer, 16, 16, QChar('0')));
|
||||||
|
row->setText(1, valid ? QString::fromUtf8(info.name) : QCoreApplication::translate("ChatGui", "Encrypted attachment"));
|
||||||
|
row->setData(1, Qt::UserRole, message.toHex());
|
||||||
|
row->setText(2, QDateTime::fromSecsSinceEpoch(sqlite3_column_int64(st, 3)).toString("dd.MM HH:mm"));
|
||||||
|
if (state == 0) {
|
||||||
|
auto* actions = new QWidget(m_keyRequestsList);
|
||||||
|
auto* layout = new QHBoxLayout(actions); layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
for (bool approve : {true, false}) {
|
||||||
|
auto* button = new QPushButton(QCoreApplication::translate("ChatGui", approve ? "Send key" : "Decline"), actions);
|
||||||
|
layout->addWidget(button);
|
||||||
|
connect(button, &QPushButton::clicked, this, [this, channel, message, peer, approve, actions]() {
|
||||||
|
if (!gui_bridge_get_inst() || !gui_bridge_is_uasync_ready() || message.size() != 32) return;
|
||||||
|
auto* request = (struct chat_attachment_decision*)u_calloc(1, sizeof(struct chat_attachment_decision));
|
||||||
|
if (!request) { GUI_ERROR("attachment keys: decision allocation failed"); return; }
|
||||||
|
request->inst = gui_bridge_get_inst(); request->peer = peer; request->approve = approve;
|
||||||
|
snprintf(request->channel, sizeof(request->channel), "%s", channel.constData());
|
||||||
|
memcpy(request->message, message.constData(), 32);
|
||||||
|
actions->setEnabled(false);
|
||||||
|
gui_bridge_post_uasync_fn(chat_attachment_decision_trampoline, request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
m_keyRequestsList->setItemWidget(row, 3, actions);
|
||||||
|
} else row->setText(3, QCoreApplication::translate("ChatGui", state == 4 ? "Key delivered" : state == 3 ? "Declined" : "Waiting to deliver key"));
|
||||||
|
}
|
||||||
|
if (rc != SQLITE_DONE) GUI_ERROR("attachment keys: request list read failed rc=%d", rc);
|
||||||
|
sqlite3_finalize(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Обычное немодальное окно: каждый запрос подтверждается своей кнопкой. */
|
||||||
|
void MessageList::showKeyRequests() {
|
||||||
|
if (isDmChannel(m_currentChannelId) || m_currentChannelId.isEmpty()) return;
|
||||||
|
if (m_keyRequestsDialog) { m_keyRequestsDialog->raise(); m_keyRequestsDialog->activateWindow(); return; }
|
||||||
|
auto* dialog = new QDialog(this); dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
dialog->setObjectName("attachmentKeyRequestsDialog");
|
||||||
|
dialog->setWindowTitle(QCoreApplication::translate("ChatGui", "Decryption requests"));
|
||||||
|
dialog->resize(820, 400); m_keyRequestsDialog = dialog;
|
||||||
|
auto* layout = new QVBoxLayout(dialog);
|
||||||
|
auto* label = new QLabel(QCoreApplication::translate("ChatGui", "Send a key to let this participant open the attachment."), dialog);
|
||||||
|
label->setWordWrap(true); layout->addWidget(label);
|
||||||
|
m_keyRequestsList = new QTreeWidget(dialog); m_keyRequestsList->setObjectName("attachmentKeyRequestsList");
|
||||||
|
m_keyRequestsList->setHeaderLabels({QCoreApplication::translate("ChatGui", "Participant"), QCoreApplication::translate("ChatGui", "Attachment"),
|
||||||
|
QCoreApplication::translate("ChatGui", "Requested"), QCoreApplication::translate("ChatGui", "Decision")});
|
||||||
|
m_keyRequestsList->setRootIsDecorated(false); m_keyRequestsList->setAlternatingRowColors(true);
|
||||||
|
m_keyRequestsList->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||||
|
m_keyRequestsList->header()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||||
|
connect(m_keyRequestsList, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem* row, int) {
|
||||||
|
jumpToMessage(QString::fromLatin1(row->data(1, Qt::UserRole).toByteArray()));
|
||||||
|
});
|
||||||
|
layout->addWidget(m_keyRequestsList);
|
||||||
|
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, dialog);
|
||||||
|
connect(buttons, &QDialogButtonBox::rejected, dialog, &QDialog::close); layout->addWidget(buttons);
|
||||||
|
connect(dialog, &QDialog::finished, this, [this]() { m_keyRequestsList = nullptr; m_keyRequestsDialog = nullptr; });
|
||||||
|
refreshKeyRequests(); dialog->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Секретов в событии нет: UI перечитывает готовые локальные данные после commit. */
|
||||||
|
void MessageList::onAttachmentKeysChanged(const uint8_t* data, int len) {
|
||||||
|
if (!data || len < 2 || data[0] > 31 || (len != data[0] + 1 && len != data[0] + 2)) {
|
||||||
|
GUI_ERROR("attachment keys: invalid UI event bytes=%d", len); return;
|
||||||
|
}
|
||||||
|
const QString channel = QString::fromUtf8((const char*)data + 1, data[0]);
|
||||||
|
if (channel == m_currentChannelId) { refresh(); refreshKeyRequests(); }
|
||||||
|
if (len == data[0] + 2 && !data[len - 1]) QMessageBox::warning(this,
|
||||||
|
QCoreApplication::translate("ChatGui", "Decryption requests"),
|
||||||
|
QCoreApplication::translate("ChatGui", "Could not save the decision. Check membership and try again."));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user