Merge pull request #599 from G10h4ck/qml_app_chat

Qml app chat
This commit is contained in:
csoler 2016-12-15 23:21:11 +01:00 committed by GitHub
commit 1ef11a27fd
12 changed files with 286 additions and 170 deletions

View file

@ -837,6 +837,7 @@ void ChatHandler::handleLobbies(Request &/*req*/, Response &resp)
{ {
tick(); tick();
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/ RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
resp.mDataStream.getStreamToMember(); resp.mDataStream.getStreamToMember();
for(std::vector<Lobby>::iterator vit = mLobbies.begin(); vit != mLobbies.end(); ++vit) for(std::vector<Lobby>::iterator vit = mLobbies.begin(); vit != mLobbies.end(); ++vit)
@ -854,6 +855,7 @@ void ChatHandler::handleLobbies(Request &/*req*/, Response &resp)
resp.mDataStream.getStreamToMember() << *vit << makeKeyValueReference("unread_msg_count", unread_msgs); resp.mDataStream.getStreamToMember() << *vit << makeKeyValueReference("unread_msg_count", unread_msgs);
} }
resp.mStateToken = mLobbiesStateToken; resp.mStateToken = mLobbiesStateToken;
}
resp.setOk(); resp.setOk();
} }
@ -921,6 +923,12 @@ ResponseTask* ChatHandler::handleLobbyParticipants(Request &req, Response &resp)
void ChatHandler::handleMessages(Request &req, Response &resp) void ChatHandler::handleMessages(Request &req, Response &resp)
{ {
/* G10h4ck: Whithout this the request processing won't happen, copied from
* ChatHandler::handleLobbies, is this a work around or is the right whay of
* doing it? */
tick();
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/ RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
ChatId id(req.mPath.top()); ChatId id(req.mPath.top());
// make response a list // make response a list
@ -941,6 +949,7 @@ void ChatHandler::handleMessages(Request &req, Response &resp)
} }
resp.mStateToken = mMsgStateToken; resp.mStateToken = mMsgStateToken;
handlePaginationRequest(req, resp, mit->second); handlePaginationRequest(req, resp, mit->second);
}
} }
void ChatHandler::handleSendMessage(Request &req, Response &resp) void ChatHandler::handleSendMessage(Request &req, Response &resp)

View file

