From 157be30b11144edc13469bdbe9799a4c71cb5806 Mon Sep 17 00:00:00 2001 From: Mick Sayson Date: Mon, 24 Sep 2018 20:08:30 -0700 Subject: [PATCH 1/7] refactor(files): Refactor FileTransferWidget Rational here is that the current FileTransferWidget is quite entangled with core logic. If we are going to instantiate the FileTransferWidget without an active file transfer the widget needs to behave sanely without getting messages from toxcore. This changeset is an attempt to allow us to move from any FileTransferWidget state to any other state without having to go through the appropriate state transitions. --- CMakeLists.txt | 2 + src/chatlog/content/filetransferwidget.cpp | 365 ++++++++++++--------- src/chatlog/content/filetransferwidget.h | 28 +- src/chatlog/toxfileprogress.cpp | 92 ++++++ src/chatlog/toxfileprogress.h | 53 +++ src/core/corefile.cpp | 14 +- src/core/toxfile.cpp | 5 +- src/core/toxfile.h | 10 +- 8 files changed, 385 insertions(+), 184 deletions(-) create mode 100644 src/chatlog/toxfileprogress.cpp create mode 100644 src/chatlog/toxfileprogress.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a229ad633..b5bdda9c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -249,6 +249,8 @@ set(${PROJECT_NAME}_SOURCES src/chatlog/documentcache.h src/chatlog/pixmapcache.cpp src/chatlog/pixmapcache.h + src/chatlog/toxfileprogress.cpp + src/chatlog/toxfileprogress.h src/chatlog/textformatter.cpp src/chatlog/textformatter.h src/core/coreav.cpp diff --git a/src/chatlog/content/filetransferwidget.cpp b/src/chatlog/content/filetransferwidget.cpp index 3da558b3c..db0428b51 100644 --- a/src/chatlog/content/filetransferwidget.cpp +++ b/src/chatlog/content/filetransferwidget.cpp @@ -41,6 +41,7 @@ #include + // The leftButton is used to accept, pause, or resume a file transfer, as well as to open a // received file. // The rightButton is used to cancel a file transfer, or to open the directory a file was @@ -50,7 +51,6 @@ FileTransferWidget::FileTransferWidget(QWidget* parent, ToxFile file) : QWidget(parent) , ui(new Ui::FileTransferWidget) , fileInfo(file) - , lastTick(QTime::currentTime()) , backgroundColor(Style::getColor(Style::LightGrey)) , buttonColor(Style::getColor(Style::Yellow)) , buttonBackgroundColor(Style::getColor(Style::White)) @@ -84,8 +84,6 @@ FileTransferWidget::FileTransferWidget(QWidget* parent, ToxFile file) update(); }); - setBackgroundColor(Style::getColor(Style::LightGrey), false); - connect(Core::getInstance(), &Core::fileTransferInfo, this, &FileTransferWidget::onFileTransferInfo); connect(Core::getInstance(), &Core::fileTransferAccepted, this, @@ -105,16 +103,11 @@ FileTransferWidget::FileTransferWidget(QWidget* parent, ToxFile file) connect(ui->previewButton, &QPushButton::clicked, this, &FileTransferWidget::onPreviewButtonClicked); - setupButtons(); + // Set lastStatus to anything but the file's current value, this forces an update + lastStatus = file.status == ToxFile::FINISHED ? ToxFile::INITIALIZING : ToxFile::FINISHED; + updateWidget(file); // preview - if (fileInfo.direction == ToxFile::SENDING) { - showPreview(fileInfo.filePath); - ui->progressLabel->setText(tr("Waiting to send...", "file transfer widget")); - } else { - ui->progressLabel->setText(tr("Accept to receive this file", "file transfer widget")); - } - setFixedHeight(64); } @@ -267,154 +260,32 @@ void FileTransferWidget::paintEvent(QPaintEvent*) void FileTransferWidget::onFileTransferInfo(ToxFile file) { - QTime now = QTime::currentTime(); - qint64 dt = lastTick.msecsTo(now); // ms - - if (fileInfo != file || dt < 1000) - return; - - fileInfo = file; - - if (fileInfo.status == ToxFile::TRANSMITTING) { - // update progress - qreal progress = static_cast(file.bytesSent) / static_cast(file.filesize); - ui->progressBar->setValue(static_cast(progress * 100.0)); - - // ETA, speed - qreal deltaSecs = dt / 1000.0; - - // (can't use ::abs or ::max on unsigned types substraction, they'd just overflow) - quint64 deltaBytes = file.bytesSent > lastBytesSent ? file.bytesSent - lastBytesSent - : lastBytesSent - file.bytesSent; - qreal bytesPerSec = static_cast(static_cast(deltaBytes) / deltaSecs); - - // calculate mean - meanIndex = meanIndex % TRANSFER_ROLLING_AVG_COUNT; - meanData[meanIndex++] = bytesPerSec; - - qreal meanBytesPerSec = 0.0; - for (size_t i = 0; i < TRANSFER_ROLLING_AVG_COUNT; ++i) - meanBytesPerSec += meanData[i]; - - meanBytesPerSec /= static_cast(TRANSFER_ROLLING_AVG_COUNT); - - // update UI - if (meanBytesPerSec > 0) { - // ETA - QTime toGo = QTime(0, 0).addSecs((file.filesize - file.bytesSent) / meanBytesPerSec); - QString format = toGo.hour() > 0 ? "hh:mm:ss" : "mm:ss"; - ui->etaLabel->setText(toGo.toString(format)); - } else { - ui->etaLabel->setText(""); - } - - ui->progressLabel->setText(getHumanReadableSize(meanBytesPerSec) + "/s"); - - lastBytesSent = file.bytesSent; - } - - lastTick = now; - - // trigger repaint - update(); + updateWidget(file); } void FileTransferWidget::onFileTransferAccepted(ToxFile file) { - if (fileInfo != file) - return; - - fileInfo = file; - - setBackgroundColor(Style::getColor(Style::LightGrey), false); - - setupButtons(); + updateWidget(file); } void FileTransferWidget::onFileTransferCancelled(ToxFile file) { - if (fileInfo != file) - return; - - fileInfo = file; - active = false; - - setBackgroundColor(Style::getColor(Style::Red), true); - - setupButtons(); - hideWidgets(); - - disconnect(Core::getInstance(), nullptr, this, nullptr); + updateWidget(file); } void FileTransferWidget::onFileTransferPaused(ToxFile file) { - if (fileInfo != file) - return; - - fileInfo = file; - - ui->etaLabel->setText(""); - ui->progressLabel->setText(tr("Paused", "file transfer widget")); - - // reset mean - meanIndex = 0; - for (size_t i = 0; i < TRANSFER_ROLLING_AVG_COUNT; ++i) - meanData[i] = 0.0; - - setBackgroundColor(Style::getColor(Style::LightGrey), false); - - setupButtons(); + updateWidget(file); } void FileTransferWidget::onFileTransferResumed(ToxFile file) { - if (fileInfo != file) - return; - - fileInfo = file; - - ui->etaLabel->setText(""); - ui->progressLabel->setText(tr("Resuming...", "file transfer widget")); - - // reset mean - meanIndex = 0; - for (size_t i = 0; i < TRANSFER_ROLLING_AVG_COUNT; ++i) - meanData[i] = 0.0; - - setBackgroundColor(Style::getColor(Style::LightGrey), false); - - setupButtons(); + updateWidget(file); } void FileTransferWidget::onFileTransferFinished(ToxFile file) { - if (fileInfo != file) - return; - - fileInfo = file; - active = false; - - setBackgroundColor(Style::getColor(Style::Green), true); - - setupButtons(); - hideWidgets(); - - ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/yes.svg"))); - ui->leftButton->setObjectName("ok"); - ui->leftButton->setToolTip(tr("Open file")); - ui->leftButton->show(); - - ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/dir.svg"))); - ui->rightButton->setObjectName("dir"); - ui->rightButton->setToolTip(tr("Open file directory")); - ui->rightButton->show(); - - // preview - if (fileInfo.direction == ToxFile::RECEIVING) - showPreview(fileInfo.filePath); - - disconnect(Core::getInstance(), nullptr, this, nullptr); + updateWidget(file); } void FileTransferWidget::fileTransferRemotePausedUnpaused(ToxFile file, bool paused) @@ -443,18 +314,146 @@ QString FileTransferWidget::getHumanReadableSize(qint64 size) return QString().setNum(size / pow(1024, exp), 'f', exp > 1 ? 2 : 0).append(suffix[exp]); } -void FileTransferWidget::hideWidgets() +void FileTransferWidget::updateWidgetColor(ToxFile const& file) { - ui->leftButton->hide(); - ui->rightButton->hide(); - ui->progressBar->hide(); - ui->progressLabel->hide(); - ui->etaLabel->hide(); + if (lastStatus == file.status) + return; + + switch (file.status) { + case ToxFile::INITIALIZING: + case ToxFile::PAUSED: + case ToxFile::TRANSMITTING: + setBackgroundColor(Style::getColor(Style::LightGrey), false); + break; + case ToxFile::BROKEN: + case ToxFile::CANCELED: + setBackgroundColor(Style::getColor(Style::Red), true); + break; + case ToxFile::FINISHED: + setBackgroundColor(Style::getColor(Style::Green), true); + break; + } } -void FileTransferWidget::setupButtons() +void FileTransferWidget::updateWidgetText(ToxFile const& file) { - switch (fileInfo.status) { + if (lastStatus == file.status) + return; + + switch (file.status) { + case ToxFile::INITIALIZING: + if (file.direction == ToxFile::SENDING) { + ui->progressLabel->setText(tr("Waiting to send...", "file transfer widget")); + } else { + ui->progressLabel->setText(tr("Accept to receive this file", "file transfer widget")); + } + break; + case ToxFile::PAUSED: + ui->etaLabel->setText(""); + ui->progressLabel->setText(tr("Paused", "file transfer widget")); + break; + case ToxFile::TRANSMITTING: + ui->etaLabel->setText(""); + ui->progressLabel->setText(tr("Resuming...", "file transfer widget")); + break; + case ToxFile::BROKEN: + case ToxFile::CANCELED: + break; + case ToxFile::FINISHED: + break; + } +} + +void FileTransferWidget::updatePreview(ToxFile const& file) +{ + 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; + } + +} + +void FileTransferWidget::updateFileProgress(ToxFile const& file) +{ + switch (file.status) { + case ToxFile::INITIALIZING: + break; + case ToxFile::PAUSED: + fileProgress.resetSpeed(); + break; + case ToxFile::TRANSMITTING: { + if (!fileProgress.needsUpdate()) + break; + + fileProgress.addSample(file); + auto speed = fileProgress.getSpeed(); + auto progress = fileProgress.getProgress(); + auto remainingTime = fileProgress.getTimeLeftSeconds(); + + ui->progressBar->setValue(static_cast(progress * 100.0)); + + // update UI + if (speed > 0) { + // ETA + QTime toGo = QTime(0, 0).addSecs(remainingTime); + QString format = toGo.hour() > 0 ? "hh:mm:ss" : "mm:ss"; + ui->etaLabel->setText(toGo.toString(format)); + } else { + ui->etaLabel->setText(""); + } + + ui->progressLabel->setText(getHumanReadableSize(speed) + "/s"); + break; + } + case ToxFile::BROKEN: + case ToxFile::CANCELED: + case ToxFile::FINISHED: { + ui->progressBar->hide(); + ui->progressLabel->hide(); + ui->etaLabel->hide(); + break; + } + } +} + +void FileTransferWidget::updateSignals(ToxFile const& file) +{ + if (lastStatus == file.status) + return; + + switch (file.status) { + case ToxFile::CANCELED: + case ToxFile::BROKEN: + case ToxFile::FINISHED: + active = false; + disconnect(Core::getInstance(), nullptr, this, nullptr); + break; + case ToxFile::INITIALIZING: + case ToxFile::PAUSED: + case ToxFile::TRANSMITTING: + break; + } +} + +void FileTransferWidget::setupButtons(ToxFile const& file) +{ + if (lastStatus == file.status) + return; + + switch (file.status) { case ToxFile::TRANSMITTING: ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/pause.svg"))); ui->leftButton->setObjectName("pause"); @@ -479,13 +478,12 @@ void FileTransferWidget::setupButtons() setButtonColor(Style::getColor(Style::LightGrey)); break; - case ToxFile::STOPPED: - case ToxFile::BROKEN: + case ToxFile::INITIALIZING: ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/no.svg"))); ui->rightButton->setObjectName("cancel"); ui->rightButton->setToolTip(tr("Cancel transfer")); - if (fileInfo.direction == ToxFile::SENDING) { + if (file.direction == ToxFile::SENDING) { ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/pause.svg"))); ui->leftButton->setObjectName("pause"); ui->leftButton->setToolTip(tr("Pause transfer")); @@ -494,6 +492,23 @@ void FileTransferWidget::setupButtons() ui->leftButton->setObjectName("accept"); ui->leftButton->setToolTip(tr("Accept transfer")); } + break; + case ToxFile::CANCELED: + case ToxFile::BROKEN: + ui->leftButton->hide(); + ui->rightButton->hide(); + break; + case ToxFile::FINISHED: + ui->leftButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/yes.svg"))); + ui->leftButton->setObjectName("ok"); + ui->leftButton->setToolTip(tr("Open file")); + ui->leftButton->show(); + + ui->rightButton->setIcon(QIcon(Style::getImagePath("fileTransferInstance/dir.svg"))); + ui->rightButton->setObjectName("dir"); + ui->rightButton->setToolTip(tr("Open file directory")); + ui->rightButton->show(); + break; } } @@ -549,7 +564,8 @@ void FileTransferWidget::showPreview(const QString& filename) } const QByteArray imageFileData = imageFile.readAll(); QImage image = QImage::fromData(imageFileData); - const int exifOrientation = getExifOrientation(imageFileData.constData(), imageFileData.size()); + const int exifOrientation = + getExifOrientation(imageFileData.constData(), imageFileData.size()); if (exifOrientation) { applyTransformation(exifOrientation, image); } @@ -562,13 +578,12 @@ void FileTransferWidget::showPreview(const QString& filename) // 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}; + const int maxPreviewHeight{desktopSize.height() / 2}; const QImage previewImage = [&image, maxPreviewWidth, maxPreviewHeight]() { if (image.width() > maxPreviewWidth || image.height() > maxPreviewHeight) { - return image.scaled(maxPreviewWidth, maxPreviewHeight, - Qt::KeepAspectRatio, Qt::SmoothTransformation); - } - else { + return image.scaled(maxPreviewWidth, maxPreviewHeight, Qt::KeepAspectRatio, + Qt::SmoothTransformation); + } else { return image; } }(); @@ -578,8 +593,7 @@ void FileTransferWidget::showPreview(const QString& filename) buffer.open(QIODevice::WriteOnly); previewImage.save(&buffer, "PNG"); buffer.close(); - ui->previewButton->setToolTip(""); + ui->previewButton->setToolTip(""); } } @@ -641,12 +655,11 @@ int FileTransferWidget::getExifOrientation(const char* data, const int size) void FileTransferWidget::applyTransformation(const int orientation, QImage& image) { QTransform exifTransform; - switch(static_cast(orientation)) - { + switch (static_cast(orientation)) { case ExifOrientation::TopLeft: break; case ExifOrientation::TopRight: - image = image.mirrored(1,0); + image = image.mirrored(1, 0); break; case ExifOrientation::BottomRight: exifTransform.rotate(180); @@ -673,3 +686,33 @@ void FileTransferWidget::applyTransformation(const int orientation, QImage& imag } image = image.transformed(exifTransform); } + +void FileTransferWidget::updateWidget(ToxFile const& file) +{ + if (fileInfo != file) + return; + + fileInfo = file; + + // If we repainted on every packet our gui would be *very* slow + bool bTransmitNeedsUpdate = fileProgress.needsUpdate(); + + updatePreview(file); + updateFileProgress(file); + updateWidgetText(file); + updateWidgetColor(file); + setupButtons(file); + updateSignals(file); + + lastStatus = file.status; + + // trigger repaint + switch (file.status) { + case ToxFile::TRANSMITTING: + if (!bTransmitNeedsUpdate) + break; + // fallthrough + default: + update(); + } +} diff --git a/src/chatlog/content/filetransferwidget.h b/src/chatlog/content/filetransferwidget.h index 10b418014..fff5ea6ba 100644 --- a/src/chatlog/content/filetransferwidget.h +++ b/src/chatlog/content/filetransferwidget.h @@ -24,6 +24,7 @@ #include #include "src/chatlog/chatlinecontent.h" +#include "src/chatlog/toxfileprogress.h" #include "src/core/toxfile.h" @@ -56,8 +57,12 @@ protected slots: protected: QString getHumanReadableSize(qint64 size); - void hideWidgets(); - void setupButtons(); + void updateWidgetColor(ToxFile const& file); + void updateWidgetText(ToxFile const& file); + void updateFileProgress(ToxFile const& file); + void updateSignals(ToxFile const& file); + void updatePreview(ToxFile const& file); + void setupButtons(ToxFile const& file); void handleButton(QPushButton* btn); void showPreview(const QString& filename); void acceptTransfer(const QString& filepath); @@ -79,28 +84,29 @@ private: static void applyTransformation(const int oritentation, QImage& image); static bool tryRemoveFile(const QString &filepath); + void updateWidget(ToxFile const& file); + private: Ui::FileTransferWidget* ui; + ToxFileProgress fileProgress; ToxFile fileInfo; - QTime lastTick; - quint64 lastBytesSent = 0; QVariantAnimation* backgroundColorAnimation = nullptr; QVariantAnimation* buttonColorAnimation = nullptr; QColor backgroundColor; QColor buttonColor; QColor buttonBackgroundColor; - static const uint8_t TRANSFER_ROLLING_AVG_COUNT = 4; - uint8_t meanIndex = 0; - qreal meanData[TRANSFER_ROLLING_AVG_COUNT] = {0.0}; - bool active; - enum class ExifOrientation { + ToxFile::FileStatus lastStatus = ToxFile::INITIALIZING; + + enum class ExifOrientation + { /* do not change values, this is exif spec * * name corresponds to where the 0 row and 0 column is in form row-column - * i.e. entry 5 here means that the 0'th row corresponds to the left side of the scene and the 0'th column corresponds - * to the top of the captured scene. This means that the image needs to be mirrored and rotated to be displayed. + * i.e. entry 5 here means that the 0'th row corresponds to the left side of the scene and + * the 0'th column corresponds to the top of the captured scene. This means that the image + * needs to be mirrored and rotated to be displayed. */ TopLeft = 1, TopRight = 2, diff --git a/src/chatlog/toxfileprogress.cpp b/src/chatlog/toxfileprogress.cpp new file mode 100644 index 000000000..e1d2f1a9b --- /dev/null +++ b/src/chatlog/toxfileprogress.cpp @@ -0,0 +1,92 @@ +/* + Copyright © 2018 by The qTox Project Contributors + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + qTox is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with qTox. If not, see . +*/ + +#include "toxfileprogress.h" + +#include "src/core/toxfile.h" + +bool ToxFileProgress::needsUpdate() const +{ + QTime now = QTime::currentTime(); + qint64 dt = lastTick.msecsTo(now); // ms + + if (dt < 1000) { + return false; + } + + return true; +} + +void ToxFileProgress::addSample(ToxFile const& file) +{ + QTime now = QTime::currentTime(); + qint64 dt = lastTick.msecsTo(now); // ms + + if (dt < 1000) { + return; + } + + // ETA, speed + qreal deltaSecs = dt / 1000.0; + + // (can't use ::abs or ::max on unsigned types substraction, they'd just overflow) + quint64 deltaBytes = file.bytesSent > lastBytesSent ? file.bytesSent - lastBytesSent + : lastBytesSent - file.bytesSent; + qreal bytesPerSec = static_cast(static_cast(deltaBytes) / deltaSecs); + + // Update member variables + meanIndex = meanIndex % TRANSFER_ROLLING_AVG_COUNT; + meanData[meanIndex++] = bytesPerSec; + + double meanBytesPerSec = 0.0; + for (size_t i = 0; i < TRANSFER_ROLLING_AVG_COUNT; ++i) + meanBytesPerSec += meanData[i]; + meanBytesPerSec /= static_cast(TRANSFER_ROLLING_AVG_COUNT); + + lastTick = now; + + progress = static_cast(file.bytesSent) / static_cast(file.filesize); + speedBytesPerSecond = meanBytesPerSec; + timeLeftSeconds = (file.filesize - file.bytesSent) / getSpeed(); + + lastBytesSent = file.bytesSent; +} + +void ToxFileProgress::resetSpeed() +{ + meanIndex = 0; + for (auto& item : meanData) { + item = 0; + } +} + +double ToxFileProgress::getProgress() const +{ + return progress; +} + +double ToxFileProgress::getSpeed() const +{ + return speedBytesPerSecond; +} + +double ToxFileProgress::getTimeLeftSeconds() const +{ + return timeLeftSeconds; +} diff --git a/src/chatlog/toxfileprogress.h b/src/chatlog/toxfileprogress.h new file mode 100644 index 000000000..c16626901 --- /dev/null +++ b/src/chatlog/toxfileprogress.h @@ -0,0 +1,53 @@ +/* + Copyright © 2018 by The qTox Project Contributors + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + qTox is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with qTox. If not, see . +*/ + +#ifndef TOXFILEPROGRESS_H +#define TOXFILEPROGRESS_H + +#include + +struct ToxFile; + +class ToxFileProgress +{ +public: + bool needsUpdate() const; + void addSample(ToxFile const& file); + void resetSpeed(); + + double getProgress() const; + double getSpeed() const; + double getTimeLeftSeconds() const; + +private: + uint64_t lastBytesSent = 0; + + static const uint8_t TRANSFER_ROLLING_AVG_COUNT = 4; + uint8_t meanIndex = 0; + double meanData[TRANSFER_ROLLING_AVG_COUNT] = {0.0}; + + QTime lastTick = QTime::currentTime(); + + double speedBytesPerSecond; + double timeLeftSeconds; + double progress; +}; + + +#endif // TOXFILEPROGRESS_H diff --git a/src/core/corefile.cpp b/src/core/corefile.cpp index 19a4edd40..44d49518f 100644 --- a/src/core/corefile.cpp +++ b/src/core/corefile.cpp @@ -199,7 +199,7 @@ void CoreFile::cancelFileSend(Core* core, uint32_t friendId, uint32_t fileId) return; } - file->status = ToxFile::STOPPED; + file->status = ToxFile::CANCELED; emit core->fileTransferCancelled(*file); tox_file_control(core->tox.get(), file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); removeFile(friendId, fileId); @@ -212,7 +212,7 @@ void CoreFile::cancelFileRecv(Core* core, uint32_t friendId, uint32_t fileId) qWarning("cancelFileRecv: No such file in queue"); return; } - file->status = ToxFile::STOPPED; + file->status = ToxFile::CANCELED; emit core->fileTransferCancelled(*file); tox_file_control(core->tox.get(), file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); removeFile(friendId, fileId); @@ -225,7 +225,7 @@ void CoreFile::rejectFileRecvRequest(Core* core, uint32_t friendId, uint32_t fil qWarning("rejectFileRecvRequest: No such file in queue"); return; } - file->status = ToxFile::STOPPED; + file->status = ToxFile::CANCELED; emit core->fileTransferCancelled(*file); tox_file_control(core->tox.get(), file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); removeFile(friendId, fileId); @@ -379,6 +379,7 @@ void CoreFile::onFileControlCallback(Tox*, uint32_t friendId, uint32_t fileId, if (control == TOX_FILE_CONTROL_CANCEL) { if (file->fileKind != TOX_FILE_KIND_AVATAR) qDebug() << "File tranfer" << friendId << ":" << fileId << "cancelled by friend"; + file->status = ToxFile::CANCELED; emit static_cast(core)->fileTransferCancelled(*file); removeFile(friendId, fileId); } else if (control == TOX_FILE_CONTROL_PAUSE) { @@ -409,6 +410,7 @@ void CoreFile::onFileDataCallback(Tox* tox, uint32_t friendId, uint32_t fileId, // If we reached EOF, ack and cleanup the transfer if (!length) { + file->status = ToxFile::FINISHED; if (file->fileKind != TOX_FILE_KIND_AVATAR) { emit static_cast(core)->fileTransferFinished(*file); emit static_cast(core)->fileUploadFinished(file->filePath); @@ -429,6 +431,7 @@ void CoreFile::onFileDataCallback(Tox* tox, uint32_t friendId, uint32_t fileId, nread = file->file->read((char*)data.get(), length); if (nread <= 0) { qWarning("onFileDataCallback: Failed to read from file"); + file->status = ToxFile::CANCELED; emit static_cast(core)->fileTransferCancelled(*file); tox_file_send_chunk(tox, friendId, fileId, pos, nullptr, 0, nullptr); removeFile(friendId, fileId); @@ -458,14 +461,17 @@ void CoreFile::onFileRecvChunkCallback(Tox* tox, uint32_t friendId, uint32_t fil if (file->bytesSent != position) { qWarning("onFileRecvChunkCallback: Received a chunk out-of-order, aborting transfer"); - if (file->fileKind != TOX_FILE_KIND_AVATAR) + if (file->fileKind != TOX_FILE_KIND_AVATAR) { + file->status = ToxFile::CANCELED; emit core->fileTransferCancelled(*file); + } tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr); removeFile(friendId, fileId); return; } if (!length) { + file->status = ToxFile::FINISHED; if (file->fileKind == TOX_FILE_KIND_AVATAR) { QPixmap pic; pic.loadFromData(file->avatarData); diff --git a/src/core/toxfile.cpp b/src/core/toxfile.cpp index bd1ac7665..2dbaf3a54 100644 --- a/src/core/toxfile.cpp +++ b/src/core/toxfile.cpp @@ -28,10 +28,9 @@ ToxFile::ToxFile(uint32_t fileNum, uint32_t friendId, QByteArray filename, QStri , file{new QFile(filePath)} , bytesSent{0} , filesize{0} - , status{STOPPED} + , status{INITIALIZING} , direction{Direction} -{ -} +{} bool ToxFile::operator==(const ToxFile& other) const { diff --git a/src/core/toxfile.h b/src/core/toxfile.h index 358366fb9..755c3393b 100644 --- a/src/core/toxfile.h +++ b/src/core/toxfile.h @@ -11,10 +11,12 @@ struct ToxFile { enum FileStatus { - STOPPED, + INITIALIZING, PAUSED, TRANSMITTING, - BROKEN + BROKEN, + CANCELED, + FINISHED, }; enum FileDirection : bool @@ -26,9 +28,7 @@ struct ToxFile ToxFile() = default; ToxFile(uint32_t FileNum, uint32_t FriendId, QByteArray FileName, QString filePath, FileDirection Direction); - ~ToxFile() - { - } + ~ToxFile() {} bool operator==(const ToxFile& other) const; bool operator!=(const ToxFile& other) const; From fb805b9cdb5f108b15da33aa0bd6c250aac7a8b1 Mon Sep 17 00:00:00 2001 From: Mick Sayson Date: Mon, 24 Sep 2018 01:05:43 -0700 Subject: [PATCH 2/7] feat(db): Support schema version upgrades --- src/persistence/history.cpp | 46 +++++++++++++++++++++++++++++++++++++ src/persistence/history.h | 1 + 2 files changed, 47 insertions(+) diff --git a/src/persistence/history.cpp b/src/persistence/history.cpp index b259e8bdd..4485ddaba 100644 --- a/src/persistence/history.cpp +++ b/src/persistence/history.cpp @@ -35,6 +35,7 @@ */ static constexpr int NUM_MESSAGES_DEFAULT = 100; // arbitrary number of messages loaded when not loading by date +static constexpr int SCHEMA_VERSION = 0; /** * @brief Prepares the database to work with the history. @@ -48,6 +49,13 @@ History::History(std::shared_ptr db) return; } + dbSchemaUpgrade(); + + // dbSchemaUpgrade may have put us in an invalid state + if (!isValid()) { + return; + } + db->execLater( "CREATE TABLE IF NOT EXISTS peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL " "UNIQUE);" @@ -493,3 +501,41 @@ QList History::getChatHistory(const QString& friendPk, con return messages; } + +/** + * @brief Upgrade the db schema + * @note On future alterations of the database all you have to do is bump the SCHEMA_VERSION + * variable and add another case to the switch statement below. Make sure to fall through on each case. + */ +void History::dbSchemaUpgrade() +{ + int64_t databaseSchemaVersion; + db->execNow(RawDatabase::Query("PRAGMA user_version", [&] (const QVector& row){ + databaseSchemaVersion = row[0].toLongLong(); + })); + + if (databaseSchemaVersion > SCHEMA_VERSION) { + qWarning() << "Database version is newer than we currently support. Please upgrade qTox"; + // We don't know what future versions have done, we have to disable db access until we re-upgrade + db.reset(); + } + else if (databaseSchemaVersion == SCHEMA_VERSION) { + // No work to do + return; + } + + switch (databaseSchemaVersion) + { + //case 0: + // do 0 -> 1 upgrade + // //fallthrough + //case 1: + // do 1 -> 2 upgrade + // //fallthrough + // etc. + default: + db->execLater(RawDatabase::Query(QStringLiteral("PRAGMA user_version = %1;").arg(SCHEMA_VERSION))); + qDebug() << "Database upgrade finished (databaseSchemaVersion " + << databaseSchemaVersion << " -> " << SCHEMA_VERSION << ")"; + } +} diff --git a/src/persistence/history.h b/src/persistence/history.h index 5da854378..693053a90 100644 --- a/src/persistence/history.h +++ b/src/persistence/history.h @@ -99,6 +99,7 @@ protected: private: QList getChatHistory(const QString& friendPk, const QDateTime& from, const QDateTime& to, int numMessages); + void dbSchemaUpgrade(); std::shared_ptr db; QHash peers; }; From 567ddfb2035dad1f3aad94789e76f8a4e4f77d07 Mon Sep 17 00:00:00 2001 From: Mick Sayson Date: Mon, 24 Sep 2018 01:05:43 -0700 Subject: [PATCH 3/7] feat(db): Database support for file history --- src/core/toxfile.h | 21 ++-- src/persistence/history.cpp | 202 ++++++++++++++++++++++++++++-------- src/persistence/history.h | 33 +++++- src/persistence/profile.cpp | 2 +- src/persistence/profile.h | 2 +- 5 files changed, 200 insertions(+), 60 deletions(-) diff --git a/src/core/toxfile.h b/src/core/toxfile.h index 755c3393b..4305cfa84 100644 --- a/src/core/toxfile.h +++ b/src/core/toxfile.h @@ -9,26 +9,29 @@ class QTimer; struct ToxFile { + // Note do not change values, these are directly inserted into the DB in their + // current form, changing order would mess up database state! enum FileStatus { - INITIALIZING, - PAUSED, - TRANSMITTING, - BROKEN, - CANCELED, - FINISHED, + INITIALIZING = 0, + PAUSED = 1, + TRANSMITTING = 2, + BROKEN = 3, + CANCELED = 4, + FINISHED = 5, }; + // Note do not change values, these are directly inserted into the DB in their + // current form (can add fields though as db representation is an int) enum FileDirection : bool { - SENDING, - RECEIVING + SENDING = 0, + RECEIVING = 1, }; ToxFile() = default; ToxFile(uint32_t FileNum, uint32_t FriendId, QByteArray FileName, QString filePath, FileDirection Direction); - ~ToxFile() {} bool operator==(const ToxFile& other) const; bool operator!=(const ToxFile& other) const; diff --git a/src/persistence/history.cpp b/src/persistence/history.cpp index 4485ddaba..3b8965790 100644 --- a/src/persistence/history.cpp +++ b/src/persistence/history.cpp @@ -34,8 +34,15 @@ * Caches mappings to speed up message saving. */ -static constexpr int NUM_MESSAGES_DEFAULT = 100; // arbitrary number of messages loaded when not loading by date -static constexpr int SCHEMA_VERSION = 0; +static constexpr int NUM_MESSAGES_DEFAULT = + 100; // arbitrary number of messages loaded when not loading by date +static constexpr int SCHEMA_VERSION = 1; + +FileDbInsertionData::FileDbInsertionData() +{ + static int id = qRegisterMetaType(); + (void)id; +} /** * @brief Prepares the database to work with the history. @@ -56,14 +63,35 @@ History::History(std::shared_ptr db) return; } + connect(this, &History::fileInsertionReady, this, &History::onFileInsertionReady); + db->execLater( "CREATE TABLE IF NOT EXISTS peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL " "UNIQUE);" "CREATE TABLE IF NOT EXISTS aliases (id INTEGER PRIMARY KEY, owner INTEGER," "display_name BLOB NOT NULL, UNIQUE(owner, display_name));" - "CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, " - "chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, " - "message BLOB NOT NULL);" + "CREATE TABLE IF NOT EXISTS history " + "(id INTEGER PRIMARY KEY," + "timestamp INTEGER NOT NULL," + "chat_id INTEGER NOT NULL," + "sender_alias INTEGER NOT NULL," + // even though technically a message can be null for file transfer, we've opted + // to just insert an empty string when there's no content, this moderately simplifies + // implementation as currently our database doesn't have support for optional fields. + // We would either have to insert "?" or "null" based on if message exists and then + // ensure that our blob vector always has the right number of fields. Better to just + // leave this as NOT NULL for now. + "message BLOB NOT NULL," + "file_id INTEGER);" + "CREATE TABLE IF NOT EXISTS file_transfers " + "(id INTEGER PRIMARY KEY," + "chat_id INTEGER NOT NULL," + "file_restart_id BLOB NOT NULL," + "file_name BLOB NOT NULL, " + "file_path BLOB NOT NULL," + "file_size INTEGER NOT NULL," + "direction INTEGER NOT NULL," + "file_state INTEGER NOT NULL);" "CREATE TABLE IF NOT EXISTS faux_offline_pending (id INTEGER PRIMARY KEY);"); // Cache our current peers @@ -116,6 +144,7 @@ void History::eraseHistory() "DELETE FROM history;" "DELETE FROM aliases;" "DELETE FROM peers;" + "DELETE FROM file_transfers;" "VACUUM;"); } @@ -144,6 +173,7 @@ void History::removeFriendHistory(const QString& friendPk) "DELETE FROM history WHERE chat_id=%1; " "DELETE FROM aliases WHERE owner=%1; " "DELETE FROM peers WHERE id=%1; " + "DELETE FROM file_transfers WHERE chat_id=%1;" "VACUUM;") .arg(id); @@ -174,7 +204,7 @@ History::generateNewMessageQueries(const QString& friendPk, const QString& messa // Get the db id of the peer we're chatting with int64_t peerId; if (peers.contains(friendPk)) { - peerId = peers[friendPk]; + peerId = (peers)[friendPk]; } else { if (peers.isEmpty()) { peerId = 0; @@ -182,7 +212,7 @@ History::generateNewMessageQueries(const QString& friendPk, const QString& messa peerId = *std::max_element(peers.begin(), peers.end()) + 1; } - peers[friendPk] = peerId; + (peers)[friendPk] = peerId; queries += RawDatabase::Query(("INSERT INTO peers (id, public_key) " "VALUES (%1, '" + friendPk + "');") @@ -192,7 +222,7 @@ History::generateNewMessageQueries(const QString& friendPk, const QString& messa // Get the db id of the sender of the message int64_t senderId; if (peers.contains(sender)) { - senderId = peers[sender]; + senderId = (peers)[sender]; } else { if (peers.isEmpty()) { senderId = 0; @@ -200,7 +230,7 @@ History::generateNewMessageQueries(const QString& friendPk, const QString& messa senderId = *std::max_element(peers.begin(), peers.end()) + 1; } - peers[sender] = senderId; + (peers)[sender] = senderId; queries += RawDatabase::Query{("INSERT INTO peers (id, public_key) " "VALUES (%1, '" + sender + "');") @@ -236,6 +266,79 @@ History::generateNewMessageQueries(const QString& friendPk, const QString& messa return queries; } +void History::onFileInsertionReady(FileDbInsertionData data) +{ + + QVector queries; + std::weak_ptr weakThis = shared_from_this(); + + // peerId is guaranteed to be inserted since we just used it in addNewMessage + auto peerId = peers[data.friendPk]; + queries += + RawDatabase::Query(QStringLiteral("INSERT INTO file_transfers (chat_id, file_restart_id, " + "file_path, file_name, file_size, direction, file_state) " + "VALUES (%1, ?, ?, ?, %2, %3, %4);") + .arg(peerId) + .arg(data.size) + .arg(static_cast(data.direction)) + .arg(ToxFile::CANCELED), + {data.fileId.toUtf8(), data.filePath.toUtf8(), data.fileName}, {}); + + + queries += RawDatabase::Query(QStringLiteral("UPDATE history " + "SET file_id = (last_insert_rowid()) " + "WHERE id = %1") + .arg(data.historyId)); + + db->execLater(queries); +} + +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) +{ + // This is an incredibly far from an optimal way of implementing this, + // but given the frequency that people are going to be initiating a file + // transfer we can probably live with it. + + // Since both inserting an alias for a user and inserting a file transfer + // will generate new ids, there is no good way to inject both new ids into the + // history query without refactoring our RawDatabase::Query and processor loops. + + // What we will do instead is chain callbacks to try to get reasonable behavior. + // We can call the generateNewMessageQueries() fn to insert a message with an empty + // message in it, and get the id with the callbck. Once we have the id we can ammend + // the data to have our newly inserted file_id as well + + ToxFile::FileDirection direction; + if (sender == friendPk) { + direction = ToxFile::RECEIVING; + } else { + direction = ToxFile::SENDING; + } + + std::weak_ptr weakThis = shared_from_this(); + FileDbInsertionData insertionData; + insertionData.friendPk = friendPk; + insertionData.fileId = fileId; + insertionData.fileName = fileName; + insertionData.filePath = filePath; + insertionData.size = size; + insertionData.direction = direction; + + auto insertFileTransferFn = [weakThis, insertionData](int64_t messageId) { + auto insertionDataRw = std::move(insertionData); + + insertionDataRw.historyId = messageId; + + auto thisPtr = weakThis.lock(); + if (thisPtr) + emit thisPtr->fileInsertionReady(std::move(insertionDataRw)); + }; + + addNewMessage(friendPk, "", sender, time, true, dispName, insertFileTransferFn); +} + /** * @brief Saves a chat message in the database. * @param friendPk Friend publick key to save. @@ -269,8 +372,8 @@ void History::addNewMessage(const QString& friendPk, const QString& message, con * @param to End of period to fetch. * @return List of messages. */ -QList History::getChatHistoryFromDate(const QString& friendPk, const QDateTime& from, - const QDateTime& to) +QList History::getChatHistoryFromDate(const QString& friendPk, + const QDateTime& from, const QDateTime& to) { if (!isValid()) { return {}; @@ -288,7 +391,8 @@ QList History::getChatHistoryDefaultNum(const QString& fri if (!isValid()) { return {}; } - return getChatHistory(friendPk, QDateTime::fromMSecsSinceEpoch(0), QDateTime::currentDateTime(), NUM_MESSAGES_DEFAULT); + return getChatHistory(friendPk, QDateTime::fromMSecsSinceEpoch(0), QDateTime::currentDateTime(), + NUM_MESSAGES_DEFAULT); } @@ -341,7 +445,8 @@ QList History::getChatHistoryCounts(const ToxPk& friendPk * @param parameter for search * @return date of the message where the phrase was found */ -QDateTime History::getDateWhereFindPhrase(const QString& friendPk, const QDateTime& from, QString phrase, const ParameterSearch& parameter) +QDateTime History::getDateWhereFindPhrase(const QString& friendPk, const QDateTime& from, + QString phrase, const ParameterSearch& parameter) { QDateTime result; auto rowCallback = [&result](const QVector& row) { @@ -357,10 +462,12 @@ QDateTime History::getDateWhereFindPhrase(const QString& friendPk, const QDateTi message = QStringLiteral("message LIKE '%%1%'").arg(phrase); break; case FilterSearch::WordsOnly: - message = QStringLiteral("message REGEXP '%1'").arg(SearchExtraFunctions::generateFilterWordsOnly(phrase).toLower()); + message = QStringLiteral("message REGEXP '%1'") + .arg(SearchExtraFunctions::generateFilterWordsOnly(phrase).toLower()); break; case FilterSearch::RegisterAndWordsOnly: - message = QStringLiteral("REGEXPSENSITIVE(message, '%1')").arg(SearchExtraFunctions::generateFilterWordsOnly(phrase)); + message = QStringLiteral("REGEXPSENSITIVE(message, '%1')") + .arg(SearchExtraFunctions::generateFilterWordsOnly(phrase)); break; case FilterSearch::Regular: message = QStringLiteral("message REGEXP '%1'").arg(phrase); @@ -384,24 +491,27 @@ QDateTime History::getDateWhereFindPhrase(const QString& friendPk, const QDateTi period = QStringLiteral("ORDER BY timestamp ASC LIMIT 1;"); break; case PeriodSearch::AfterDate: - period = QStringLiteral("AND timestamp > '%1' ORDER BY timestamp ASC LIMIT 1;").arg(date.toMSecsSinceEpoch()); + period = QStringLiteral("AND timestamp > '%1' ORDER BY timestamp ASC LIMIT 1;") + .arg(date.toMSecsSinceEpoch()); break; case PeriodSearch::BeforeDate: - period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;").arg(date.toMSecsSinceEpoch()); + period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;") + .arg(date.toMSecsSinceEpoch()); break; default: - period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;").arg(date.toMSecsSinceEpoch()); + period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;") + .arg(date.toMSecsSinceEpoch()); break; } QString queryText = QStringLiteral("SELECT timestamp " - "FROM history " - "LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id " - "JOIN peers chat ON chat_id = chat.id " - "WHERE chat.public_key='%1' " - "AND %2 " - "%3") + "FROM history " + "LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id " + "JOIN peers chat ON chat_id = chat.id " + "WHERE chat.public_key='%1' " + "AND %2 " + "%3") .arg(friendPk) .arg(message) .arg(period); @@ -416,7 +526,7 @@ QDateTime History::getDateWhereFindPhrase(const QString& friendPk, const QDateTi * @param friendPk Friend public key * @return start date of correspondence */ -QDateTime History::getStartDateChatHistory(const QString &friendPk) +QDateTime History::getStartDateChatHistory(const QString& friendPk) { QDateTime result; auto rowCallback = [&result](const QVector& row) { @@ -424,11 +534,11 @@ QDateTime History::getStartDateChatHistory(const QString &friendPk) }; QString queryText = - QStringLiteral("SELECT timestamp " - "FROM history " - "LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id " - "JOIN peers chat ON chat_id = chat.id " - "WHERE chat.public_key='%1' ORDER BY timestamp ASC LIMIT 1;") + QStringLiteral("SELECT timestamp " + "FROM history " + "LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id " + "JOIN peers chat ON chat_id = chat.id " + "WHERE chat.public_key='%1' ORDER BY timestamp ASC LIMIT 1;") .arg(friendPk); db->execNow({queryText, rowCallback}); @@ -491,8 +601,9 @@ QList History::getChatHistory(const QString& friendPk, con .arg(to.toMSecsSinceEpoch()) .arg(friendPk); if (numMessages) { - queryText = "SELECT * FROM (" + queryText + - QString(" ORDER BY history.id DESC limit %1) AS T1 ORDER BY T1.id ASC;").arg(numMessages); + queryText = + "SELECT * FROM (" + queryText + + QString(" ORDER BY history.id DESC limit %1) AS T1 ORDER BY T1.id ASC;").arg(numMessages); } else { queryText = queryText + ";"; } @@ -510,32 +621,31 @@ QList History::getChatHistory(const QString& friendPk, con void History::dbSchemaUpgrade() { int64_t databaseSchemaVersion; - db->execNow(RawDatabase::Query("PRAGMA user_version", [&] (const QVector& row){ - databaseSchemaVersion = row[0].toLongLong(); - })); + db->execNow(RawDatabase::Query("PRAGMA user_version", [&](const QVector& row) { + databaseSchemaVersion = row[0].toLongLong(); + })); if (databaseSchemaVersion > SCHEMA_VERSION) { qWarning() << "Database version is newer than we currently support. Please upgrade qTox"; // We don't know what future versions have done, we have to disable db access until we re-upgrade db.reset(); - } - else if (databaseSchemaVersion == SCHEMA_VERSION) { + } else if (databaseSchemaVersion == SCHEMA_VERSION) { // No work to do return; } - switch (databaseSchemaVersion) - { - //case 0: - // do 0 -> 1 upgrade - // //fallthrough - //case 1: + switch (databaseSchemaVersion) { + case 0: + db->execLater(RawDatabase::Query("ALTER TABLE history ADD file_id INTEGER;")); + // fallthrough + // case 1: // do 1 -> 2 upgrade // //fallthrough // etc. default: - db->execLater(RawDatabase::Query(QStringLiteral("PRAGMA user_version = %1;").arg(SCHEMA_VERSION))); - qDebug() << "Database upgrade finished (databaseSchemaVersion " - << databaseSchemaVersion << " -> " << SCHEMA_VERSION << ")"; + db->execLater( + RawDatabase::Query(QStringLiteral("PRAGMA user_version = %1;").arg(SCHEMA_VERSION))); + qDebug() << "Database upgrade finished (databaseSchemaVersion " << databaseSchemaVersion + << " -> " << SCHEMA_VERSION << ")"; } } diff --git a/src/persistence/history.h b/src/persistence/history.h index 693053a90..0733076e0 100644 --- a/src/persistence/history.h +++ b/src/persistence/history.h @@ -34,8 +34,23 @@ class Profile; class HistoryKeeper; -class History +struct FileDbInsertionData { + FileDbInsertionData(); + + int64_t historyId; + QString friendPk; + QString fileId; + QByteArray fileName; + QString filePath; + int64_t size; + int direction; +}; +Q_DECLARE_METATYPE(FileDbInsertionData); + +class History : public QObject, public std::enable_shared_from_this +{ + Q_OBJECT public: struct HistMessage { @@ -48,8 +63,7 @@ public: , timestamp{timestamp} , id{id} , isSent{isSent} - { - } + {} QString chat; QString sender; @@ -80,6 +94,10 @@ public: const QDateTime& time, bool isSent, QString dispName, const std::function& insertIdCallback = {}); + void 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); + QList getChatHistoryFromDate(const QString& friendPk, const QDateTime& from, const QDateTime& to); QList getChatHistoryDefaultNum(const QString& friendPk); @@ -96,11 +114,20 @@ protected: const QString& sender, const QDateTime& time, bool isSent, QString dispName, std::function insertIdCallback = {}); +signals: + void fileInsertionReady(FileDbInsertionData data); + +private slots: + void onFileInsertionReady(FileDbInsertionData data); + private: QList getChatHistory(const QString& friendPk, const QDateTime& from, const QDateTime& to, int numMessages); void dbSchemaUpgrade(); + std::shared_ptr db; + + QHash peers; }; diff --git a/src/persistence/profile.cpp b/src/persistence/profile.cpp index 67308d798..696c6945b 100644 --- a/src/persistence/profile.cpp +++ b/src/persistence/profile.cpp @@ -748,7 +748,7 @@ QStringList Profile::remove() qWarning() << "Could not remove file " << dbPath; } - history.release(); + history.reset(); database.reset(); return ret; diff --git a/src/persistence/profile.h b/src/persistence/profile.h index f16dbea99..ac1004b4e 100644 --- a/src/persistence/profile.h +++ b/src/persistence/profile.h @@ -109,7 +109,7 @@ private: QString name; std::unique_ptr passkey = nullptr; std::shared_ptr database; - std::unique_ptr history; + std::shared_ptr history; bool isRemoved; bool encrypted = false; static QStringList profiles; From d9b39b3102eff686a072630610c199639f0d8219 Mon Sep 17 00:00:00 2001 From: Mick Sayson Date: Sat, 29 Sep 2018 02:28:17 -0700 Subject: [PATCH 4/7] feat(db): Hookup file history to the rest of the system --- src/chatlog/content/filetransferwidget.cpp | 114 +++++++++++++-------- src/chatlog/toxfileprogress.cpp | 3 +- src/persistence/history.cpp | 93 +++++++++++++++-- src/persistence/history.h | 90 +++++++++++++++- src/widget/form/chatform.cpp | 83 +++++++++++++-- src/widget/form/chatform.h | 5 +- 6 files changed, 325 insertions(+), 63 deletions(-) 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(); From 8427be6678eae2ef151b704feb6bf408038ccdd3 Mon Sep 17 00:00:00 2001 From: Anthony Bilinski Date: Fri, 30 Nov 2018 01:40:46 -0800 Subject: [PATCH 5/7] feat(db): add file hash to file history Not currently used, but there are plans to display if a transfered file has been modified, which the file hash will be needed for. Adding file hash at the same time as file history also saves a db schema update. --- src/core/corefile.cpp | 2 ++ src/core/toxfile.h | 2 ++ src/persistence/history.cpp | 20 +++++++++++--------- src/persistence/history.h | 5 +++-- src/widget/form/chatform.cpp | 6 +++--- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/core/corefile.cpp b/src/core/corefile.cpp index 44d49518f..ef8f82bb4 100644 --- a/src/core/corefile.cpp +++ b/src/core/corefile.cpp @@ -438,6 +438,7 @@ void CoreFile::onFileDataCallback(Tox* tox, uint32_t friendId, uint32_t fileId, return; } file->bytesSent += length; + file->hashGenerator->addData((const char*)data.get(), length); } if (!tox_file_send_chunk(tox, friendId, fileId, pos, data.get(), nread, nullptr)) { @@ -492,6 +493,7 @@ void CoreFile::onFileRecvChunkCallback(Tox* tox, uint32_t friendId, uint32_t fil else file->file->write((char*)data, length); file->bytesSent += length; + file->hashGenerator->addData((const char*)data, length); if (file->fileKind != TOX_FILE_KIND_AVATAR) emit static_cast(core)->fileTransferInfo(*file); diff --git a/src/core/toxfile.h b/src/core/toxfile.h index 4305cfa84..8bed32937 100644 --- a/src/core/toxfile.h +++ b/src/core/toxfile.h @@ -3,6 +3,7 @@ #include #include +#include class QFile; class QTimer; @@ -51,6 +52,7 @@ struct ToxFile FileDirection direction; QByteArray avatarData; QByteArray resumeFileId; + std::shared_ptr hashGenerator = std::make_shared(QCryptographicHash::Sha256); }; #endif // CORESTRUCTS_H diff --git a/src/persistence/history.cpp b/src/persistence/history.cpp index 82c66f61b..0ad5215e3 100644 --- a/src/persistence/history.cpp +++ b/src/persistence/history.cpp @@ -90,6 +90,7 @@ History::History(std::shared_ptr db) "file_restart_id BLOB NOT NULL," "file_name BLOB NOT NULL, " "file_path BLOB NOT NULL," + "file_hash BLOB NOT NULL," "file_size INTEGER NOT NULL," "direction INTEGER NOT NULL," "file_state INTEGER NOT NULL);" @@ -279,13 +280,13 @@ void History::onFileInsertionReady(FileDbInsertionData data) 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) " - "VALUES (%1, ?, ?, ?, %2, %3, %4);") + "file_path, file_name, file_hash, file_size, direction, file_state) " + "VALUES (%1, ?, ?, ?, ?, %2, %3, %4);") .arg(peerId) .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, QByteArray()}, [weakThis, fileId](int64_t id) { auto pThis = weakThis.lock(); if (pThis) { @@ -306,7 +307,7 @@ void History::onFileInserted(int64_t dbId, QString fileId) { auto& fileInfo = fileInfos[fileId]; if (fileInfo.finished) { - db->execLater(generateFileFinished(dbId, fileInfo.success, fileInfo.filePath)); + db->execLater(generateFileFinished(dbId, fileInfo.success, fileInfo.filePath, fileInfo.fileHash)); fileInfos.remove(fileId); } else { fileInfo.finished = false; @@ -314,16 +315,16 @@ void History::onFileInserted(int64_t dbId, QString fileId) } } -RawDatabase::Query History::generateFileFinished(int64_t id, bool success, const QString& filePath) +RawDatabase::Query History::generateFileFinished(int64_t id, bool success, const QString& filePath, const QByteArray& fileHash) { auto file_state = success ? ToxFile::FINISHED : ToxFile::CANCELED; if (filePath.length()) { return RawDatabase::Query(QStringLiteral("UPDATE file_transfers " - "SET file_state = %1, file_path = ? " + "SET file_state = %1, file_path = ?, file_hash = ?" "WHERE id = %2") .arg(file_state) .arg(id), - {filePath.toUtf8()}); + {filePath.toUtf8(), fileHash}); } else { return RawDatabase::Query(QStringLiteral("UPDATE file_transfers " "SET finished = %1 " @@ -405,15 +406,16 @@ void History::addNewMessage(const QString& friendPk, const QString& message, con insertIdCallback)); } -void History::setFileFinished(const QString& fileId, bool success, const QString& filePath) +void History::setFileFinished(const QString& fileId, bool success, const QString& filePath, const QByteArray& fileHash) { auto& fileInfo = fileInfos[fileId]; if (fileInfo.fileId == -1) { fileInfo.finished = true; fileInfo.success = success; fileInfo.filePath = filePath; + fileInfo.fileHash = fileHash; } else { - db->execLater(generateFileFinished(fileInfo.fileId, success, filePath)); + db->execLater(generateFileFinished(fileInfo.fileId, success, filePath, fileHash)); } fileInfos.remove(fileId); diff --git a/src/persistence/history.h b/src/persistence/history.h index 7f606793e..3d1c158c4 100644 --- a/src/persistence/history.h +++ b/src/persistence/history.h @@ -167,7 +167,7 @@ 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); + void setFileFinished(const QString& fileId, bool success, const QString& filePath, const QByteArray& fileHash); QList getChatHistoryFromDate(const QString& friendPk, const QDateTime& from, const QDateTime& to); @@ -198,7 +198,7 @@ private: const QDateTime& to, int numMessages); static RawDatabase::Query generateFileFinished(int64_t fileId, bool success, - const QString& filePath); + const QString& filePath, const QByteArray& fileHash); void dbSchemaUpgrade(); std::shared_ptr db; @@ -210,6 +210,7 @@ private: bool finished = false; bool success = false; QString filePath; + QByteArray fileHash; int64_t fileId = -1; }; diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index eeb6fd574..21cd43ecf 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -329,19 +329,19 @@ void ChatForm::startFileSend(ToxFile file) void ChatForm::onFileTransferFinished(ToxFile file) { - history->setFileFinished(file.resumeFileId, true, file.filePath); + history->setFileFinished(file.resumeFileId, true, file.filePath, file.hashGenerator->result()); } void ChatForm::onFileTransferBrokenUnbroken(ToxFile file, bool broken) { if (broken) { - history->setFileFinished(file.resumeFileId, false, file.filePath); + history->setFileFinished(file.resumeFileId, false, file.filePath, file.hashGenerator->result()); } } void ChatForm::onFileTransferCancelled(ToxFile file) { - history->setFileFinished(file.resumeFileId, false, file.filePath); + history->setFileFinished(file.resumeFileId, false, file.filePath, file.hashGenerator->result()); } void ChatForm::onFileRecvRequest(ToxFile file) From 25005c5c19c80a3cbd2d966a2bb6bfeaa20eab36 Mon Sep 17 00:00:00 2001 From: Mick Sayson Date: Wed, 5 Dec 2018 20:50:29 -0800 Subject: [PATCH 6/7] feat(db): File transfer history review comments --- src/chatlog/content/filetransferwidget.cpp | 8 ++++++-- src/persistence/history.cpp | 20 ++++++++++++++------ src/widget/form/chatform.cpp | 9 ++++----- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/chatlog/content/filetransferwidget.cpp b/src/chatlog/content/filetransferwidget.cpp index a95be0f09..c846a62f8 100644 --- a/src/chatlog/content/filetransferwidget.cpp +++ b/src/chatlog/content/filetransferwidget.cpp @@ -108,7 +108,6 @@ FileTransferWidget::FileTransferWidget(QWidget* parent, ToxFile file) lastStatus = file.status == ToxFile::FINISHED ? ToxFile::INITIALIZING : ToxFile::FINISHED; updateWidget(file); - // preview setFixedHeight(64); } @@ -340,6 +339,7 @@ void FileTransferWidget::updateWidgetColor(ToxFile const& file) setBackgroundColor(Style::getColor(Style::Green), true); break; default: + qCritical() << "Invalid file status"; assert(false); } } @@ -372,6 +372,7 @@ void FileTransferWidget::updateWidgetText(ToxFile const& file) case ToxFile::FINISHED: break; default: + qCritical() << "Invalid file status"; assert(false); } } @@ -396,6 +397,7 @@ void FileTransferWidget::updatePreview(ToxFile const& file) showPreview(file.filePath); break; default: + qCritical() << "Invalid file status"; assert(false); } } @@ -442,6 +444,7 @@ void FileTransferWidget::updateFileProgress(ToxFile const& file) break; } default: + qCritical() << "Invalid file status"; assert(false); } } @@ -464,6 +467,7 @@ void FileTransferWidget::updateSignals(ToxFile const& file) case ToxFile::TRANSMITTING: break; default: + qCritical() << "Invalid file status"; assert(false); } } @@ -532,6 +536,7 @@ void FileTransferWidget::setupButtons(ToxFile const& file) break; default: + qCritical() << "Invalid file status"; assert(false); } } @@ -583,7 +588,6 @@ void FileTransferWidget::showPreview(const QString& filename) QFile imageFile(filename); if (!imageFile.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file for preview"; return; } const QByteArray imageFileData = imageFile.readAll(); diff --git a/src/persistence/history.cpp b/src/persistence/history.cpp index 0ad5215e3..fad34c3ec 100644 --- a/src/persistence/history.cpp +++ b/src/persistence/history.cpp @@ -279,9 +279,10 @@ void History::onFileInsertionReady(FileDbInsertionData data) // 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_hash, file_size, direction, file_state) " - "VALUES (%1, ?, ?, ?, ?, %2, %3, %4);") + RawDatabase::Query(QStringLiteral( + "INSERT INTO file_transfers (chat_id, file_restart_id, " + "file_path, file_name, file_hash, file_size, direction, file_state) " + "VALUES (%1, ?, ?, ?, ?, %2, %3, %4);") .arg(peerId) .arg(data.size) .arg(static_cast(data.direction)) @@ -307,7 +308,8 @@ void History::onFileInserted(int64_t dbId, QString fileId) { auto& fileInfo = fileInfos[fileId]; if (fileInfo.finished) { - db->execLater(generateFileFinished(dbId, fileInfo.success, fileInfo.filePath, fileInfo.fileHash)); + db->execLater( + generateFileFinished(dbId, fileInfo.success, fileInfo.filePath, fileInfo.fileHash)); fileInfos.remove(fileId); } else { fileInfo.finished = false; @@ -315,7 +317,8 @@ void History::onFileInserted(int64_t dbId, QString fileId) } } -RawDatabase::Query History::generateFileFinished(int64_t id, bool success, const QString& filePath, const QByteArray& fileHash) +RawDatabase::Query History::generateFileFinished(int64_t id, bool success, const QString& filePath, + const QByteArray& fileHash) { auto file_state = success ? ToxFile::FINISHED : ToxFile::CANCELED; if (filePath.length()) { @@ -406,7 +409,8 @@ void History::addNewMessage(const QString& friendPk, const QString& message, con insertIdCallback)); } -void History::setFileFinished(const QString& fileId, bool success, const QString& filePath, const QByteArray& fileHash) +void History::setFileFinished(const QString& fileId, bool success, const QString& filePath, + const QByteArray& fileHash) { auto& fileInfo = fileInfos[fileId]; if (fileInfo.fileId == -1) { @@ -704,13 +708,17 @@ void History::dbSchemaUpgrade() qWarning() << "Database version is newer than we currently support. Please upgrade qTox"; // We don't know what future versions have done, we have to disable db access until we re-upgrade db.reset(); + return; } else if (databaseSchemaVersion == SCHEMA_VERSION) { // No work to do return; } + // Make sure to handle the un-created case as well in the following upgrade code switch (databaseSchemaVersion) { case 0: + // This will generate a warning on new profiles, but we have no easy way to chain execs. I + // don't want to block the rest of the program on db creation so I guess we can just live with the warning for now db->execLater(RawDatabase::Query("ALTER TABLE history ADD file_id INTEGER;")); // fallthrough // case 1: diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 21cd43ecf..1cde8ddf2 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -913,6 +913,7 @@ ChatMessage::Ptr ChatForm::chatMessageFromHistMessage(History::HistMessage const break; } default: + qCritical() << "Invalid HistMessageContentType"; assert(false); } @@ -1206,11 +1207,9 @@ void ChatForm::onExportChat() ToxPk authorPk(ToxId(it.sender).getPublicKey()); QString author = getMsgAuthorDispName(authorPk, it.dispName); - if (it.content.getType() == HistMessageContentType::message) { - buffer = buffer - % QString{datestamp % '\t' % timestamp % '\t' % author % '\t' - % it.content.asMessage() % '\n'}; - } + buffer = buffer + % QString{datestamp % '\t' % timestamp % '\t' % author % '\t' + % it.content.asMessage() % '\n'}; } file.write(buffer.toUtf8()); file.close(); From 84362244dadb87cdd2be0344249ac92a7fdc71e2 Mon Sep 17 00:00:00 2001 From: Anthony Bilinski Date: Wed, 12 Dec 2018 03:31:04 -0800 Subject: [PATCH 7/7] refactor(files): change ToxFile's fileName to QString --- src/core/corefile.cpp | 10 ++++------ src/core/toxfile.cpp | 2 +- src/core/toxfile.h | 4 ++-- src/persistence/history.cpp | 14 ++++++-------- src/persistence/history.h | 4 ++-- 5 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/core/corefile.cpp b/src/core/corefile.cpp index ef8f82bb4..8b38e1546 100644 --- a/src/core/corefile.cpp +++ b/src/core/corefile.cpp @@ -108,7 +108,6 @@ void CoreFile::sendAvatarFile(Core* core, uint32_t friendId, const QByteArray& d ToxFile file{fileNum, friendId, "", "", ToxFile::SENDING}; file.filesize = filesize; - file.fileName = QByteArray((char*)avatarHash, TOX_HASH_LENGTH); file.fileKind = TOX_FILE_KIND_AVATAR; file.avatarData = data; file.resumeFileId.resize(TOX_FILE_ID_LENGTH); @@ -121,19 +120,18 @@ void CoreFile::sendFile(Core* core, uint32_t friendId, QString filename, QString long long filesize) { QMutexLocker mlocker(&fileSendMutex); - - QByteArray fileName = filename.toUtf8(); + ToxString fileName(filename); TOX_ERR_FILE_SEND sendErr; uint32_t fileNum = tox_file_send(core->tox.get(), friendId, TOX_FILE_KIND_DATA, filesize, - nullptr, (uint8_t*)fileName.data(), fileName.size(), &sendErr); + nullptr, fileName.data(), fileName.size(), &sendErr); if (sendErr != TOX_ERR_FILE_SEND_OK) { qWarning() << "sendFile: Can't create the Tox file sender (" << sendErr << ")"; - emit core->fileSendFailed(friendId, filename); + emit core->fileSendFailed(friendId, fileName.getQString()); return; } qDebug() << QString("sendFile: Created file sender %1 with friend %2").arg(fileNum).arg(friendId); - ToxFile file{fileNum, friendId, fileName, filePath, ToxFile::SENDING}; + ToxFile file{fileNum, friendId, fileName.getQString(), filePath, ToxFile::SENDING}; file.filesize = filesize; file.resumeFileId.resize(TOX_FILE_ID_LENGTH); tox_file_get_file_id(core->tox.get(), friendId, fileNum, (uint8_t*)file.resumeFileId.data(), diff --git a/src/core/toxfile.cpp b/src/core/toxfile.cpp index 2dbaf3a54..47a8820d3 100644 --- a/src/core/toxfile.cpp +++ b/src/core/toxfile.cpp @@ -18,7 +18,7 @@ /** * @brief ToxFile constructor */ -ToxFile::ToxFile(uint32_t fileNum, uint32_t friendId, QByteArray filename, QString filePath, +ToxFile::ToxFile(uint32_t fileNum, uint32_t friendId, QString filename, QString filePath, FileDirection Direction) : fileKind{TOX_FILE_KIND_DATA} , fileNum(fileNum) diff --git a/src/core/toxfile.h b/src/core/toxfile.h index 8bed32937..fa47e0f9c 100644 --- a/src/core/toxfile.h +++ b/src/core/toxfile.h @@ -31,7 +31,7 @@ struct ToxFile }; ToxFile() = default; - ToxFile(uint32_t FileNum, uint32_t FriendId, QByteArray FileName, QString filePath, + ToxFile(uint32_t FileNum, uint32_t FriendId, QString FileName, QString filePath, FileDirection Direction); bool operator==(const ToxFile& other) const; @@ -43,7 +43,7 @@ struct ToxFile uint8_t fileKind; uint32_t fileNum; uint32_t friendId; - QByteArray fileName; + QString fileName; QString filePath; std::shared_ptr file; quint64 bytesSent; diff --git a/src/persistence/history.cpp b/src/persistence/history.cpp index fad34c3ec..573d4f767 100644 --- a/src/persistence/history.cpp +++ b/src/persistence/history.cpp @@ -287,7 +287,7 @@ void History::onFileInsertionReady(FileDbInsertionData data) .arg(data.size) .arg(static_cast(data.direction)) .arg(ToxFile::CANCELED), - {data.fileId.toUtf8(), data.filePath.toUtf8(), data.fileName, QByteArray()}, + {data.fileId.toUtf8(), data.filePath.toUtf8(), data.fileName.toUtf8(), QByteArray()}, [weakThis, fileId](int64_t id) { auto pThis = weakThis.lock(); if (pThis) { @@ -338,7 +338,7 @@ RawDatabase::Query History::generateFileFinished(int64_t id, bool success, const } void History::addNewFileMessage(const QString& friendPk, const QString& fileId, - const QByteArray& fileName, const QString& filePath, int64_t size, + const QString& fileName, const QString& filePath, int64_t size, const QString& sender, const QDateTime& time, QString const& dispName) { // This is an incredibly far from an optimal way of implementing this, @@ -644,16 +644,14 @@ QList History::getChatHistory(const QString& friendPk, con 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()}; + 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.filePath = row[8].toString(); + file.fileName = row[9].toString(); file.filesize = row[10].toLongLong(); file.direction = static_cast(row[11].toLongLong()); file.status = static_cast(row[12].toInt()); diff --git a/src/persistence/history.h b/src/persistence/history.h index 3d1c158c4..4da8a9266 100644 --- a/src/persistence/history.h +++ b/src/persistence/history.h @@ -98,7 +98,7 @@ struct FileDbInsertionData int64_t historyId; QString friendPk; QString fileId; - QByteArray fileName; + QString fileName; QString filePath; int64_t size; int direction; @@ -164,7 +164,7 @@ public: const std::function& insertIdCallback = {}); void addNewFileMessage(const QString& friendPk, const QString& fileId, - const QByteArray& fileName, const QString& filePath, int64_t size, + const QString& 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, const QByteArray& fileHash);