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:
parent
567ddfb203
commit
d9b39b3102
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user