Retroshare QML App: Implemesh some basic stuff

Implement location creation, selection and login
Implement people listing
Implement firends adding (not working yet)
Depend on androidextra qt module only if compiling for android
LibresapiLocalClient parse one line at time to avoid error if two
requests are sent rapidly one after another
LibresapiLocalClient socket path now is a parameter of openConnection()
to use it as qml type constructor without parameter must be useful
Added JSONListModel for JASON based MVC pattern
This commit is contained in:
Gio 2016-09-15 13:07:13 +02:00
parent ad21d7202a
commit 8d6d3d1894
15 changed files with 565 additions and 99 deletions

View File

@ -2,7 +2,7 @@
TARGET = retroshare-android-service
QT += core network androidextras
QT += core network
QT -= gui
CONFIG += c++11

View File

@ -18,7 +18,10 @@
#include <QCoreApplication>
#include <QDebug>
#include <QtAndroidExtras>
#ifdef __ANDROID__
# include <QtAndroidExtras>
#endif
#include "retroshare/rsinit.h"
#include "api/ApiServer.h"
@ -39,8 +42,10 @@ int main(int argc, char *argv[])
qDebug() << "Listening on:" << sockPath;
ApiServerLocal apiServerLocal(&api, sockPath); (void) apiServerLocal;
#ifdef __ANDROID__
qDebug() << "Is service.cpp running as a service?" << QtAndroid::androidService().isValid();
qDebug() << "Is service.cpp running as an activity?" << QtAndroid::androidActivity().isValid();
#endif
while (!ctrl_mod.processShouldExit())
{

View File

@ -1,35 +1,39 @@
/*
* RetroShare Android QML App
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2016 Manu Pineda <manu@cooperativa.cat>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "libresapilocalclient.h"
#include "debugutils.h"
#include <QChar>
/* Constructor de còpia per proves, no s'ha d'usar.
LibresapiLocalClient::LibresapiLocalClient(const LibresapiLocalClient & l)
{
//mLocalSocket = l.mLocalSocket;
receivedBytes = l.receivedBytes;
json = l.json;
}*/
LibresapiLocalClient::LibresapiLocalClient(const QString & socketPath) :
mLocalSocket(this)
void LibresapiLocalClient::openConnection(QString socketPath)
{
myDebug(this);
mSocketPath = socketPath;
connect(& mLocalSocket, SIGNAL(error(QLocalSocket::LocalSocketError)),
this, SLOT(socketError(QLocalSocket::LocalSocketError)));
connect(& mLocalSocket, SIGNAL(readyRead()),
this, SLOT(read()));
//openConnection();
}
void LibresapiLocalClient::openConnection()
{
mLocalSocket.connectToServer(mSocketPath);
connect(& mLocalSocket, SIGNAL(error(QLocalSocket::LocalSocketError)),
this, SLOT(socketError(QLocalSocket::LocalSocketError)));
connect(& mLocalSocket, SIGNAL(readyRead()),
this, SLOT(read()));
mLocalSocket.connectToServer(socketPath);
}
int LibresapiLocalClient::request(const QString & path, const QString & jsonData)
{
qDebug() << "LibresapiLocalClient::request()" << path << jsonData;
QByteArray data;
data.append(path); data.append('\n');
data.append(jsonData); data.append('\n');
@ -38,25 +42,24 @@ int LibresapiLocalClient::request(const QString & path, const QString & jsonData
return 1;
}
void LibresapiLocalClient::socketError(QLocalSocket::LocalSocketError error)
void LibresapiLocalClient::socketError(QLocalSocket::LocalSocketError)
{
myDebug("error!!!!\n" + mLocalSocket.errorString());//error.errorString());
myDebug("error!!!!\n" + mLocalSocket.errorString());
}
void LibresapiLocalClient::read()
{
receivedBytes = mLocalSocket.readAll();
if(parseResponse()){ // pensar en fer un buffer per parsejar, per evitar errors.
emit goodResponseReceived(QString(receivedBytes));
return;
}
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));
receivedBytes = mLocalSocket.readLine();
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"
"It should be a JSON formatted text file\n"
"Its contents were:\n" + receivedBytes;
myDebug(errMess.replace(QChar('\n'), QChar::LineSeparator));
}
}
bool LibresapiLocalClient::parseResponse()

