diff --git a/src/chatlog/content/filetransferwidget.cpp b/src/chatlog/content/filetransferwidget.cpp index db0428b51..a95be0f09 100644 --- a/src/chatlog/content/filetransferwidget.cpp +++ b/src/chatlog/content/filetransferwidget.cpp @@ -39,6 +39,7 @@ #include #include +#include #include @@ -148,10 +149,11 @@ void FileTransferWidget::autoAcceptTransfer(const QString& path) // Do not automatically accept the file-transfer if the path is not writable. // The user can still accept it manually. - if (tryRemoveFile(filepath)) + if (tryRemoveFile(filepath)) { Core::getInstance()->acceptFileRecvRequest(fileInfo.friendId, fileInfo.fileNum, filepath); - else + } else { qWarning() << "Cannot write to " << filepath; + } } bool FileTransferWidget::isActive() const @@ -161,8 +163,9 @@ bool FileTransferWidget::isActive() const void FileTransferWidget::acceptTransfer(const QString& filepath) { - if (filepath.isEmpty()) + if (filepath.isEmpty()) { return; + } // test if writable if (!tryRemoveFile(filepath)) { @@ -290,17 +293,19 @@ void FileTransferWidget::onFileTransferFinished(ToxFile file) void FileTransferWidget::fileTransferRemotePausedUnpaused(ToxFile file, bool paused) { - if (paused) + if (paused) { onFileTransferPaused(file); - else + } else { onFileTransferResumed(file); + } } void FileTransferWidget::fileTransferBrokenUnbroken(ToxFile file, bool broken) { // TODO: Handle broken transfer differently once we have resuming code - if (broken) + if (broken) { onFileTransferCancelled(file); + } } QString FileTransferWidget::getHumanReadableSize(qint64 size) @@ -308,16 +313,18 @@ QString FileTransferWidget::getHumanReadableSize(qint64 size) static const char* suffix[] = {"B", "kiB", "MiB", "GiB", "TiB"}; int exp = 0; - if (size > 0) + if (size > 0) { exp = std::min((int)(log(size) / log(1024)), (int)(sizeof(suffix) / sizeof(suffix[0]) - 1)); + } return QString().setNum(size / pow(1024, exp), 'f', exp > 1 ? 2 : 0).append(suffix[exp]); } void FileTransferWidget::updateWidgetColor(ToxFile const& file) { - if (lastStatus == file.status) + if (lastStatus == file.status) { return; + } switch (file.status) { case ToxFile::INITIALIZING: @@ -332,13 +339,16 @@ void FileTransferWidget::updateWidgetColor(ToxFile const& file) case ToxFile::FINISHED: setBackgroundColor(Style::getColor(Style::Green), true); break; + default: + assert(false); } } void FileTransferWidget::updateWidgetText(ToxFile const& file) { - if (lastStatus == file.status) + if (lastStatus == file.status) { return; + } switch (file.status) { case ToxFile::INITIALIZING: @@ -361,29 +371,33 @@ void FileTransferWidget::updateWidgetText(ToxFile const& file) break; case ToxFile::FINISHED: break; + default: + assert(false); } } void FileTransferWidget::updatePreview(ToxFile const& file) { - if (lastStatus == file.status) + if (lastStatus == file.status) { return; - - switch (file.status) { - case ToxFile::INITIALIZING: - case ToxFile::PAUSED: - case ToxFile::TRANSMITTING: - case ToxFile::BROKEN: - case ToxFile::CANCELED: - if (file.direction == ToxFile::SENDING) { - showPreview(file.filePath); - } - break; - case ToxFile::FINISHED: - showPreview(file.filePath); - break; } + switch (file.status) { + case ToxFile::INITIALIZING: + case ToxFile::PAUSED: + case ToxFile::TRANSMITTING: + case ToxFile::BROKEN: + case ToxFile::CANCELED: + if (file.direction == ToxFile::SENDING) { + showPreview(file.filePath); + } + break; + case ToxFile::FINISHED: + showPreview(file.filePath); + break; + default: + assert(false); + } } void FileTransferWidget::updateFileProgress(ToxFile const& file) @@ -395,8 +409,9 @@ void FileTransferWidget::updateFileProgress(ToxFile const& file) fileProgress.resetSpeed(); break; case ToxFile::TRANSMITTING: { - if (!fileProgress.needsUpdate()) + if (!fileProgress.needsUpdate()) { break; + } fileProgress.addSample(file); auto speed = fileProgress.getSpeed(); @@ -426,13 +441,16 @@ void FileTransferWidget::updateFileProgress(ToxFile const& file) ui->etaLabel->hide(); break; } + default: + assert(false); } } void FileTransferWidget::updateSignals(ToxFile const& file) { - if (lastStatus == file.status) + if (lastStatus == file.status) { return; + } switch (file.status) { case ToxFile::CANCELED: @@ -445,13 +463,16 @@ void FileTransferWidget::updateSignals(ToxFile const& file) case ToxFile::PAUSED: case ToxFile::TRANSMITTING: break; + default: + assert(false); } } void FileTransferWidget::setupButtons(ToxFile const& file) { - if (lastStatus == file.status) + if (lastStatus == file.status) { return; + } switch (file.status) { case ToxFile::TRANSMITTING: @@ -510,27 +531,30 @@ void FileTransferWidget::setupButtons(ToxFile const& file) ui->rightButton->show(); break; + default: + assert(false); } } void FileTransferWidget::handleButton(QPushButton* btn) { if (fileInfo.direction == ToxFile::SENDING) { - if (btn->objectName() == "cancel") + if (btn->objectName() == "cancel") { Core::getInstance()->cancelFileSend(fileInfo.friendId, fileInfo.fileNum); - else if (btn->objectName() == "pause") + } else if (btn->objectName() == "pause") { Core::getInstance()->pauseResumeFileSend(fileInfo.friendId, fileInfo.fileNum); - else if (btn->objectName() == "resume") + } else if (btn->objectName() == "resume") { Core::getInstance()->pauseResumeFileSend(fileInfo.friendId, fileInfo.fileNum); + } } else // receiving or paused { - if (btn->objectName() == "cancel") + if (btn->objectName() == "cancel") { Core::getInstance()->cancelFileRecv(fileInfo.friendId, fileInfo.fileNum); - else if (btn->objectName() == "pause") + } else if (btn->objectName() == "pause") { Core::getInstance()->pauseResumeFileRecv(fileInfo.friendId, fileInfo.fileNum); - else if (btn->objectName() == "resume") + } else if (btn->objectName() == "resume") { Core::getInstance()->pauseResumeFileRecv(fileInfo.friendId, fileInfo.fileNum); - else if (btn->objectName() == "accept") { + } else if (btn->objectName() == "accept") { QString path = QFileDialog::getSaveFileName(Q_NULLPTR, tr("Save a file", "Title of the file saving dialog"), @@ -575,7 +599,8 @@ void FileTransferWidget::showPreview(const QString& filename) ui->previewButton->setIcon(QIcon(iconPixmap)); ui->previewButton->setIconSize(iconPixmap.size()); ui->previewButton->show(); - // Show mouseover preview, but make sure it's not larger than 50% of the screen width/height + // Show mouseover preview, but make sure it's not larger than 50% of the screen + // width/height const QRect desktopSize = QApplication::desktop()->screenGeometry(); const int maxPreviewWidth{desktopSize.width() / 2}; const int maxPreviewHeight{desktopSize.height() / 2}; @@ -616,7 +641,8 @@ QPixmap FileTransferWidget::scaleCropIntoSquare(const QPixmap& source, const int { QPixmap result; - // Make sure smaller-than-icon images (at least one dimension is smaller) will not be upscaled + // Make sure smaller-than-icon images (at least one dimension is smaller) will not be + // upscaled if (source.width() < targetSize || source.height() < targetSize) { result = source; } else { @@ -626,10 +652,11 @@ QPixmap FileTransferWidget::scaleCropIntoSquare(const QPixmap& source, const int // Then, image has to be cropped (if needed) so it will not overflow rectangle // Only one dimension will be bigger after Qt::KeepAspectRatioByExpanding - if (result.width() > targetSize) + if (result.width() > targetSize) { return result.copy((result.width() - targetSize) / 2, 0, targetSize, targetSize); - else if (result.height() > targetSize) + } else if (result.height() > targetSize) { return result.copy(0, (result.height() - targetSize) / 2, targetSize, targetSize); + } // Picture was rectangle in the first place, no cropping return result; @@ -639,8 +666,9 @@ int FileTransferWidget::getExifOrientation(const char* data, const int size) { ExifData* exifData = exif_data_new_from_data(reinterpret_cast(data), size); - if (!exifData) + if (!exifData) { return 0; + } int orientation = 0; const ExifByteOrder byteOrder = exif_data_get_byte_order(exifData); @@ -689,8 +717,9 @@ void FileTransferWidget::applyTransformation(const int orientation, QImage& imag void FileTransferWidget::updateWidget(ToxFile const& file) { - if (fileInfo != file) + if (fileInfo != file) { return; + } fileInfo = file; @@ -709,9 +738,10 @@ void FileTransferWidget::updateWidget(ToxFile const& file) // trigger repaint switch (file.status) { case ToxFile::TRANSMITTING: - if (!bTransmitNeedsUpdate) + if (!bTransmitNeedsUpdate) { break; - // fallthrough + } + // fallthrough default: update(); } diff --git a/src/chatlog/toxfileprogress.cpp b/src/chatlog/toxfileprogress.cpp index e1d2f1a9b..d048ab8de 100644 --- a/src/chatlog/toxfileprogress.cpp +++ b/src/chatlog/toxfileprogress.cpp @@ -55,8 +55,9 @@ void ToxFileProgress::addSample(ToxFile const& file) meanData[meanIndex++] = bytesPerSec; double meanBytesPerSec = 0.0; - for (size_t i = 0; i < TRANSFER_ROLLING_AVG_COUNT; ++i) + for (size_t i = 0; i < TRANSFER_ROLLING_AVG_COUNT; ++i) { meanBytesPerSec += meanData[i]; + } meanBytesPerSec /= static_cast(TRANSFER_ROLLING_AVG_COUNT); lastTick = now; diff --git a/src/persistence/history.cpp b/src/persistence/history.cpp index 3b8965790..82c66f61b 100644 --- a/src/persistence/history.cpp +++ b/src/persistence/history.cpp @@ -64,6 +64,7 @@ History::History(std::shared_ptr db) } connect(this, &History::fileInsertionReady, this, &History::onFileInsertionReady); + connect(this, &History::fileInserted, this, &History::onFileInserted); db->execLater( "CREATE TABLE IF NOT EXISTS peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL " @@ -274,6 +275,8 @@ void History::onFileInsertionReady(FileDbInsertionData data) // peerId is guaranteed to be inserted since we just used it in addNewMessage auto peerId = peers[data.friendPk]; + // Copy to pass into labmda for later + auto fileId = data.fileId; queries += RawDatabase::Query(QStringLiteral("INSERT INTO file_transfers (chat_id, file_restart_id, " "file_path, file_name, file_size, direction, file_state) " @@ -282,7 +285,13 @@ void History::onFileInsertionReady(FileDbInsertionData data) .arg(data.size) .arg(static_cast(data.direction)) .arg(ToxFile::CANCELED), - {data.fileId.toUtf8(), data.filePath.toUtf8(), data.fileName}, {}); + {data.fileId.toUtf8(), data.filePath.toUtf8(), data.fileName}, + [weakThis, fileId](int64_t id) { + auto pThis = weakThis.lock(); + if (pThis) { + emit pThis->fileInserted(id, fileId); + } + }); queries += RawDatabase::Query(QStringLiteral("UPDATE history " @@ -293,6 +302,37 @@ void History::onFileInsertionReady(FileDbInsertionData data) db->execLater(queries); } +void History::onFileInserted(int64_t dbId, QString fileId) +{ + auto& fileInfo = fileInfos[fileId]; + if (fileInfo.finished) { + db->execLater(generateFileFinished(dbId, fileInfo.success, fileInfo.filePath)); + fileInfos.remove(fileId); + } else { + fileInfo.finished = false; + fileInfo.fileId = dbId; + } +} + +RawDatabase::Query History::generateFileFinished(int64_t id, bool success, const QString& filePath) +{ + auto file_state = success ? ToxFile::FINISHED : ToxFile::CANCELED; + if (filePath.length()) { + return RawDatabase::Query(QStringLiteral("UPDATE file_transfers " + "SET file_state = %1, file_path = ? " + "WHERE id = %2") + .arg(file_state) + .arg(id), + {filePath.toUtf8()}); + } else { + return RawDatabase::Query(QStringLiteral("UPDATE file_transfers " + "SET finished = %1 " + "WHERE id = %2") + .arg(file_state) + .arg(id)); + } +} + void History::addNewFileMessage(const QString& friendPk, const QString& fileId, const QByteArray& fileName, const QString& filePath, int64_t size, const QString& sender, const QDateTime& time, QString const& dispName) @@ -365,6 +405,19 @@ void History::addNewMessage(const QString& friendPk, const QString& message, con insertIdCallback)); } +void History::setFileFinished(const QString& fileId, bool success, const QString& filePath) +{ + auto& fileInfo = fileInfos[fileId]; + if (fileInfo.fileId == -1) { + fileInfo.finished = true; + fileInfo.success = success; + fileInfo.filePath = filePath; + } else { + db->execLater(generateFileFinished(fileInfo.fileId, success, filePath)); + } + + fileInfos.remove(fileId); +} /** * @brief Fetches chat messages from the database. * @param friendPk Friend publick key to fetch. @@ -578,24 +631,44 @@ QList History::getChatHistory(const QString& friendPk, con auto rowCallback = [&messages](const QVector& row) { // dispName and message could have null bytes, QString::fromUtf8 // truncates on null bytes so we strip them - messages += {row[0].toLongLong(), - row[1].isNull(), - QDateTime::fromMSecsSinceEpoch(row[2].toLongLong()), - row[3].toString(), - QString::fromUtf8(row[4].toByteArray().replace('\0', "")), - row[5].toString(), - QString::fromUtf8(row[6].toByteArray().replace('\0', ""))}; + auto id = row[0].toLongLong(); + auto isOfflineMessage = row[1].isNull(); + auto timestamp = QDateTime::fromMSecsSinceEpoch(row[2].toLongLong()); + auto friend_key = row[3].toString(); + auto display_name = QString::fromUtf8(row[4].toByteArray().replace('\0', "")); + auto sender_key = row[5].toString(); + if (row[7].isNull()) { + messages += {id, isOfflineMessage, timestamp, friend_key, + display_name, sender_key, row[6].toString()}; + } else { + ToxFile file; + file.fileKind = TOX_FILE_KIND_DATA; + file.resumeFileId = row[7].toString().toUtf8(); + file.filePath = QString::fromUtf8(row[8].toByteArray().replace('\0', "")); + file.fileName = + QString::fromUtf8(row[9].toByteArray()) + .toUtf8(); // for some reason the path has to be utf8 parsed even though it went in as a straight array + file.filesize = row[10].toLongLong(); + file.direction = static_cast(row[11].toLongLong()); + file.status = static_cast(row[12].toInt()); + messages += + {id, isOfflineMessage, timestamp, friend_key, display_name, sender_key, file}; + } }; // Don't forget to update the rowCallback if you change the selected columns! QString queryText = QString("SELECT history.id, faux_offline_pending.id, timestamp, " "chat.public_key, aliases.display_name, sender.public_key, " - "message FROM history " + "message, file_transfers.file_restart_id, " + "file_transfers.file_path, file_transfers.file_name, " + "file_transfers.file_size, file_transfers.direction, " + "file_transfers.file_state FROM history " "LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id " - "JOIN peers chat ON chat_id = chat.id " + "JOIN peers chat ON history.chat_id = chat.id " "JOIN aliases ON sender_alias = aliases.id " "JOIN peers sender ON aliases.owner = sender.id " + "LEFT JOIN file_transfers ON history.file_id = file_transfers.id " "WHERE timestamp BETWEEN %1 AND %2 AND chat.public_key='%3'") .arg(from.toMSecsSinceEpoch()) .arg(to.toMSecsSinceEpoch()) diff --git a/src/persistence/history.h b/src/persistence/history.h index 0733076e0..7f606793e 100644 --- a/src/persistence/history.h +++ b/src/persistence/history.h @@ -22,11 +22,14 @@ #include #include +#include #include +#include #include #include +#include "src/core/toxfile.h" #include "src/core/toxpk.h" #include "src/persistence/db/rawdatabase.h" #include "src/widget/searchtypes.h" @@ -34,6 +37,60 @@ class Profile; class HistoryKeeper; +enum class HistMessageContentType +{ + message, + file +}; + +class HistMessageContent +{ +public: + HistMessageContent(QString message) + : data(std::make_shared(std::move(message))) + , type(HistMessageContentType::message) + {} + + HistMessageContent(ToxFile file) + : data(std::make_shared(std::move(file))) + , type(HistMessageContentType::file) + {} + + HistMessageContentType getType() const + { + return type; + } + + QString& asMessage() + { + assert(type == HistMessageContentType::message); + return *static_cast(data.get()); + } + + ToxFile& asFile() + { + assert(type == HistMessageContentType::file); + return *static_cast(data.get()); + } + + const QString& asMessage() const + { + assert(type == HistMessageContentType::message); + return *static_cast(data.get()); + } + + const ToxFile& asFile() const + { + assert(type == HistMessageContentType::file); + return *static_cast(data.get()); + } + +private: + // Not really shared but shared_ptr has support for shared_ptr + std::shared_ptr data; + HistMessageContentType type; +}; + struct FileDbInsertionData { FileDbInsertionData(); @@ -58,20 +115,32 @@ public: QString sender, QString message) : chat{chat} , sender{sender} - , message{message} , dispName{dispName} , timestamp{timestamp} , id{id} , isSent{isSent} + , content(std::move(message)) {} + HistMessage(qint64 id, bool isSent, QDateTime timestamp, QString chat, QString dispName, + QString sender, ToxFile file) + : chat{chat} + , sender{sender} + , dispName{dispName} + , timestamp{timestamp} + , id{id} + , isSent{isSent} + , content(std::move(file)) + {} + + QString chat; QString sender; - QString message; QString dispName; QDateTime timestamp; qint64 id; bool isSent; + HistMessageContent content; }; struct DateMessages @@ -98,6 +167,8 @@ public: const QByteArray& fileName, const QString& filePath, int64_t size, const QString& sender, const QDateTime& time, QString const& dispName); + void setFileFinished(const QString& fileId, bool success, const QString& filePath); + QList getChatHistoryFromDate(const QString& friendPk, const QDateTime& from, const QDateTime& to); QList getChatHistoryDefaultNum(const QString& friendPk); @@ -116,19 +187,34 @@ protected: signals: void fileInsertionReady(FileDbInsertionData data); + void fileInserted(int64_t dbId, QString fileId); private slots: void onFileInsertionReady(FileDbInsertionData data); + void onFileInserted(int64_t dbId, QString fileId); private: QList getChatHistory(const QString& friendPk, const QDateTime& from, const QDateTime& to, int numMessages); + + static RawDatabase::Query generateFileFinished(int64_t fileId, bool success, + const QString& filePath); void dbSchemaUpgrade(); std::shared_ptr db; QHash peers; + struct FileInfo + { + bool finished = false; + bool success = false; + QString filePath; + int64_t fileId = -1; + }; + + // This needs to be a shared pointer to avoid callback lifetime issues + QHash fileInfos; }; #endif // HISTORY_H diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index a1f930285..eeb6fd574 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -161,6 +161,9 @@ ChatForm::ChatForm(Friend* chatFriend, History* history) connect(core, &Core::fileReceiveRequested, this, &ChatForm::onFileRecvRequest); connect(profile, &Profile::friendAvatarChanged, this, &ChatForm::onAvatarChanged); connect(core, &Core::fileSendStarted, this, &ChatForm::startFileSend); + connect(core, &Core::fileTransferFinished, this, &ChatForm::onFileTransferFinished); + connect(core, &Core::fileTransferCancelled, this, &ChatForm::onFileTransferCancelled); + connect(core, &Core::fileTransferBrokenUnbroken, this, &ChatForm::onFileTransferBrokenUnbroken); connect(core, &Core::fileSendFailed, this, &ChatForm::onFileSendFailed); connect(core, &Core::receiptRecieved, this, &ChatForm::onReceiptReceived); connect(core, &Core::friendMessageReceived, this, &ChatForm::onFriendMessageReceived); @@ -312,9 +315,35 @@ void ChatForm::startFileSend(ToxFile file) insertChatMessage( ChatMessage::createFileTransferMessage(name, file, true, QDateTime::currentDateTime())); + + if (history && Settings::getInstance().getEnableLogging()) { + auto selfPk = Core::getInstance()->getSelfId().toString(); + auto pk = f->getPublicKey().toString(); + auto name = Core::getInstance()->getUsername(); + history->addNewFileMessage(pk, file.resumeFileId, file.fileName, file.filePath, + file.filesize, selfPk, QDateTime::currentDateTime(), name); + } + Widget::getInstance()->updateFriendActivity(f); } +void ChatForm::onFileTransferFinished(ToxFile file) +{ + history->setFileFinished(file.resumeFileId, true, file.filePath); +} + +void ChatForm::onFileTransferBrokenUnbroken(ToxFile file, bool broken) +{ + if (broken) { + history->setFileFinished(file.resumeFileId, false, file.filePath); + } +} + +void ChatForm::onFileTransferCancelled(ToxFile file) +{ + history->setFileFinished(file.resumeFileId, false, file.filePath); +} + void ChatForm::onFileRecvRequest(ToxFile file) { if (file.friendId != f->getId()) { @@ -331,9 +360,17 @@ void ChatForm::onFileRecvRequest(ToxFile file) ChatMessage::Ptr msg = ChatMessage::createFileTransferMessage(name, file, false, QDateTime::currentDateTime()); + insertChatMessage(msg); + if (history && Settings::getInstance().getEnableLogging()) { + auto pk = f->getPublicKey().toString(); + auto name = f->getDisplayedName(); + history->addNewFileMessage(pk, file.resumeFileId, file.fileName, file.filePath, + file.filesize, pk, QDateTime::currentDateTime(), name); + } ChatLineContentProxy* proxy = static_cast(msg->getContent(1)); + assert(proxy->getWidgetType() == ChatLineContentProxy::FileTransferWidgetType); FileTransferWidget* tfWidget = static_cast(proxy->getWidget()); @@ -794,6 +831,11 @@ void ChatForm::handleLoadedMessages(QList newHistMsgs, boo MessageMetadata const metadata = getMessageMetadata(histMessage); lastDate = addDateLineIfNeeded(chatLines, lastDate, histMessage, metadata); auto msg = chatMessageFromHistMessage(histMessage, metadata); + + if (!msg) { + continue; + } + if (processUndelivered) { sendLoadedMessage(msg, metadata); } @@ -838,7 +880,9 @@ ChatForm::MessageMetadata ChatForm::getMessageMetadata(History::HistMessage cons const QDateTime msgDateTime = histMessage.timestamp.toLocalTime(); const bool isSelf = Core::getInstance()->getSelfId().getPublicKey() == authorPk; const bool needSending = !histMessage.isSent && isSelf; - const bool isAction = histMessage.message.startsWith(ACTION_PREFIX, Qt::CaseInsensitive); + const bool isAction = + histMessage.content.getType() == HistMessageContentType::message + && histMessage.content.asMessage().startsWith(ACTION_PREFIX, Qt::CaseInsensitive); const qint64 id = histMessage.id; return {isSelf, needSending, isAction, id, authorPk, msgDateTime}; } @@ -848,11 +892,30 @@ ChatMessage::Ptr ChatForm::chatMessageFromHistMessage(History::HistMessage const { ToxPk authorPk(ToxId(histMessage.sender).getPublicKey()); QString authorStr = getMsgAuthorDispName(authorPk, histMessage.dispName); - QString messageText = - metadata.isAction ? histMessage.message.mid(ACTION_PREFIX.length()) : histMessage.message; - ChatMessage::MessageType type = metadata.isAction ? ChatMessage::ACTION : ChatMessage::NORMAL; QDateTime dateTime = metadata.needSending ? QDateTime() : metadata.msgDateTime; - auto msg = ChatMessage::createChatMessage(authorStr, messageText, type, metadata.isSelf, dateTime); + + + ChatMessage::Ptr msg; + + switch (histMessage.content.getType()) { + case HistMessageContentType::message: { + ChatMessage::MessageType type = metadata.isAction ? ChatMessage::ACTION : ChatMessage::NORMAL; + auto& message = histMessage.content.asMessage(); + QString messageText = metadata.isAction ? message.mid(ACTION_PREFIX.length()) : message; + + msg = ChatMessage::createChatMessage(authorStr, messageText, type, metadata.isSelf, dateTime); + break; + } + case HistMessageContentType::file: { + auto& file = histMessage.content.asFile(); + bool isMe = file.direction == ToxFile::SENDING; + msg = ChatMessage::createFileTransferMessage(authorStr, file, isMe, dateTime); + break; + } + default: + assert(false); + } + if (!metadata.isAction && needsToHideName(authorPk, metadata.msgDateTime)) { msg->hideSender(); } @@ -1135,13 +1198,19 @@ void ChatForm::onExportChat() QString buffer; for (const auto& it : msgs) { + if (it.content.getType() != HistMessageContentType::message) { + continue; + } QString timestamp = it.timestamp.time().toString("hh:mm:ss"); QString datestamp = it.timestamp.date().toString("yyyy-MM-dd"); ToxPk authorPk(ToxId(it.sender).getPublicKey()); QString author = getMsgAuthorDispName(authorPk, it.dispName); - buffer = buffer - % QString{datestamp % '\t' % timestamp % '\t' % author % '\t' % it.message % '\n'}; + if (it.content.getType() == HistMessageContentType::message) { + buffer = buffer + % QString{datestamp % '\t' % timestamp % '\t' % author % '\t' + % it.content.asMessage() % '\n'}; + } } file.write(buffer.toUtf8()); file.close(); diff --git a/src/widget/form/chatform.h b/src/widget/form/chatform.h index 9f166d2db..3ee1045e4 100644 --- a/src/widget/form/chatform.h +++ b/src/widget/form/chatform.h @@ -68,11 +68,14 @@ signals: public slots: void startFileSend(ToxFile file); + void onFileTransferFinished(ToxFile file); + void onFileTransferCancelled(ToxFile file); + void onFileTransferBrokenUnbroken(ToxFile file, bool broken); void onFileRecvRequest(ToxFile file); void onAvInvite(uint32_t friendId, bool video); void onAvStart(uint32_t friendId, bool video); void onAvEnd(uint32_t friendId, bool error); - void onAvatarChanged(const ToxPk &friendPk, const QPixmap& pic); + void onAvatarChanged(const ToxPk& friendPk, const QPixmap& pic); void onFileNameChanged(const ToxPk& friendPk); void clearChatArea();