@ -121,86 +121,55 @@ void IdentityHandler::notifyGxsChange(const RsGxsChanges &changes)
} }
} }
void IdentityHandler::handleWildcard(Request &req, Response &resp) void IdentityHandler::handleWildcard(Request & /*req*/, Response &resp)
{ {
bool ok = true; bool ok = true;
if(req.isPut()) {
{ RS_STACK_MUTEX(mMtx);
#ifdef REMOVE resp.mStateToken = mStateToken;
RsIdentityParameters params; }
req.mStream << makeKeyValueReference("name", params.nickname); RsTokReqOptions opts;
if(req.mStream.isOK()) opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA;
{ uint32_t token;
uint32_t token; mRsIdentity->getTokenService()->requestGroupInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts);
mRsIdentity->createIdentity(token, params);
// not sure if should acknowledge the token
// for now go the easier way
}
else
{
ok = false;
}
#endif
}
else
{
{
RS_STACK_MUTEX(mMtx); // ********** LOCKED **********
resp.mStateToken = mStateToken;
}
RsTokReqOptions opts;
opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA;
uint32_t token;
mRsIdentity->getTokenService()->requestGroupInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts);
time_t start = time(NULL); time_t start = time(NULL);
while((mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE) while((mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
&&(mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_FAILED) &&(mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_FAILED)
&&((time(NULL) < (start+10))) &&((time(NULL) < (start+10)))
) )
{ {
#ifdef WINDOWS_SYS #ifdef WINDOWS_SYS
Sleep(500); Sleep(500);
#else #else
usleep(500*1000) ; usleep(500*1000);
#endif #endif
} }
if(mRsIdentity->getTokenService()->requestStatus(token) == RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE) if(mRsIdentity->getTokenService()->requestStatus(token) == RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
{ {
std::vector<RsGxsIdGroup> grps; std::vector<RsGxsIdGroup> grps;
ok &= mRsIdentity->getGroupData(token, grps); ok &= mRsIdentity->getGroupData(token, grps);
for(std::vector<RsGxsIdGroup>::iterator vit = grps.begin(); vit != grps.end(); vit++) for(std::vector<RsGxsIdGroup>::iterator vit = grps.begin(); vit != grps.end(); vit++)
{ {
RsGxsIdGroup& grp = *vit; RsGxsIdGroup& grp = *vit;
KeyValueReference<RsGxsGroupId> id("id", grp.mMeta.mGroupId); //electron: not very happy about this, i think the flags should stay hidden in rsidentities
KeyValueReference<RsPgpId> pgp_id("pgp_id",grp.mPgpId ); bool own = (grp.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_ADMIN);
// not very happy about this, i think the flags should stay hidden in rsidentities bool pgp_linked = (grp.mMeta.mGroupFlags & RSGXSID_GROUPFLAG_REALID);
bool own = (grp.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_ADMIN); resp.mDataStream.getStreamToMember()
bool pgp_linked = (grp.mMeta.mGroupFlags & RSGXSID_GROUPFLAG_REALID); << makeKeyValueReference("id", grp.mMeta.mGroupId) /// @deprecated using "id" as key can cause problems in some JS based languages like Qml @see gxs_id instead
resp.mDataStream.getStreamToMember() << makeKeyValueReference("gxs_id", grp.mMeta.mGroupId)
<< id << makeKeyValueReference("pgp_id",grp.mPgpId )
<< pgp_id << makeKeyValueReference("name", grp.mMeta.mGroupName)
<< makeKeyValueReference("name", grp.mMeta.mGroupName) << makeKeyValueReference("own", own)
<< makeKeyValueReference("own", own) << makeKeyValueReference("pgp_linked", pgp_linked);
<< makeKeyValueReference("pgp_linked", pgp_linked); }
} }
} else ok = false;
else
{
ok = false;
}
}
if(ok) if(ok) resp.setOk();
{ else resp.setFail();
resp.setOk();
}
else
{
resp.setFail();
}
} }
ResponseTask* IdentityHandler::handleOwn(Request & /* req */, Response &resp) ResponseTask* IdentityHandler::handleOwn(Request & /* req */, Response &resp)

View file

