Browse Source

Add author request list with individual key approvals

stream-multiview
evgeny 6 days ago
parent
commit
7cffb15c73
  1. 1
      src/chat/chat_attachment.c
  2. 1
      tools/chatgui/CMakeLists.txt
  3. 119
      tools/chatgui/src/attachment_key_ui.cpp

1
src/chat/chat_attachment.c

@ -6,6 +6,7 @@
#include "chat_feedback.h"
#include "member_sync.h"
#include "../utun_instance.h"
#include "../transport_layer/etcp_connections.h"
#include "../routing_layer/etcp_router.h"
#include "../routing_layer/topo_node_sqlite.h"
#include "../media_delivery/media_index.h"

1
tools/chatgui/CMakeLists.txt

@ -266,6 +266,7 @@ add_executable(vibechat WIN32
src/messagedelegate.cpp
src/message_links.cpp
src/messagelist.cpp
src/attachment_key_ui.cpp
src/messagefeedback.cpp
src/accountlist.cpp
src/accountdelegate.cpp

119
tools/chatgui/src/attachment_key_ui.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."));
}
Loading…
Cancel
Save