|
|
|
|
@ -34,6 +34,11 @@
|
|
|
|
|
#include <QMenu> |
|
|
|
|
#include <QFileDialog> |
|
|
|
|
#include <QToolTip> |
|
|
|
|
#include <QTreeWidget> |
|
|
|
|
#include <QHeaderView> |
|
|
|
|
#include <QDialog> |
|
|
|
|
#include <QDialogButtonBox> |
|
|
|
|
#include <QMessageBox> |
|
|
|
|
#include <vector> |
|
|
|
|
#include <algorithm> |
|
|
|
|
#include <memory> |
|
|
|
|
@ -44,6 +49,7 @@ static std::vector<std::unique_ptr<LottieIcon>> s_icons;
|
|
|
|
|
extern "C" { |
|
|
|
|
#include "../transport/gui_bridge.h" |
|
|
|
|
#include "chat/chat_core.h" |
|
|
|
|
#include "chat/chat_attachment.h" |
|
|
|
|
#include "chat/chat_poll.h" |
|
|
|
|
#include "dm/dm_core.h" |
|
|
|
|
#include "dm/dm_media.h" |
|
|
|
|
@ -136,7 +142,8 @@ static QString contentTypeFromJson(const QByteArray& data) {
|
|
|
|
|
|
|
|
|
|
static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data, |
|
|
|
|
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; |
|
|
|
|
if (body.startsWith('*')) { |
|
|
|
|
int pipePos = body.indexOf('|'); |
|
|
|
|
@ -153,6 +160,56 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
|
|
|
|
|
item->setData(ct, MsgContentTypeRole); |
|
|
|
|
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") { |
|
|
|
|
QString raw = QString::fromUtf8(body); |
|
|
|
|
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->setText(QCoreApplication::translate("ChatGui", "Channel")); |
|
|
|
|
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->setStyleSheet("color: #27ae60; font-size: 12px;"); |
|
|
|
|
m_talkingLabel->hide(); |
|
|
|
|
@ -780,6 +842,8 @@ void MessageList::loadChannel(const QString& channelId) {
|
|
|
|
|
m_avatarCache.clear(); |
|
|
|
|
if (m_currentChannelId != channelId) { m_feedback.clear(); m_pendingPoll.clear(); } |
|
|
|
|
m_currentChannelId = channelId; |
|
|
|
|
if (!sameChannel && m_keyRequestsDialog) m_keyRequestsDialog->close(); |
|
|
|
|
refreshKeyRequests(); |
|
|
|
|
clearStreams(); watchStreams(); |
|
|
|
|
m_groupMenuButton->setVisible(channelId.toULongLong() != 0); |
|
|
|
|
clearDmCallRoute(); |
|
|
|
|
@ -855,7 +919,7 @@ void MessageList::loadChannel(const QString& channelId) {
|
|
|
|
|
item->setData(QVariant::fromValue(m.authorNodeId), MsgAuthorNodeIdRole); |
|
|
|
|
item->setData(m.isOutgoing ? 1 : 0, MsgIsOutgoingRole); |
|
|
|
|
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); |
|
|
|
|
setupFeedback(item, m, channelId); |
|
|
|
|
updateReply(item, m.replyTo); |
|
|
|
|
@ -933,7 +997,7 @@ void MessageList::refresh() {
|
|
|
|
|
item->setData(QVariant::fromValue(m.authorNodeId), MsgAuthorNodeIdRole); |
|
|
|
|
item->setData(m.isOutgoing ? 1 : 0, MsgIsOutgoingRole); |
|
|
|
|
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); |
|
|
|
|
setupFeedback(item, m, m_currentChannelId); |
|
|
|
|
updateReply(item, m.replyTo); |
|
|
|
|
@ -1127,7 +1191,7 @@ void MessageList::onAttachmentDownloaded(const QString& chId, int64_t msgId) {
|
|
|
|
|
|
|
|
|
|
QStandardItem* item = m_model->item(row); |
|
|
|
|
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(false, MsgVoiceDownloadPendingRole); |
|
|
|
|
|
|
|
|
|
@ -1239,7 +1303,7 @@ void MessageList::onMessageContextMenu(const QPoint& pos) {
|
|
|
|
|
QString ct = idx.data(MsgContentTypeRole).toString(); |
|
|
|
|
bool isVideo = (ct == "video/mp4"); |
|
|
|
|
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()); |
|
|
|
|
|
|
|
|
|
QString filePath = idx.data(MsgVoiceFileRole).toString(); |
|
|
|
|
|