View File

@ -1,5 +1,6 @@
/*
* libresapi local socket client
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2016 Manu Pineda <manu@cooperativa.cat>
*
* This program is free software: you can redistribute it and/or modify
@ -26,39 +27,30 @@
class LibresapiLocalClient : public QObject
{
Q_OBJECT
Q_OBJECT
public:
public:
LibresapiLocalClient() : mLocalSocket(this) {}
LibresapiLocalClient() {}
// LibresapiLocalClient(const LibresapiLocalClient & l);
LibresapiLocalClient(const QString & socketPath);
// 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 void openConnection();
// 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 void openConnection(QString socketPath);
private:
private:
QLocalSocket mLocalSocket;
QByteArray receivedBytes;
QJsonDocument json;
//QVector<QJsonDocument> responses;
QString mSocketPath;
QLocalSocket mLocalSocket;
QByteArray receivedBytes;
QJsonDocument json;
//QVector<QJsonDocument> responses;
bool parseResponse(); //std::string msg);
bool parseResponse(); //std::string msg);
private slots:
void socketError(QLocalSocket::LocalSocketError error);
void read();
signals:
void goodResponseReceived(const QString & msg);//, int requestId);
public slots:
private slots:
void socketError(QLocalSocket::LocalSocketError error);
void read();
signals:
void goodResponseReceived(const QString & msg);//, int requestId);
};
#endif // LIBRESAPILOCALCLIENT_H

View File

@ -22,7 +22,10 @@
#include <QQmlComponent>
#include <QDebug>
#include <QtAndroidExtras>
#ifdef __ANDROID__
# include <QtAndroidExtras>
#endif
#include <QFileInfo>
#include <QDateTime>
@ -35,20 +38,28 @@ int main(int argc, char *argv[])
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<LibresapiLocalClient>(
"org.retroshare.qml_components.LibresapiLocalClient", 1, 0,
"LibresapiLocalClient");
QString sockPath = QString::fromStdString(RsAccounts::ConfigDirectory());
sockPath.append("/libresapi.sock");
LibresapiLocalClient llc(sockPath);
qmlRegisterType<LibresapiLocalClient>("LibresapiLocalClientQml", 1, 0, "LibresapiLocalClientComm");
engine.rootContext()->setContextProperty("llc", &llc);
engine.load(QUrl(QLatin1String("qrc:/qml/main.qml")));
#ifndef __ANDROID__
sockPath = "/home/gio/.retroshare/LOC06_8730499b55bb946424d537b180bee10a/libresapi.sock";
#endif
QFileInfo fileInfo(sockPath);
engine.rootContext()->setContextProperty("apiSocketPath", sockPath);
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();
qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString();
#endif
qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString();
return app.exec();
}

View File

@ -15,6 +15,10 @@
<file>qml/ChannelGroupDelegate.qml</file>
<file>qml/ApplicationBar.qml</file>
<file>qml/AppButton.qml</file>
<file>qml/LibresapiLocalClientComm.qml</file>
<file>qml/Locations.qml</file>
<file>qml/jsonpath.js</file>
<file>qml/JSONListModel.qml</file>
<file>qml/Contacts.qml</file>
<file>qml/AddTrustedNode.qml</file>
</qresource>
</RCC>

View File

@ -0,0 +1,39 @@
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import org.retroshare.qml_components.LibresapiLocalClient 1.0
Item
{
Component.onCompleted:
{
rsApi.openConnection(apiSocketPath)
rsApi.request("/peers/self/certificate/", "")
}
LibresapiLocalClient
{
id: rsApi
onGoodResponseReceived: myKeyField.text = JSON.parse(msg).data.cert_string
}
ColumnLayout
{
anchors.top: parent.top
anchors.bottom: bottomButton.top
TextField { id: myKeyField }
TextField { id: otherKeyField }
}
Button
{
id: bottomButton
text: "Add trusted node"
anchors.bottom: parent.bottom
onClicked:
{
rsApi.request("/peers/examine_cert/", JSON.stringify({ cert_string: otherKeyField.text }))
}
}
}

View File

@ -0,0 +1,56 @@
/*
* RetroShare Android QML App
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 1.4
import org.retroshare.qml_components.LibresapiLocalClient 1.0
Item
{
function refreshData()
{
rsApi.openConnection(apiSocketPath)
rsApi.request("/identity/*/", "")
}
Component.onCompleted: refreshData()
onFocusChanged: focus && refreshData()
LibresapiLocalClient
{
id: rsApi
onGoodResponseReceived: locationsModel.json = msg
}
JSONListModel
{
id: locationsModel
query: "$.data[*]"
}
ListView
{
id: locationsListView
width: parent.width
height: 300
model: locationsModel.model
delegate: Text { text: model.name }
}
Text { text: "Contacts View"; anchors.bottom: parent.bottom }
}