@ -482,8 +482,8 @@ bool RsDirUtil::checkCreateDirectory(const std::string& dir)
std::string RsDirUtil::removeSymLinks(const std::string& path) std::string RsDirUtil::removeSymLinks(const std::string& path)
{ {
#if defined(WINDOWS_SYS) || defined(__APPLE__) #if defined(WINDOWS_SYS) || defined(__APPLE__) || defined(__ANDROID__)
#warning (Mr.Alice): I don't know how to do this on windows/MacOS. See https://msdn.microsoft.com/en-us/library/windows/desktop/hh707084(v=vs.85).aspx' #warning (Mr.Alice): I don't know how to do this on windows/MacOS/Android. See https://msdn.microsoft.com/en-us/library/windows/desktop/hh707084(v=vs.85).aspx'
//if(!S_OK == PathCchCanonicalizeEx(tmp,...) ; //if(!S_OK == PathCchCanonicalizeEx(tmp,...) ;
return path ; return path ;
#else #else

View file

@ -1,15 +0,0 @@
#ifndef DEBUGUTILS_H
#define DEBUGUTILS_H
#include <QDebug>
//To switch between debugging and normal mode, un-/comment next line
#define DEBUGGING
#ifdef DEBUGGING
#define myDebug(line) qDebug() << "| FILE:" << __FILE__ << " | LINE_NUMBER:"\
<< __LINE__ << " | FUNCTION:" << __FUNCTION__ << " | CONTENT:" << line
#else
#define myDebug(line)
#endif
#endif // DEBUGUTILS_H

View file

@ -18,8 +18,8 @@
*/ */
#include "libresapilocalclient.h" #include "libresapilocalclient.h"
#include "debugutils.h"
#include <QChar> #include <QJSEngine>
void LibresapiLocalClient::openConnection(QString socketPath) void LibresapiLocalClient::openConnection(QString socketPath)
@ -31,54 +31,35 @@ void LibresapiLocalClient::openConnection(QString socketPath)
mLocalSocket.connectToServer(socketPath); mLocalSocket.connectToServer(socketPath);
} }
int LibresapiLocalClient::request(const QString & path, const QString & jsonData) int LibresapiLocalClient::request( const QString& path, const QString& jsonData,
QJSValue callback )
{ {
qDebug() << "LibresapiLocalClient::request()" << path << jsonData; QByteArray data;
QByteArray data; data.append(path); data.append('\n');
data.append(path); data.append('\n'); data.append(jsonData); data.append('\n');
data.append(jsonData); data.append('\n'); callbackQueue.enqueue(callback);
mLocalSocket.write(data); mLocalSocket.write(data);
return 1; return 1;
} }
void LibresapiLocalClient::socketError(QLocalSocket::LocalSocketError) void LibresapiLocalClient::socketError(QLocalSocket::LocalSocketError)
{ {
myDebug("error!!!!\n" + mLocalSocket.errorString()); qDebug() << "Socket Eerror!!" << mLocalSocket.errorString();
} }
void LibresapiLocalClient::read() void LibresapiLocalClient::read()
{ {
receivedBytes = mLocalSocket.readLine(); QString receivedMsg(mLocalSocket.readLine());
QJSValue callback(callbackQueue.dequeue());
qDebug() << receivedBytes; if(callback.isCallable())
if(parseResponse()) // pensar en fer un buffer per parsejar, per evitar errors.
emit goodResponseReceived(QString(receivedBytes));
else
{ {
QString errMess = "The message was not understood!\n" QJSValue params = callback.engine()->newObject();
"It should be a JSON formatted text file\n" params.setProperty("response", receivedMsg);
"Its contents were:\n" + receivedBytes;
myDebug(errMess.replace(QChar('\n'), QChar::LineSeparator)); callback.call(QJSValueList { params });
} }
}
emit goodResponseReceived(receivedMsg); /// @deprecated
bool LibresapiLocalClient::parseResponse() emit responseReceived(receivedMsg);
{
QJsonParseError error;
json = QJsonDocument::fromJson(receivedBytes, &error);
myDebug(QString(json.toJson()).replace(QChar('\n'), QChar::LineSeparator));
if(error.error == QJsonParseError::NoError){
return true;
}
myDebug(error.errorString());
return false;
}
const QJsonDocument & LibresapiLocalClient::getJson()
{
return json;
} }

View file

@ -21,9 +21,8 @@
#define LIBRESAPILOCALCLIENT_H #define LIBRESAPILOCALCLIENT_H
#include <QLocalSocket> #include <QLocalSocket>
#include <QDir> #include <QQueue>
#include <QJsonDocument> #include <QJSValue>
#include <QVector>
class LibresapiLocalClient : public QObject class LibresapiLocalClient : public QObject
{ {
@ -32,25 +31,27 @@ class LibresapiLocalClient : public QObject
public: public:
LibresapiLocalClient() : mLocalSocket(this) {} LibresapiLocalClient() : mLocalSocket(this) {}
// potser abstreure el següent amb QUrl urlPath (path) i amb QJson jsonData. Q_INVOKABLE int request( const QString& path, const QString& jsonData = "",
Q_INVOKABLE int request(const QString & path, const QString & jsonData); QJSValue callback = QJSValue::NullValue);
const QJsonDocument & getJson();
Q_INVOKABLE void openConnection(QString socketPath); Q_INVOKABLE void openConnection(QString socketPath);
private: private:
QLocalSocket mLocalSocket; QLocalSocket mLocalSocket;
QByteArray receivedBytes; QQueue<QJSValue> callbackQueue;
QJsonDocument json;
//QVector<QJsonDocument> responses;
bool parseResponse(); //std::string msg);
private slots: private slots:
void socketError(QLocalSocket::LocalSocketError error); void socketError(QLocalSocket::LocalSocketError error);
void read(); void read();
signals: signals:
void goodResponseReceived(const QString & msg);//, int requestId); /// @deprecated @see LibresapiLocalClient::responseReceived instead
void goodResponseReceived(const QString & msg);
/**
* @brief responseReceived emitted when a response is received
* @param msg
*/
void responseReceived(const QString & msg);
}; };
#endif // LIBRESAPILOCALCLIENT_H #endif // LIBRESAPILOCALCLIENT_H

