Render asynchronous PM history and submit desktop file sends
This commit is contained in:
1 parent
1ceb09062f
commit
7fee3ec551
1 file changed
+65
-31
@@ -17,6 +17,7 @@
|
||||
#include <QDateTime>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QFile>
|
||||
#include <QScrollBar>
|
||||
#include <QApplication>
|
||||
@@ -37,6 +38,7 @@ extern "C" {
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include "chat/chat_core.h"
|
||||
#include "dm/dm_core.h"
|
||||
#include "dm/dm_media.h"
|
||||
#include "video/video.h"
|
||||
#include "radio/radio.h"
|
||||
#include "../../lib/u_async.h"
|
||||
@@ -501,6 +503,20 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent
|
||||
connect(m_inputBar, &InputBar::sendFileAttachment, this, [this](const QString& mediaSrc, const QString& mediaDest,
|
||||
const QString& displayName) {
|
||||
if (m_currentChannelId.isEmpty()) return;
|
||||
if (isDmChannel(m_currentChannelId)) {
|
||||
auto* req = (struct dm_file_req*)u_calloc(1, sizeof(struct dm_file_req));
|
||||
if (!req) { GUI_ERROR("PM file request allocation failed"); return; }
|
||||
req->inst = gui_bridge_get_inst();
|
||||
QByteArray conv = dmConvId(m_currentChannelId).toUtf8(), path = mediaSrc.toUtf8(), name = displayName.toUtf8();
|
||||
if (path.size() >= (int)sizeof(req->path) || name.size() >= (int)sizeof(req->name)) {
|
||||
GUI_ERROR("PM attachment path or name is too long"); u_free(req); return;
|
||||
}
|
||||
snprintf(req->conv, sizeof(req->conv), "%s", conv.constData());
|
||||
snprintf(req->path, sizeof(req->path), "%s", path.constData());
|
||||
snprintf(req->name, sizeof(req->name), "%s", name.constData());
|
||||
gui_bridge_post_uasync_fn(dm_send_file_trampoline, req);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray textData = displayName.toUtf8();
|
||||
auto ts = QDateTime::currentMSecsSinceEpoch();
|
||||
@@ -663,10 +679,10 @@ void MessageList::loadChannel(const QString& channelId) {
|
||||
}
|
||||
if (m_channelTitle) m_channelTitle->setText(title);
|
||||
|
||||
/* DM: только текст; сообщения расшифровываются в dm_core (uasync-поток) */
|
||||
m_inputBar->setDmMode(isDmChannel(channelId));
|
||||
/* История и состояния PM поступают из ядра в одном снимке. */
|
||||
if (isDmChannel(channelId)) {
|
||||
m_radioBtn->setEnabled(false);
|
||||
m_inputBar->setMediaEnabled(false);
|
||||
requestDmMessages(channelId);
|
||||
if (prevFocus)
|
||||
prevFocus->setFocus();
|
||||
@@ -1005,7 +1021,8 @@ void MessageList::onMessageContextMenu(const QPoint& pos) {
|
||||
QString ct = idx.data(MsgContentTypeRole).toString();
|
||||
bool isVideo = (ct == "video/mp4");
|
||||
bool isImage = ct.startsWith("image/");
|
||||
if (!isVideo && !isImage) return;
|
||||
bool isDmFile = isDmChannel(idx.data(MsgChannelIdRole).toString()) && ct == "application/octet-stream";
|
||||
if (!isVideo && !isImage && !isDmFile) return;
|
||||
|
||||
QString filePath = idx.data(MsgVoiceFileRole).toString();
|
||||
QString displayName = idx.data(MsgFileDisplayNameRole).toString();
|
||||
@@ -1015,7 +1032,7 @@ void MessageList::onMessageContextMenu(const QPoint& pos) {
|
||||
QAction* saveAct = menu.addAction(QString::fromUtf8("Сохранить в файл"));
|
||||
saveAct->setEnabled(downloaded);
|
||||
QAction* dlAct = nullptr;
|
||||
if (!downloaded) dlAct = menu.addAction(QString::fromUtf8("Скачать"));
|
||||
if (!downloaded && !isDmFile) dlAct = menu.addAction(QString::fromUtf8("Скачать"));
|
||||
|
||||
QAction* chosen = menu.exec(m_view->viewport()->mapToGlobal(pos));
|
||||
|
||||
@@ -1024,17 +1041,15 @@ void MessageList::onMessageContextMenu(const QPoint& pos) {
|
||||
if (base.isEmpty()) base = QFileInfo(filePath).fileName();
|
||||
QString dest = QFileDialog::getSaveFileName(this, QString::fromUtf8("Сохранить файл"), base);
|
||||
if (dest.isEmpty()) return;
|
||||
if (!QFile::copy(filePath, dest)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "MessageList: save failed src=%s dst=%s",
|
||||
qPrintable(filePath), qPrintable(dest));
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "MessageList: saved %s -> %s",
|
||||
qPrintable(filePath), qPrintable(dest));
|
||||
}
|
||||
std::thread([filePath, dest]() {
|
||||
if (!QFile::copy(filePath, dest))
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "MessageList: save failed src=%s dst=%s", qPrintable(filePath), qPrintable(dest));
|
||||
else DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "MessageList: saved %s -> %s", qPrintable(filePath), qPrintable(dest));
|
||||
}).detach();
|
||||
return;
|
||||
}
|
||||
|
||||
if (chosen == dlAct) {
|
||||
if (dlAct && chosen == dlAct) {
|
||||
qint64 msgId = idx.data(MsgIdRole).toLongLong();
|
||||
QString chId = idx.data(MsgChannelIdRole).toString();
|
||||
if (chId.isEmpty() || msgId <= 0) return;
|
||||
@@ -1076,27 +1091,34 @@ void MessageList::onDmMessages(const uint8_t* data, int len) {
|
||||
QString convId = QString::fromUtf8((const char*)data + 1, cl);
|
||||
if (m_currentChannelId != QStringLiteral("dm:") + convId) return; /* stale response */
|
||||
|
||||
uint16_t count; memcpy(&count, data + 1 + cl, 2);
|
||||
const uint8_t* p = data + 1 + cl + 2;
|
||||
const uint8_t* end = data + len;
|
||||
|
||||
QJsonParseError error;
|
||||
auto document = QJsonDocument::fromJson(QByteArray((const char*)data + 1 + cl, len - 1 - cl), &error);
|
||||
if (error.error != QJsonParseError::NoError || !document.isArray()) {
|
||||
GUI_ERROR("Invalid PM history JSON: %s", qPrintable(error.errorString())); return;
|
||||
}
|
||||
auto* scrollbar = m_view->verticalScrollBar();
|
||||
bool atBottom = m_model->rowCount() == 0 || scrollbar->value() >= scrollbar->maximum();
|
||||
int previousScroll = scrollbar->value();
|
||||
m_model->clear();
|
||||
m_avatarCache.clear();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (p + 1 + 8 + 8 + 8 + 1 > end) break;
|
||||
int dir = p[0]; p += 1;
|
||||
uint64_t seq; memcpy(&seq, p, 8); p += 8;
|
||||
uint64_t ts; memcpy(&ts, p, 8); p += 8;
|
||||
quint64 author; memcpy(&author, p, 8); p += 8;
|
||||
uint8_t ctl = *p; p += 1;
|
||||
if (p + ctl > end) break;
|
||||
p += ctl; /* ct игнорируем: DM v1 — текст */
|
||||
if (p + 2 > end) break;
|
||||
uint16_t dlen; memcpy(&dlen, p, 2); p += 2;
|
||||
if (p + dlen > end) break;
|
||||
QString text = QString::fromUtf8((const char*)p, dlen);
|
||||
p += dlen;
|
||||
for (const auto& entry : document.array()) {
|
||||
auto message = entry.toObject();
|
||||
int dir = message.value("dir").toInt();
|
||||
qint64 seq = (qint64)message.value("seq").toDouble();
|
||||
qint64 ts = (qint64)message.value("ts").toDouble();
|
||||
quint64 author = message.value("author").toString().toULongLong();
|
||||
QString text = message.value("data").toString();
|
||||
bool file = message.value("ct").toString() == "file";
|
||||
QString state = message.value("media_status").toString();
|
||||
QString status;
|
||||
if (file) {
|
||||
if (state == "delivered") status = QString::fromUtf8("Доставлено");
|
||||
else if (state == "expired") status = QString::fromUtf8("Срок хранения истёк");
|
||||
else if (dir == 1) status = QString::fromUtf8("Ожидает доставки");
|
||||
else if (state == "ready") status = QString::fromUtf8("Получено");
|
||||
else status = QString::fromUtf8("Загрузка…");
|
||||
} else if (dir == 1) status = message.value("pending").toBool() ? QString::fromUtf8("Ожидает доставки") : QString::fromUtf8("Доставлено");
|
||||
|
||||
AccountRow acc = m_db ? m_db->getAccount(author) : AccountRow();
|
||||
QString name = authorName(author);
|
||||
@@ -1106,6 +1128,7 @@ void MessageList::onDmMessages(const uint8_t* data, int len) {
|
||||
m_avatarCache[author] = color;
|
||||
auto avatar = makeAvatar(color, letter);
|
||||
auto time = QDateTime::fromMSecsSinceEpoch((qint64)ts).toString("HH:mm");
|
||||
if (!file && !status.isEmpty()) time += QStringLiteral(" · ") + status;
|
||||
|
||||
auto *item = new QStandardItem();
|
||||
setMsg(item, name, text, time, avatar, false,
|
||||
@@ -1113,10 +1136,21 @@ void MessageList::onDmMessages(const uint8_t* data, int len) {
|
||||
item->setData((qint64)seq, MsgIdRole);
|
||||
item->setData(QVariant::fromValue(author), MsgAuthorNodeIdRole);
|
||||
item->setData(dir == 1 ? 1 : 0, MsgIsOutgoingRole);
|
||||
item->setData(m_currentChannelId, MsgChannelIdRole);
|
||||
item->setData(file ? QStringLiteral("application/octet-stream") : QStringLiteral("text/plain"), MsgContentTypeRole);
|
||||
if (file) {
|
||||
QString path = message.value("media_path").toString();
|
||||
item->setData(path, MsgVoiceFileRole);
|
||||
item->setData(text, MsgFileDisplayNameRole);
|
||||
item->setData((qint64)message.value("media_size").toDouble(), MsgFileSizeRole);
|
||||
item->setData(path.isEmpty() ? (state == "expired" ? 0 : 1) : 2, MsgFileDownloadStateRole);
|
||||
item->setData(status, MsgDeliveryStateRole);
|
||||
}
|
||||
m_model->appendRow(item);
|
||||
}
|
||||
|
||||
m_view->scrollToBottom();
|
||||
if (atBottom) m_view->scrollToBottom();
|
||||
else scrollbar->setValue(previousScroll);
|
||||
}
|
||||
|
||||
/* ── рация ── */
|
||||
|
||||
Reference in new issue
Block a user