View File

@ -0,0 +1,51 @@
/* JSONListModel - a QML ListModel with JSON and JSONPath support
*
* Copyright (c) 2012 Romain Pokrzywka (KDAB) (romain@kdab.com)
* Licensed under the MIT licence (http://opensource.org/licenses/mit-license.php)
*/
import QtQuick 2.0
import "jsonpath.js" as JSONPath
Item {
property string source: ""
property string json: ""
property string query: ""
property ListModel model : ListModel { id: jsonModel }
property alias count: jsonModel.count
onSourceChanged: {
var xhr = new XMLHttpRequest;
xhr.open("GET", source);
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE)
json = xhr.responseText;
}
xhr.send();
}
onJsonChanged: updateJSONModel()
onQueryChanged: updateJSONModel()
function updateJSONModel() {
jsonModel.clear();
if ( json === "" )
return;
var objectArray = parseJSONString(json, query);
for ( var key in objectArray ) {
var jo = objectArray[key];
jsonModel.append( jo );
}
}
function parseJSONString(jsonString, jsonPathQuery) {
var objectArray = JSON.parse(jsonString);
if ( jsonPathQuery !== "" )
objectArray = JSONPath.jsonPath(objectArray, jsonPathQuery);
return objectArray;
}
}

View File

@ -1,5 +0,0 @@
import LibresapiLocalClientQml 1.0
LibresapiLocalClientComm {
id: llc
}

View File

