feat: media_async + media_index modules, media_files table, tests

- media_async: thread-per-task async engine with primitives (sha256, sign, copy, uuid, file_size)
- media_index: media_files DB table with register_async for indexing chunks
- chat_msg: async media submission via register_async, simplified chat_msg_submit fields
- voice_recorder/attachment_sender: write single file (no chunk splitting on caller side)
- inputbar/messagelist: updated to new simplified submit interface
- test_media_async: 4-thread stress test (200K ops / 2 sec, 0 errors)
- test_media_index: 9 cases covering commit edge cases + async flow
- build: Makefile.am, CMakeLists.txt, utun_sources.cmake updated
This commit is contained in:
evgeny committed 2026-07-29 14:15:10 +03:00
1 parent ce5a84da24
commit 65dbfa6813
43 files changed
+1983 -391

No files matched your search

+79
View File
@@ -185,6 +185,16 @@ static int measureTextHeight(const QFont &font, const QString &text, int width)
return r.height();
}
static QString formatFileSize(qint64 bytes) {
if (bytes < 1024) return QString("%1B").arg(bytes);
double kb = bytes / 1024.0;
if (kb < 1024.0) return QString("%1KB").arg(kb, 0, 'f', kb < 10.0 ? 1 : 0);
double mb = kb / 1024.0;
if (mb < 1024.0) return QString("%1MB").arg(mb, 0, 'f', mb < 10.0 ? 1 : 0);
double gb = mb / 1024.0;
return QString("%1GB").arg(gb, 0, 'f', 1);
}
static int calcMaxLineWidth(const QFont &font, const QString &text, int availW) {
int maxW = 0;
int pos = 0;
@@ -259,6 +269,35 @@ MessageDelegate::Layout MessageDelegate::calcLayout(
return L;
}
/* File attachment detection */
L.isFile = (contentType == "application/octet-stream");
if (L.isFile) {
QFontMetrics fm(option.font);
int th = fm.height();
int ih = 24; /* icon height */
L.bubbleWidth = 230;
int bubbleH = kPadTop + ih + 2 + th + kPadBot;
L.totalHeight = qMax(bubbleH, kAvatar) + kItemGap;
int contentEdge = L.isOutgoing
? L.viewWidth - (L.narrow ? kMarginH : (kMarginH + kAvatar + kGap))
: (L.narrow ? kMarginH : (kMarginH + kAvatar + kGap));
int bx = L.isOutgoing ? (contentEdge - L.bubbleWidth) : contentEdge;
L.bubbleRect = QRect(bx, 0, L.bubbleWidth, bubbleH);
L.textRect = QRect(bx + kPadH + ih + 8, kPadTop, L.bubbleWidth - 2*kPadH - ih - 8, th);
L.statusRect = QRect(bx + kPadH + ih + 8 - 48, kPadTop + th + 2, L.bubbleWidth - 2*kPadH - ih - 8 + 48 - 40, th);
if (!L.narrow) {
int avatarY = L.bubbleRect.bottom() - kAvatar;
if (!L.isOutgoing)
L.avatarRect = QRect(kMarginH, avatarY, kAvatar, kAvatar);
else
L.avatarRect = QRect(L.viewWidth - kMarginH - kAvatar, avatarY, kAvatar, kAvatar);
}
return L;
}
L.emojiCount = detectEmojiOnly(text);
L.emojiOnly = (L.emojiCount >= 1);
@@ -758,6 +797,46 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
return;
}
if (L.isFile) {
drawBubble(painter, L, option);
if (!L.narrow) drawAvatar(painter, L, index);
/* File icon */
QFont iconFont = option.font;
iconFont.setPointSize(iconFont.pointSize() + 6);
painter->setFont(iconFont);
painter->setPen(option.palette.text().color());
QRect iconRect(L.bubbleRect.left() + kPadH, kPadTop, 24, 24);
painter->drawText(iconRect, Qt::AlignCenter, QString::fromUtf8("\xF0\x9F\x93\x84"));
QFont fileFont = option.font;
fileFont.setBold(true);
painter->setFont(fileFont);
painter->setPen(option.palette.text().color());
QString displayName = index.data(MsgFileDisplayNameRole).toString();
painter->drawText(L.textRect, Qt::AlignLeft | Qt::AlignVCenter, displayName);
QFont sizeFont = option.font;
sizeFont.setPointSize(sizeFont.pointSize() - 2);
painter->setFont(sizeFont);
painter->setPen(QColor(0x80, 0x80, 0x80));
qint64 fsz = index.data(MsgFileSizeRole).toLongLong();
QString sizeStr = formatFileSize(fsz);
int nameH = QFontMetrics(fileFont).height();
QRect sizeRect(L.textRect.left(), L.textRect.top() + nameH, L.textRect.width(), QFontMetrics(sizeFont).height());
painter->drawText(sizeRect, Qt::AlignLeft | Qt::AlignVCenter, sizeStr);
/* Timestamp */
QFont tsFont = option.font;
tsFont.setPointSize(tsFont.pointSize() - 1);
painter->setFont(tsFont);
painter->drawText(QRect(L.textRect.right() - 40, L.textRect.top() + nameH, 40, QFontMetrics(sizeFont).height()),
Qt::AlignRight | Qt::AlignVCenter, index.data(MsgTimeRole).toString());
painter->restore();
return;
}
auto *cv = qobject_cast<const ChatView *>(option.widget);
bool singleTextSel = false;
bool multiRowSel = false;