Render locked group attachments and local decrypted media
This commit is contained in:
1 parent
9f813424bb
commit
2b7def369d
3 files changed
+77
-5
No files matched your search
@@ -50,6 +50,7 @@ enum MessageDataRole {
|
|||||||
MsgRawTimestampRole = Qt::UserRole + 39,
|
MsgRawTimestampRole = Qt::UserRole + 39,
|
||||||
MsgReplyToRole = Qt::UserRole + 40,
|
MsgReplyToRole = Qt::UserRole + 40,
|
||||||
MsgHighlightRole = Qt::UserRole + 41,
|
MsgHighlightRole = Qt::UserRole + 41,
|
||||||
|
MsgEncryptedRole = Qt::UserRole + 42,
|
||||||
};
|
};
|
||||||
|
|
||||||
class MessageDelegate : public QStyledItemDelegate {
|
class MessageDelegate : public QStyledItemDelegate {
|
||||||
|
|||||||
@@ -34,6 +34,11 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QToolTip>
|
#include <QToolTip>
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -44,6 +49,7 @@ static std::vector<std::unique_ptr<LottieIcon>> s_icons;
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#include "../transport/gui_bridge.h"
|
#include "../transport/gui_bridge.h"
|
||||||
#include "chat/chat_core.h"
|
#include "chat/chat_core.h"
|
||||||
|
#include "chat/chat_attachment.h"
|
||||||
#include "chat/chat_poll.h"
|
#include "chat/chat_poll.h"
|
||||||
#include "dm/dm_core.h"
|
#include "dm/dm_core.h"
|
||||||
#include "dm/dm_media.h"
|
#include "dm/dm_media.h"
|
||||||
@@ -136,7 +142,8 @@ static QString contentTypeFromJson(const QByteArray& data) {
|
|||||||
|
|
||||||
static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
|
static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
|
||||||
const QString& contentType, const QString& mediaDirBase,
|
const QString& contentType, const QString& mediaDirBase,
|
||||||
const QString& channelId, const QByteArray& localAttrs) {
|
const QString& channelId, const QByteArray& localAttrs,
|
||||||
|
DbManager* db, const QByteArray& messageId) {
|
||||||
QByteArray body = data;
|
QByteArray body = data;
|
||||||
if (body.startsWith('*')) {
|
if (body.startsWith('*')) {
|
||||||
int pipePos = body.indexOf('|');
|
int pipePos = body.indexOf('|');
|
||||||
@@ -153,6 +160,56 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
|
|||||||
item->setData(ct, MsgContentTypeRole);
|
item->setData(ct, MsgContentTypeRole);
|
||||||
item->setData(channelId, MsgChannelIdRole);
|
item->setData(channelId, MsgChannelIdRole);
|
||||||
|
|
||||||
|
if (ct == QLatin1String(ENCRYPTED_ATTACHMENT_TYPE)) {
|
||||||
|
item->setData(true, MsgEncryptedRole);
|
||||||
|
item->setData(QStringLiteral("application/octet-stream"), MsgContentTypeRole);
|
||||||
|
item->setData(QCoreApplication::translate("ChatGui", "Encrypted attachment"), MsgFileDisplayNameRole);
|
||||||
|
item->setData(QString(), MsgVoiceFileRole);
|
||||||
|
item->setData(0, MsgFileDownloadStateRole);
|
||||||
|
const auto parts = QString::fromUtf8(body).split('|');
|
||||||
|
if (parts.size() > 3) { item->setData(parts[1].toLongLong(), MsgFileSizeRole); item->setData(parts[3].toInt(), MsgMediaNumBlocksRole); }
|
||||||
|
sqlite3_stmt* st = db ? db->prepareOrNull("SELECT a.ready,a.downloaded,a.decrypting,a.error,a.plain_path,a.info,a.content_type,"
|
||||||
|
"COALESCE(r.state,-1),length(a.key)=32 FROM attachment_keys a LEFT JOIN attachment_key_requests r"
|
||||||
|
" ON r.channel=a.channel AND r.message_id=a.message_id AND r.incoming=0 WHERE a.channel=? AND a.message_id=?") : nullptr;
|
||||||
|
if (!st) return;
|
||||||
|
const QByteArray channel = channelId.toUtf8();
|
||||||
|
sqlite3_bind_text(st, 1, channel.constData(), -1, SQLITE_STATIC); sqlite3_bind_blob(st, 2, messageId.constData(), messageId.size(), SQLITE_STATIC);
|
||||||
|
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||||
|
const bool ready = sqlite3_column_int(st, 0), downloaded = sqlite3_column_int(st, 1), decrypting = sqlite3_column_int(st, 2);
|
||||||
|
const bool error = sqlite3_column_int(st, 3), key = sqlite3_column_int(st, 8);
|
||||||
|
const int state = sqlite3_column_int(st, 7);
|
||||||
|
const QString path = DbManager::colText(st, 4);
|
||||||
|
struct attachment_info info = {};
|
||||||
|
const bool available = ready && QFile::exists(path) &&
|
||||||
|
!attachment_decode((const uint8_t*)sqlite3_column_blob(st, 5), sqlite3_column_bytes(st, 5), &info);
|
||||||
|
if (available) {
|
||||||
|
item->setData(DbManager::colText(st, 6), MsgContentTypeRole);
|
||||||
|
item->setData(QString::fromUtf8(info.name), MsgFileDisplayNameRole);
|
||||||
|
item->setData(QFileInfo(path).size(), MsgFileSizeRole); item->setData(QFileInfo(path).size(), MsgVoiceFileSizeRole);
|
||||||
|
item->setData(path, MsgVoiceFileRole); item->setData(2, MsgFileDownloadStateRole);
|
||||||
|
item->setData(QCoreApplication::translate("ChatGui", "Encrypted"), MsgDeliveryStateRole);
|
||||||
|
item->setData(QString::number(info.duration_ms / 1000.0, 'f', 1) + "s", MsgVoiceDurationRole);
|
||||||
|
item->setData(QString::fromLatin1(info.waveform), MsgVoiceWaveformRole);
|
||||||
|
item->setData(info.duration_ms, MsgVideoDurationMsRole);
|
||||||
|
item->setData(info.width, MsgVideoWidthRole); item->setData(info.height, MsgVideoHeightRole);
|
||||||
|
item->setData(info.width, MsgImageWidthRole); item->setData(info.height, MsgImageHeightRole);
|
||||||
|
} else {
|
||||||
|
QString status;
|
||||||
|
if (error) status = QCoreApplication::translate("ChatGui", "Decryption failed; click to retry");
|
||||||
|
else if (state == 3) status = QCoreApplication::translate("ChatGui", "Access denied by author");
|
||||||
|
else if (decrypting || (downloaded && key)) status = QCoreApplication::translate("ChatGui", "Decrypting…");
|
||||||
|
else if (state == 1) status = QCoreApplication::translate("ChatGui", "Awaiting author's approval");
|
||||||
|
else if (state == 0) status = QCoreApplication::translate("ChatGui", "Sending key request…");
|
||||||
|
else if (downloaded) status = QCoreApplication::translate("ChatGui", "Requesting decryption…");
|
||||||
|
else status = QCoreApplication::translate("ChatGui", "Download encrypted attachment");
|
||||||
|
item->setData(status, MsgDeliveryStateRole);
|
||||||
|
item->setData(downloaded && !error ? 1 : 0, MsgFileDownloadStateRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_finalize(st);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ct == "audio/opus") {
|
if (ct == "audio/opus") {
|
||||||
QString raw = QString::fromUtf8(body);
|
QString raw = QString::fromUtf8(body);
|
||||||
if (raw.startsWith('{')) return;
|
if (raw.startsWith('{')) return;
|
||||||
@@ -379,6 +436,11 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
|
|||||||
m_channelTitle->setStyleSheet("font-weight: bold; font-size: 14px;");
|
m_channelTitle->setStyleSheet("font-weight: bold; font-size: 14px;");
|
||||||
m_channelTitle->setText(QCoreApplication::translate("ChatGui", "Channel"));
|
m_channelTitle->setText(QCoreApplication::translate("ChatGui", "Channel"));
|
||||||
hl->addWidget(m_channelTitle, 1);
|
hl->addWidget(m_channelTitle, 1);
|
||||||
|
m_keyRequestsButton = new QPushButton(header);
|
||||||
|
m_keyRequestsButton->setObjectName("attachmentKeyRequestsButton");
|
||||||
|
m_keyRequestsButton->hide();
|
||||||
|
connect(m_keyRequestsButton, &QPushButton::clicked, this, &MessageList::showKeyRequests);
|
||||||
|
hl->addWidget(m_keyRequestsButton);
|
||||||
m_talkingLabel = new QLabel(header);
|
m_talkingLabel = new QLabel(header);
|
||||||
m_talkingLabel->setStyleSheet("color: #27ae60; font-size: 12px;");
|
m_talkingLabel->setStyleSheet("color: #27ae60; font-size: 12px;");
|
||||||
m_talkingLabel->hide();
|
m_talkingLabel->hide();
|
||||||
@@ -780,6 +842,8 @@ void MessageList::loadChannel(const QString& channelId) {
|
|||||||
m_avatarCache.clear();
|
m_avatarCache.clear();
|
||||||
if (m_currentChannelId != channelId) { m_feedback.clear(); m_pendingPoll.clear(); }
|
if (m_currentChannelId != channelId) { m_feedback.clear(); m_pendingPoll.clear(); }
|
||||||
m_currentChannelId = channelId;
|
m_currentChannelId = channelId;
|
||||||
|
if (!sameChannel && m_keyRequestsDialog) m_keyRequestsDialog->close();
|
||||||
|
refreshKeyRequests();
|
||||||
clearStreams(); watchStreams();
|
clearStreams(); watchStreams();
|
||||||
m_groupMenuButton->setVisible(channelId.toULongLong() != 0);
|
m_groupMenuButton->setVisible(channelId.toULongLong() != 0);
|
||||||
clearDmCallRoute();
|
clearDmCallRoute();
|
||||||
@@ -855,7 +919,7 @@ void MessageList::loadChannel(const QString& channelId) {
|
|||||||
item->setData(QVariant::fromValue(m.authorNodeId), MsgAuthorNodeIdRole);
|
item->setData(QVariant::fromValue(m.authorNodeId), MsgAuthorNodeIdRole);
|
||||||
item->setData(m.isOutgoing ? 1 : 0, MsgIsOutgoingRole);
|
item->setData(m.isOutgoing ? 1 : 0, MsgIsOutgoingRole);
|
||||||
setVoiceMessageRoles(item, m.data, m.contentType,
|
setVoiceMessageRoles(item, m.data, m.contentType,
|
||||||
m_db ? m_db->dbPath() + "/media" : QString(), channelId, m.localAttrs);
|
m_db ? m_db->dbPath() + "/media" : QString(), channelId, m.localAttrs, m_db, m.messageId);
|
||||||
m_model->appendRow(item);
|
m_model->appendRow(item);
|
||||||
setupFeedback(item, m, channelId);
|
setupFeedback(item, m, channelId);
|
||||||
updateReply(item, m.replyTo);
|
updateReply(item, m.replyTo);
|
||||||
@@ -933,7 +997,7 @@ void MessageList::refresh() {
|
|||||||
item->setData(QVariant::fromValue(m.authorNodeId), MsgAuthorNodeIdRole);
|
item->setData(QVariant::fromValue(m.authorNodeId), MsgAuthorNodeIdRole);
|
||||||
item->setData(m.isOutgoing ? 1 : 0, MsgIsOutgoingRole);
|
item->setData(m.isOutgoing ? 1 : 0, MsgIsOutgoingRole);
|
||||||
setVoiceMessageRoles(item, m.data, m.contentType,
|
setVoiceMessageRoles(item, m.data, m.contentType,
|
||||||
m_db ? m_db->dbPath() + "/media" : QString(), m_currentChannelId, m.localAttrs);
|
m_db ? m_db->dbPath() + "/media" : QString(), m_currentChannelId, m.localAttrs, m_db, m.messageId);
|
||||||
m_model->appendRow(item);
|
m_model->appendRow(item);
|
||||||
setupFeedback(item, m, m_currentChannelId);
|
setupFeedback(item, m, m_currentChannelId);
|
||||||
updateReply(item, m.replyTo);
|
updateReply(item, m.replyTo);
|
||||||
@@ -1127,7 +1191,7 @@ void MessageList::onAttachmentDownloaded(const QString& chId, int64_t msgId) {
|
|||||||
|
|
||||||
QStandardItem* item = m_model->item(row);
|
QStandardItem* item = m_model->item(row);
|
||||||
setVoiceMessageRoles(item, m.data, m.contentType,
|
setVoiceMessageRoles(item, m.data, m.contentType,
|
||||||
m_db->dbPath() + "/media", chId, m.localAttrs);
|
m_db->dbPath() + "/media", chId, m.localAttrs, m_db, m.messageId);
|
||||||
item->setData(0, MsgMediaBlocksDoneRole);
|
item->setData(0, MsgMediaBlocksDoneRole);
|
||||||
item->setData(false, MsgVoiceDownloadPendingRole);
|
item->setData(false, MsgVoiceDownloadPendingRole);
|
||||||
|
|
||||||
@@ -1239,7 +1303,7 @@ void MessageList::onMessageContextMenu(const QPoint& pos) {
|
|||||||
QString ct = idx.data(MsgContentTypeRole).toString();
|
QString ct = idx.data(MsgContentTypeRole).toString();
|
||||||
bool isVideo = (ct == "video/mp4");
|
bool isVideo = (ct == "video/mp4");
|
||||||
bool isImage = ct.startsWith("image/");
|
bool isImage = ct.startsWith("image/");
|
||||||
bool isDmFile = isDmChannel(idx.data(MsgChannelIdRole).toString()) && ct == "application/octet-stream";
|
bool isDmFile = (isDmChannel(idx.data(MsgChannelIdRole).toString()) || idx.data(MsgEncryptedRole).toBool()) && ct == "application/octet-stream";
|
||||||
bool isDm = isDmChannel(idx.data(MsgChannelIdRole).toString());
|
bool isDm = isDmChannel(idx.data(MsgChannelIdRole).toString());
|
||||||
|
|
||||||
QString filePath = idx.data(MsgVoiceFileRole).toString();
|
QString filePath = idx.data(MsgVoiceFileRole).toString();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class QPushButton;
|
|||||||
class RadioPanel;
|
class RadioPanel;
|
||||||
class QDialog;
|
class QDialog;
|
||||||
class QMenu;
|
class QMenu;
|
||||||
|
class QTreeWidget;
|
||||||
struct MessageRow;
|
struct MessageRow;
|
||||||
|
|
||||||
class MessageList : public QWidget {
|
class MessageList : public QWidget {
|
||||||
@@ -45,6 +46,7 @@ public:
|
|||||||
void onFeedbackChanged(const uint8_t* data, int len);
|
void onFeedbackChanged(const uint8_t* data, int len);
|
||||||
void onFeedbackResult(const uint8_t* data, int len);
|
void onFeedbackResult(const uint8_t* data, int len);
|
||||||
void onMessageSubmitted(const uint8_t* data, int len);
|
void onMessageSubmitted(const uint8_t* data, int len);
|
||||||
|
void onAttachmentKeysChanged(const uint8_t* data, int len);
|
||||||
void onRadioTalk(quint64 groupId, quint64 srcNodeId, bool on); /* индикация говорящего */
|
void onRadioTalk(quint64 groupId, quint64 srcNodeId, bool on); /* индикация говорящего */
|
||||||
void onStreamSources(const uint8_t* data, int len);
|
void onStreamSources(const uint8_t* data, int len);
|
||||||
void clearStreams();
|
void clearStreams();
|
||||||
@@ -85,6 +87,11 @@ private:
|
|||||||
QPushButton* m_callBtn = nullptr;
|
QPushButton* m_callBtn = nullptr;
|
||||||
QPushButton* m_inviteBtn = nullptr;
|
QPushButton* m_inviteBtn = nullptr;
|
||||||
QPushButton* m_groupMenuButton = nullptr;
|
QPushButton* m_groupMenuButton = nullptr;
|
||||||
|
QPushButton* m_keyRequestsButton = nullptr;
|
||||||
|
QPointer<QDialog> m_keyRequestsDialog;
|
||||||
|
QTreeWidget* m_keyRequestsList = nullptr;
|
||||||
|
void refreshKeyRequests();
|
||||||
|
void showKeyRequests();
|
||||||
quint64 m_callPeer = 0;
|
quint64 m_callPeer = 0;
|
||||||
quint64 m_callGroup = 0;
|
quint64 m_callGroup = 0;
|
||||||
QPushButton* m_radioBtn = nullptr; /* шапка: переключатель рации */
|
QPushButton* m_radioBtn = nullptr; /* шапка: переключатель рации */
|
||||||
|
|||||||
Reference in new issue
Block a user