@ -0,0 +1,152 @@
/*
* RetroShare Android QML App
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import org.retroshare.qml_components.LibresapiLocalClient 1.0
Item
{
id: locationView
state: "selectLocation"
states:
[
State
{
name: "selectLocation"
PropertyChanges { target: locationsListView; visible: true }
PropertyChanges { target: createLocationView; visible: false }
PropertyChanges
{
target: bottomButton
text: "Create new location"
onClicked: locationView.state = "createLocation"
}
},
State
{
name: "createLocation"
PropertyChanges { target: locationsListView; visible: false }
PropertyChanges { target: createLocationView; visible: true }
PropertyChanges
{
target: bottomButton
text: "Save"
onClicked:
{
var jsonData = { pgp_name: nameField.text, ssl_name: nameField.text, pgp_password: passwordField.text }
rsApi.request("/control/create_location/", JSON.stringify(jsonData))
onClicked: locationView.state = "savingLocation"
}
}
},
State
{
name: "savingLocation"
PropertyChanges { target: locationsListView; visible: false }
PropertyChanges { target: createLocationView; color: "grey" }
PropertyChanges
{
target: bottomButton
text: "Saving..."
enabled: false
}
},
State
{
name: "loggingIn"
PropertyChanges { target: locationsListView; visible: false }
PropertyChanges { target: createLocationView; visible: true }
PropertyChanges { target: nameField; enabled: false}
PropertyChanges
{
target: bottomButton
text: "Login"
enabled: true
onClicked:
{
var jsonData = { id: nameField.sslid, autologin: false }
rsApi.request("/control/login/", JSON.stringify(jsonData))
jsonData = { password: passwordField.text }
rsApi.request("/control/password/", JSON.stringify(jsonData))
}
}
}
]
Component.onCompleted:
{
rsApi.openConnection(apiSocketPath)
rsApi.request("/control/locations/", "")
}
LibresapiLocalClient
{
id: rsApi
onGoodResponseReceived: locationsModel.json = msg
}
JSONListModel
{
id: locationsModel
query: "$.data[*]"
}
ListView
{
id: locationsListView
width: parent.width
anchors.top: parent.top
anchors.bottom: bottomButton.top
model: locationsModel.model
delegate: Button
{
text: model.name
property string sslid: model.id
onClicked:
{
locationView.state = "loggingIn"
nameField.text = text
}
}
visible: false
}
ColumnLayout
{
id: createLocationView
width: parent.width
anchors.top: parent.top
anchors.bottom: bottomButton.top
visible: false
Row { Text {text: "Name:" } TextField { id: nameField; property string sslid } }
Row { Text {text: "Password:" } TextField { id: passwordField; echoMode: PasswordEchoOnEdit } }
}
Text { text: "Locations View"; anchors.bottom: bottomButton.top }
Button
{
id: bottomButton
text: "Create new location"
anchors.bottom: parent.bottom
}
}

View File

@ -0,0 +1,88 @@
/* JSONPath 0.8.5 - XPath for JSON
*
* Copyright (c) 2007 Stefan Goessner (goessner.net)
* Licensed under the MIT (MIT-LICENSE.txt) licence.
*
*/
function jsonPath(obj, expr, arg) {
var P = {
resultType: arg && arg.resultType || "VALUE",
result: [],
normalize: function(expr) {
var subx = [];
return expr.replace(/[\['](\??\(.*?\))[\]']|\['(.*?)'\]/g, function($0,$1,$2){return "[#"+(subx.push($1||$2)-1)+"]";}) /* http://code.google.com/p/jsonpath/issues/detail?id=4 */
.replace(/'?\.'?|\['?/g, ";")
.replace(/;;;|;;/g, ";..;")
.replace(/;$|'?\]|'$/g, "")
.replace(/#([0-9]+)/g, function($0,$1){return subx[$1];});
},
asPath: function(path) {
var x = path.split(";"), p = "$";
for (var i=1,n=x.length; i<n; i++)
p += /^[0-9*]+$/.test(x[i]) ? ("["+x[i]+"]") : ("['"+x[i]+"']");
return p;
},
store: function(p, v) {
if (p) P.result[P.result.length] = P.resultType == "PATH" ? P.asPath(p) : v;
return !!p;
},
trace: function(expr, val, path) {
if (expr !== "") {
var x = expr.split(";"), loc = x.shift();
x = x.join(";");
if (val && val.hasOwnProperty(loc))
P.trace(x, val[loc], path + ";" + loc);
else if (loc === "*")
P.walk(loc, x, val, path, function(m,l,x,v,p) { P.trace(m+";"+x,v,p); });
else if (loc === "..") {
P.trace(x, val, path);
P.walk(loc, x, val, path, function(m,l,x,v,p) { typeof v[m] === "object" && P.trace("..;"+x,v[m],p+";"+m); });
}
else if (/^\(.*?\)$/.test(loc)) // [(expr)]
P.trace(P.eval(loc, val, path.substr(path.lastIndexOf(";")+1))+";"+x, val, path);
else if (/^\?\(.*?\)$/.test(loc)) // [?(expr)]
P.walk(loc, x, val, path, function(m,l,x,v,p) { if (P.eval(l.replace(/^\?\((.*?)\)$/,"$1"), v instanceof Array ? v[m] : v, m)) P.trace(m+";"+x,v,p); }); // issue 5 resolved
else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) // [start:end:step] phyton slice syntax
P.slice(loc, x, val, path);
else if (/,/.test(loc)) { // [name1,name2,...]
for (var s=loc.split(/'?,'?/),i=0,n=s.length; i<n; i++)
P.trace(s[i]+";"+x, val, path);
}
}
else
P.store(path, val);
},
walk: function(loc, expr, val, path, f) {
if (val instanceof Array) {
for (var i=0,n=val.length; i<n; i++)
if (i in val)
f(i,loc,expr,val,path);
}
else if (typeof val === "object") {
for (var m in val)
if (val.hasOwnProperty(m))
f(m,loc,expr,val,path);
}
},
slice: function(loc, expr, val, path) {
if (val instanceof Array) {
var len=val.length, start=0, end=len, step=1;
loc.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g, function($0,$1,$2,$3){start=parseInt($1||start);end=parseInt($2||end);step=parseInt($3||step);});
start = (start < 0) ? Math.max(0,start+len) : Math.min(len,start);
end = (end < 0) ? Math.max(0,end+len) : Math.min(len,end);
for (var i=start; i<end; i+=step)
P.trace(i+";"+expr, val, path);
}
},
eval: function(x, _v, _vname) {
try { return $ && _v && eval(x.replace(/(^|[^\\])@/g, "$1_v").replace(/\\@/g, "@")); } // issue 7 : resolved ..
catch(e) { throw new SyntaxError("jsonPath: " + e.message + ": " + x.replace(/(^|[^\\])@/g, "$1_v").replace(/\\@/g, "@")); } // issue 7 : resolved ..
}
};
var $ = obj;
if (expr && obj && (P.resultType == "VALUE" || P.resultType == "PATH")) {
P.trace(P.normalize(expr).replace(/^\$;?/,""), obj, "$"); // issue 6 resolved
return P.result.length ? P.result : false;
}
}

View File

@ -1,27 +1,98 @@
//import QtQuick 2.7 //2.2
//import QtQuick.Layouts 1.0 //1.1
//import QtQuick.Controls 2.0 //1.1
import "."
/*
* RetroShare Android QML App
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1 // millor fer servir 2.0 o més
import LibresapiLocalClientQml 1.0
import QtQuick.Controls 2.0
import org.retroshare.qml_components.LibresapiLocalClient 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("RSChat")
ApplicationWindow
{
id: mainWindow
visible: true
title: qsTr("RSChat")
Rectangle
{
id: mainView
anchors.fill: parent;
states:
[
State
{
name: "waiting_account_select";
PropertyChanges { target: swipeView; currentIndex: 1 }
},
State
{
name: "running_ok"
PropertyChanges { target: swipeView; currentIndex: 2 }
},
State
{
name: "running_ok_no_full_control"
PropertyChanges { target: swipeView; currentIndex: 2 }
}
]
LibresapiLocalClient
{
onGoodResponseReceived:
{
var jsonReponse = JSON.parse(msg)
mainView.state = jsonReponse.data.runstate
}
Component.onCompleted:
{
openConnection(apiSocketPath)
request("/control/runstate/", "")
}
}
SwipeView
{
id: swipeView
anchors.fill: parent
visible: true
currentIndex: 1
Locations
{
id: locationsView
visible: true
}
AddTrustedNode
{
id: addTrustedNodeView
visible: true
}
Contacts
{
id: contactsView
visible: true
}
}
}
/*
LibresapiLocalClientComm{
id: llc
onGoodResponseReceived: gxss.title = msg
}*/
onSceneGraphInitialized: llc.openConnection()
onSceneGraphInitialized: llc.openConnection()
Rectangle {
id: page
@ -154,7 +225,5 @@ ApplicationWindow {
}
}
}
*/
}

View File

@ -1,6 +1,6 @@
!include("../../retroshare.pri"): error("Could not include file ../../retroshare.pri")
QT += qml quick androidextras
QT += qml quick
CONFIG += c++11

View File

@ -52,6 +52,7 @@ unix {
android-g++ {
CONFIG *= no_libresapihttpserver no_sqlcipher upnp_libupnp
CONFIG -= libresapihttpserver sqlcipher upnp_miniupnpc
QT *= androidextras
DEFINES *= "fopen64=fopen"
DEFINES *= "fseeko64=fseeko"
DEFINES *= "ftello64=ftello"