View file

@ -34,28 +34,28 @@
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
/// @deprecated
qmlRegisterType<LibresapiLocalClient>( qmlRegisterType<LibresapiLocalClient>(
"org.retroshare.qml_components.LibresapiLocalClient", 1, 0, "org.retroshare.qml_components.LibresapiLocalClient", 1, 0,
"LibresapiLocalClient"); "LibresapiLocalClient");
QString sockPath = QString::fromStdString(RsAccounts::ConfigDirectory()); QString sockPath = QString::fromStdString(RsAccounts::ConfigDirectory());
sockPath.append("/libresapi.sock"); sockPath.append("/libresapi.sock");
LibresapiLocalClient rsApi;
rsApi.openConnection(sockPath);
engine.rootContext()->setContextProperty("apiSocketPath", sockPath); engine.rootContext()->setContextProperty("apiSocketPath", sockPath);
engine.rootContext()->setContextProperty("rsApi", &rsApi);
engine.load(QUrl(QLatin1String("qrc:/qml/main.qml"))); engine.load(QUrl(QLatin1String("qrc:/qml/main.qml")));
QFileInfo fileInfo(sockPath); QFileInfo fileInfo(sockPath);
#ifdef __ANDROID__
qDebug() << "Is main.cpp running as a service?" << QtAndroid::androidService().isValid();
qDebug() << "Is main.cpp running as an activity?" << QtAndroid::androidActivity().isValid();
#endif
qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString(); qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString();
return app.exec(); return app.exec();
} }

View file

@ -22,5 +22,6 @@
<file>qml/AddTrustedNode.qml</file> <file>qml/AddTrustedNode.qml</file>
<file>qml/RsLoginPassView.qml</file> <file>qml/RsLoginPassView.qml</file>
<file>qml/TrustedNodesView.qml</file> <file>qml/TrustedNodesView.qml</file>
<file>qml/ChatView.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -0,0 +1,88 @@
import QtQuick 2.0
import QtQuick.Controls 1.4
import org.retroshare.qml_components.LibresapiLocalClient 1.0
Item
{
id: chatView
property string chatId
function refreshData() { rsApi.request("/chat/messages/"+ chatId, "", function(par) { chatModel.json = par.response }) }
onFocusChanged: focus && refreshData()
JSONListModel
{
id: chatModel
query: "$.data[*]"
}
Component
{
id: chatMessageDelegate
Item
{
height: 20
Row
{
Text { text: author_name }
Text { text: ": " + msg }
}
}
}
ListView
{
width: parent.width
height: 300
model: chatModel.model
delegate: chatMessageDelegate
}
Rectangle
{
color: "green"
anchors.bottom: parent.bottom
anchors.left: parent.left
width: chatView.width - sendButton.width
height: Math.max(20, msgComposer.height)
}
TextEdit
{
id: msgComposer
anchors.bottom: parent.bottom
anchors.left: parent.left
width: chatView.width - sendButton.width
}
Button
{
id: sendButton
text: "Send"
anchors.bottom: parent.bottom
anchors.right: parent.right
onClicked:
{
var jsonData = {"chat_id":chatView.chatId, "msg":msgComposer.text}
sendRsApi.request("/chat/send_message", JSON.stringify(jsonData))
}
LibresapiLocalClient
{
id: sendRsApi
onGoodResponseReceived: { msgComposer.text = ""; console.log(msg)}
Component.onCompleted: { openConnection(apiSocketPath) }
}
}
Timer
{
id: refreshTimer
interval: 800
repeat: true
onTriggered: if(chatView.visible) chatView.refreshData()
Component.onCompleted: start()
}
}

View file

