1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

feat(db): Hookup file history to the rest of the system

This commit is contained in:
Mick Sayson 2018-09-29 02:28:17 -07:00
parent 567ddfb203
commit d9b39b3102
6 changed files with 325 additions and 63 deletions

View File

@ -39,6 +39,7 @@
#include <QPainter> #include <QPainter>
#include <QVariantAnimation> #include <QVariantAnimation>
#include <cassert>
#include <math.h> #include <math.h>
@ -148,10 +149,11 @@ void FileTransferWidget::autoAcceptTransfer(const QString& path)
// Do not automatically accept the file-transfer if the path is not writable. // Do not automatically accept the file-transfer if the path is not writable.
// The user can still accept it manually. // The user can still accept it manually.
if (tryRemoveFile(filepath)) if (tryRemoveFile(filepath)) {
Core::getInstance()->acceptFileRecvRequest(fileInfo.friendId, fileInfo.fileNum, filepath); Core::getInstance()->acceptFileRecvRequest(fileInfo.friendId, fileInfo.fileNum, filepath);
else } else {
qWarning() << "Cannot write to " << filepath; qWarning() << "Cannot write to " << filepath;
}
} }
bool FileTransferWidget::isActive() const bool FileTransferWidget::isActive() const
@ -161,8 +163,9 @@ bool FileTransferWidget::isActive() const
void FileTransferWidget::acceptTransfer(const QString& filepath) void FileTransferWidget::acceptTransfer(const QString& filepath)
{ {
if (filepath.isEmpty()) if (filepath.isEmpty()) {
return; return;
}
// test if writable // test if writable
if (!tryRemoveFile(filepath)) { if (!tryRemoveFile(filepath)) {
@ -290,17 +293,19 @@ void FileTransferWidget::onFileTransferFinished(ToxFile file)
void FileTransferWidget::fileTransferRemotePausedUnpaused(ToxFile file, bool paused) void FileTransferWidget::fileTransferRemotePausedUnpaused(ToxFile file, bool paused)
{ {
if (paused) if (paused) {
onFileTransferPaused(file); onFileTransferPaused(file);
else } else {
onFileTransferResumed(file); onFileTransferResumed(file);
}
} }
void FileTransferWidget::fileTransferBrokenUnbroken(ToxFile file, bool broken) void FileTransferWidget::fileTransferBrokenUnbroken(ToxFile file, bool broken)
{ {
// TODO: Handle broken transfer differently once we have resuming code // TODO: Handle broken transfer differently once we have resuming code
if (broken) if (broken) {
onFileTransferCancelled(file); onFileTransferCancelled(file);
}
} }
QString FileTransferWidget::getHumanReadableSize(qint64 size) QString FileTransferWidget::getHumanReadableSize(qint64 size)
@ -308,16 +313,18 @@ QString FileTransferWidget::getHumanReadableSize(qint64 size)
static const char* suffix[] = {"B", "kiB", "MiB", "GiB", "TiB"}; static const char* suffix[] = {"B", "kiB", "MiB", "GiB", "TiB"};
int exp = 0; int exp = 0;
if (size > 0) if (size > 0) {
exp = std::min((int)(log(size) / log(1024)), (int)(sizeof(suffix) / sizeof(suffix[0]) - 1)); 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]); return QString().setNum(size / pow(1024, exp), 'f', exp > 1 ? 2 : 0).append(suffix[exp]);
} }
void FileTransferWidget::updateWidgetColor(ToxFile const& file) void FileTransferWidget::updateWidgetColor(ToxFile const& file)
{ {
if (lastStatus == file.status) if (lastStatus == file.status) {
return; return;
}
switch (file.status) { switch (file.status) {
case ToxFile::INITIALIZING: case ToxFile::INITIALIZING:
@ -332,13 +339,16 @@ void FileTransferWidget::updateWidgetColor(ToxFile const& file)
case ToxFile::FINISHED: case ToxFile::FINISHED:
setBackgroundColor(Style::getColor(Style::Green), true); setBackgroundColor(Style::getColor(Style::Green), true);
break; break;
default:
assert(false);
} }
} }
void FileTransferWidget::updateWidgetText(ToxFile const& file) void FileTransferWidget::updateWidgetText(ToxFile const& file)
{ {
if (lastStatus == file.status) if (lastStatus == file.status) {
return; return;
}
switch (file.status) { switch (file.status) {
case ToxFile::INITIALIZING: case ToxFile::INITIALIZING:
@ -361,13 +371,16 @@ void FileTransferWidget::updateWidgetText(ToxFile const& file)
break; break;
case ToxFile::FINISHED: case ToxFile::FINISHED:
break; break;
default:
assert(false);
} }
} }
void FileTransferWidget::updatePreview(ToxFile const& file) void FileTransferWidget::updatePreview(ToxFile const& file)
{ {
if (lastStatus == file.status) if (lastStatus == file.status) {
return; return;
}
switch (file.status) { switch (file.status) {
case ToxFile::INITIALIZING: case ToxFile::INITIALIZING:
@ -382,8 +395,9 @@ void FileTransferWidget::updatePreview(ToxFile const& file)
case ToxFile::FINISHED: case ToxFile::FINISHED:
showPreview(file.filePath); showPreview(file.filePath);
break; break;
default:
assert(false);
} }
} }
void FileTransferWidget::updateFileProgress(ToxFile const& file) void FileTransferWidget::updateFileProgress(ToxFile const& file)
@ -395,8 +409,9 @@ void FileTransferWidget::updateFileProgress(ToxFile const& file)
fileProgress.resetSpeed(); fileProgress.resetSpeed();
break; break;
case ToxFile::TRANSMITTING: { case ToxFile::TRANSMITTING: {
if (!fileProgress.needsUpdate()) if (!fileProgress.needsUpdate()) {
break; break;
}
fileProgress.addSample(file); fileProgress.addSample(file);
auto speed = fileProgress.getSpeed(); auto speed = fileProgress.getSpeed();
@ -426,13 +441,16 @@ void FileTransferWidget::updateFileProgress(ToxFile const& file)
ui->etaLabel->hide(); ui->etaLabel->hide();
break; break;
} }
default:
assert(false);
} }
} }
void FileTransferWidget::updateSignals(ToxFile const& file) void FileTransferWidget::updateSignals(ToxFile const& file)
{ {
if (lastStatus == file.status) if (lastStatus == file.status) {
return; return;
}
switch (file.status) { switch (file.status) {
case ToxFile::CANCELED: case ToxFile::CANCELED:
@ -445,13 +463,16 @@ void FileTransferWidget::updateSignals(ToxFile const& file)
case ToxFile::PAUSED: case ToxFile::PAUSED:
case ToxFile::TRANSMITTING: case ToxFile::TRANSMITTING:
break; break;
default:
assert(false);
} }
} }
void FileTransferWidget::setupButtons(ToxFile const& file) void FileTransferWidget::setupButtons(ToxFile const& file)
{ {
if (lastStatus == file.status) if (lastStatus == file.status) {
return; return;
}
switch (file.status) { switch (file.status) {
case ToxFile::TRANSMITTING: case ToxFile::TRANSMITTING:
@ -510,27 +531,30 @@ void FileTransferWidget::setupButtons(ToxFile const& file)
ui->rightButton->show(); ui->rightButton->show();
break; break;
default:
assert(false);
} }
} }
void FileTransferWidget::handleButton(QPushButton* btn) void FileTransferWidget::handleButton(QPushButton* btn)
{ {
if (fileInfo.direction == ToxFile::SENDING) { if (fileInfo.direction == ToxFile::SENDING) {
if (btn->objectName() == "cancel") if (btn->objectName() == "cancel") {
Core::getInstance()->cancelFileSend(fileInfo.friendId, fileInfo.fileNum); Core::getInstance()->cancelFileSend(fileInfo.friendId, fileInfo.fileNum);
else if (btn->objectName() == "pause") } else if (btn->objectName() == "pause") {
Core::getInstance()->pauseResumeFileSend(fileInfo.friendId, fileInfo.fileNum); Core::getInstance()->pauseResumeFileSend(fileInfo.friendId, fileInfo.fileNum);
else if (btn->objectName() == "resume") } else if (btn->objectName() == "resume") {
Core::getInstance()->pauseResumeFileSend(fileInfo.friendId, fileInfo.fileNum); Core::getInstance()->pauseResumeFileSend(fileInfo.friendId, fileInfo.fileNum);
}
} else // receiving or paused } else // receiving or paused
{ {
if (btn->objectName() == "cancel") if (btn->objectName() == "cancel") {
Core::getInstance()->cancelFileRecv(fileInfo.friendId, fileInfo.fileNum); Core::getInstance()->cancelFileRecv(fileInfo.friendId, fileInfo.fileNum);
else if (btn->objectName() == "pause") } else if (btn->objectName() == "pause") {
Core::getInstance()->pauseResumeFileRecv(fileInfo.friendId, fileInfo.fileNum); Core::getInstance()->pauseResumeFileRecv(fileInfo.friendId, fileInfo.fileNum);
else if (btn->objectName() == "resume") } else if (btn->objectName() == "resume") {
Core::getInstance()->pauseResumeFileRecv(fileInfo.friendId, fileInfo.fileNum); Core::getInstance()->pauseResumeFileRecv(fileInfo.friendId, fileInfo.fileNum);
else if (btn->objectName() == "accept") { } else if (btn->objectName() == "accept") {
QString path = QString path =
QFileDialog::getSaveFileName(Q_NULLPTR, QFileDialog::getSaveFileName(Q_NULLPTR,
tr("Save a file", "Title of the file saving dialog"), 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->setIcon(QIcon(iconPixmap));
ui->previewButton->setIconSize(iconPixmap.size()); ui->previewButton->setIconSize(iconPixmap.size());
ui->previewButton->show(); 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 QRect desktopSize = QApplication::desktop()->screenGeometry();
const int maxPreviewWidth{desktopSize.width() / 2}; const int maxPreviewWidth{desktopSize.width() / 2};
const int maxPreviewHeight{desktopSize.height() / 2}; const int maxPreviewHeight{desktopSize.height() / 2};
@ -616,7 +641,8 @@ QPixmap FileTransferWidget::scaleCropIntoSquare(const QPixmap& source, const int
{ {
QPixmap result; 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) { if (source.width() < targetSize || source.height() < targetSize) {
result = source; result = source;
} else { } 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 // Then, image has to be cropped (if needed) so it will not overflow rectangle
// Only one dimension will be bigger after Qt::KeepAspectRatioByExpanding // 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); 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); return result.copy(0, (result.height() - targetSize) / 2, targetSize, targetSize);
}
// Picture was rectangle in the first place, no cropping // Picture was rectangle in the first place, no cropping
return result; return result;
@ -639,8 +666,9 @@ int FileTransferWidget::getExifOrientation(const char* data, const int size)
{ {
ExifData* exifData = exif_data_new_from_data(reinterpret_cast<const unsigned char*>(data), size); ExifData* exifData = exif_data_new_from_data(reinterpret_cast<const unsigned char*>(data), size);
if (!exifData) if (!exifData) {
return 0; return 0;
}
int orientation = 0; int orientation = 0;
const ExifByteOrder byteOrder = exif_data_get_byte_order(exifData); 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) void FileTransferWidget::updateWidget(ToxFile const& file)
{ {
if (fileInfo != file) if (fileInfo != file) {
return; return;
}
fileInfo = file; fileInfo = file;
@ -709,8 +738,9 @@ void FileTransferWidget::updateWidget(ToxFile const& file)
// trigger repaint // trigger repaint
switch (file.status) { switch (file.status) {
case ToxFile::TRANSMITTING: case ToxFile::TRANSMITTING:
if (!bTransmitNeedsUpdate) if (!bTransmitNeedsUpdate) {
break; break;
}
// fallthrough // fallthrough
default: default:
update(); update();

View File

@ -55,8 +55,9 @@ void ToxFileProgress::addSample(ToxFile const& file)
meanData[meanIndex++] = bytesPerSec; meanData[meanIndex++] = bytesPerSec;
double meanBytesPerSec = 0.0; 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 += meanData[i];
}
meanBytesPerSec /= static_cast<qreal>(TRANSFER_ROLLING_AVG_COUNT); meanBytesPerSec /= static_cast<qreal>(TRANSFER_ROLLING_AVG_COUNT);
lastTick = now; lastTick = now;

View File

@ -64,6 +64,7 @@ History::History(std::shared_ptr<RawDatabase> db)
} }
connect(this, &History::fileInsertionReady, this, &History::onFileInsertionReady); connect(this, &History::fileInsertionReady, this, &History::onFileInsertionReady);
connect(this, &History::fileInserted, this, &History::onFileInserted);
db->execLater( db->execLater(
"CREATE TABLE IF NOT EXISTS peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL " "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 // peerId is guaranteed to be inserted since we just used it in addNewMessage
auto peerId = peers[data.friendPk]; auto peerId = peers[data.friendPk];
// Copy to pass into labmda for later
auto fileId = data.fileId;
queries += queries +=
RawDatabase::Query(QStringLiteral("INSERT INTO file_transfers (chat_id, file_restart_id, " RawDatabase::Query(QStringLiteral("INSERT INTO file_transfers (chat_id, file_restart_id, "
"file_path, file_name, file_size, direction, file_state) " "file_path, file_name, file_size, direction, file_state) "
@ -282,7 +285,13 @@ void History::onFileInsertionReady(FileDbInsertionData data)
.arg(data.size) .arg(data.size)
.arg(static_cast<int>(data.direction)) .arg(static_cast<int>(data.direction))
.arg(ToxFile::CANCELED), .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 " queries += RawDatabase::Query(QStringLiteral("UPDATE history "
@ -293,6 +302,37 @@ void History::onFileInsertionReady(FileDbInsertionData data)
db->execLater(queries); 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, void History::addNewFileMessage(const QString& friendPk, const QString& fileId,
const QByteArray& fileName, const QString& filePath, int64_t size, const QByteArray& fileName, const QString& filePath, int64_t size,
const QString& sender, const QDateTime& time, QString const& dispName) const QString& sender, const QDateTime& time, QString const& dispName)
@ -365,6 +405,19 @@ void History::addNewMessage(const QString& friendPk, const QString& message, con
insertIdCallback)); 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. * @brief Fetches chat messages from the database.
* @param friendPk Friend publick key to fetch. * @param friendPk Friend publick key to fetch.
@ -578,24 +631,44 @@ QList<History::HistMessage> History::getChatHistory(const QString& friendPk, con
auto rowCallback = [&messages](const QVector<QVariant>& row) { auto rowCallback = [&messages](const QVector<QVariant>& row) {
// dispName and message could have null bytes, QString::fromUtf8 // dispName and message could have null bytes, QString::fromUtf8
// truncates on null bytes so we strip them // truncates on null bytes so we strip them
messages += {row[0].toLongLong(), auto id = row[0].toLongLong();
row[1].isNull(), auto isOfflineMessage = row[1].isNull();
QDateTime::fromMSecsSinceEpoch(row[2].toLongLong()), auto timestamp = QDateTime::fromMSecsSinceEpoch(row[2].toLongLong());
row[3].toString(), auto friend_key = row[3].toString();
QString::fromUtf8(row[4].toByteArray().replace('\0', "")), auto display_name = QString::fromUtf8(row[4].toByteArray().replace('\0', ""));
row[5].toString(), auto sender_key = row[5].toString();
QString::fromUtf8(row[6].toByteArray().replace('\0', ""))}; 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<ToxFile::FileDirection>(row[11].toLongLong());
file.status = static_cast<ToxFile::FileStatus>(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! // Don't forget to update the rowCallback if you change the selected columns!
QString queryText = QString queryText =
QString("SELECT history.id, faux_offline_pending.id, timestamp, " QString("SELECT history.id, faux_offline_pending.id, timestamp, "
"chat.public_key, aliases.display_name, sender.public_key, " "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 " "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 aliases ON sender_alias = aliases.id "
"JOIN peers sender ON aliases.owner = sender.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'") "WHERE timestamp BETWEEN %1 AND %2 AND chat.public_key='%3'")
.arg(from.toMSecsSinceEpoch()) .arg(from.toMSecsSinceEpoch())
.arg(to.toMSecsSinceEpoch()) .arg(to.toMSecsSinceEpoch())

View File

@ -22,11 +22,14 @@
#include <QDateTime> #include <QDateTime>
#include <QHash> #include <QHash>
#include <QPointer>
#include <QVector> #include <QVector>
#include <cassert>
#include <cstdint> #include <cstdint>
#include <tox/toxencryptsave.h> #include <tox/toxencryptsave.h>
#include "src/core/toxfile.h"
#include "src/core/toxpk.h" #include "src/core/toxpk.h"
#include "src/persistence/db/rawdatabase.h" #include "src/persistence/db/rawdatabase.h"
#include "src/widget/searchtypes.h" #include "src/widget/searchtypes.h"
@ -34,6 +37,60 @@
class Profile; class Profile;
class HistoryKeeper; class HistoryKeeper;
enum class HistMessageContentType
{
message,
file
};
class HistMessageContent
{
public:
HistMessageContent(QString message)
: data(std::make_shared<QString>(std::move(message)))
, type(HistMessageContentType::message)
{}
HistMessageContent(ToxFile file)
: data(std::make_shared<ToxFile>(std::move(file)))
, type(HistMessageContentType::file)
{}
HistMessageContentType getType() const
{
return type;
}
QString& asMessage()
{
assert(type == HistMessageContentType::message);
return *static_cast<QString*>(data.get());
}
ToxFile& asFile()
{
assert(type == HistMessageContentType::file);
return *static_cast<ToxFile*>(data.get());
}
const QString& asMessage() const
{
assert(type == HistMessageContentType::message);
return *static_cast<QString*>(data.get());
}
const ToxFile& asFile() const
{
assert(type == HistMessageContentType::file);
return *static_cast<ToxFile*>(data.get());
}
private:
// Not really shared but shared_ptr has support for shared_ptr<void>
std::shared_ptr<void> data;
HistMessageContentType type;
};
struct FileDbInsertionData struct FileDbInsertionData
{ {
FileDbInsertionData(); FileDbInsertionData();
@ -58,20 +115,32 @@ public:
QString sender, QString message) QString sender, QString message)
: chat{chat} : chat{chat}
, sender{sender} , sender{sender}
, message{message}
, dispName{dispName} , dispName{dispName}
, timestamp{timestamp} , timestamp{timestamp}
, id{id} , id{id}
, isSent{isSent} , 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 chat;
QString sender; QString sender;
QString message;
QString dispName; QString dispName;
QDateTime timestamp; QDateTime timestamp;
qint64 id; qint64 id;
bool isSent; bool isSent;
HistMessageContent content;
}; };
struct DateMessages struct DateMessages
@ -98,6 +167,8 @@ public:
const QByteArray& fileName, const QString& filePath, int64_t size, const QByteArray& fileName, const QString& filePath, int64_t size,
const QString& sender, const QDateTime& time, QString const& dispName); const QString& sender, const QDateTime& time, QString const& dispName);
void setFileFinished(const QString& fileId, bool success, const QString& filePath);
QList<HistMessage> getChatHistoryFromDate(const QString& friendPk, const QDateTime& from, QList<HistMessage> getChatHistoryFromDate(const QString& friendPk, const QDateTime& from,
const QDateTime& to); const QDateTime& to);
QList<HistMessage> getChatHistoryDefaultNum(const QString& friendPk); QList<HistMessage> getChatHistoryDefaultNum(const QString& friendPk);
@ -116,19 +187,34 @@ protected:
signals: signals:
void fileInsertionReady(FileDbInsertionData data); void fileInsertionReady(FileDbInsertionData data);
void fileInserted(int64_t dbId, QString fileId);
private slots: private slots:
void onFileInsertionReady(FileDbInsertionData data); void onFileInsertionReady(FileDbInsertionData data);
void onFileInserted(int64_t dbId, QString fileId);
private: private:
QList<HistMessage> getChatHistory(const QString& friendPk, const QDateTime& from, QList<HistMessage> getChatHistory(const QString& friendPk, const QDateTime& from,
const QDateTime& to, int numMessages); const QDateTime& to, int numMessages);
static RawDatabase::Query generateFileFinished(int64_t fileId, bool success,
const QString& filePath);
void dbSchemaUpgrade(); void dbSchemaUpgrade();
std::shared_ptr<RawDatabase> db; std::shared_ptr<RawDatabase> db;
QHash<QString, int64_t> peers; QHash<QString, int64_t> 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<QString, FileInfo> fileInfos;
}; };
#endif // HISTORY_H #endif // HISTORY_H

View File

@ -161,6 +161,9 @@ ChatForm::ChatForm(Friend* chatFriend, History* history)
connect(core, &Core::fileReceiveRequested, this, &ChatForm::onFileRecvRequest); connect(core, &Core::fileReceiveRequested, this, &ChatForm::onFileRecvRequest);
connect(profile, &Profile::friendAvatarChanged, this, &ChatForm::onAvatarChanged); connect(profile, &Profile::friendAvatarChanged, this, &ChatForm::onAvatarChanged);
connect(core, &Core::fileSendStarted, this, &ChatForm::startFileSend); 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::fileSendFailed, this, &ChatForm::onFileSendFailed);
connect(core, &Core::receiptRecieved, this, &ChatForm::onReceiptReceived); connect(core, &Core::receiptRecieved, this, &ChatForm::onReceiptReceived);
connect(core, &Core::friendMessageReceived, this, &ChatForm::onFriendMessageReceived); connect(core, &Core::friendMessageReceived, this, &ChatForm::onFriendMessageReceived);
@ -312,9 +315,35 @@ void ChatForm::startFileSend(ToxFile file)
insertChatMessage( insertChatMessage(
ChatMessage::createFileTransferMessage(name, file, true, QDateTime::currentDateTime())); 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); 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) void ChatForm::onFileRecvRequest(ToxFile file)
{ {
if (file.friendId != f->getId()) { if (file.friendId != f->getId()) {
@ -331,9 +360,17 @@ void ChatForm::onFileRecvRequest(ToxFile file)
ChatMessage::Ptr msg = ChatMessage::Ptr msg =
ChatMessage::createFileTransferMessage(name, file, false, QDateTime::currentDateTime()); ChatMessage::createFileTransferMessage(name, file, false, QDateTime::currentDateTime());
insertChatMessage(msg); 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<ChatLineContentProxy*>(msg->getContent(1)); ChatLineContentProxy* proxy = static_cast<ChatLineContentProxy*>(msg->getContent(1));
assert(proxy->getWidgetType() == ChatLineContentProxy::FileTransferWidgetType); assert(proxy->getWidgetType() == ChatLineContentProxy::FileTransferWidgetType);
FileTransferWidget* tfWidget = static_cast<FileTransferWidget*>(proxy->getWidget()); FileTransferWidget* tfWidget = static_cast<FileTransferWidget*>(proxy->getWidget());
@ -794,6 +831,11 @@ void ChatForm::handleLoadedMessages(QList<History::HistMessage> newHistMsgs, boo
MessageMetadata const metadata = getMessageMetadata(histMessage); MessageMetadata const metadata = getMessageMetadata(histMessage);
lastDate = addDateLineIfNeeded(chatLines, lastDate, histMessage, metadata); lastDate = addDateLineIfNeeded(chatLines, lastDate, histMessage, metadata);
auto msg = chatMessageFromHistMessage(histMessage, metadata); auto msg = chatMessageFromHistMessage(histMessage, metadata);
if (!msg) {
continue;
}
if (processUndelivered) { if (processUndelivered) {
sendLoadedMessage(msg, metadata); sendLoadedMessage(msg, metadata);
} }
@ -838,7 +880,9 @@ ChatForm::MessageMetadata ChatForm::getMessageMetadata(History::HistMessage cons
const QDateTime msgDateTime = histMessage.timestamp.toLocalTime(); const QDateTime msgDateTime = histMessage.timestamp.toLocalTime();
const bool isSelf = Core::getInstance()->getSelfId().getPublicKey() == authorPk; const bool isSelf = Core::getInstance()->getSelfId().getPublicKey() == authorPk;
const bool needSending = !histMessage.isSent && isSelf; 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; const qint64 id = histMessage.id;
return {isSelf, needSending, isAction, id, authorPk, msgDateTime}; return {isSelf, needSending, isAction, id, authorPk, msgDateTime};
} }
@ -848,11 +892,30 @@ ChatMessage::Ptr ChatForm::chatMessageFromHistMessage(History::HistMessage const
{ {
ToxPk authorPk(ToxId(histMessage.sender).getPublicKey()); ToxPk authorPk(ToxId(histMessage.sender).getPublicKey());
QString authorStr = getMsgAuthorDispName(authorPk, histMessage.dispName); 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; 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)) { if (!metadata.isAction && needsToHideName(authorPk, metadata.msgDateTime)) {
msg->hideSender(); msg->hideSender();
} }
@ -1135,13 +1198,19 @@ void ChatForm::onExportChat()
QString buffer; QString buffer;
for (const auto& it : msgs) { for (const auto& it : msgs) {
if (it.content.getType() != HistMessageContentType::message) {
continue;
}
QString timestamp = it.timestamp.time().toString("hh:mm:ss"); QString timestamp = it.timestamp.time().toString("hh:mm:ss");
QString datestamp = it.timestamp.date().toString("yyyy-MM-dd"); QString datestamp = it.timestamp.date().toString("yyyy-MM-dd");
ToxPk authorPk(ToxId(it.sender).getPublicKey()); ToxPk authorPk(ToxId(it.sender).getPublicKey());
QString author = getMsgAuthorDispName(authorPk, it.dispName); QString author = getMsgAuthorDispName(authorPk, it.dispName);
if (it.content.getType() == HistMessageContentType::message) {
buffer = buffer buffer = buffer
% QString{datestamp % '\t' % timestamp % '\t' % author % '\t' % it.message % '\n'}; % QString{datestamp % '\t' % timestamp % '\t' % author % '\t'
% it.content.asMessage() % '\n'};
}
} }
file.write(buffer.toUtf8()); file.write(buffer.toUtf8());
file.close(); file.close();

View File

@ -68,11 +68,14 @@ signals:
public slots: public slots:
void startFileSend(ToxFile file); void startFileSend(ToxFile file);
void onFileTransferFinished(ToxFile file);
void onFileTransferCancelled(ToxFile file);
void onFileTransferBrokenUnbroken(ToxFile file, bool broken);
void onFileRecvRequest(ToxFile file); void onFileRecvRequest(ToxFile file);
void onAvInvite(uint32_t friendId, bool video); void onAvInvite(uint32_t friendId, bool video);
void onAvStart(uint32_t friendId, bool video); void onAvStart(uint32_t friendId, bool video);
void onAvEnd(uint32_t friendId, bool error); 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 onFileNameChanged(const ToxPk& friendPk);
void clearChatArea(); void clearChatArea();