3 changed files with 121 additions and 0 deletions
@ -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…
Reference in new issue