@ -18,21 +18,34 @@
import QtQuick 2.0 import QtQuick 2.0
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import org.retroshare.qml_components.LibresapiLocalClient 1.0 import org.retroshare.qml_components.LibresapiLocalClient 1.0
Item Item
{ {
function refreshData() { rsApi.request("/identity/*/", "") } id: contactsView
property string own_gxs_id: ""
property string own_nick: ""
Component.onCompleted: refreshOwn()
function refreshData() { rsApi.request("/identity/*/", "", function(par) { locationsModel.json = par.response; if(contactsView.own_gxs_id == "") refreshOwn() }) }
function refreshOwn()
{
rsApi.request("/identity/own", "", function(par)
{
var json = JSON.parse(par.response)
if(json.data.length > 0)
{
contactsView.own_gxs_id = json.data[0].gxs_id
contactsView.own_nick = json.data[0].name
}
else createIdentityDialog.visible = true
})
}
onFocusChanged: focus && refreshData() onFocusChanged: focus && refreshData()
LibresapiLocalClient
{
id: rsApi
onGoodResponseReceived: locationsModel.json = msg
Component.onCompleted: { openConnection(apiSocketPath) }
}
JSONListModel JSONListModel
{ {
id: locationsModel id: locationsModel
@ -45,8 +58,73 @@ Item
width: parent.width width: parent.width
height: 300 height: 300
model: locationsModel.model model: locationsModel.model
delegate: Text { text: model.name } delegate: Item
{
height: 20
width: parent.width
MouseArea
{
anchors.fill: parent
onClicked:
{
console.log("Contacts view onclicked:", model.name, model.gxs_id)
if(model.own) contactsView.own_gxs_id = model.gxs_id
else
{
var jsonData = { "own_gxs_hex": contactsView.own_gxs_id, "remote_gxs_hex": model.gxs_id }
rsApi.request("/chat/initiate_distant_chat", JSON.stringify(jsonData), function (par) { mainWindow.activeChatId = JSON.parse(par.response).data.chat_id })
}
}
Text
{
color: model.own ? "blue" : "black"
text: model.name + " " + model.gxs_id
}
}
}
} }
Text { text: "Contacts View"; anchors.bottom: parent.bottom } Text
{
id: selectedOwnIdentityView
color: "green"
anchors.bottom: parent.bottom
anchors.left: parent.left
width: parent.width
text: "Open Chat as: " + contactsView.own_nick + " " + contactsView.own_gxs_id
}
Timer
{
id: refreshTimer
interval: 5000
repeat: true
onTriggered: if(contactsView.visible) contactsView.refreshData()
Component.onCompleted: start()
}
Dialog
{
id: createIdentityDialog
visible: false
title: "You need to create a GXS identity to chat!"
standardButtons: StandardButton.Save
onAccepted: rsApi.request("/identity/create_identity", JSON.stringify({"name":identityNameTE.text, "pgp_linked": !psdnmCheckBox.checked }))
TextField
{
id: identityNameTE
width: 300
}
Row
{
anchors.top: identityNameTE.bottom
Text { text: "Pseudonymous: " }
CheckBox { id: psdnmCheckBox; checked: true; enabled: false }
}
}
} }

View file

@ -28,6 +28,8 @@ ApplicationWindow
width: 400 width: 400
height: 400 height: 400
property string activeChatId;
Rectangle Rectangle
{ {
id: mainView id: mainView
@ -102,8 +104,13 @@ ApplicationWindow
Tab Tab
{ {
title: "Blue" title: "Chat"
Rectangle { color: "blue"; anchors.fill: parent } ChatView
{
id: chatView
chatId: mainWindow.activeChatId
onVisibleChanged: focus = visible
}
} }
} }
} }

View file

@ -4,13 +4,14 @@ QT += qml quick
CONFIG += c++11 CONFIG += c++11
HEADERS += libresapilocalclient.h
SOURCES += main.cpp \ SOURCES += main.cpp \
libresapilocalclient.cpp libresapilocalclient.cpp
RESOURCES += qml.qrc RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model # Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH = #QML_IMPORT_PATH =
# Default rules for deployment. # Default rules for deployment.
include(deployment.pri) include(deployment.pri)
@ -30,7 +31,3 @@ DEPENDPATH *= ../../libretroshare/src
INCLUDEPATH *= ../../libretroshare/src INCLUDEPATH *= ../../libretroshare/src
PRE_TARGETDEPS *= ../../libretroshare/src/lib/libretroshare.a PRE_TARGETDEPS *= ../../libretroshare/src/lib/libretroshare.a
LIBS *= ../../libretroshare/src/lib/libretroshare.a LIBS *= ../../libretroshare/src/lib/libretroshare.a
HEADERS += \
libresapilocalclient.h \
debugutils.h