diff --git a/.gitignore b/.gitignore index ec8c70c..37192ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ +.project *.user - -/.project - -*.autosave \ No newline at end of file +*.autosave +*.o diff --git a/doc/ru/main.md b/doc/ru/main.md index e2c2c33..545c467 100644 --- a/doc/ru/main.md +++ b/doc/ru/main.md @@ -1,22 +1,13 @@ # Общая документация мессенджера Influence -## Структура мессенджера - -Связь между узлами - точка-точка - -GUI - API ==== API - GUI - -GUI - примитивная программа под Linux/Windows/Android, которая умеет отображать сообщения и вызывать функции ядра (API). -API - ядро мессенджера. Достаточно подключить *main.hpp*, чтобы вызывать функции ядра. Такая структура позволяет писать какие угодно клиенты. - ## Протокол обмена Обмен сообщениями ведётся по UDP протоколу в формате JSON. -При любой успешной отправке данных обновляем поле last_connect у контакта. -Если пришло сообщение от контакта, которого нет в адресной книге - то оно отбрасывается. +При любой успешной отправке данных обновляем поле last_connect у контакта. (not implemented) +Если пришло сообщение от контакта, которого нет в адресной книге - то оно отбрасывается. Исключения - запрос handshake. -### Добавление в друзья +### Добавление в друзья (not implemented) Используется, чтобы установить связь с контактом. Тот кто хочет добавиться в друзья - отправляет такой запрос: @@ -32,8 +23,7 @@ API - ядро мессенджера. Достаточно подключить ```json { "action":"handshakeSuccess", - "peerID":"*IPv6*", - "status": true + "peerID":"*IPv6*" } ``` @@ -72,7 +62,7 @@ API - ядро мессенджера. Достаточно подключить Если нет ответа, то сохраняем сообщение у себя в истории, чтобы потом переотправить. `status == true` - доставлено. -### Пинг - проверка онлайна +### Пинг - проверка онлайна (not implemented) Если прошло время last_connect > 1 минуты, от отправляем запрос @@ -113,7 +103,7 @@ API - ядро мессенджера. Достаточно подключить В ответ приходит массив с `true` или `false`. Если `true` - сообщение успешно отправлено, `false` - сообщение недоставлено. -### Отправление сообщения для другого абонента (это нужно чтобы отправлять тем, кто в offline) +### Отправление сообщения для другого абонента (это нужно чтобы отправлять тем, кто в offline) (not implemented) Если человек, которому мы отправляем сообщение offline - то, во-первых, оно сохраняется у себя на сервере, для последующей отправки. Во-вторых, можно попросить какого-то друга его переотправить. Для этого выбираем случайно несколько друзей из списка контактов, у которых `can_resend == 1` и отправляем им запрос. diff --git a/src/Influence.pro b/src/Influence.pro index 97a68fb..ad05a91 100644 --- a/src/Influence.pro +++ b/src/Influence.pro @@ -27,12 +27,15 @@ SOURCES += \ main.cpp \ mainwindow.cpp \ kernel/network.cpp \ - kernel/handler.cpp + kernel/handler.cpp \ + chatwindow.cpp HEADERS += \ kernel/network.hpp \ mainwindow.h \ - kernel/handler.hpp + kernel/handler.hpp \ + chatwindow.hpp FORMS += \ - mainwindow.ui + mainwindow.ui \ + chatwindow.ui diff --git a/src/chatwindow.cpp b/src/chatwindow.cpp new file mode 100644 index 0000000..b9dc5ef --- /dev/null +++ b/src/chatwindow.cpp @@ -0,0 +1,70 @@ +#include "chatwindow.hpp" +#include "ui_chatwindow.h" + +ChatWindow::ChatWindow(QString pID, QString cUUID, Handler *h, Network *n, QWidget *parent) : + QWidget(parent), + ui(new Ui::ChatWindow) +{ + ui->setupUi(this); + this->setAttribute(Qt::WA_DeleteOnClose); + chatID = cUUID; + peerID = pID; + handler = h; + network = n; + ui->peerIDLabel->setText(ui->peerIDLabel->text() + pID); + ui->chatIDLabel->setText(ui->chatIDLabel->text() + cUUID); + connect(ui->sendMsgButton, &QAbstractButton::clicked, this, &ChatWindow::sendMsgButtonClicked); +} +ChatWindow::~ChatWindow() +{ + leftFromChat(); + emit deleteChat(chatID); + delete ui; + this->deleteLater(); +} + +void ChatWindow::sendMessage(QString msgText) +{ + QJsonObject jSend; + jSend["action"] = "msgSend"; + jSend["peerID"] = *network->myIPv6; + jSend["chatID"] = chatID; + jSend["msgText"] = msgText; + network->sendDatagram(jSend, peerID); + QDateTime date(QDateTime::currentDateTime()); + ui->chatEdit->append("[" + date.toString() + "] " + tr("You: ") + msgText); +} + +void ChatWindow::sendMsgButtonClicked() +{ + QString msg = ui->msgEdit->text(); + sendMessage(msg); + ui->msgEdit->setText(""); +} + +void ChatWindow::displayMsg(QString msgText) +{ + QDateTime date(QDateTime::currentDateTime()); + ui->chatEdit->append("[" + date.toString() + "] " + "" + peerID + "" + ": " + msgText); +} + +void ChatWindow::leftFromChat() +{ + QJsonObject json; + json["action"] = "leftChat"; + json["peerID"] = *network->myIPv6; + json["chatID"] = chatID; + network->sendDatagram(json, peerID); +} + +void ChatWindow::peerReceiverLeftFromChat() +{ + QString msg; + msg += tr("Peer "); + msg += peerID; + msg += tr(" left from this chat."); + QMessageBox::warning(this, tr("Peer receiver left from chat!"), msg, QMessageBox::Ok); + ui->chatEdit->append("" + peerID + "" + tr(" left from this chat.")); + ui->msgEdit->setEnabled(false); + ui->sendMsgButton->setEnabled(false); +} diff --git a/src/chatwindow.hpp b/src/chatwindow.hpp new file mode 100644 index 0000000..1da87cd --- /dev/null +++ b/src/chatwindow.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include "kernel/handler.hpp" +#include "kernel/network.hpp" + +namespace Ui { + class ChatWindow; +} + +class ChatWindow : public QWidget +{ + Q_OBJECT + + public: + explicit ChatWindow(QString pID, QString cUUID, Handler *h, Network *n, QWidget *parent = 0); + ~ChatWindow(); + QString chatID; + QString peerID; + Handler *handler; + Network *network; + void displayMsg(QString msgText); + void peerReceiverLeftFromChat(); + private: + Ui::ChatWindow *ui; + void sendMessage(QString msgText); + void leftFromChat(); + signals: + void deleteChat(QString cID); + private slots: + void sendMsgButtonClicked(); +}; diff --git a/src/chatwindow.ui b/src/chatwindow.ui new file mode 100644 index 0000000..76458d5 --- /dev/null +++ b/src/chatwindow.ui @@ -0,0 +1,78 @@ + + + ChatWindow + + + + 0 + 0 + 807 + 519 + + + + + + + + + + + + + + 697 + 0 + + + + Contact PeerID: + + + + + + + + + + Send + + + + + + + true + + + + + + + ChatID: + + + + + + + + + msgEdit + returnPressed() + sendMsgButton + click() + + + 360 + 497 + + + 757 + 497 + + + + + diff --git a/src/kernel/handler.cpp b/src/kernel/handler.cpp index 50f75b9..14646dd 100644 --- a/src/kernel/handler.cpp +++ b/src/kernel/handler.cpp @@ -2,24 +2,101 @@ Handler::Handler() { + using namespace std::placeholders; + + handlers = { + {"checkPeer", std::bind(&Handler::checkPeer, this, _1)}, + {"checkPeerSuccess", std::bind(&Handler::checkPeerSuccessMethod, this, _1)}, + {"createChat", std::bind(&Handler::createChatMethod, this, _1)}, + {"createChatSuccess", std::bind(&Handler::createChatSuccessMethod, this, _1)}, + {"createChatFailed", std::bind(&Handler::createChatFailedMethod, this, _1)}, + {"msgSend", std::bind(&Handler::msgReceiveMethod, this, _1)}, + {"leftChat", std::bind(&Handler::peerReceiverLeftFromChatMethod, this, _1)} + }; + peerReceiver = new QString(); network = new Network(); - connect(network, &Network::json_received, this, &Handler::handle); + connect(network, &Network::jsonReceived, this, &Handler::handle); } void Handler::handle(QJsonObject jsonReceived) +{ + QString action = jsonReceived["action"].toString(); + *peerReceiver = jsonReceived["peerID"].toString(); + handlers[action](jsonReceived); +} + +void Handler::checkPeer(QJsonObject jsonReceived) { QJsonObject jsonSend; - if(jsonReceived["action"] == "createSession" && !jsonReceived.contains("status")) + jsonSend["peerID"] = *network->myIPv6; + jsonSend["action"] = "checkPeerSuccess"; + network->sendDatagram(jsonSend, *peerReceiver); +} + +void Handler::checkPeerSuccessMethod(QJsonObject jsonReceived) +{ + emit checkPeerSuccess(); +} + +Handler::~Handler() +{ + delete peerReceiver; + delete network; + //delete handlers; +} + +void Handler::createChatMethod(QJsonObject jsonReceived) +{ + QWidget *parent = 0; + QString msgTitle; + QString msg; + msgTitle += "Create chat"; + msg += tr("Peer "); + msg += *peerReceiver; + msg += tr(" want to create chat with you.\n"); + msg += tr("Do you want to create chat?"); + int ret = QMessageBox::warning(parent, msgTitle, msg, QMessageBox::Yes | QMessageBox::No); + if(ret == QMessageBox::Yes) { - jsonSend["peerID"] = my_ipv6; - jsonSend["action"] = "createSession"; - jsonSend["status"] = true; - QString peerReceiver = jsonReceived["peerID"].toString(); - network->sendDatagram(jsonSend, peerReceiver); + QJsonObject jsonSend; + jsonSend["peerID"] = *network->myIPv6; + jsonSend["action"] = "createChatSuccess"; + jsonSend["chatID"] = jsonReceived["chatUUID"].toString(); + network->sendDatagram(jsonSend, *peerReceiver); + emit createChatSuccess(jsonReceived["peerID"].toString(), jsonReceived["chatUUID"].toString()); } else { - emit createSessionSuccess(); + QJsonObject jsonSend; + jsonSend["peerID"] = *network->myIPv6; + jsonSend["action"] = "createChatFailed"; + network->sendDatagram(jsonSend, *peerReceiver); } } + +void Handler::createChatFailedMethod(QJsonObject jsonReceived) +{ + QString msg; + msg += tr("Peer "); + msg += *peerReceiver; + msg += tr(" refused to create a chat with you"); + QWidget *parent = 0; + QMessageBox::critical(parent, tr("Create chat failed!"), msg, QMessageBox::Ok); + emit createChatFailed(); +} + +void Handler::createChatSuccessMethod(QJsonObject jsonReceived) +{ + emit createChatSuccess(jsonReceived["peerID"].toString(), jsonReceived["chatID"].toString()); +} + +void Handler::msgReceiveMethod(QJsonObject jsonReceived) +{ + emit msgReceived(jsonReceived["peerID"].toString(), jsonReceived["chatID"].toString(), jsonReceived["msgText"].toString()); +} + +void Handler::peerReceiverLeftFromChatMethod(QJsonObject jsonReceived) +{ + emit peerReceiverLeftFromChat(jsonReceived["peerID"].toString(), jsonReceived["chatID"].toString()); +} diff --git a/src/kernel/handler.hpp b/src/kernel/handler.hpp index e2ace3a..de2dca9 100644 --- a/src/kernel/handler.hpp +++ b/src/kernel/handler.hpp @@ -2,21 +2,32 @@ #include "network.hpp" #include +#include class Handler : public QObject { Q_OBJECT - const QString my_ipv6 = Network::local_ipv6(); - public: Handler(); - signals: - void createSessionSuccess(); + ~Handler(); + QString *peerReceiver; private: Network *network; + void checkPeer(QJsonObject jsonReceived); + std::map> handlers; + void checkPeerSuccessMethod(QJsonObject jsonReceived); + void createChatMethod(QJsonObject jsonReceived); + void createChatSuccessMethod(QJsonObject jsonReceived); + void createChatFailedMethod(QJsonObject jsonReceived); + void msgReceiveMethod(QJsonObject jsonReceived); + void peerReceiverLeftFromChatMethod(QJsonObject jsonReceived); + signals: + void checkPeerSuccess(); + void createChatSuccess(QString peerID, QString chatID); + void createChatFailed(); + void msgReceived(QString peerID, QString chatID, QString msgText); + void peerReceiverLeftFromChat(QString peerID, QString chatID); private slots: void handle(QJsonObject jsonReceived); - - }; diff --git a/src/kernel/network.cpp b/src/kernel/network.cpp index f90cf17..c055a6a 100644 --- a/src/kernel/network.cpp +++ b/src/kernel/network.cpp @@ -1,4 +1,5 @@ #include "network.hpp" + Network::Network(bool is_server) { udpSocket = new QUdpSocket(this); @@ -7,6 +8,7 @@ Network::Network(bool is_server) udpSocket->bind(QHostAddress::AnyIPv6, 6552); connect(udpSocket, SIGNAL(readyRead()), this, SLOT(processTheDatagram())); } + myIPv6 = new QString(localIPv6()); } void Network::sendDatagram(QJsonObject j, QString s) @@ -23,21 +25,35 @@ void Network::processTheDatagram() { QByteArray baDatagram; do { - baDatagram.resize(udpSocket->pendingDatagramSize ()) ; + baDatagram.resize(udpSocket->pendingDatagramSize()) ; udpSocket->readDatagram (baDatagram.data(), baDatagram.size()) ; } while (udpSocket->hasPendingDatagrams()) ; QJsonDocument jbuff = QJsonDocument::fromJson(baDatagram); QJsonObject j = QJsonObject(jbuff.object()); - emit json_received(j); + emit jsonReceived(j); } -QString Network::local_ipv6() +QString Network::localIPv6() { QHostAddress address; + QString addressString; foreach (address, QNetworkInterface::allAddresses()) { - if (address.protocol() == QAbstractSocket::IPv6Protocol && address != QHostAddress(QHostAddress::LocalHost)) - break; + if (address.protocol() == QAbstractSocket::IPv6Protocol && address != QHostAddress(QHostAddress::LocalHostIPv6) && (address.toString()).contains("fc")) + { + addressString = address.toString(); + break; + } + else + { + addressString = "null"; + } } - return(address.toString()); + return(addressString); +} + +Network::~Network() +{ + delete udpSocket; + delete myIPv6; } diff --git a/src/kernel/network.hpp b/src/kernel/network.hpp index 6b0679f..f94b89f 100644 --- a/src/kernel/network.hpp +++ b/src/kernel/network.hpp @@ -9,15 +9,18 @@ class Network : public QObject { Q_OBJECT - private: - QUdpSocket* udpSocket; + public: Network(bool is_server = true); - static QString local_ipv6(); + ~Network(); + static QString localIPv6(); + const QString *myIPv6; + private: + QUdpSocket* udpSocket; public slots: void sendDatagram(QJsonObject j, QString s); signals: - void json_received(QJsonObject &jsonReceived); + void jsonReceived(QJsonObject &jsonReceived); private slots: void processTheDatagram(); }; diff --git a/src/main.cpp b/src/main.cpp index b48f94e..c86e878 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,10 @@ int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; + if(w.doQuit()) + { + return -1; + } w.show(); return a.exec(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 49df6fa..ddb1da2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5,47 +5,140 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { - ui->setupUi(this); network = new Network(false); + if(*network->myIPv6 == "null") + { + QMessageBox::critical(parent, tr("Cjdns is not running!"), tr("Cjdns is not running, so the application will be closed.")); + mDoQuit = true; + } + ui->setupUi(this); handler = new Handler(); - connect(handler, &Handler::createSessionSuccess, this, &MainWindow::peerReceiverConnected); + timer = new QTimer(); + connect(timer, SIGNAL(timeout()), this, SLOT(slotTimerAlarm())); + connect(handler, &Handler::checkPeerSuccess, this, &MainWindow::peerReceiverAvailable); + connect(handler, &Handler::createChatSuccess, this, &MainWindow::createChat); + connect(handler, &Handler::createChatFailed, this, &MainWindow::createChatFailedMethod); + connect(handler, &Handler::msgReceived, this, &MainWindow::msgReceivedMethod); + connect(handler, &Handler::peerReceiverLeftFromChat, this, &MainWindow::peerReceiverLeftFromChatMethod); + ui->myIP->setText(*network->myIPv6); + } MainWindow::~MainWindow() { delete ui; + delete network; + delete handler; + delete timer; } void MainWindow::on_connectToPeer_clicked() { + setButtonToWaiting(); QJsonObject j; - j["peerID"] = my_ipv6; - j["action"] = "createSession"; + j["peerID"] = *network->myIPv6; + j["action"] = "checkPeer"; QString s = ui->peerID->text(); network->sendDatagram(j, s); - timer = new QTimer(); - connect(timer, SIGNAL(timeout()), this, SLOT(slotTimerAlarm())); timer->start(10000); } void MainWindow::slotTimerAlarm() { - if(receive) + if(receive == true) { timer->stop(); receive = false; } else { - int ret = QMessageBox::critical(this,QObject::tr("Error"),tr("Timeout Error")); + QMessageBox::critical(this, tr("Error"), tr("Timeout Error\n\nPerhaps you have not started the cjdns daemon, you entered the wrong IP, the peer is off, or the peer did not start the messenger.")); timer->stop(); - delete timer; + setButtonToConnect(); } } -void MainWindow::peerReceiverConnected() +void MainWindow::peerReceiverAvailable() { receive = true; - int ret = QMessageBox::information(this,QObject::tr("Info"),tr("Peer Available!")); + int ret = QMessageBox::information(this, tr("Peer Available!"), + tr("Peer Available!\nDo you want to create chat?"), QMessageBox::Yes | QMessageBox::No); + if(ret == QMessageBox::Yes) + { + createChatSendDatagram(*handler->peerReceiver); + } + else + { + setButtonToConnect(); + } +} + +void MainWindow::setButtonToWaiting() // Function, which sets button "connectToPeer" in status "Waiting..." +{ + ui->connectToPeer->setEnabled(false); + ui->connectToPeer->setText(tr("Waiting...")); +} + +void MainWindow::setButtonToConnect() // Function, which sets button "connectToPeer" in status "Connect" +{ + ui->connectToPeer->setEnabled(true); + ui->connectToPeer->setText(tr("Connect")); +} + +void MainWindow::createChatSendDatagram(QString peerReceiver) +{ + QUuid chatID = QUuid::createUuid(); + QJsonObject jSend; + jSend["action"] = "createChat"; + jSend["peerID"] = *network->myIPv6; + jSend["chatUUID"] = chatID.toString(); + network->sendDatagram(jSend, peerReceiver); +} + +void MainWindow::createChat(QString peerID, QString chatID) +{ + pChatWindows.push_back(new ChatWindow(peerID, chatID, handler, network)); + connect(pChatWindows.back(), &ChatWindow::deleteChat, this, &MainWindow::deleteChatMethod); + pChatWindows.back()->show(); + setButtonToConnect(); +} + +void MainWindow::createChatFailedMethod() +{ + setButtonToConnect(); +} + +void MainWindow::deleteChatMethod(QString cID) +{ + for(int i = 0; i < pChatWindows.size(); i++) + { + if(pChatWindows.at(i)->chatID == cID) + { + pChatWindows.remove(i); + break; + } + } +} + +void MainWindow::msgReceivedMethod(QString peerID, QString chatID, QString msgText) +{ + for(int i = 0; i < pChatWindows.size(); i++) + { + if(pChatWindows.at(i)->peerID == peerID && pChatWindows.at(i)->chatID == chatID) + { + pChatWindows.at(i)->displayMsg(msgText); + } + } +} + +void MainWindow::peerReceiverLeftFromChatMethod(QString peerID, QString chatID) +{ + for(int i = 0; i < pChatWindows.size(); i++) + { + if(pChatWindows.at(i)->peerID == peerID && pChatWindows.at(i)->chatID == chatID) + { + pChatWindows.at(i)->peerReceiverLeftFromChat(); + } + } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 278c2b1..fa791d2 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -3,9 +3,12 @@ #include #include "kernel/network.hpp" #include "kernel/handler.hpp" +#include "chatwindow.hpp" +#include #include #include #include +#include namespace Ui { @@ -19,18 +22,26 @@ class MainWindow : public QMainWindow public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); + QVector pChatWindows; + void setButtonToWaiting(); + void setButtonToConnect(); + inline bool doQuit() const { return mDoQuit; } public slots: - void peerReceiverConnected(); + void peerReceiverAvailable(); private slots: void on_connectToPeer_clicked(); void slotTimerAlarm(); - + void createChatFailedMethod(); + void createChat(QString peerID, QString chatID); + void deleteChatMethod(QString cID); + void msgReceivedMethod(QString peerID, QString chatID, QString msgText); + void peerReceiverLeftFromChatMethod(QString peerID, QString chatID); private: + void createChatSendDatagram(QString peerReceiver); Ui::MainWindow *ui; QTimer *timer; Network *network; Handler *handler; - - bool receive; - const QString my_ipv6 = Network::local_ipv6(); + bool receive = false; + bool mDoQuit = false; }; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 7e212b7..4ed2caa 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -29,18 +29,38 @@ Influence - - + + - - + + + + + - Подключиться + My IPv6: + + + + + + + + + + Connect + + + + + + + true @@ -49,5 +69,22 @@ - + + + peerID + returnPressed() + connectToPeer + click() + + + 122 + 90 + + + 281 + 90 + + + + diff --git a/src/make.sh b/src/make.sh new file mode 100755 index 0000000..a412cc1 --- /dev/null +++ b/src/make.sh @@ -0,0 +1,3 @@ +rm -f Influence +qmake +make