Merge pull request #3 from ChronosX88/develop

0.1.0 alpha release
This commit is contained in:
ChronosX88 2018-07-26 17:58:11 +03:00 committed by GitHub
commit cdb17e08bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 497 additions and 69 deletions

7
.gitignore vendored
View File

@ -1,5 +1,4 @@
.project
*.user
/.project
*.autosave
*.autosave
*.o

View File

@ -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` и отправляем им запрос.

View File

@ -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

70
src/chatwindow.cpp Normal file
View File

@ -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("<i>[" + date.toString() + "]</i> " + tr("<b>You</b>: ") + 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("<i>[" + date.toString() + "]</i> " + "<b>" + peerID + "</b>" + ": " + 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("<b>" + peerID + "</b>" + tr(" left from this chat."));
ui->msgEdit->setEnabled(false);
ui->sendMsgButton->setEnabled(false);
}

33
src/chatwindow.hpp Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <QWidget>
#include <QJsonObject>
#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();
};

78
src/chatwindow.ui Normal file
View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChatWindow</class>
<widget class="QWidget" name="ChatWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>807</width>
<height>519</height>
</rect>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0" colspan="2">
<widget class="QLineEdit" name="msgEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="peerIDLabel">
<property name="minimumSize">
<size>
<width>697</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Contact PeerID: </string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QPushButton" name="sendMsgButton">
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="text">
<string>Send</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QTextEdit" name="chatEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="chatIDLabel">
<property name="text">
<string>ChatID: </string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>msgEdit</sender>
<signal>returnPressed()</signal>
<receiver>sendMsgButton</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>360</x>
<y>497</y>
</hint>
<hint type="destinationlabel">
<x>757</x>
<y>497</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -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());
}

View File

@ -2,21 +2,32 @@
#include "network.hpp"
#include <QJsonObject>
#include <QMessageBox>
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<QString, std::function<void(QJsonObject)>> 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);
};

View File

@ -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;
}

View File

@ -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();
};

View File

@ -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();

View File

@ -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();
}
}
}

View File

@ -3,9 +3,12 @@
#include <QMainWindow>
#include "kernel/network.hpp"
#include "kernel/handler.hpp"
#include "chatwindow.hpp"
#include <QUuid>
#include <QMessageBox>
#include <QJsonObject>
#include <QJsonDocument>
#include <QObject>
namespace Ui {
@ -19,18 +22,26 @@ class MainWindow : public QMainWindow
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QVector<ChatWindow*> 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;
};

View File

@ -29,18 +29,38 @@
<string>Influence</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="4">
<widget class="QLineEdit" name="peerID">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="connectToPeer">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="text">
<string>Подключиться</string>
<string>My IPv6:</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QPushButton" name="connectToPeer">
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
<property name="text">
<string>Connect</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="myIP">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
@ -49,5 +69,22 @@
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
<connections>
<connection>
<sender>peerID</sender>
<signal>returnPressed()</signal>
<receiver>connectToPeer</receiver>
<slot>click()</slot>
<hints>
<hint type="sourcelabel">
<x>122</x>
<y>90</y>
</hint>
<hint type="destinationlabel">
<x>281</x>
<y>90</y>
</hint>
</hints>
</connection>
</connections>
</ui>

3
src/make.sh Executable file
View File

@ -0,0 +1,3 @@
rm -f Influence
qmake
make