From 8aacd214050e92956ebb2214e8786da1db7d4548 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 25 Jun 2014 22:43:28 +0200 Subject: [PATCH] Add file sending (silently, send only) --- .gitignore | 1 + chatform.cpp | 36 +++++++++++++-- chatform.h | 5 +- core.cpp | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++ core.h | 39 ++++++++++++++-- widget.cpp | 4 ++ 6 files changed, 202 insertions(+), 10 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..6df975821 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pro.user* diff --git a/chatform.cpp b/chatform.cpp index 1a7ebab82..fd1792c94 100644 --- a/chatform.cpp +++ b/chatform.cpp @@ -5,6 +5,7 @@ #include #include #include +#include ChatForm::ChatForm(Friend* chatFriend) : f(chatFriend), curRow{0}, lockSliderToBottom{true} @@ -15,7 +16,7 @@ ChatForm::ChatForm(Friend* chatFriend) headTextLayout = new QVBoxLayout(), mainLayout = new QVBoxLayout(); mainChatLayout = new QGridLayout(); msgEdit = new ChatTextEdit(); - sendButton = new QPushButton(); + sendButton = new QPushButton(), fileButton = new QPushButton(); chatArea = new QScrollArea(); QFont bold; @@ -31,14 +32,19 @@ ChatForm::ChatForm(Friend* chatFriend) mainChatLayout->setColumnStretch(1,1); mainChatLayout->setSpacing(10); + msgEdit->setFixedHeight(50); + QPalette toxgreen; + toxgreen.setColor(QPalette::Button, QColor(107,194,96)); // Tox Green sendButton->setIcon(QIcon("img/button icons/sendmessage_2x.png")); sendButton->setFlat(true); - QPalette pal; - pal.setColor(QPalette::Button, QColor(107,194,96)); // Tox Green - sendButton->setPalette(pal); + sendButton->setPalette(toxgreen); sendButton->setAutoFillBackground(true); - msgEdit->setFixedHeight(50); sendButton->setFixedSize(50, 50); + fileButton->setIcon(QIcon("img/button icons/attach_2x.png")); + fileButton->setFlat(true); + fileButton->setPalette(toxgreen); + fileButton->setAutoFillBackground(true); + fileButton->setIconSize(QSize(40,40)); main->setLayout(mainLayout); mainLayout->addWidget(chatArea); @@ -52,6 +58,7 @@ ChatForm::ChatForm(Friend* chatFriend) headLayout->addWidget(avatar); headLayout->addLayout(headTextLayout); headLayout->addStretch(); + headLayout->addWidget(fileButton); headTextLayout->addStretch(); headTextLayout->addWidget(name); @@ -61,6 +68,7 @@ ChatForm::ChatForm(Friend* chatFriend) chatArea->setWidget(chatAreaWidget); connect(sendButton, SIGNAL(clicked()), this, SLOT(onSendTriggered())); + connect(fileButton, SIGNAL(clicked()), this, SLOT(onAttachClicked())); connect(msgEdit, SIGNAL(enterPressed()), this, SLOT(onSendTriggered())); connect(chatArea->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(onSliderRangeChanged())); } @@ -145,6 +153,24 @@ void ChatForm::addMessage(QLabel* author, QLabel* message, QLabel* date) curRow++; } +void ChatForm::onAttachClicked() +{ + QString path = QFileDialog::getOpenFileName(0,"Send a file"); + if (path.isEmpty()) + return; + + QFile file(path); + if (!file.exists() || !file.open(QIODevice::ReadOnly)) + return; + QByteArray fileData = file.readAll(); + file.close(); + QFileInfo fi(path); + + // TODO: Show file send widget + + emit sendFile(f->friendId, fi.fileName(), fileData); +} + void ChatForm::onSliderRangeChanged() { QScrollBar* scroll = chatArea->verticalScrollBar(); diff --git a/chatform.h b/chatform.h index defafe7d2..3a6607a14 100644 --- a/chatform.h +++ b/chatform.h @@ -31,12 +31,13 @@ public: void addMessage(QString author, QString message, QString date=QTime::currentTime().toString("hh:mm")); void addMessage(QLabel* author, QLabel* message, QLabel* date); - signals: void sendMessage(int, QString); + void sendFile(int32_t, QString, QByteArray); private slots: void onSendTriggered(); + void onAttachClicked(); void onSliderRangeChanged(); private: @@ -46,7 +47,7 @@ private: QGridLayout *mainChatLayout; QLabel *avatar, *name, *statusMessage; ChatTextEdit *msgEdit; - QPushButton *sendButton; + QPushButton *sendButton, *fileButton; QScrollArea *chatArea; QWidget *main, *head, *chatAreaWidget; QString previousName; diff --git a/core.cpp b/core.cpp index 69f90cba2..273e09b65 100644 --- a/core.cpp +++ b/core.cpp @@ -29,6 +29,7 @@ #include "settings.h" const QString Core::CONFIG_FILE_NAME = "tox_save"; +QList Core::fileSendQueue; Core::Core() : tox(nullptr) @@ -37,8 +38,11 @@ Core::Core() : toxTimer->setSingleShot(true); saveTimer = new QTimer(this); saveTimer->start(TOX_SAVE_INTERVAL); + fileTimer = new QTimer(this); + fileTimer->start(TOX_FILE_INTERVAL); connect(toxTimer, &QTimer::timeout, this, &Core::process); connect(saveTimer, &QTimer::timeout, this, &Core::saveConfiguration); + connect(fileTimer, &QTimer::timeout, this, &Core::fileHeartbeat); connect(&Settings::getInstance(), &Settings::dhtServerListChanged, this, &Core::bootstrapDht); } @@ -126,6 +130,74 @@ void Core::onGroupNamelistChange(Tox*, int groupnumber, int peernumber, uint8_t emit static_cast(core)->groupNamelistChanged(groupnumber, peernumber, change); } +void Core::onFileSendRequestCallback(Tox* tox, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, + uint8_t *filename, uint16_t filename_length, void *userdata) +{ + qDebug() << "Core: File send request callback"; +} +void Core::onFileControlCallback(Tox* tox, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, + uint8_t control_type, uint8_t *data, uint16_t length, void *userdata) +{ + if (control_type == TOX_FILECONTROL_ACCEPT && receive_send == 1) + { + qDebug() << "Core: File control callback, file accepted"; + int chunkSize = tox_file_data_size(tox, friendnumber); + if (chunkSize == -1) + { + qWarning("Core::onFileControlCallback: Error getting preffered chunk size, aborting file send"); + // TODO: Warn the Friend* that we're aborting (emit) + return; + } + ToxFile* file{nullptr}; + for (ToxFile& f : fileSendQueue) + { + if (f.fileNum == filenumber) + { + file = &f; + break; + } + } + if (!file) + { + qWarning("Core::onFileControlCallback: No such file in queue"); + // TODO: Warn the Friend* that we're aborting (emit) + return; + } + chunkSize = std::min(chunkSize, file->fileData.size()); + QByteArray toSend = file->fileData.mid(file->bytesSent, chunkSize); + if (tox_file_send_data(tox, friendnumber, filenumber, (uint8_t*)toSend.data(), toSend.size()) == -1) + { + qWarning("Core::onFileControlCallback: Error sending first data chunk, aborting"); + // TODO: Warn the Friend* that we're aborting (emit) + return; + } + else + { + file->bytesSent += chunkSize; + if (file->bytesSent >= file->fileData.size()) + { + qWarning("Core::onFileControlCallback: Transfer finished"); + tox_file_send_control(tox, friendnumber, 0, filenumber, TOX_FILECONTROL_FINISHED, nullptr, 0); + // TODO: Notify the Friend* that we're done sending (emit) + } + else + { + file->status = ToxFile::TRANSMITTING; + } + } + } + else + { + qDebug() << QString("Core: File control callback, receive_send=%1, control_type=%2") + .arg(receive_send).arg(control_type); + } +} + +void Core::onFileDataCallback(Tox* tox, int32_t friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length, void *userdata) +{ + qDebug() << "Core: File data callback"; +} + void Core::acceptFriendRequest(const QString& userId) { int friendId = tox_add_friend_norequest(tox, CUserId(userId).data()); @@ -179,6 +251,20 @@ void Core::sendGroupMessage(int groupId, const QString& message) tox_group_message_send(tox, groupId, cMessage.data(), cMessage.size()); } +void Core::sendFile(int32_t friendId, QString Filename, QByteArray data) +{ + QByteArray fileName = Filename.toUtf8(); + int fileNum = tox_new_file_sender(tox, friendId, data.size(), (uint8_t*)fileName.data(), fileName.size()); + if (fileNum == -1) + { + qWarning() << "Core::sendFile: Can't create the Tox file sender"; + // TODO: Notify Widget (with the friendId), Widget will notify the chatForm + return; + } + + fileSendQueue.append(ToxFile(fileNum, friendId, data, fileName)); +} + void Core::removeFriend(int friendId) { if (tox_del_friend(tox, friendId) == -1) { @@ -263,6 +349,44 @@ void Core::process() toxTimer->start(tox_do_interval(tox)); } +void Core::fileHeartbeat() +{ + for (ToxFile& file : fileSendQueue) + { + if (file.status == ToxFile::TRANSMITTING) + { + int chunkSize = tox_file_data_size(tox, file.friendId); + if (chunkSize == -1) + { + qWarning("Core::fileHeartbeat: Error getting preffered chunk size, aborting file send"); + file.status = ToxFile::STOPPED; + // TODO: Warn the Friend* and the peer that we're aborting (emit and tox_control_...) + return; + } + chunkSize = std::min(chunkSize, file.fileData.size()); + QByteArray toSend = file.fileData.mid(file.bytesSent, chunkSize); + if (tox_file_send_data(tox, file.friendId, file.fileNum, (uint8_t*)toSend.data(), toSend.size()) == -1) + { + qWarning("Core::fileHeartbeat: Error sending data chunk"); + continue; + } + else + { + file.bytesSent += chunkSize; + if (file.bytesSent >= file.fileData.size()) + { + qDebug("Core::fileHeartbeat: Transfer finished"); + tox_file_send_control(tox, file.friendId, 0, file.fileNum, TOX_FILECONTROL_FINISHED, nullptr, 0); + file.status = ToxFile::STOPPED; // TODO: Remove it from the list and return; + // TODO: Notify the Friend* that we're done sending (emit) + } + else + qDebug() << QString("Core::fileHeartbeat: sent %1/%2 bytes").arg(file.bytesSent).arg(file.fileData.size()); + } + } + } +} + void Core::checkConnection() { static bool isConnected = false; @@ -400,6 +524,9 @@ void Core::start() tox_callback_group_invite(tox, onGroupInvite, this); tox_callback_group_message(tox, onGroupMessage, this); tox_callback_group_namelist_change(tox, onGroupNamelistChange, this); + tox_callback_file_send_request(tox, onFileSendRequestCallback, this); + tox_callback_file_control(tox, onFileControlCallback, this); + tox_callback_file_data(tox, onFileDataCallback, this); uint8_t friendAddress[TOX_FRIEND_ADDRESS_SIZE]; tox_get_address(tox, friendAddress); diff --git a/core.h b/core.h index 5f9c3da1b..ea7091a74 100644 --- a/core.h +++ b/core.h @@ -21,14 +21,17 @@ #include +#include #include #include #include #include #include +#include #define GROUPCHAT_MAX_SIZE 32 -#define TOX_SAVE_INTERVAL 10*1000 +#define TOX_SAVE_INTERVAL 30*1000 +#define TOX_FILE_INTERVAL 50 struct DhtServer { @@ -38,6 +41,27 @@ struct DhtServer int port; }; +struct ToxFile +{ + enum FileStatus + { + STOPPED, + PAUSED, + TRANSMITTING + }; + + ToxFile(int FileNum, int FriendId, QByteArray FileData, QByteArray FileName) + : fileNum(FileNum), friendId(FriendId), fileData{FileData}, + fileName{FileName}, bytesSent{0}, status{STOPPED} {} + + int fileNum; + int friendId; + QByteArray fileData; + QByteArray fileName; + long long bytesSent; + FileStatus status; +}; + class Core : public QObject { Q_OBJECT @@ -57,6 +81,11 @@ private: static void onGroupInvite(Tox *tox, int friendnumber, uint8_t *group_public_key, void *userdata); static void onGroupMessage(Tox *tox, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata); static void onGroupNamelistChange(Tox *tox, int groupnumber, int peernumber, uint8_t change, void *userdata); + static void onFileSendRequestCallback(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, + uint8_t *filename, uint16_t filename_length, void *userdata); + static void onFileControlCallback(Tox *tox, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, + uint8_t control_type, uint8_t *data, uint16_t length, void *userdata); + static void onFileDataCallback(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length, void *userdata); void checkConnection(); @@ -67,9 +96,10 @@ private: void checkLastOnline(int friendId); Tox* tox; - QTimer *toxTimer, *saveTimer; + QTimer *toxTimer, *saveTimer, *fileTimer; QList dhtServerList; int dhtServerId; + static QList fileSendQueue; static const QString CONFIG_FILE_NAME; @@ -153,15 +183,18 @@ public slots: void removeGroup(int groupId); void sendMessage(int friendId, const QString& message); + void sendGroupMessage(int groupId, const QString& message); void sendAction(int friendId, const QString& action); void sendTyping(int friendId, bool typing); - void sendGroupMessage(int groupId, const QString& message); + + void sendFile(int32_t friendId, QString Filename, QByteArray data); void setUsername(const QString& username); void setStatusMessage(const QString& message); void setStatus(Status status); void process(); + void fileHeartbeat(); void bootstrapDht(); diff --git a/widget.cpp b/widget.cpp index c3c445fe0..db88aa04f 100644 --- a/widget.cpp +++ b/widget.cpp @@ -21,6 +21,8 @@ Widget::Widget(QWidget *parent) : ui->setupUi(this); ui->mainContent->setLayout(new QVBoxLayout()); ui->mainHead->setLayout(new QVBoxLayout()); + ui->mainHead->layout()->setMargin(0); + ui->mainHead->layout()->setSpacing(0); QWidget* friendListWidget = new QWidget(); friendListWidget->setLayout(new QVBoxLayout()); friendListWidget->layout()->setSpacing(0); @@ -34,6 +36,7 @@ Widget::Widget(QWidget *parent) : qRegisterMetaType("Status"); qRegisterMetaType("uint8_t"); + qRegisterMetaType("int32_t"); core = new Core(); coreThread = new QThread(this); @@ -223,6 +226,7 @@ void Widget::addFriend(int friendId, const QString &userId) connect(newfriend->widget, SIGNAL(friendWidgetClicked(FriendWidget*)), this, SLOT(onFriendWidgetClicked(FriendWidget*))); connect(newfriend->widget, SIGNAL(removeFriend(int)), this, SLOT(removeFriend(int))); connect(newfriend->chatForm, SIGNAL(sendMessage(int,QString)), core, SLOT(sendMessage(int,QString))); + connect(newfriend->chatForm, SIGNAL(sendFile(int32_t,QString,QByteArray)), core, SLOT(sendFile(int32_t,QString,QByteArray))); } void Widget::onFriendStatusChanged(int friendId, Status status)