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();
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
resp.mDataStream.getStreamToMember();
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.mStateToken = mLobbiesStateToken;
}
resp.setOk();
}
@ -921,6 +923,12 @@ ResponseTask* ChatHandler::handleLobbyParticipants(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 **********/
ChatId id(req.mPath.top());
// make response a list
@ -941,6 +949,7 @@ void ChatHandler::handleMessages(Request &req, Response &resp)
}
resp.mStateToken = mMsgStateToken;
handlePaginationRequest(req, resp, mit->second);
}
}
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())
{
#ifdef REMOVE
RsIdentityParameters params;
req.mStream << makeKeyValueReference("name", params.nickname);
if(req.mStream.isOK())
{
uint32_t token;
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);
{
RS_STACK_MUTEX(mMtx);
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);
while((mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
&&(mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_FAILED)
&&((time(NULL) < (start+10)))
)
{
time_t start = time(NULL);
while((mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
&&(mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_FAILED)
&&((time(NULL) < (start+10)))
)
{
#ifdef WINDOWS_SYS
Sleep(500);
Sleep(500);
#else
usleep(500*1000) ;
usleep(500*1000);
#endif
}
}
if(mRsIdentity->getTokenService()->requestStatus(token) == RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
{
std::vector<RsGxsIdGroup> grps;
ok &= mRsIdentity->getGroupData(token, grps);
for(std::vector<RsGxsIdGroup>::iterator vit = grps.begin(); vit != grps.end(); vit++)
{
RsGxsIdGroup& grp = *vit;
KeyValueReference<RsGxsGroupId> id("id", grp.mMeta.mGroupId);
KeyValueReference<RsPgpId> pgp_id("pgp_id",grp.mPgpId );
// not very happy about this, i think the flags should stay hidden in rsidentities
bool own = (grp.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_ADMIN);
bool pgp_linked = (grp.mMeta.mGroupFlags & RSGXSID_GROUPFLAG_REALID);
resp.mDataStream.getStreamToMember()
<< id
<< pgp_id
<< makeKeyValueReference("name", grp.mMeta.mGroupName)
<< makeKeyValueReference("own", own)
<< makeKeyValueReference("pgp_linked", pgp_linked);
}
}
else
{
ok = false;
}
}
if(mRsIdentity->getTokenService()->requestStatus(token) == RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
{
std::vector<RsGxsIdGroup> grps;
ok &= mRsIdentity->getGroupData(token, grps);
for(std::vector<RsGxsIdGroup>::iterator vit = grps.begin(); vit != grps.end(); vit++)
{
RsGxsIdGroup& grp = *vit;
//electron: not very happy about this, i think the flags should stay hidden in rsidentities
bool own = (grp.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_ADMIN);
bool pgp_linked = (grp.mMeta.mGroupFlags & RSGXSID_GROUPFLAG_REALID);
resp.mDataStream.getStreamToMember()
<< makeKeyValueReference("id", grp.mMeta.mGroupId) /// @deprecated using "id" as key can cause problems in some JS based languages like Qml @see gxs_id instead
<< makeKeyValueReference("gxs_id", grp.mMeta.mGroupId)
<< makeKeyValueReference("pgp_id",grp.mPgpId )
<< makeKeyValueReference("name", grp.mMeta.mGroupName)
<< makeKeyValueReference("own", own)
<< makeKeyValueReference("pgp_linked", pgp_linked);
}
}
else ok = false;
if(ok)
{
resp.setOk();
}
else
{
resp.setFail();
}
if(ok) resp.setOk();
else resp.setFail();
}
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)
{
#if defined(WINDOWS_SYS) || defined(__APPLE__)
#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'
#if defined(WINDOWS_SYS) || defined(__APPLE__) || defined(__ANDROID__)
#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,...) ;
return path ;
#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 "debugutils.h"
#include <QChar>
#include <QJSEngine>
void LibresapiLocalClient::openConnection(QString socketPath)
@ -31,54 +31,35 @@ void LibresapiLocalClient::openConnection(QString 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;
data.append(path); data.append('\n');
data.append(jsonData); data.append('\n');
mLocalSocket.write(data);
QByteArray data;
data.append(path); data.append('\n');
data.append(jsonData); data.append('\n');
callbackQueue.enqueue(callback);
mLocalSocket.write(data);
return 1;
return 1;
}
void LibresapiLocalClient::socketError(QLocalSocket::LocalSocketError)
{
myDebug("error!!!!\n" + mLocalSocket.errorString());
qDebug() << "Socket Eerror!!" << mLocalSocket.errorString();
}
void LibresapiLocalClient::read()
{
receivedBytes = mLocalSocket.readLine();
qDebug() << receivedBytes;
if(parseResponse()) // pensar en fer un buffer per parsejar, per evitar errors.
emit goodResponseReceived(QString(receivedBytes));
else
QString receivedMsg(mLocalSocket.readLine());
QJSValue callback(callbackQueue.dequeue());
if(callback.isCallable())
{
QString errMess = "The message was not understood!\n"
"It should be a JSON formatted text file\n"
"Its contents were:\n" + receivedBytes;
myDebug(errMess.replace(QChar('\n'), QChar::LineSeparator));
QJSValue params = callback.engine()->newObject();
params.setProperty("response", receivedMsg);
callback.call(QJSValueList { params });
}
}
bool LibresapiLocalClient::parseResponse()
{
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;
emit goodResponseReceived(receivedMsg); /// @deprecated
emit responseReceived(receivedMsg);
}

View File

@ -21,9 +21,8 @@
#define LIBRESAPILOCALCLIENT_H
#include <QLocalSocket>
#include <QDir>
#include <QJsonDocument>
#include <QVector>
#include <QQueue>
#include <QJSValue>
class LibresapiLocalClient : public QObject
{
@ -32,25 +31,27 @@ class LibresapiLocalClient : public QObject
public:
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);
const QJsonDocument & getJson();
Q_INVOKABLE int request( const QString& path, const QString& jsonData = "",
QJSValue callback = QJSValue::NullValue);
Q_INVOKABLE void openConnection(QString socketPath);
private:
QLocalSocket mLocalSocket;
QByteArray receivedBytes;
QJsonDocument json;
//QVector<QJsonDocument> responses;
bool parseResponse(); //std::string msg);
QQueue<QJSValue> callbackQueue;
private slots:
void socketError(QLocalSocket::LocalSocketError error);
void read();
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

View File

@ -34,28 +34,28 @@
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlApplicationEngine engine;
/// @deprecated
qmlRegisterType<LibresapiLocalClient>(
"org.retroshare.qml_components.LibresapiLocalClient", 1, 0,
"LibresapiLocalClient");
QString sockPath = QString::fromStdString(RsAccounts::ConfigDirectory());
sockPath.append("/libresapi.sock");
QString sockPath = QString::fromStdString(RsAccounts::ConfigDirectory());
sockPath.append("/libresapi.sock");
LibresapiLocalClient rsApi;
rsApi.openConnection(sockPath);
engine.rootContext()->setContextProperty("apiSocketPath", sockPath);
engine.rootContext()->setContextProperty("rsApi", &rsApi);
engine.load(QUrl(QLatin1String("qrc:/qml/main.qml")));
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();
return app.exec();
return app.exec();
}

View File

@ -22,5 +22,6 @@
<file>qml/AddTrustedNode.qml</file>
<file>qml/RsLoginPassView.qml</file>
<file>qml/TrustedNodesView.qml</file>
<file>qml/ChatView.qml</file>
</qresource>
</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.Controls 1.4
import QtQuick.Dialogs 1.2
import org.retroshare.qml_components.LibresapiLocalClient 1.0
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()
LibresapiLocalClient
{
id: rsApi
onGoodResponseReceived: locationsModel.json = msg
Component.onCompleted: { openConnection(apiSocketPath) }
}
JSONListModel
{
id: locationsModel
@ -45,8 +58,73 @@ Item
width: parent.width
height: 300
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
height: 400
property string activeChatId;
Rectangle
{
id: mainView
@ -102,8 +104,13 @@ ApplicationWindow
Tab
{
title: "Blue"
Rectangle { color: "blue"; anchors.fill: parent }
title: "Chat"
ChatView
{
id: chatView
chatId: mainWindow.activeChatId
onVisibleChanged: focus = visible
}
}
}
}

View File

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