Merge pull request #863 from G10h4ck/qmlapp_pex_alpha
Multiple improvements to android qml app and libreaspilocal
@ -3,14 +3,28 @@ Compile Retroshare for Android
|
||||
|
||||
== Introduction
|
||||
|
||||
Compiling an application for Android is not as easy as one would imagine, expecially one like RetroShare that has a big codebase and is not well documented. This document is aimed to empower the reader so she can hopefully succed or at least have a significant help in compiling her own RetroShare APK package installable on Android.
|
||||
Compiling an application for Android is not as easy as one would imagine,
|
||||
expecially one like RetroShare that has a big codebase and is not well
|
||||
documented.
|
||||
This document is aimed to empower the reader so she can hopefully succed or at
|
||||
least have a significant help in compiling her own RetroShare APK package
|
||||
installable on Android.
|
||||
|
||||
== Preparing The Environement
|
||||
|
||||
First of all setup your Qt for Android development environement following the guide on the link:http://doc.qt.io/qt-5/androidgs.html[Qt for android web site].
|
||||
At this point you should have Android SDK, Android NDK, and Qt for Android working fine, and you should be capable of executing on an Android emulator or on your Android phone Qt for Android examples.
|
||||
First of all setup your Qt for Android development environement following the
|
||||
guide on the link:http://doc.qt.io/qt-5/androidgs.html[Qt for android web site].
|
||||
At this point you should have Android SDK, Android NDK, and Qt for Android
|
||||
working fine, and you should be capable of executing on an Android emulator or
|
||||
on your Android phone Qt for Android examples.
|
||||
|
||||
But RetroShare is not as simple to compile as those examples. In particular, the Android NDK precompiled toolchain is limited and doesn't support the full C++ specification, and it is missing some part that is needed to build RetroShare. The good news is that Android NDK ships all the necessary to build a custom toolchain that is suitable to build RetroShare. In order to build the toolchain with needed library RetroShare provides the +android-prepare-toolchain.sh+ script; before you execute it you should define some variables the script cannot determine in an easy and reliable manner by itself in your terminal.
|
||||
But RetroShare is not as simple to compile as those examples. The good news is
|
||||
that Android NDK ships all the necessary to build a custom toolchain that is
|
||||
suitable to build RetroShare.
|
||||
In order to build the toolchain with needed library RetroShare provides the
|
||||
+android-prepare-toolchain.sh+ script; before you execute it you should define
|
||||
some variables the script cannot determine in an easy and reliable manner by
|
||||
itself in your terminal.
|
||||
|
||||
[source,bash]
|
||||
-------------------------------------------------------------------------------
|
||||
@ -27,56 +41,56 @@ export ANDROID_NDK_ARCH="arm"
|
||||
## The Android API level the Android device you want to target
|
||||
export ANDROID_PLATFORM_VER="19"
|
||||
|
||||
## The number of core that yout host CPU have (just to speed up compilation) set it to 1 if unsure
|
||||
## The number of core that yout host CPU have (just to speed up compilation) set
|
||||
## it to 1 if unsure
|
||||
export HOST_NUM_CPU=1
|
||||
|
||||
./android-prepare-toolchain.sh
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Now is time for the bad news: as of today Qt assumes you use the NDK precompiled toolchain and doesn't have an option to use the custom toolchain you just generated, so you need to tweak Qt internals a little to make usage of the custom toolchain. First of all you need to determine your Qt for Android installation path -in my case it is +/opt/Qt5.7.0/+ - then find the +mkspecs+ directory -in my case it is +/opt/Qt5.7.0/5.7/android_armv7/mkspecs+ - and then modify qmake configuration for the android target according to your toolchain, in my case I have done it with the following commands (in the same shell I used before, to take advantage of the already set variables):
|
||||
|
||||
WARNING: This may need a slight modification if you have a different Qt version.
|
||||
|
||||
[source,bash]
|
||||
-------------------------------------------------------------------------------
|
||||
export QT_PATH="/opt/Qt5.7.0/"
|
||||
export QT_MAJOR_VERSION="5.7"
|
||||
|
||||
cd ${QT_PATH}/${QT_MAJOR_VERSION}/android_armv7/mkspecs
|
||||
sudo --preserve-env -s
|
||||
cp -r android-g++ android-g++-backup
|
||||
cd android-g++
|
||||
sed -i "s|^NDK_TOOLCHAIN_PATH =.*|NDK_TOOLCHAIN_PATH = ${NDK_TOOLCHAIN_PATH}|" qmake.conf
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
== Preparing Qt Creator
|
||||
|
||||
Now that your environement is set up you should configure Qt Creator for Android following the link:http://doc.qt.io/qtcreator/creator-developing-android.html[official guide]. At the end of this step your Qt Creator should recognize the Android compiler and the Qt for Android kit. As we use a custom toolchain one more step is needed.
|
||||
Now that your environement is set up you should configure Qt Creator for Android
|
||||
following the
|
||||
link:http://doc.qt.io/qtcreator/creator-developing-android.html[official guide].
|
||||
At the end of this step your Qt Creator should recognize the Android compiler
|
||||
and the Qt for Android kit.
|
||||
|
||||
From the top menu click
|
||||
_Tools -> Options... -> Build & Run -> Compilers -> Android GCC (arm-4.9) -> Clone_
|
||||
Your Kit is now ready to use. Now you can open RetroShare as a Qt Creator
|
||||
project and in the Projects left menu add the newly created kit if not already
|
||||
present, so you can select it on the build type selection button down on the
|
||||
left.
|
||||
|
||||
Now a new compiler (usually named +Clone of Android GCC (arm-4.9)+) should have appeared on your compilers list. Select that compiler and press the Browse button to look for your custom toolchain compiler. You should find it at +$NDK_TOOLCHAIN_PATH/bin/arm-linux-androideabi-gcc+. Select it, then press Apply.
|
||||
As we use a custom toolchain one more step is needed +
|
||||
|
||||
Now go to the Kits tab, select +Android for armeabi-v7a (GCC 4.9, Qt 5.7.0)+ and press the Clone button. A new kit is created (usually named +Clone of Android for armeabi-v7a (GCC 4.9, Qt 5.7.0)+). Now select the new kit and change the compiler to the one you have just created.
|
||||
_Qt Creator left pane -> Projects -> Build and Run -> Android SOMESTUFF kit ->
|
||||
Build Environement -> Add
|
||||
|
||||
Your Kit is now ready to use. Now you can open RetroShare as a Qt Creator project and in the Projects left menu add the newly created kit if not already present, so you can select it on the build type selection button down on the left.
|
||||
Variable: +NATIVE_LIBS_TOOLCHAIN_PATH+
|
||||
Value: +Same value as NDK_TOOLCHAIN_PATH in Preparing The Environement step+
|
||||
|
||||
Some of RetroShare modules like +retroshare-gui+, +WebUI+ and +sqlcipher+ are not supported yet on Android so to be able to compile RetroShare without errors you will have to go to +
|
||||
Some of RetroShare modules like +retroshare-gui+ and +WebUI+ are not available
|
||||
on Android so to be able to compile RetroShare without errors you will have to
|
||||
go to +
|
||||
|
||||
_Qt Creator left pane -> Projects -> Build and Run -> Android SOMESTUFF kit -> Build Steps -> qmake -> Additional arguments_
|
||||
_Qt Creator left pane -> Projects -> Build and Run -> Android SOMESTUFF kit ->
|
||||
Build Steps -> qmake -> Additional arguments_
|
||||
|
||||
and add the following configurations
|
||||
|
||||
[source,makefile]
|
||||
-------------------------------------------------------------------------------
|
||||
CONFIG+=no_retroshare_gui CONFIG+=no_retroshare_nogui CONFIG+=no_retroshare_plugins CONFIG+=retroshare_android_service CONFIG+=libresapilocalserver CONFIG+=no_libresapihttpserver CONFIG+=no_sqlcipher CONFIG+=retroshare_qml_app
|
||||
CONFIG+=no_retroshare_gui CONFIG+=no_retroshare_nogui CONFIG+=no_retroshare_plugins CONFIG+=retroshare_android_service CONFIG+=libresapilocalserver CONFIG+=no_libresapihttpserver CONFIG+=retroshare_qml_app
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
WARNING: SQLCipher is not supported yet on RetroShare for Android. This poses a major security concern, we are working to fix this ASAP.
|
||||
WARNING: SQLCipher is not supported yet on RetroShare for Android. This poses a
|
||||
major security concern, we are working to fix this ASAP.
|
||||
|
||||
WARNING: Some versions of QtCreator try to find the Android SDK in +/opt/android/sdk+. A workaround to this is to make a symbolic link there pointing to your SDK installation path, like +mkdir -p /opt/android/sdk && ln -s /home/user/android-sdk-linux /opt/android/sdk+
|
||||
WARNING: Some versions of QtCreator try to find the Android SDK in
|
||||
+/opt/android/sdk+. A workaround to this is to make a symbolic link there
|
||||
pointing to your SDK installation path, like
|
||||
+mkdir -p /opt/android/sdk && ln -s /home/user/android-sdk-linux
|
||||
/opt/android/sdk+
|
||||
|
||||
== Quircks
|
||||
|
||||
|
@ -34,15 +34,26 @@ retroshare_nogui {
|
||||
retroshare_android_service {
|
||||
SUBDIRS += retroshare_android_service
|
||||
retroshare_android_service.file = retroshare-android-service/src/retroshare-android-service.pro
|
||||
retroshare_android_service.depends = libretroshare libresapi
|
||||
retroshare_android_service.depends = libresapi
|
||||
retroshare_android_service.target = retroshare_android_service
|
||||
}
|
||||
|
||||
retroshare_android_notify_service {
|
||||
SUBDIRS += retroshare_android_notify_service
|
||||
retroshare_android_notify_service.file = retroshare-android-notify-service/src/retroshare-android-notify-service.pro
|
||||
retroshare_android_notify_service.depends = retroshare_android_service
|
||||
retroshare_android_notify_service.target = retroshare_android_notify_service
|
||||
}
|
||||
|
||||
retroshare_qml_app {
|
||||
SUBDIRS += retroshare_qml_app
|
||||
retroshare_qml_app.file = retroshare-qml-app/src/retroshare-qml-app.pro
|
||||
retroshare_qml_app.depends = libretroshare retroshare_android_service
|
||||
retroshare_qml_app.depends = retroshare_android_service
|
||||
retroshare_qml_app.target = retroshare_qml_app
|
||||
|
||||
android-g++ {
|
||||
retroshare_qml_app.depends += retroshare_android_notify_service
|
||||
}
|
||||
}
|
||||
|
||||
retroshare_plugins {
|
||||
|
@ -105,9 +105,17 @@ readPath:
|
||||
resource_api::Request req(reqJson);
|
||||
req.mMethod = reqMeth;
|
||||
req.setPath(reqPath);
|
||||
std::string resultString = mApiServer->handleRequest(req);
|
||||
mLocalSocket->write(resultString.c_str(), resultString.length());
|
||||
|
||||
// Need this idiom because binary result may contains \0
|
||||
std::string&& resultString = mApiServer->handleRequest(req);
|
||||
QByteArray rB(resultString.data(), resultString.length());
|
||||
|
||||
// Dirty trick to support avatars answers
|
||||
if(rB.contains("\n") || !rB.startsWith("{") || !rB.endsWith("}"))
|
||||
mLocalSocket->write(rB.toBase64());
|
||||
else mLocalSocket->write(rB);
|
||||
mLocalSocket->write("\n\0");
|
||||
|
||||
mState = WAITING_PATH;
|
||||
|
||||
/* Because QLocalSocket is SOCK_STREAM some clients implementations
|
||||
|
@ -1,3 +1,22 @@
|
||||
/*
|
||||
* libresapi
|
||||
* Copyright (C) 2015 electron128 <electron128@yahoo.com>
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
#include "ChatHandler.h"
|
||||
#include "Pagination.h"
|
||||
#include "Operators.h"
|
||||
@ -936,7 +955,7 @@ 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
|
||||
* ChatHandler::handleLobbies, is this a work around or is the right way of
|
||||
* doing it? */
|
||||
tick();
|
||||
|
||||
@ -1132,24 +1151,28 @@ void ChatHandler::handleUnreadMsgs(Request &/*req*/, Response &resp)
|
||||
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
|
||||
|
||||
resp.mDataStream.getStreamToMember();
|
||||
for(std::map<ChatId, std::list<Msg> >::const_iterator mit = mMsgs.begin(); mit != mMsgs.end(); ++mit)
|
||||
for( std::map<ChatId, std::list<Msg> >::const_iterator mit = mMsgs.begin();
|
||||
mit != mMsgs.end(); ++mit )
|
||||
{
|
||||
uint32_t count = 0;
|
||||
for(std::list<Msg>::const_iterator lit = mit->second.begin(); lit != mit->second.end(); ++lit)
|
||||
if(!lit->read)
|
||||
count++;
|
||||
for( std::list<Msg>::const_iterator lit = mit->second.begin();
|
||||
lit != mit->second.end(); ++lit ) if(!lit->read) ++count;
|
||||
std::map<ChatId, ChatInfo>::iterator mit2 = mChatInfo.find(mit->first);
|
||||
if(mit2 == mChatInfo.end())
|
||||
std::cerr << "Error in ChatHandler::handleUnreadMsgs(): ChatInfo not found. It is weird if this happens. Normally it should not happen." << std::endl;
|
||||
if(count && (mit2 != mChatInfo.end()))
|
||||
{
|
||||
resp.mDataStream.getStreamToMember()
|
||||
<< makeKeyValue("id", mit->first.toStdString())
|
||||
#warning @deprecated using "id" as key can cause problems in some JS based \
|
||||
languages like Qml @see chat_id instead
|
||||
<< makeKeyValue("id", mit->first.toStdString())
|
||||
<< makeKeyValue("chat_id", mit->first.toStdString())
|
||||
<< makeKeyValueReference("unread_count", count)
|
||||
<< mit2->second;
|
||||
}
|
||||
}
|
||||
resp.mStateToken = mUnreadMsgsStateToken;
|
||||
resp.setOk();
|
||||
}
|
||||
|
||||
void ChatHandler::handleInitiateDistantChatConnexion(Request& req, Response& resp)
|
||||
@ -1176,7 +1199,8 @@ void ChatHandler::handleInitiateDistantChatConnexion(Request& req, Response& res
|
||||
DistantChatPeerId distant_chat_id;
|
||||
uint32_t error_code;
|
||||
|
||||
if(mRsMsgs->initiateDistantChatConnexion(receiver_id, sender_id, distant_chat_id, error_code))
|
||||
if(mRsMsgs->initiateDistantChatConnexion(receiver_id, sender_id,
|
||||
distant_chat_id, error_code))
|
||||
resp.setOk();
|
||||
else resp.setFail("Failed to initiate distant chat");
|
||||
|
||||
|
@ -1,3 +1,23 @@
|
||||
/*
|
||||
* libresapi
|
||||
*
|
||||
* Copyright (C) 2015 electron128 <electron128@yahoo.com>
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
#include "IdentityHandler.h"
|
||||
|
||||
#include <retroshare/rsidentity.h>
|
||||
@ -62,7 +82,9 @@ protected:
|
||||
{
|
||||
case BEGIN:{
|
||||
RsIdentityParameters params;
|
||||
req.mStream << makeKeyValueReference("name", params.nickname) << makeKeyValueReference("pgp_linked", params.isPgpLinked);
|
||||
req.mStream
|
||||
<< makeKeyValueReference("name", params.nickname)
|
||||
<< makeKeyValueReference("pgp_linked", params.isPgpLinked);
|
||||
|
||||
if(params.nickname == "")
|
||||
{
|
||||
@ -127,17 +149,25 @@ private:
|
||||
RsGxsId mId;
|
||||
};
|
||||
|
||||
IdentityHandler::IdentityHandler(StateTokenServer *sts, RsNotify *notify, RsIdentity *identity):
|
||||
IdentityHandler::IdentityHandler(StateTokenServer *sts, RsNotify *notify,
|
||||
RsIdentity *identity) :
|
||||
mStateTokenServer(sts), mNotify(notify), mRsIdentity(identity),
|
||||
mMtx("IdentityHandler Mtx"), mStateToken(sts->getNewToken())
|
||||
{
|
||||
mNotify->registerNotifyClient(this);
|
||||
mNotify->registerNotifyClient(this);
|
||||
|
||||
addResourceHandler("*", this, &IdentityHandler::handleWildcard);
|
||||
addResourceHandler("own", this, &IdentityHandler::handleOwn);
|
||||
|
||||
addResourceHandler("own_ids", this, &IdentityHandler::handleOwnIdsRequest);
|
||||
addResourceHandler("notown_ids", this, &IdentityHandler::handleNotOwnIdsRequest);
|
||||
addResourceHandler("notown_ids", this,
|
||||
&IdentityHandler::handleNotOwnIdsRequest);
|
||||
|
||||
addResourceHandler("create_identity", this,
|
||||
&IdentityHandler::handleCreateIdentity);
|
||||
|
||||
addResourceHandler("export_key", this, &IdentityHandler::handleExportKey);
|
||||
addResourceHandler("import_key", this, &IdentityHandler::handleImportKey);
|
||||
|
||||
addResourceHandler("add_contact", this, &IdentityHandler::handleAddContact);
|
||||
addResourceHandler("remove_contact", this, &IdentityHandler::handleRemoveContact);
|
||||
@ -158,12 +188,10 @@ IdentityHandler::~IdentityHandler()
|
||||
|
||||
void IdentityHandler::notifyGxsChange(const RsGxsChanges &changes)
|
||||
{
|
||||
RS_STACK_MUTEX(mMtx); // ********** LOCKED **********
|
||||
// if changes come from identity service, invalidate own state token
|
||||
if(changes.mService == mRsIdentity->getTokenService())
|
||||
{
|
||||
mStateTokenServer->replaceToken(mStateToken);
|
||||
}
|
||||
RS_STACK_MUTEX(mMtx);
|
||||
// if changes come from identity service, invalidate own state token
|
||||
if(changes.mService == mRsIdentity->getTokenService())
|
||||
mStateTokenServer->replaceToken(mStateToken);
|
||||
}
|
||||
|
||||
void IdentityHandler::handleWildcard(Request & /*req*/, Response &resp)
|
||||
@ -177,39 +205,44 @@ void IdentityHandler::handleWildcard(Request & /*req*/, Response &resp)
|
||||
RsTokReqOptions opts;
|
||||
opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA;
|
||||
uint32_t token;
|
||||
mRsIdentity->getTokenService()->requestGroupInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts);
|
||||
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 timeout = time(NULL)+10;
|
||||
uint8_t rStatus = mRsIdentity->getTokenService()->requestStatus(token);
|
||||
while( rStatus != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE &&
|
||||
rStatus != RsTokenService::GXS_REQUEST_V2_STATUS_FAILED &&
|
||||
time(NULL) < timeout )
|
||||
{
|
||||
#ifdef WINDOWS_SYS
|
||||
Sleep(500);
|
||||
#else
|
||||
usleep(500*1000);
|
||||
#endif
|
||||
usleep(50*1000);
|
||||
rStatus = mRsIdentity->getTokenService()->requestStatus(token);
|
||||
}
|
||||
|
||||
if(mRsIdentity->getTokenService()->requestStatus(token) == RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
|
||||
if(rStatus == 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++)
|
||||
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_kept_for_compatibility ) ;
|
||||
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
|
||||
/* 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_kept_for_compatibility);
|
||||
resp.mDataStream.getStreamToMember()
|
||||
#warning @deprecated using "id" as key can cause problems in some JS based \
|
||||
languages like Qml @see gxs_id instead
|
||||
<< makeKeyValueReference("id", grp.mMeta.mGroupId)
|
||||
<< makeKeyValueReference("gxs_id", grp.mMeta.mGroupId)
|
||||
<< makeKeyValueReference("pgp_id",grp.mPgpId )
|
||||
<< makeKeyValueReference("name", grp.mMeta.mGroupName)
|
||||
<< makeKeyValueReference("contact", grp.mIsAContact)
|
||||
<< makeKeyValueReference("own", own)
|
||||
<< makeKeyValueReference("pgp_linked", pgp_linked);
|
||||
<< makeKeyValueReference("pgp_linked", pgp_linked)
|
||||
<< makeKeyValueReference("is_contact", grp.mIsAContact);
|
||||
}
|
||||
}
|
||||
else ok = false;
|
||||
@ -218,7 +251,6 @@ void IdentityHandler::handleWildcard(Request & /*req*/, Response &resp)
|
||||
else resp.setFail();
|
||||
}
|
||||
|
||||
|
||||
void IdentityHandler::handleNotOwnIdsRequest(Request & /*req*/, Response &resp)
|
||||
{
|
||||
bool ok = true;
|
||||
@ -230,38 +262,38 @@ void IdentityHandler::handleNotOwnIdsRequest(Request & /*req*/, Response &resp)
|
||||
RsTokReqOptions opts;
|
||||
opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA;
|
||||
uint32_t token;
|
||||
mRsIdentity->getTokenService()->requestGroupInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts);
|
||||
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 timeout = time(NULL)+10;
|
||||
uint8_t rStatus = mRsIdentity->getTokenService()->requestStatus(token);
|
||||
while( rStatus != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE &&
|
||||
rStatus != RsTokenService::GXS_REQUEST_V2_STATUS_FAILED &&
|
||||
time(NULL) < timeout )
|
||||
{
|
||||
#ifdef WINDOWS_SYS
|
||||
Sleep(500);
|
||||
#else
|
||||
usleep(500*1000);
|
||||
#endif
|
||||
usleep(50*1000);
|
||||
rStatus = mRsIdentity->getTokenService()->requestStatus(token);
|
||||
}
|
||||
|
||||
if(mRsIdentity->getTokenService()->requestStatus(token) == RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
|
||||
if(rStatus == 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++)
|
||||
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
|
||||
if(!(grp.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_ADMIN) && grp.mIsAContact)
|
||||
if(!(grp.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_ADMIN))
|
||||
{
|
||||
bool pgp_linked = (grp.mMeta.mGroupFlags & RSGXSID_GROUPFLAG_REALID_kept_for_compatibility ) ;
|
||||
bool pgp_linked = (
|
||||
grp.mMeta.mGroupFlags &
|
||||
RSGXSID_GROUPFLAG_REALID_kept_for_compatibility );
|
||||
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("pgp_linked", pgp_linked);
|
||||
<< makeKeyValueReference("pgp_linked", pgp_linked)
|
||||
<< makeKeyValueReference("is_contact", grp.mIsAContact);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -545,9 +577,51 @@ ResponseTask* IdentityHandler::handleCreateIdentity(Request & /* req */, Respons
|
||||
return new CreateIdentityTask(mRsIdentity);
|
||||
}
|
||||
|
||||
ResponseTask* IdentityHandler::handleDeleteIdentity(Request& /* req */, Response& /* resp */)
|
||||
void IdentityHandler::handleExportKey(Request& req, Response& resp)
|
||||
{
|
||||
return new DeleteIdentityTask(mRsIdentity);
|
||||
RsGxsId gxs_id;
|
||||
req.mStream << makeKeyValueReference("gxs_id", gxs_id);
|
||||
|
||||
std::string radix;
|
||||
time_t timeout = time(NULL)+2;
|
||||
bool found = mRsIdentity->serialiseIdentityToMemory(gxs_id, radix);
|
||||
while(!found && time(nullptr) < timeout)
|
||||
{
|
||||
usleep(5000);
|
||||
found = mRsIdentity->serialiseIdentityToMemory(gxs_id, radix);
|
||||
}
|
||||
|
||||
if(found)
|
||||
{
|
||||
resp.mDataStream << makeKeyValueReference("gxs_id", gxs_id)
|
||||
<< makeKeyValueReference("radix", radix);
|
||||
|
||||
resp.setOk();
|
||||
return;
|
||||
}
|
||||
|
||||
resp.setFail();
|
||||
}
|
||||
|
||||
void IdentityHandler::handleImportKey(Request& req, Response& resp)
|
||||
{
|
||||
std::string radix;
|
||||
req.mStream << makeKeyValueReference("radix", radix);
|
||||
|
||||
RsGxsId gxs_id;
|
||||
if(mRsIdentity->deserialiseIdentityFromMemory(radix, &gxs_id))
|
||||
{
|
||||
resp.mDataStream << makeKeyValueReference("gxs_id", gxs_id)
|
||||
<< makeKeyValueReference("radix", radix);
|
||||
resp.setOk();
|
||||
return;
|
||||
}
|
||||
|
||||
resp.setFail();
|
||||
}
|
||||
|
||||
ResponseTask* IdentityHandler::handleDeleteIdentity(Request& req,
|
||||
Response& resp)
|
||||
{ return new DeleteIdentityTask(mRsIdentity); }
|
||||
|
||||
} // namespace resource_api
|
||||
|
@ -1,4 +1,23 @@
|
||||
#pragma once
|
||||
/*
|
||||
* libresapi
|
||||
*
|
||||
* Copyright (C) 2015 electron128 <electron128@yahoo.com>
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
#include <retroshare/rsnotify.h>
|
||||
#include <util/rsthreads.h>
|
||||
@ -22,10 +41,13 @@ public:
|
||||
virtual void notifyGxsChange(const RsGxsChanges &changes);
|
||||
|
||||
private:
|
||||
void handleWildcard(Request& req, Response& resp);
|
||||
void handleWildcard(Request& req, Response& resp);
|
||||
void handleNotOwnIdsRequest(Request& req, Response& resp);
|
||||
void handleOwnIdsRequest(Request& req, Response& resp);
|
||||
|
||||
void handleExportKey(Request& req, Response& resp);
|
||||
void handleImportKey(Request& req, Response& resp);
|
||||
|
||||
void handleAddContact(Request& req, Response& resp);
|
||||
void handleRemoveContact(Request& req, Response& resp);
|
||||
|
||||
|
@ -1,4 +1,22 @@
|
||||
#pragma once
|
||||
/*
|
||||
* libresapi
|
||||
* Copyright (C) 2015 electron128 <electron128@yahoo.com>
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
#include "ApiTypes.h"
|
||||
|
||||
@ -13,13 +31,6 @@ namespace resource_api
|
||||
template<class C>
|
||||
void handlePaginationRequest(Request& req, Response& resp, C& data)
|
||||
{
|
||||
/*
|
||||
if(!req.isGet()){
|
||||
resp.mDebug << "unsupported method. only GET is allowed." << std::endl;
|
||||
resp.setFail();
|
||||
return;
|
||||
}
|
||||
*/
|
||||
if(data.begin() == data.end()){
|
||||
// set result type to list
|
||||
resp.mDataStream.getStreamToMember();
|
||||
@ -30,7 +41,8 @@ void handlePaginationRequest(Request& req, Response& resp, C& data)
|
||||
|
||||
std::string begin_after;
|
||||
std::string last;
|
||||
req.mStream << makeKeyValueReference("begin_after", begin_after) << makeKeyValueReference("last", last);
|
||||
req.mStream << makeKeyValueReference("begin_after", begin_after)
|
||||
<< makeKeyValueReference("last", last);
|
||||
|
||||
typename C::iterator it_first = data.begin();
|
||||
if(begin_after != "begin" && begin_after != "")
|
||||
@ -62,7 +74,17 @@ void handlePaginationRequest(Request& req, Response& resp, C& data)
|
||||
++it_last; // increment to get iterator to element after the last wanted element
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
/* G10h4ck: Guarded message count limitation with
|
||||
* JSON_API_LIMIT_CHAT_MSG_COUNT_BY_DEFAULT as ATM it seems that handling a
|
||||
* big bunch of messages hasn't been a problem for client apps, and even in
|
||||
* that case the client can specify +begin_after+ and +last+ in the request,
|
||||
* this way we don't make more difficult the life of those who just want get
|
||||
* the whole list of chat messages that seems to be a common usecase
|
||||
*/
|
||||
#ifdef JSON_API_LIMIT_CHAT_MSG_COUNT_BY_DEFAULT
|
||||
int count = 0;
|
||||
#endif
|
||||
|
||||
for(typename C::iterator it = it_first; it != it_last; ++it)
|
||||
{
|
||||
StreamBase& stream = resp.mDataStream.getStreamToMember();
|
||||
@ -71,11 +93,16 @@ void handlePaginationRequest(Request& req, Response& resp, C& data)
|
||||
|
||||
// todo: also handle the case when the last element is specified and the first element is begin
|
||||
// then want to return the elements near the specified element
|
||||
count++;
|
||||
if(count > 20){
|
||||
resp.mDebug << "limited the number of returned items to 20" << std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
// G10h4ck: @see first comment about JSON_API_LIMIT_CHAT_MSG_COUNT_BY_DEFAULT
|
||||
#ifdef JSON_API_LIMIT_CHAT_MSG_COUNT_BY_DEFAULT
|
||||
++count;
|
||||
if(count > 20)
|
||||
{
|
||||
resp.mDebug << "limited the number of returned items to 20";
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
resp.setOk();
|
||||
}
|
||||
|
@ -1,3 +1,23 @@
|
||||
/*
|
||||
* libresapi
|
||||
*
|
||||
* Copyright (C) 2015 electron128 <electron128@yahoo.com>
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
#include "PeersHandler.h"
|
||||
|
||||
#include <retroshare/rspeers.h>
|
||||
@ -194,6 +214,7 @@ PeersHandler::PeersHandler(StateTokenServer* sts, RsNotify* notify, RsPeers *pee
|
||||
mNotify->registerNotifyClient(this);
|
||||
mStateTokenServer->registerTickClient(this);
|
||||
addResourceHandler("*", this, &PeersHandler::handleWildcard);
|
||||
addResourceHandler("attempt_connection", this, &PeersHandler::handleAttemptConnection);
|
||||
addResourceHandler("get_state_string", this, &PeersHandler::handleGetStateString);
|
||||
addResourceHandler("set_state_string", this, &PeersHandler::handleSetStateString);
|
||||
addResourceHandler("get_custom_state_string", this, &PeersHandler::handleGetCustomStateString);
|
||||
@ -203,6 +224,7 @@ PeersHandler::PeersHandler(StateTokenServer* sts, RsNotify* notify, RsPeers *pee
|
||||
addResourceHandler("get_node_options", this, &PeersHandler::handleGetNodeOptions);
|
||||
addResourceHandler("set_node_options", this, &PeersHandler::handleSetNodeOptions);
|
||||
addResourceHandler("examine_cert", this, &PeersHandler::handleExamineCert);
|
||||
|
||||
}
|
||||
|
||||
PeersHandler::~PeersHandler()
|
||||
@ -601,6 +623,19 @@ void PeersHandler::handleWildcard(Request &req, Response &resp)
|
||||
}
|
||||
}
|
||||
|
||||
void PeersHandler::handleAttemptConnection(Request &req, Response &resp)
|
||||
{
|
||||
std::string ssl_peer_id;
|
||||
req.mStream << makeKeyValueReference("peer_id", ssl_peer_id);
|
||||
RsPeerId peerId(ssl_peer_id);
|
||||
if(peerId.isNull()) resp.setFail("Invalid peer_id");
|
||||
else
|
||||
{
|
||||
mRsPeers->connectAttempt(peerId);
|
||||
resp.setOk();
|
||||
}
|
||||
}
|
||||
|
||||
void PeersHandler::handleExamineCert(Request &req, Response &resp)
|
||||
{
|
||||
std::string cert_string;
|
||||
|
@ -1,4 +1,23 @@
|
||||
#pragma once
|
||||
/*
|
||||
* libresapi
|
||||
*
|
||||
* Copyright (C) 2015 electron128 <electron128@yahoo.com>
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
#include "ResourceRouter.h"
|
||||
#include "StateTokenServer.h"
|
||||
@ -33,8 +52,11 @@ public:
|
||||
virtual void notifyUnreadMsgCountChanged(const RsPeerId& peer, uint32_t count);
|
||||
|
||||
private:
|
||||
void handleWildcard(Request& req, Response& resp);
|
||||
void handleExamineCert(Request& req, Response& resp);
|
||||
void handleWildcard(Request& req, Response& resp);
|
||||
|
||||
void handleAttemptConnection(Request& req, Response& resp);
|
||||
|
||||
void handleExamineCert(Request& req, Response& resp);
|
||||
|
||||
void handleGetStateString(Request& req, Response& resp);
|
||||
void handleSetStateString(Request& req, Response& resp);
|
||||
|
@ -1271,7 +1271,9 @@ bool RsGenExchange::getMsgRelatedMeta(const uint32_t &token, GxsMsgRelatedMetaMa
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool RsGenExchange::getSerializedGroupData(const uint32_t &token, RsGxsGroupId& id,unsigned char *& data,uint32_t& size)
|
||||
bool RsGenExchange::getSerializedGroupData(uint32_t token, RsGxsGroupId& id,
|
||||
unsigned char *& data,
|
||||
uint32_t& size)
|
||||
{
|
||||
RS_STACK_MUTEX(mGenMtx) ;
|
||||
|
||||
@ -1304,24 +1306,30 @@ bool RsGenExchange::getSerializedGroupData(const uint32_t &token, RsGxsGroupId&
|
||||
return RsNxsSerialiser(mServType).serialise(nxs_grp,data,&size) ;
|
||||
}
|
||||
|
||||
bool RsGenExchange::deserializeGroupData(unsigned char *data,uint32_t size)
|
||||
bool RsGenExchange::deserializeGroupData(unsigned char *data, uint32_t size,
|
||||
RsGxsGroupId* gId /*= nullptr*/)
|
||||
{
|
||||
RS_STACK_MUTEX(mGenMtx) ;
|
||||
|
||||
RsItem *item = RsNxsSerialiser(mServType).deserialise(data, &size);
|
||||
|
||||
RsNxsGrp *nxs_grp = dynamic_cast<RsNxsGrp*>(item) ;
|
||||
RsNxsGrp *nxs_grp = dynamic_cast<RsNxsGrp*>(item);
|
||||
|
||||
if(item == NULL)
|
||||
{
|
||||
std::cerr << "(EE) RsGenExchange::deserializeGroupData(): cannot deserialise this data. Something's wrong." << std::endl;
|
||||
delete item ;
|
||||
return false ;
|
||||
}
|
||||
if(item == NULL)
|
||||
{
|
||||
std::cerr << "(EE) RsGenExchange::deserializeGroupData(): cannot "
|
||||
<< "deserialise this data. Something's wrong." << std::endl;
|
||||
delete item;
|
||||
return false;
|
||||
}
|
||||
|
||||
mReceivedGrps.push_back( GxsPendingItem<RsNxsGrp*, RsGxsGroupId>(nxs_grp, nxs_grp->grpId,time(NULL)) );
|
||||
mReceivedGrps.push_back(
|
||||
GxsPendingItem<RsNxsGrp*, RsGxsGroupId>(
|
||||
nxs_grp, nxs_grp->grpId,time(NULL)) );
|
||||
|
||||
return true ;
|
||||
if(gId) *gId = nxs_grp->grpId;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RsGenExchange::getGroupData(const uint32_t &token, std::vector<RsGxsGrpItem *>& grpItem)
|
||||
|
@ -299,8 +299,10 @@ protected:
|
||||
* \return
|
||||
*/
|
||||
|
||||
bool getSerializedGroupData(const uint32_t &token, RsGxsGroupId &id, unsigned char *& data, uint32_t& size);
|
||||
bool deserializeGroupData(unsigned char *data, uint32_t size);
|
||||
bool getSerializedGroupData(uint32_t token, RsGxsGroupId &id,
|
||||
unsigned char *& data, uint32_t& size);
|
||||
bool deserializeGroupData(unsigned char *data, uint32_t size,
|
||||
RsGxsGroupId* gId = nullptr);
|
||||
|
||||
template<class GrpType>
|
||||
bool getGroupDataT(const uint32_t &token, std::vector<GrpType*>& grpItem)
|
||||
|
@ -922,15 +922,15 @@ android-g++ {
|
||||
## Add this here and not in retroshare.pri because static library are very
|
||||
## sensible to order in command line, has to be in the end of file for the
|
||||
## same reason
|
||||
LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl
|
||||
INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a
|
||||
LIBS += -L$$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl
|
||||
INCLUDEPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
DEPENDPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
PRE_TARGETDEPS += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a
|
||||
|
||||
LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto
|
||||
INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a
|
||||
LIBS += -L$$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto
|
||||
INCLUDEPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
DEPENDPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
PRE_TARGETDEPS += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a
|
||||
|
||||
HEADERS += util/androiddebug.h
|
||||
}
|
||||
|
@ -91,11 +91,11 @@ class GxsReputation
|
||||
};
|
||||
|
||||
|
||||
class RsGxsIdGroup
|
||||
struct RsGxsIdGroup
|
||||
{
|
||||
public:
|
||||
RsGxsIdGroup(): mLastUsageTS(0), mPgpKnown(false),mIsAContact(false) { return; }
|
||||
~RsGxsIdGroup() { return; }
|
||||
RsGxsIdGroup() :
|
||||
mLastUsageTS(0), mPgpKnown(false), mIsAContact(false) {}
|
||||
~RsGxsIdGroup() {}
|
||||
|
||||
|
||||
RsGroupMetaData mMeta;
|
||||
@ -305,8 +305,10 @@ public:
|
||||
virtual bool setAsRegularContact(const RsGxsId& id,bool is_a_contact) = 0 ;
|
||||
virtual bool isARegularContact(const RsGxsId& id) = 0 ;
|
||||
|
||||
virtual bool serialiseIdentityToMemory(const RsGxsId& id,std::string& radix_string)=0;
|
||||
virtual bool deserialiseIdentityFromMemory(const std::string& radix_string)=0;
|
||||
virtual bool serialiseIdentityToMemory( const RsGxsId& id,
|
||||
std::string& radix_string ) = 0;
|
||||
virtual bool deserialiseIdentityFromMemory( const std::string& radix_string,
|
||||
RsGxsId* id = nullptr ) = 0;
|
||||
|
||||
/*!
|
||||
* \brief overallReputationLevel
|
||||
|
@ -698,9 +698,10 @@ bool p3IdService::getOwnIds(std::list<RsGxsId> &ownIds)
|
||||
return true ;
|
||||
}
|
||||
|
||||
bool p3IdService::serialiseIdentityToMemory(const RsGxsId& id,std::string& radix_string)
|
||||
bool p3IdService::serialiseIdentityToMemory( const RsGxsId& id,
|
||||
std::string& radix_string )
|
||||
{
|
||||
RsStackMutex stack(mIdMtx); /********** STACK LOCKED MTX ******/
|
||||
RS_STACK_MUTEX(mIdMtx);
|
||||
|
||||
// look into cache. If available, return the data. If not, request it.
|
||||
|
||||
@ -758,23 +759,27 @@ void p3IdService::handle_get_serialized_grp(uint32_t token)
|
||||
mSerialisedIdentities[RsGxsId(id)] = s ;
|
||||
}
|
||||
|
||||
bool p3IdService::deserialiseIdentityFromMemory(const std::string& radix_string)
|
||||
bool p3IdService::deserialiseIdentityFromMemory(const std::string& radix_string,
|
||||
RsGxsId* id /* = nullptr */)
|
||||
{
|
||||
std::vector<uint8_t> mem = Radix64::decode(radix_string) ;
|
||||
std::vector<uint8_t> mem = Radix64::decode(radix_string);
|
||||
|
||||
if(mem.empty())
|
||||
if(mem.empty())
|
||||
{
|
||||
std::cerr << "Cannot decode radix string \"" << radix_string << "\"" << std::endl;
|
||||
return false ;
|
||||
std::cerr << __PRETTY_FUNCTION__ << "Cannot decode radix string \""
|
||||
<< radix_string << "\"" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!RsGenExchange::deserializeGroupData(mem.data(),mem.size()))
|
||||
{
|
||||
std::cerr << "Cannot load identity from radix string \"" << radix_string << "\"" << std::endl;
|
||||
return false ;
|
||||
}
|
||||
if( !RsGenExchange::deserializeGroupData(
|
||||
mem.data(), mem.size(), reinterpret_cast<RsGxsGroupId*>(id)) )
|
||||
{
|
||||
std::cerr << __PRETTY_FUNCTION__ << "Cannot load identity from radix "
|
||||
<< "string \"" << radix_string << "\"" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true ;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool p3IdService::createIdentity(uint32_t& token, RsIdentityParameters ¶ms)
|
||||
|
@ -350,8 +350,10 @@ public:
|
||||
const RsIdentityUsage &use_info );
|
||||
virtual bool requestPrivateKey(const RsGxsId &id);
|
||||
|
||||
virtual bool serialiseIdentityToMemory(const RsGxsId& id,std::string& radix_string);
|
||||
virtual bool deserialiseIdentityFromMemory(const std::string& radix_string);
|
||||
virtual bool serialiseIdentityToMemory(const RsGxsId& id,
|
||||
std::string& radix_string);
|
||||
virtual bool deserialiseIdentityFromMemory(const std::string& radix_string,
|
||||
RsGxsId* id = nullptr);
|
||||
|
||||
/**************** RsGixsReputation Implementation ****************/
|
||||
|
||||
|
@ -126,14 +126,14 @@ android-g++ {
|
||||
|
||||
## Add this here and not in retroshare.pri because static library are very
|
||||
## sensible to order in command line
|
||||
LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl
|
||||
INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a
|
||||
LIBS += -L$$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl
|
||||
INCLUDEPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
DEPENDPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
PRE_TARGETDEPS += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a
|
||||
|
||||
LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto
|
||||
INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a
|
||||
LIBS += -L$$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto
|
||||
INCLUDEPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
DEPENDPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
|
||||
PRE_TARGETDEPS += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a
|
||||
|
||||
}
|
||||
|
177
retroshare-android-notify-service/src/AutologinManager.qml
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
* RetroShare Android Autologin Service
|
||||
* Copyright (C) 2017 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 QtQml 2.2
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
|
||||
QtObject
|
||||
{
|
||||
id: am
|
||||
|
||||
property bool coreReady: false
|
||||
|
||||
property string profileName
|
||||
property string profileSslId
|
||||
property string hardcodedPassword: "hardcoded default password"
|
||||
property int loginAttemptCount: 0
|
||||
property bool attemptingLogin: false
|
||||
|
||||
property int loginNotificationTime: 0
|
||||
|
||||
function delay(msecs, func)
|
||||
{
|
||||
var tmr = Qt.createQmlObject("import QtQml 2.2; Timer {}", am);
|
||||
tmr.interval = msecs;
|
||||
tmr.repeat = false;
|
||||
tmr.triggered.connect(function() { func(); tmr.destroy(msecs) });
|
||||
tmr.start();
|
||||
}
|
||||
|
||||
property Timer runStateTimer: Timer
|
||||
{
|
||||
repeat: true
|
||||
interval: 5000
|
||||
triggeredOnStart: true
|
||||
Component.onCompleted: start()
|
||||
onTriggered:
|
||||
rsApi.request("/control/runstate/", "", am.runStateCallback)
|
||||
}
|
||||
|
||||
function runStateCallback(par)
|
||||
{
|
||||
var jsonReponse = JSON.parse(par.response)
|
||||
var runState = jsonReponse.data.runstate
|
||||
if(typeof(runState) !== 'string')
|
||||
{
|
||||
console.log("runStateCallback(par)", "Core hanged!", par.response)
|
||||
return
|
||||
}
|
||||
|
||||
switch(runState)
|
||||
{
|
||||
case "waiting_init":
|
||||
coreReady = false
|
||||
console.log("Core is starting")
|
||||
break
|
||||
case "fatal_error":
|
||||
coreReady = false
|
||||
console.log("Core hanged")
|
||||
break
|
||||
case "waiting_account_select":
|
||||
coreReady = false
|
||||
if(!attemptingLogin && loginAttemptCount < 5)
|
||||
rsApi.request("/control/locations/", "", requestLocationsListCB)
|
||||
else if (loginAttemptCount >= 5 &&
|
||||
/* Avoid flooding non logged in with login requests, wait
|
||||
* at least 1 hour before notifying the user again */
|
||||
Date.now() - loginNotificationTime > 3600000)
|
||||
{
|
||||
notificationsBridge.notify(qsTr("Login needed"))
|
||||
loginNotificationTime = Date.now()
|
||||
}
|
||||
break
|
||||
case "waiting_startup":
|
||||
coreReady = false
|
||||
break
|
||||
case "running_ok":
|
||||
case "running_ok_no_full_control":
|
||||
coreReady = true
|
||||
runStateTimer.interval = 30000
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function requestLocationsListCB(par)
|
||||
{
|
||||
console.log("requestLocationsListCB")
|
||||
var jsonData = JSON.parse(par.response).data
|
||||
if(jsonData.length === 1)
|
||||
{
|
||||
// There is only one location so we can attempt autologin
|
||||
var location = jsonData[0]
|
||||
profileName = location.name
|
||||
profileSslId = location.peer_id
|
||||
if(!attemptingLogin && loginAttemptCount < 5) attemptLogin()
|
||||
}
|
||||
else if (jsonData.length === 0)
|
||||
{
|
||||
console.log("requestLocationsListCB 0")
|
||||
// The user haven't created a location yet
|
||||
// TODO: notify user to create a location
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log("requestLocationsListCB *")
|
||||
// There is more then one location to choose from
|
||||
// TODO: notify user to login manually
|
||||
}
|
||||
}
|
||||
|
||||
function attemptLogin()
|
||||
{
|
||||
console.log("attemptLogin")
|
||||
attemptingLogin = true
|
||||
++loginAttemptCount
|
||||
rsApi.request(
|
||||
"/control/login/", JSON.stringify({ id: profileSslId }),
|
||||
attemptLoginCB)
|
||||
}
|
||||
|
||||
function attemptLoginCB(par)
|
||||
{
|
||||
console.log("attemptLoginCB")
|
||||
var jsonRet = JSON.parse(par.response).returncode
|
||||
if (jsonRet === "ok") attemptPassTimer.start()
|
||||
else console.log("Login hanged!")
|
||||
}
|
||||
|
||||
property Timer attemptPassTimer: Timer
|
||||
{
|
||||
interval: 700
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: rsApi.request("/control/password/", "", attemptPasswordCB)
|
||||
|
||||
function attemptPasswordCB(par)
|
||||
{
|
||||
if(JSON.parse(par.response).data.want_password)
|
||||
{
|
||||
console.log("attemptPasswordCB want_password")
|
||||
rsApi.request(
|
||||
"/control/password/",
|
||||
JSON.stringify({ password: am.hardcodedPassword }),
|
||||
attemptPasswordCBCB)
|
||||
}
|
||||
}
|
||||
|
||||
function attemptPasswordCBCB()
|
||||
{
|
||||
console.log("attemptPasswordCBCB")
|
||||
stop()
|
||||
am.runStateTimer.stop()
|
||||
|
||||
/* Wait 10 seconds so the core has time to process login and update
|
||||
* runstate */
|
||||
delay(10000, function()
|
||||
{
|
||||
am.attemptingLogin = false
|
||||
am.runStateTimer.start()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
1
retroshare-android-notify-service/src/TokensManager.qml
Symbolic link
@ -0,0 +1 @@
|
||||
../../retroshare-qml-app/src/TokensManager.qml
|
1
retroshare-android-notify-service/src/libresapilocalclient.cpp
Symbolic link
@ -0,0 +1 @@
|
||||
../../retroshare-qml-app/src/libresapilocalclient.cpp
|
1
retroshare-android-notify-service/src/libresapilocalclient.h
Symbolic link
@ -0,0 +1 @@
|
||||
../../retroshare-qml-app/src/libresapilocalclient.h
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* RetroShare Android Service
|
||||
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -16,46 +16,67 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlComponent>
|
||||
#include <QFileInfo>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QThread>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
# include <QtAndroidExtras>
|
||||
# include "util/androiddebug.h"
|
||||
#endif
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "libresapilocalclient.h"
|
||||
#include "retroshare/rsinit.h"
|
||||
#include "notificationsbridge.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QGuiApplication app(argc, argv);
|
||||
#ifdef __ANDROID__
|
||||
AndroidStdIOCatcher dbg; (void) dbg;
|
||||
#endif
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
QString sockPath = QDir::homePath() + "/.retroshare";
|
||||
sockPath.append("/libresapi.sock");
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
/// @deprecated
|
||||
qmlRegisterType<NotificationsBridge>(
|
||||
"org.retroshare.qml_components.NotificationsBridge", 1, 0,
|
||||
"NotificationsBridge");
|
||||
NotificationsBridge notificationsBridge;
|
||||
engine.rootContext()->setContextProperty("notificationsBridge",
|
||||
¬ificationsBridge);
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
engine.rootContext()->setContextProperty("QT_DEBUG", true);
|
||||
#else
|
||||
engine.rootContext()->setContextProperty("QT_DEBUG", false);
|
||||
#endif // QT_DEBUG
|
||||
|
||||
engine.rootContext()->setContextProperty("apiSocketPath", sockPath);
|
||||
|
||||
LibresapiLocalClient rsApi;
|
||||
|
||||
while (!QFileInfo::exists(sockPath))
|
||||
{
|
||||
qDebug() << "RetroShareAndroidNotifyService waiting for core to"
|
||||
<< "listen on:" << sockPath;
|
||||
|
||||
QThread::sleep(2);
|
||||
}
|
||||
|
||||
rsApi.openConnection(sockPath);
|
||||
|
||||
qmlRegisterType<LibresapiLocalClient>(
|
||||
"org.retroshare.qml_components.LibresapiLocalClient", 1, 0,
|
||||
"LibresapiLocalClient");
|
||||
|
||||
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);
|
||||
qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString();
|
||||
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
|
||||
|
||||
return app.exec();
|
||||
}
|
56
retroshare-android-notify-service/src/main.qml
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* RetroShare Android Autologin Service
|
||||
* Copyright (C) 2017 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 QtQml 2.2
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
import "." //Needed for TokensManager singleton
|
||||
|
||||
QtObject
|
||||
{
|
||||
id: notifyRoot
|
||||
|
||||
property alias coreReady: coreWatcher.coreReady
|
||||
onCoreReadyChanged: if(coreReady) refreshUnread()
|
||||
|
||||
property AutologinManager coreWatcher: AutologinManager { id: coreWatcher }
|
||||
|
||||
function refreshUnreadCallback(par)
|
||||
{
|
||||
console.log("notifyRoot.refreshUnreadCB()")
|
||||
var json = JSON.parse(par.response)
|
||||
TokensManager.registerToken(json.statetoken, refreshUnread)
|
||||
var convCnt = json.data.length
|
||||
if(convCnt > 0)
|
||||
{
|
||||
console.log("notifyRoot.refreshUnreadCB() got", json.data.length,
|
||||
"unread conversations")
|
||||
notificationsBridge.notify(
|
||||
qsTr("New message!"),
|
||||
qsTr("Unread messages in %1 %2").arg(convCnt).arg(
|
||||
convCnt > 1 ?
|
||||
qsTr("conversations") : qsTr("conversation")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
function refreshUnread()
|
||||
{
|
||||
console.log("notifyRoot.refreshUnread()")
|
||||
rsApi.request("/chat/unread_msgs", "", refreshUnreadCallback)
|
||||
}
|
||||
}
|
49
retroshare-android-notify-service/src/notificationsbridge.h
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
/*
|
||||
* RetroShare Android Service
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
# include <QtAndroid>
|
||||
# include <QtAndroidExtras/QAndroidJniObject>
|
||||
#endif // __ANDROID__
|
||||
|
||||
struct NotificationsBridge : QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
static void notify(const QString& title, const QString& text = "",
|
||||
const QString& uri = "")
|
||||
{
|
||||
qDebug() << __PRETTY_FUNCTION__ << title << text << uri;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
QtAndroid::androidService().callMethod<void>(
|
||||
"notify",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
|
||||
QAndroidJniObject::fromString(title).object(),
|
||||
QAndroidJniObject::fromString(text).object(),
|
||||
QAndroidJniObject::fromString(uri).object()
|
||||
);
|
||||
#endif // __ANDROID__
|
||||
}
|
||||
};
|
8
retroshare-android-notify-service/src/qml.qrc
Normal file
@ -0,0 +1,8 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>main.qml</file>
|
||||
<file>TokensManager.qml</file>
|
||||
<file>AutologinManager.qml</file>
|
||||
<file>qmldir</file>
|
||||
</qresource>
|
||||
</RCC>
|
1
retroshare-android-notify-service/src/qmldir
Normal file
@ -0,0 +1 @@
|
||||
singleton TokensManager 1.0 TokensManager.qml
|
@ -0,0 +1,26 @@
|
||||
!include("../../retroshare.pri"): error("Could not include file ../../retroshare.pri")
|
||||
|
||||
TARGET = retroshare-android-notify-service
|
||||
|
||||
QT += core network qml
|
||||
QT -= gui
|
||||
|
||||
CONFIG += c++11
|
||||
CONFIG += dll
|
||||
|
||||
RESOURCES += qml.qrc
|
||||
|
||||
TEMPLATE = app
|
||||
|
||||
android-g++ {
|
||||
TEMPLATE = lib
|
||||
QT += androidextras
|
||||
}
|
||||
|
||||
HEADERS += libresapilocalclient.h notificationsbridge.h
|
||||
SOURCES += libresapilocalclient.cpp main.cpp
|
||||
|
||||
DEPENDPATH *= ../../libretroshare/src
|
||||
INCLUDEPATH *= ../../libretroshare/src
|
||||
PRE_TARGETDEPS *= ../../libretroshare/src/lib/libretroshare.a
|
||||
LIBS *= ../../libretroshare/src/lib/libretroshare.a
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* RetroShare Android Service
|
||||
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
|
||||
* Copyright (C) 2016-2017 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
|
||||
@ -18,9 +18,10 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QMetaObject>
|
||||
#include <QDir>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
# include <QtAndroidExtras>
|
||||
# include "util/androiddebug.h"
|
||||
#endif
|
||||
|
||||
@ -36,32 +37,36 @@ int main(int argc, char *argv[])
|
||||
AndroidStdIOCatcher dbg; (void) dbg;
|
||||
#endif
|
||||
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
ApiServer api;
|
||||
RsControlModule ctrl_mod(argc, argv, api.getStateTokenServer(), &api, true);
|
||||
api.addResourceHandler("control", dynamic_cast<resource_api::ResourceRouter*>(&ctrl_mod), &resource_api::RsControlModule::handleRequest);
|
||||
api.addResourceHandler(
|
||||
"control",
|
||||
dynamic_cast<resource_api::ResourceRouter*>(&ctrl_mod),
|
||||
&resource_api::RsControlModule::handleRequest);
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
#if defined(Q_OS_WIN) && defined(QT_DEBUG)
|
||||
QString sockPath = "RS/";
|
||||
#else
|
||||
#elif defined(Q_OS_WIN)
|
||||
QString sockPath = QCoreApplication::applicationDirPath();
|
||||
#else
|
||||
QString sockPath = QDir::homePath() + "/.retroshare";
|
||||
#endif
|
||||
|
||||
sockPath.append("/libresapi.sock");
|
||||
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())
|
||||
{
|
||||
a.processEvents();
|
||||
app.processEvents();
|
||||
usleep(20000);
|
||||
}
|
||||
|
||||
return 0;
|
||||
/* Since QCoreApplication::quit() is a no-op until the event loop has been
|
||||
* started, we need to defer the call until it starts. Thus, we queue a
|
||||
* deferred method call to quit() */
|
||||
QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection);
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
140
retroshare-qml-app/src/AddTrustedNode.qml
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2016-2017 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.7
|
||||
import QtQuick.Controls 2.0
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
import "." //Needed for ClipboardWrapper singleton
|
||||
import "URI.js" as UriJs
|
||||
|
||||
Item
|
||||
{
|
||||
property ApplicationWindow mW
|
||||
|
||||
Column
|
||||
{
|
||||
anchors.fill: parent
|
||||
|
||||
Text
|
||||
{
|
||||
text: qsTr("Import/export node from/to clipboard")
|
||||
font.bold: true
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
text: qsTr("Export own certificate link")
|
||||
onClicked:
|
||||
{
|
||||
console.log("onClicked", text)
|
||||
rsApi.request(
|
||||
"/peers/self/certificate/", "",
|
||||
function(par)
|
||||
{
|
||||
var radix = JSON.parse(par.response).data.cert_string
|
||||
var name = mainWindow.user_name
|
||||
var encodedName = UriJs.URI.encode(name)
|
||||
ClipboardWrapper.postToClipBoard(
|
||||
"retroshare://certificate?" +
|
||||
"name=" + encodedName +
|
||||
"&radix=" + UriJs.URI.encode(radix) +
|
||||
"&location=" + encodedName
|
||||
)
|
||||
|
||||
linkCopiedPopup.itemName = name
|
||||
linkCopiedPopup.open()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
text: qsTr("Import trusted node")
|
||||
|
||||
onClicked:
|
||||
{
|
||||
var cptext = ClipboardWrapper.getFromClipBoard()
|
||||
|
||||
console.log("typeof(cptext)", typeof(cptext))
|
||||
if(cptext.search("://") > 0)
|
||||
mainWindow.handleIntentUri(cptext)
|
||||
else
|
||||
rsApi.request(
|
||||
"/peers/examine_cert/",
|
||||
JSON.stringify({cert_string: cptext}),
|
||||
function(par)
|
||||
{
|
||||
console.log("/peers/examine_cert/ CB",
|
||||
par.response)
|
||||
var resp = JSON.parse(par.response)
|
||||
if(resp.returncode === "fail")
|
||||
{
|
||||
importErrorPop.text = resp.debug_msg
|
||||
importErrorPop.open()
|
||||
return
|
||||
}
|
||||
|
||||
var jData = resp.data
|
||||
stackView.push(
|
||||
"qrc:/TrustedNodeDetails.qml",
|
||||
{
|
||||
nodeCert: cptext,
|
||||
pgpName: jData.name,
|
||||
pgpId: jData.pgp_id,
|
||||
locationName: jData.location,
|
||||
sslIdTxt: jData.peer_id
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
text: qsTr("Export own plain certificate")
|
||||
onClicked:
|
||||
{
|
||||
rsApi.request(
|
||||
"/peers/self/certificate/", "",
|
||||
function(par)
|
||||
{
|
||||
var radix = JSON.parse(par.response).data.cert_string
|
||||
var name = mainWindow.user_name
|
||||
ClipboardWrapper.postToClipBoard(radix)
|
||||
|
||||
linkCopiedPopup.itemName = name
|
||||
linkCopiedPopup.open()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimedPopup
|
||||
{
|
||||
id: importErrorPop
|
||||
property alias text: popText.text
|
||||
|
||||
Text
|
||||
{
|
||||
id: popText
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
}
|
43
retroshare-qml-app/src/ApiImageExample.qml
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2017 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.7
|
||||
|
||||
|
||||
Rectangle
|
||||
{
|
||||
color: "red"
|
||||
|
||||
Image
|
||||
{
|
||||
id: testImg
|
||||
height: 300
|
||||
width: 300
|
||||
}
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
rsApi.request(
|
||||
"/peers/d441e8890164a0f335ad75acc59b5a49/avatar_image",
|
||||
"", setImgCallback )
|
||||
}
|
||||
function setImgCallback(par)
|
||||
{
|
||||
testImg.source = "data:image/png;base64," + par.response
|
||||
}
|
||||
}
|
44
retroshare-qml-app/src/BusyOverlay.qml
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2017 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.7
|
||||
import QtQuick.Controls 2.0
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: busyOverlay
|
||||
|
||||
property string message
|
||||
|
||||
enabled: false
|
||||
anchors.fill: parent
|
||||
|
||||
BusyIndicator
|
||||
{
|
||||
running: true
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
text: busyOverlay.message
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
103
retroshare-qml-app/src/ChatView.qml
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2016-2017 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.7
|
||||
import QtQuick.Controls 2.0
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
import "." //Needed for TokensManager singleton
|
||||
|
||||
Item
|
||||
{
|
||||
id: chatView
|
||||
property string chatId
|
||||
property int token: 0
|
||||
|
||||
function refreshData()
|
||||
{
|
||||
console.log("chatView.refreshData()", visible)
|
||||
if(!visible) return
|
||||
|
||||
rsApi.request( "/chat/messages/"+chatId, "", function(par)
|
||||
{
|
||||
chatModel.json = par.response
|
||||
token = JSON.parse(par.response).statetoken
|
||||
TokensManager.registerToken(token, refreshData)
|
||||
|
||||
if(chatListView.visible)
|
||||
{
|
||||
chatListView.positionViewAtEnd()
|
||||
rsApi.request("/chat/mark_chat_as_read/"+chatId)
|
||||
}
|
||||
} )
|
||||
}
|
||||
|
||||
Component.onCompleted: refreshData()
|
||||
onFocusChanged: focus && refreshData()
|
||||
|
||||
JSONListModel
|
||||
{
|
||||
id: chatModel
|
||||
query: "$.data[*]"
|
||||
}
|
||||
|
||||
Component
|
||||
{
|
||||
id: chatMessageDelegate
|
||||
Item
|
||||
{
|
||||
height: 20
|
||||
Row
|
||||
{
|
||||
Text { text: author_name }
|
||||
Text { text: ": " + msg }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView
|
||||
{
|
||||
id: chatListView
|
||||
width: parent.width
|
||||
height: 300
|
||||
model: chatModel.model
|
||||
delegate: chatMessageDelegate
|
||||
}
|
||||
|
||||
TextField
|
||||
{
|
||||
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}
|
||||
rsApi.request( "/chat/send_message", JSON.stringify(jsonData),
|
||||
function(par) { msgComposer.text = ""; } )
|
||||
}
|
||||
}
|
||||
}
|
47
retroshare-qml-app/src/ClipboardWrapper.qml
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
pragma Singleton
|
||||
|
||||
import QtQml 2.2
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.0
|
||||
|
||||
QtObject
|
||||
{
|
||||
/// Public API
|
||||
function postToClipBoard(str)
|
||||
{
|
||||
console.log("postToClipBoard(str)", str)
|
||||
privTF.text = str
|
||||
privTF.selectAll()
|
||||
privTF.cut()
|
||||
}
|
||||
|
||||
/// Public API
|
||||
function getFromClipBoard()
|
||||
{
|
||||
privTF.text = "getFromClipBoard()" // Need some text for selectAll()
|
||||
privTF.selectAll()
|
||||
privTF.paste()
|
||||
return privTF.text.toString()
|
||||
}
|
||||
|
||||
/// Private
|
||||
property TextInput privTF: TextInput { visible: false }
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
|
||||
* Copyright (C) 2017 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
|
||||
@ -16,37 +16,52 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQml 2.2
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
import QtQuick 2.7
|
||||
|
||||
Item
|
||||
Rectangle
|
||||
{
|
||||
id: loginView
|
||||
property string buttonText: "Login"
|
||||
property string login
|
||||
property string password
|
||||
signal submit(string login, string password)
|
||||
property string hash
|
||||
|
||||
ColumnLayout
|
||||
width: height
|
||||
property int childHeight : height/2
|
||||
color: "white"
|
||||
|
||||
Image
|
||||
{
|
||||
id: inputView
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: bottomButton.top
|
||||
|
||||
Row { Text {text: "Name:" } TextField { id: nameField; text: loginView.login } }
|
||||
Row { Text {text: "Password:" } TextField { id: passwordField; text: loginView.password } }
|
||||
source: "qrc:/icons/edit-image-face-detect.png"
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Button
|
||||
Rectangle
|
||||
{
|
||||
id: bottomButton
|
||||
text: loginView.buttonText
|
||||
color: '#' + hash.substring(1, 9)
|
||||
height: parent.childHeight
|
||||
width: height
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
color: '#' + hash.substring(9, 17)
|
||||
height: parent.childHeight
|
||||
width: height
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
color: '#' + hash.substring(17, 25)
|
||||
height: parent.childHeight
|
||||
width: height
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
color: '#' + hash.slice(-8)
|
||||
height: parent.childHeight
|
||||
width: height
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
onClicked: loginView.submit(nameField.text, passwordField.text)
|
||||
}
|
||||
}
|
142
retroshare-qml-app/src/ContactDetails.qml
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2017 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.7
|
||||
import QtQuick.Controls 2.0
|
||||
import "." //Needed for ClipboardWrapper singleton
|
||||
import "URI.js" as UriJs
|
||||
|
||||
Item
|
||||
{
|
||||
id: cntDt
|
||||
property var md
|
||||
property bool is_contact: cntDt.md.is_contact
|
||||
|
||||
ColorHash
|
||||
{
|
||||
id: colorHash
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
height: 150
|
||||
hash: cntDt.md.gxs_id
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
anchors.top: colorHash.bottom
|
||||
anchors.topMargin: 6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
spacing: 6
|
||||
|
||||
Row
|
||||
{
|
||||
height: 50
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 6
|
||||
|
||||
Text
|
||||
{
|
||||
text: cntDt.md.name
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Image
|
||||
{
|
||||
|
||||
source: cntDt.is_contact ?
|
||||
"qrc:/icons/rating.png" :
|
||||
"qrc:/icons/rating-unrated.png"
|
||||
height: parent.height - 4
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked:
|
||||
{
|
||||
var jDt = JSON.stringify({gxs_id: cntDt.md.gxs_id})
|
||||
if(cntDt.is_contact)
|
||||
rsApi.request("/identity/remove_contact", jDt, tgCt)
|
||||
else rsApi.request("/identity/add_contact", jDt, tgCt)
|
||||
}
|
||||
|
||||
function tgCt() { cntDt.is_contact = !cntDt.is_contact }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text
|
||||
{
|
||||
text: "<pre>"+cntDt.md.gxs_id+"</pre>"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text
|
||||
{
|
||||
visible: cntDt.md.pgp_linked
|
||||
text: qsTr("Owned by: %1").arg(cntDt.md.pgp_id)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
spacing: 6
|
||||
|
||||
Button
|
||||
{
|
||||
text: qsTr("Contact full link")
|
||||
onClicked:
|
||||
{
|
||||
rsApi.request(
|
||||
"/identity/export_key",
|
||||
JSON.stringify({ gxs_id: cntDt.md.gxs_id }),
|
||||
function(par)
|
||||
{
|
||||
var jD = JSON.parse(par.response).data
|
||||
ClipboardWrapper.postToClipBoard(
|
||||
"retroshare://" +
|
||||
"identity?gxsid=" +
|
||||
cntDt.md.gxs_id +
|
||||
"&name=" +
|
||||
UriJs.URI.encode(cntDt.md.name) +
|
||||
"&groupdata=" +
|
||||
UriJs.URI.encode(jD.radix))
|
||||
linkCopiedPopup.itemName = cntDt.md.name
|
||||
linkCopiedPopup.visible = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
text: qsTr("Contact short link")
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
101
retroshare-qml-app/src/ContactSort.js
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
function strcmp(left, right)
|
||||
{ return ( left < right ? -1 : ( left > right ? 1:0 ) ) }
|
||||
|
||||
var unreadMessages = {}
|
||||
var contactsData = {}
|
||||
|
||||
function cntcmp(left, right, searchText)
|
||||
{
|
||||
if(typeof searchText !== 'undefined' && searchText.length > 0)
|
||||
{
|
||||
var mtc = searchText.toLowerCase()
|
||||
var lfn = left.name.toLowerCase()
|
||||
var rgn = right.name.toLowerCase()
|
||||
var lfml = lfn.indexOf(mtc)
|
||||
var rgml = rgn.indexOf(mtc)
|
||||
if ( lfml !== rgml )
|
||||
{
|
||||
lfml = lfml >= 0 ? lfml : Number.MAX_VALUE
|
||||
rgml = rgml >= 0 ? rgml : Number.MAX_VALUE
|
||||
return lfml - rgml
|
||||
}
|
||||
}
|
||||
|
||||
var lfun = left.hasOwnProperty("unread_count") ? left.unread_count : 0
|
||||
var rgun = right.hasOwnProperty("unread_count") ? right.unread_count : 0
|
||||
if( lfun !== rgun ) return rgun - lfun
|
||||
var lcon = left.is_contact
|
||||
var rcon = right.is_contact
|
||||
if( lcon !== rcon ) return rcon - lcon
|
||||
var lname = left.name.toLowerCase()
|
||||
var rname = right.name.toLowerCase()
|
||||
if(lname !== rname) return strcmp(lname, rname)
|
||||
return strcmp(left.gxs_id, right.gxs_id)
|
||||
}
|
||||
|
||||
function mergeContactsUnread()
|
||||
{
|
||||
var jsonData = contactsData.data
|
||||
var dataLen = jsonData.length
|
||||
for ( var i=0; i<dataLen; ++i)
|
||||
{
|
||||
var el = jsonData[i]
|
||||
if(unreadMessages.hasOwnProperty(el.gxs_id))
|
||||
el['unread_count'] = unreadMessages[el.gxs_id]
|
||||
else el['unread_count'] = "0" // This must be string
|
||||
}
|
||||
}
|
||||
|
||||
function parseUnread(responseStr)
|
||||
{
|
||||
var jsonData = JSON.parse(responseStr).data
|
||||
var dataLen = jsonData.length
|
||||
unreadMessages = {}
|
||||
for ( var i=0; i<dataLen; ++i)
|
||||
{
|
||||
var el = jsonData[i]
|
||||
if(el.is_distant_chat_id)
|
||||
unreadMessages[el.remote_author_id] = el.unread_count
|
||||
}
|
||||
|
||||
mergeContactsUnread()
|
||||
}
|
||||
|
||||
function parseContacts(responseStr)
|
||||
{
|
||||
contactsData = JSON.parse(responseStr)
|
||||
mergeContactsUnread()
|
||||
}
|
||||
|
||||
WorkerScript.onMessage = function(message)
|
||||
{
|
||||
var sortFn = cntcmp
|
||||
message.action = message.hasOwnProperty("action") ? message.action : "rSort"
|
||||
|
||||
if(message.action === "refreshContacts") parseContacts(message.response)
|
||||
else if(message.action === "refreshUnread") parseUnread(message.response)
|
||||
else if(message.action === "searchContact")
|
||||
sortFn = function cmp(l,r) { return cntcmp(l,r, message.sexp) }
|
||||
|
||||
contactsData.data.sort(sortFn)
|
||||
|
||||
WorkerScript.sendMessage(contactsData)
|
||||
}
|
190
retroshare-qml-app/src/Contacts.qml
Normal file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2016-2017 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.7
|
||||
import QtQuick.Controls 2.0
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
import "." //Needed for TokensManager singleton
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
Item
|
||||
{
|
||||
id: contactsView
|
||||
property string own_gxs_id: ""
|
||||
property string own_nick: ""
|
||||
property bool searching: false
|
||||
onSearchingChanged: !searching && contactsSortWorker.sendMessage({})
|
||||
|
||||
Component.onCompleted: refreshAll()
|
||||
onFocusChanged: focus && refreshAll()
|
||||
|
||||
WorkerScript
|
||||
{
|
||||
id: contactsSortWorker
|
||||
source: "qrc:/ContactSort.js"
|
||||
onMessage: contactsListModel.json = JSON.stringify(messageObject)
|
||||
}
|
||||
|
||||
function refreshAll()
|
||||
{
|
||||
refreshOwn()
|
||||
refreshContacts()
|
||||
refreshUnread()
|
||||
}
|
||||
|
||||
function refreshContactsCallback(par)
|
||||
{
|
||||
console.log("contactsView.refreshContactsCB()", visible)
|
||||
var token = JSON.parse(par.response).statetoken
|
||||
TokensManager.registerToken(token, refreshContacts)
|
||||
contactsSortWorker.sendMessage(
|
||||
{'action': 'refreshContacts', 'response': par.response})
|
||||
}
|
||||
function refreshContacts()
|
||||
{
|
||||
console.log("contactsView.refreshContacts()", visible)
|
||||
if(!visible) return
|
||||
rsApi.request("/identity/*/", "", refreshContactsCallback)
|
||||
}
|
||||
|
||||
function refreshOwnCallback(par)
|
||||
{
|
||||
console.log("contactsView.refreshOwnCallback(par)", visible)
|
||||
var json = JSON.parse(par.response)
|
||||
var token = json.statetoken
|
||||
TokensManager.registerToken(token, refreshOwn)
|
||||
|
||||
if(json.data.length > 0)
|
||||
{
|
||||
contactsView.own_gxs_id = json.data[0].gxs_id
|
||||
contactsView.own_nick = json.data[0].name
|
||||
if(mainWindow.user_name.length === 0)
|
||||
mainWindow.user_name = json.data[0].name
|
||||
}
|
||||
else if (!settings.defaultIdentityCreated)
|
||||
{
|
||||
console.log("refreshOwnCallback(par)", "creating new identity" )
|
||||
settings.defaultIdentityCreated = true
|
||||
|
||||
var jsonData = { "name": mainWindow.user_name, "pgp_linked": false }
|
||||
rsApi.request(
|
||||
"/identity/create_identity",
|
||||
JSON.stringify(jsonData),
|
||||
refreshOwn)
|
||||
}
|
||||
}
|
||||
function refreshOwn()
|
||||
{
|
||||
console.log("contactsView.refreshOwn()", visible)
|
||||
rsApi.request("/identity/own", "", refreshOwnCallback)
|
||||
}
|
||||
|
||||
function refreshUnreadCallback(par)
|
||||
{
|
||||
console.log("contactsView.refreshUnreadCB()", visible)
|
||||
var json = JSON.parse(par.response)
|
||||
TokensManager.registerToken(json.statetoken, refreshUnread)
|
||||
contactsSortWorker.sendMessage(
|
||||
{'action': 'refreshUnread', 'response': par.response})
|
||||
}
|
||||
function refreshUnread()
|
||||
{
|
||||
console.log("contactsView.refreshUnread()", visible)
|
||||
if(!visible) return
|
||||
rsApi.request("/chat/unread_msgs", "", refreshUnreadCallback)
|
||||
}
|
||||
|
||||
function startChatCallback(par)
|
||||
{
|
||||
var chId = JSON.parse(par.response).data.chat_id
|
||||
stackView.push("qrc:/ChatView.qml", {'chatId': chId})
|
||||
}
|
||||
|
||||
|
||||
/** This must be equivalent to
|
||||
p3GxsTunnelService::makeGxsTunnelId(...) */
|
||||
function getChatId(from_gxs, to_gxs)
|
||||
{
|
||||
return from_gxs < to_gxs ? from_gxs + to_gxs : to_gxs + from_gxs
|
||||
}
|
||||
|
||||
JSONListModel
|
||||
{
|
||||
id: contactsListModel
|
||||
query: "$.data[*]"
|
||||
}
|
||||
|
||||
ListView
|
||||
{
|
||||
id: contactsListView
|
||||
width: parent.width
|
||||
height: contactsView.searching ?
|
||||
parent.height - searchBox.height : parent.height
|
||||
model: contactsListModel.model
|
||||
anchors.top: contactsView.searching ? searchBox.bottom : parent.top
|
||||
delegate: GxsIdentityDelegate {}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: searchBox
|
||||
visible: contactsView.searching
|
||||
|
||||
height: searchText.height
|
||||
width: searchText.width
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
Image
|
||||
{
|
||||
id: searchIcon
|
||||
height: searchText.height - 4
|
||||
width: searchText.height - 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
source: "qrc:/icons/edit-find.png"
|
||||
}
|
||||
|
||||
TextField
|
||||
{
|
||||
id: searchText
|
||||
anchors.left: searchIcon.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onTextChanged:
|
||||
contactsSortWorker.sendMessage(
|
||||
{'action': 'searchContact', 'sexp': text})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Settings
|
||||
{
|
||||
id: settings
|
||||
category: "contactsView"
|
||||
|
||||
property bool defaultIdentityCreated: false
|
||||
}
|
||||
}
|
126
retroshare-qml-app/src/GxsIdentityDelegate.qml
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2017 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.7
|
||||
import QtQuick.Controls 2.0
|
||||
|
||||
Item
|
||||
{
|
||||
id: delegateRoot
|
||||
height: 40
|
||||
width: parent.width
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
onClicked:
|
||||
{
|
||||
console.log("GxsIntentityDelegate onclicked:", model.name,
|
||||
model.gxs_id)
|
||||
contactsView.searching = false
|
||||
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),
|
||||
contactsView.startChatCallback)
|
||||
}
|
||||
}
|
||||
|
||||
onPressAndHold: showDetails()
|
||||
|
||||
ColorHash
|
||||
{
|
||||
id: colorHash
|
||||
|
||||
hash: model.gxs_id
|
||||
height: parent.height - 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
onClicked: delegateRoot.showDetails()
|
||||
}
|
||||
}
|
||||
|
||||
Text
|
||||
{
|
||||
id: nickText
|
||||
color: model.own ? "blue" : "black"
|
||||
text: model.name
|
||||
anchors.left: colorHash.right
|
||||
anchors.leftMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: parent.height - 10
|
||||
spacing: 4
|
||||
|
||||
Rectangle
|
||||
{
|
||||
visible: model.unread_count > 0
|
||||
|
||||
color: "cornflowerblue"
|
||||
antialiasing: true
|
||||
border.color: "blue"
|
||||
border.width: 1
|
||||
height: parent.height - 4
|
||||
radius: height/2
|
||||
width: height
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text
|
||||
{
|
||||
color: "white"
|
||||
font.bold: true
|
||||
text: model.unread_count
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Image
|
||||
{
|
||||
source: model.is_contact ?
|
||||
"qrc:/icons/rating.png" :
|
||||
"qrc:/icons/rating-unrated.png"
|
||||
height: parent.height - 4
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showDetails()
|
||||
{
|
||||
console.log("showDetails()", index)
|
||||
contactsView.searching = false
|
||||
stackView.push(
|
||||
"qrc:/ContactDetails.qml",
|
||||
{md: contactsListView.model.get(index)})
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
* Licensed under the MIT licence (http://opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
|
||||
import QtQuick 2.0
|
||||
import QtQuick 2.7
|
||||
import "jsonpath.js" as JSONPath
|
||||
|
||||
Item {
|
@ -16,15 +16,14 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.0
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
|
||||
Item
|
||||
{
|
||||
id: locationView
|
||||
state: "selectLocation"
|
||||
property var qParent
|
||||
property bool attemptLogin: false
|
||||
property string password
|
||||
property string sslid
|
||||
@ -42,17 +41,26 @@ Item
|
||||
{
|
||||
name: "createLocation"
|
||||
PropertyChanges { target: locationsListView; visible: false }
|
||||
PropertyChanges { target: bottomButton; visible: false }
|
||||
PropertyChanges { target: bottomButton; visible: false }
|
||||
PropertyChanges
|
||||
{
|
||||
target: loginView
|
||||
visible: true
|
||||
buttonText: "Save"
|
||||
buttonText: qsTr("Save")
|
||||
iconUrl: "qrc:/icons/edit-image-face-detect.png"
|
||||
suggestionText: qsTr("Create your profile")
|
||||
onSubmit:
|
||||
{
|
||||
var jsonData = { pgp_name: login, ssl_name: login, pgp_password: password }
|
||||
rsApi.request("/control/create_location/", JSON.stringify(jsonData))
|
||||
busyIndicator.running = true
|
||||
var jsonData = { pgp_name: login, ssl_name: login,
|
||||
pgp_password: password }
|
||||
rsApi.request(
|
||||
"/control/create_location/",
|
||||
JSON.stringify(jsonData))
|
||||
mainWindow.user_name = login
|
||||
locationView.state = "selectLocation"
|
||||
bottomButton.enabled = false
|
||||
bottomButton.text = "Creating profile..."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -60,86 +68,59 @@ Item
|
||||
{
|
||||
name: "login"
|
||||
PropertyChanges { target: locationsListView; visible: false }
|
||||
PropertyChanges { target: bottomButton; visible: false }
|
||||
PropertyChanges { target: bottomButton; visible: false }
|
||||
PropertyChanges
|
||||
{
|
||||
target: loginView
|
||||
visible: true
|
||||
advancedMode: true
|
||||
onSubmit:
|
||||
{
|
||||
locationView.password = password
|
||||
console.log("locationView.sslid: ", locationView.sslid)
|
||||
rsApi.request("/control/login/", JSON.stringify({id: locationView.sslid}))
|
||||
rsApi.request( "/control/login/",
|
||||
JSON.stringify({id: locationView.sslid}) )
|
||||
locationView.attemptLogin = true
|
||||
busyIndicator.running = true
|
||||
attemptTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
function requestLocationsList() { rsApi.request("/control/locations/", "") }
|
||||
|
||||
onFocusChanged: focus && requestLocationsList()
|
||||
|
||||
LibresapiLocalClient
|
||||
function requestLocationsListCB(par)
|
||||
{
|
||||
id: rsApi
|
||||
Component.onCompleted:
|
||||
var jsonData = JSON.parse(par.response).data
|
||||
if(jsonData.length === 1)
|
||||
{
|
||||
openConnection(apiSocketPath)
|
||||
locationView.requestLocationsList()
|
||||
// There is only one location so we can jump selecting location
|
||||
var location = jsonData[0]
|
||||
loginView.login = location.name
|
||||
mainWindow.user_name = location.name
|
||||
locationView.sslid = location.peer_id
|
||||
locationView.state = "login"
|
||||
}
|
||||
onGoodResponseReceived:
|
||||
else if (jsonData.length === 0)
|
||||
{
|
||||
var jsonData = JSON.parse(msg)
|
||||
|
||||
|
||||
if(jsonData)
|
||||
{
|
||||
if(jsonData.data)
|
||||
{
|
||||
if(jsonData.data[0] && jsonData.data[0].pgp_id)
|
||||
{
|
||||
// if location list update
|
||||
locationsModel.json = msg
|
||||
busyIndicator.running = false
|
||||
}
|
||||
if (jsonData.data.key_name)
|
||||
{
|
||||
if(jsonData.data.want_password)
|
||||
{
|
||||
// if Server requested password
|
||||
var jsonPass = { password: locationView.password }
|
||||
rsApi.request("/control/password/", JSON.stringify(jsonPass))
|
||||
locationView.attemptLogin = false
|
||||
console.debug("RS core asked for password")
|
||||
}
|
||||
else
|
||||
{
|
||||
// if Already logged in
|
||||
bottomButton.enabled = false
|
||||
bottomButton.text = "Already logged in"
|
||||
locationView.attemptLogin = false
|
||||
busyIndicator.running = false
|
||||
locationView.state = "selectLocation"
|
||||
locationsListView.enabled = false
|
||||
console.debug("Already logged in")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The user haven't created a location yet
|
||||
locationView.state = "createLocation"
|
||||
}
|
||||
else
|
||||
{
|
||||
// There is more then one location to choose from
|
||||
locationsModel.json = par.response
|
||||
}
|
||||
}
|
||||
function requestLocationsList()
|
||||
{ rsApi.request("/control/locations/", "", requestLocationsListCB) }
|
||||
|
||||
BusyIndicator { id: busyIndicator; anchors.centerIn: parent }
|
||||
onFocusChanged: focus && requestLocationsList()
|
||||
Component.onCompleted: requestLocationsList()
|
||||
|
||||
JSONListModel
|
||||
{
|
||||
id: locationsModel
|
||||
query: "$.data[*]"
|
||||
}
|
||||
|
||||
ListView
|
||||
{
|
||||
id: locationsListView
|
||||
@ -149,22 +130,24 @@ Item
|
||||
model: locationsModel.model
|
||||
delegate: Button
|
||||
{
|
||||
text: model.name
|
||||
text: model.name
|
||||
onClicked:
|
||||
{
|
||||
loginView.login = text
|
||||
locationView.sslid = model.id
|
||||
locationView.state = "login"
|
||||
mainWindow.user_name = model.name
|
||||
}
|
||||
}
|
||||
visible: false
|
||||
}
|
||||
visible: false
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
Button
|
||||
{
|
||||
id: bottomButton
|
||||
text: "Create new location"
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onClicked: locationView.state = "createLocation"
|
||||
}
|
||||
|
||||
@ -175,18 +158,60 @@ Item
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
BusyIndicator
|
||||
{
|
||||
id: busyIndicator
|
||||
anchors.centerIn: parent
|
||||
running: false
|
||||
|
||||
Connections
|
||||
{
|
||||
target: locationView
|
||||
onAttemptLoginChanged:
|
||||
if(locationView.attemptLogin) busyIndicator.running = true
|
||||
}
|
||||
}
|
||||
|
||||
LibresapiLocalClient
|
||||
{
|
||||
id: loginApi
|
||||
Component.onCompleted: openConnection(apiSocketPath)
|
||||
onGoodResponseReceived:
|
||||
{
|
||||
var jsonData = JSON.parse(msg)
|
||||
if(jsonData && jsonData.data && jsonData.data.key_name)
|
||||
{
|
||||
if(jsonData.data.want_password)
|
||||
{
|
||||
// if Server requested password
|
||||
var jsonPass = { password: locationView.password }
|
||||
request( "/control/password/", JSON.stringify(jsonPass) )
|
||||
locationView.attemptLogin = false
|
||||
console.debug("RS core asked for password")
|
||||
}
|
||||
else
|
||||
{
|
||||
// if Already logged in
|
||||
bottomButton.enabled = false
|
||||
bottomButton.text = "Unlocking location..."
|
||||
locationView.attemptLogin = false
|
||||
locationView.state = "selectLocation"
|
||||
locationsListView.enabled = false
|
||||
console.debug("Already logged in")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Timer
|
||||
{
|
||||
id: attemptTimer
|
||||
interval: 500
|
||||
interval: 1000
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered:
|
||||
{
|
||||
if(locationView.focus)
|
||||
locationView.requestLocationsList()
|
||||
|
||||
if (locationView.attemptLogin)
|
||||
rsApi.request("/control/password/", "")
|
||||
loginApi.request("/control/password/", "")
|
||||
}
|
||||
}
|
||||
}
|
1
retroshare-qml-app/src/NativeCalls.cpp
Symbolic link
@ -0,0 +1 @@
|
||||
android/src/NativeCalls.cpp
|
1
retroshare-qml-app/src/NativeCalls.h
Symbolic link
@ -0,0 +1 @@
|
||||
android/src/org_retroshare_android_qml_app_jni_NativeCalls.h
|
134
retroshare-qml-app/src/RsLoginPassView.qml
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2016-2017 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.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.0
|
||||
|
||||
Item
|
||||
{
|
||||
id: loginView
|
||||
property string buttonText: qsTr("Unlock")
|
||||
property string iconUrl: "qrc:/icons/emblem-locked.png"
|
||||
property string login
|
||||
property bool loginPreset: false
|
||||
property bool advancedMode: false
|
||||
property string hardcodedPassword: "hardcoded default password"
|
||||
property string password: advancedMode ? "" : hardcodedPassword
|
||||
property string suggestionText
|
||||
signal submit(string login, string password)
|
||||
|
||||
Component.onCompleted: loginPreset = login.length > 0
|
||||
|
||||
ColumnLayout
|
||||
{
|
||||
id: inputView
|
||||
width: parent.width
|
||||
anchors.centerIn: parent
|
||||
|
||||
Text
|
||||
{
|
||||
text: loginView.suggestionText
|
||||
visible: loginView.suggestionText.length > 0
|
||||
font.bold: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Image
|
||||
{
|
||||
source: loginView.iconUrl
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Text
|
||||
{
|
||||
text: qsTr("Name")
|
||||
visible: !loginView.loginPreset
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
anchors.bottom: nameField.top
|
||||
anchors.bottomMargin: 5
|
||||
}
|
||||
TextField
|
||||
{
|
||||
id: nameField
|
||||
text: loginView.login
|
||||
visible: !loginView.loginPreset
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
ToolTip
|
||||
{
|
||||
text: qsTr("Choose a descriptive name, one<br/>" +
|
||||
"that your friends can recognize.",
|
||||
"The linebreak is to make the text fit better in " +
|
||||
"tooltip")
|
||||
visible: nameField.activeFocus
|
||||
timeout: 5000
|
||||
}
|
||||
}
|
||||
|
||||
Text
|
||||
{
|
||||
id: passLabel
|
||||
visible: loginView.advancedMode || loginView.loginPreset
|
||||
text: nameField.visible ?
|
||||
qsTr("Password") :
|
||||
qsTr("Enter password for %1").arg(loginView.login)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
anchors.bottom: passwordField.top
|
||||
anchors.bottomMargin: 5
|
||||
}
|
||||
TextField
|
||||
{
|
||||
id: passwordField
|
||||
visible: loginView.advancedMode || loginView.loginPreset
|
||||
text: loginView.password
|
||||
echoMode: TextInput.Password
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
ToolTip
|
||||
{
|
||||
visible: passwordField.activeFocus && !loginView.loginPreset
|
||||
timeout: 5000
|
||||
text: qsTr("Choose a strong password and don't forget it,<br/>"+
|
||||
"there is no way to recover lost password.",
|
||||
"The linebreak is to make the text fit better in " +
|
||||
"tooltip")
|
||||
}
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
spacing: 3
|
||||
|
||||
Button
|
||||
{
|
||||
text: qsTr("Advanced...")
|
||||
visible: !loginView.loginPreset
|
||||
onClicked: loginView.advancedMode = !loginView.advancedMode
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: bottomButton
|
||||
text: loginView.buttonText
|
||||
onClicked: loginView.submit(nameField.text, passwordField.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
retroshare-qml-app/src/TimedPopup.qml
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2017 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.7
|
||||
import QtQuick.Controls 2.0
|
||||
|
||||
Popup
|
||||
{
|
||||
property alias duration: timer.interval
|
||||
|
||||
|
||||
id: popupRoot
|
||||
visible: false
|
||||
onVisibleChanged: if(visible) timer.start()
|
||||
x: parent.x + parent.width/2 - width/2
|
||||
y: parent.y + parent.height/2 - height/2
|
||||
|
||||
|
||||
Timer
|
||||
{
|
||||
id: timer
|
||||
interval: 1500
|
||||
onTriggered: popupRoot.close()
|
||||
}
|
||||
}
|
||||
|
115
retroshare-qml-app/src/TokensManager.qml
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
pragma Singleton
|
||||
|
||||
import QtQml 2.2
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
|
||||
QtObject
|
||||
{
|
||||
id: tokensManager
|
||||
|
||||
property var tokens: ({})
|
||||
function registerToken(token, callback)
|
||||
{
|
||||
if (Array.isArray(tokens[token]))
|
||||
{
|
||||
if(QT_DEBUG)
|
||||
{
|
||||
/* Haven't properly investigated yet if it may happen in normal
|
||||
* situations that a callback is registered more then once, so
|
||||
* if we are in a debug session and that happens print warning
|
||||
* and stacktrace */
|
||||
var arrLen = tokens[token].length
|
||||
for(var i=0; i<arrLen; ++i)
|
||||
{
|
||||
if(callback === tokens[token][i])
|
||||
{
|
||||
console.warn("tokensManager.registerToken(token," +
|
||||
" callback) Attempt to register same" +
|
||||
" callback twice for:",
|
||||
i, token, callback.name)
|
||||
console.trace()
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens[token].push(callback)
|
||||
}
|
||||
else tokens[token] = [callback]
|
||||
}
|
||||
function tokenExpire(token)
|
||||
{
|
||||
if(Array.isArray(tokens[token]))
|
||||
{
|
||||
var arrLen = tokens[token].length
|
||||
for(var i=0; i<arrLen; ++i)
|
||||
{
|
||||
var tokCallback = tokens[token][i]
|
||||
if (typeof tokCallback == 'function')
|
||||
{
|
||||
console.log("event token", token, tokCallback.name)
|
||||
tokCallback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete tokens[token]
|
||||
}
|
||||
function isTokenValid(token) { return Array.isArray(tokens[token]) }
|
||||
|
||||
property alias refreshInterval: refreshTokensTimer.interval
|
||||
|
||||
property LibresapiLocalClient refreshTokensApi: LibresapiLocalClient
|
||||
{
|
||||
id: refreshTokensApi
|
||||
|
||||
onResponseReceived:
|
||||
{
|
||||
var jsonData = JSON.parse(msg).data
|
||||
var arrayLength = jsonData.length
|
||||
for (var i = 0; i < arrayLength; i++)
|
||||
{
|
||||
tokensManager.tokenExpire(jsonData[i])
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
if(QT_DEBUG) debug = false
|
||||
|
||||
openConnection(apiSocketPath)
|
||||
refreshTokensTimer.start()
|
||||
}
|
||||
|
||||
function refreshTokens()
|
||||
{
|
||||
request("/statetokenservice/*",
|
||||
'['+Object.keys(tokensManager.tokens)+']')
|
||||
}
|
||||
}
|
||||
|
||||
property Timer refreshTokensTimer: Timer
|
||||
{
|
||||
id: refreshTokensTimer
|
||||
interval: 1500
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: refreshTokensApi.refreshTokens()
|
||||
}
|
||||
}
|
132
retroshare-qml-app/src/TrustedNodeDetails.qml
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2017 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.7
|
||||
import QtQuick.Controls 2.0
|
||||
|
||||
Item
|
||||
{
|
||||
id: nodeDetailsRoot
|
||||
|
||||
property string pgpName
|
||||
property alias pgpId: pgpIdTxt.text
|
||||
|
||||
property string nodeCert
|
||||
|
||||
property var locations
|
||||
|
||||
Column
|
||||
{
|
||||
id: pgpColumn
|
||||
|
||||
anchors.top: parent.top
|
||||
|
||||
Text { text: nodeDetailsRoot.pgpName.replace(" (Generated by RetroShare) <>", "") }
|
||||
Text { id: pgpIdTxt }
|
||||
}
|
||||
|
||||
JSONListModel
|
||||
{
|
||||
id: jsonModel
|
||||
json: JSON.stringify(nodeDetailsRoot.locations)
|
||||
}
|
||||
|
||||
ListView
|
||||
{
|
||||
width: parent.width
|
||||
anchors.top: pgpColumn.bottom
|
||||
anchors.bottom: buttonsRow.top
|
||||
model: jsonModel.model
|
||||
delegate: Column
|
||||
{
|
||||
height: 60
|
||||
width: parent.width
|
||||
leftPadding: 4
|
||||
spacing: 4
|
||||
|
||||
Row
|
||||
{
|
||||
height: 30
|
||||
spacing: 10
|
||||
|
||||
Image
|
||||
{
|
||||
id: statusImage
|
||||
source: model.is_online ?
|
||||
"icons/state-ok.png" :
|
||||
"icons/state-offline.png"
|
||||
|
||||
height: parent.height - 4
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
Text
|
||||
{
|
||||
id: locNameText
|
||||
text: model.location
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text { text: model.peer_id }
|
||||
}
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
id: buttonsRow
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 6
|
||||
|
||||
Button
|
||||
{
|
||||
text: qsTr("Revoke")
|
||||
onClicked:
|
||||
rsApi.request(
|
||||
"/peers/"+nodeDetailsRoot.pgpId+"/delete", "",
|
||||
function()
|
||||
{ stackView.push("qrc:/TrustedNodesView.qml") })
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
text: qsTr("Entrust")
|
||||
|
||||
visible: nodeDetailsRoot.nodeCert.length > 0
|
||||
onClicked:
|
||||
{
|
||||
var jsonData =
|
||||
{
|
||||
cert_string: nodeCert,
|
||||
flags:
|
||||
{
|
||||
allow_direct_download: true,
|
||||
allow_push: false,
|
||||
require_whitelist: false,
|
||||
}
|
||||
}
|
||||
rsApi.request(
|
||||
"PUT /peers", JSON.stringify(jsonData),
|
||||
function()
|
||||
{ stackView.push("qrc:/TrustedNodesView.qml") })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
120
retroshare-qml-app/src/TrustedNodesView.qml
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2016-2017 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.7
|
||||
import QtQuick.Controls 2.0
|
||||
import "jsonpath.js" as JSONPath
|
||||
import "." //Needed for TokensManager singleton
|
||||
|
||||
Item
|
||||
{
|
||||
id: trustedNodesView
|
||||
property int token: 0
|
||||
|
||||
Component.onCompleted: refreshData()
|
||||
onVisibleChanged: visible && refreshData()
|
||||
|
||||
function refreshDataCallback(par)
|
||||
{
|
||||
jsonModel.json = par.response
|
||||
token = JSON.parse(par.response).statetoken
|
||||
TokensManager.registerToken(token, refreshData)
|
||||
}
|
||||
function refreshData()
|
||||
{ if(visible) rsApi.request("/peers/*", "", refreshDataCallback) }
|
||||
|
||||
JSONListModel
|
||||
{
|
||||
id: jsonModel
|
||||
query: "$.data[*]"
|
||||
|
||||
function isOnline(pgpId)
|
||||
{
|
||||
var qr = "$.data[?(@.pgp_id=='"+pgpId+"')].locations[*].is_online"
|
||||
var locOn = JSONPath.jsonPath(JSON.parse(jsonModel.json), qr)
|
||||
if (Array.isArray(locOn))
|
||||
return locOn.reduce(function(cur,acc){return cur || acc}, false)
|
||||
return Boolean(locOn)
|
||||
}
|
||||
|
||||
function getLocations(pgpId)
|
||||
{
|
||||
var qr = "$.data[?(@.pgp_id=='"+pgpId+"')].locations"
|
||||
return JSONPath.jsonPath(JSON.parse(jsonModel.json), qr)
|
||||
}
|
||||
}
|
||||
|
||||
ListView
|
||||
{
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: bottomButton.top
|
||||
model: jsonModel.model
|
||||
delegate: Item
|
||||
{
|
||||
height: 30
|
||||
width: parent.width
|
||||
|
||||
Image
|
||||
{
|
||||
id: statusImage
|
||||
source: jsonModel.isOnline(model.pgp_id) ?
|
||||
"icons/state-ok.png" :
|
||||
"icons/state-offline.png"
|
||||
|
||||
height: parent.height - 4
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 3
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
Text
|
||||
{
|
||||
text: model.name
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: statusImage.right
|
||||
anchors.leftMargin: 10
|
||||
}
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
onClicked:
|
||||
{
|
||||
stackView.push(
|
||||
"qrc:/TrustedNodeDetails.qml",
|
||||
{
|
||||
pgpName: model.name,
|
||||
pgpId: model.pgp_id,
|
||||
locations: jsonModel.getLocations(
|
||||
model.pgp_id)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: bottomButton
|
||||
text: qsTr("Add Trusted Node")
|
||||
anchors.bottom: parent.bottom
|
||||
onClicked: stackView.push("qrc:/AddTrustedNode.qml")
|
||||
width: parent.width
|
||||
}
|
||||
}
|
69
retroshare-qml-app/src/URI.js
Normal file
@ -0,0 +1,69 @@
|
||||
/*! URI.js v1.18.10 http://medialize.github.io/URI.js/ */
|
||||
/* build contains: URI.js */
|
||||
/*
|
||||
URI.js - Mutating URLs
|
||||
|
||||
Version: 1.18.10
|
||||
|
||||
Author: Rodney Rehm
|
||||
Web: http://medialize.github.io/URI.js/
|
||||
|
||||
Licensed under
|
||||
MIT License http://www.opensource.org/licenses/mit-license
|
||||
|
||||
*/
|
||||
(function(m,w){"object"===typeof module&&module.exports?module.exports=w(require("./punycode"),require("./IPv6"),require("./SecondLevelDomains")):"function"===typeof define&&define.amd?define(["./punycode","./IPv6","./SecondLevelDomains"],w):m.URI=w(m.punycode,m.IPv6,m.SecondLevelDomains,m)})(this,function(m,w,u,h){function d(a,b){var c=1<=arguments.length,g=2<=arguments.length;if(!(this instanceof d))return c?g?new d(a,b):new d(a):new d;if(void 0===a){if(c)throw new TypeError("undefined is not a valid argument for URI");
|
||||
a="undefined"!==typeof location?location.href+"":""}if(null===a&&c)throw new TypeError("null is not a valid argument for URI");this.href(a);return void 0!==b?this.absoluteTo(b):this}function r(a){return a.replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")}function v(a){return void 0===a?"Undefined":String(Object.prototype.toString.call(a)).slice(8,-1)}function l(a){return"Array"===v(a)}function E(a,b){var c={},d,f;if("RegExp"===v(b))c=null;else if(l(b))for(d=0,f=b.length;d<f;d++)c[b[d]]=!0;else c[b]=!0;
|
||||
d=0;for(f=a.length;d<f;d++)if(c&&void 0!==c[a[d]]||!c&&b.test(a[d]))a.splice(d,1),f--,d--;return a}function A(a,b){var c,d;if(l(b)){c=0;for(d=b.length;c<d;c++)if(!A(a,b[c]))return!1;return!0}var f=v(b);c=0;for(d=a.length;c<d;c++)if("RegExp"===f){if("string"===typeof a[c]&&a[c].match(b))return!0}else if(a[c]===b)return!0;return!1}function F(a,b){if(!l(a)||!l(b)||a.length!==b.length)return!1;a.sort();b.sort();for(var c=0,d=a.length;c<d;c++)if(a[c]!==b[c])return!1;return!0}function B(a){return a.replace(/^\/+|\/+$/g,
|
||||
"")}function H(a){return escape(a)}function C(a){return encodeURIComponent(a).replace(/[!'()*]/g,H).replace(/\*/g,"%2A")}function x(a){return function(b,c){if(void 0===b)return this._parts[a]||"";this._parts[a]=b||null;this.build(!c);return this}}function G(a,b){return function(c,d){if(void 0===c)return this._parts[a]||"";null!==c&&(c+="",c.charAt(0)===b&&(c=c.substring(1)));this._parts[a]=c;this.build(!d);return this}}var I=h&&h.URI;d.version="1.18.10";var e=d.prototype,n=Object.prototype.hasOwnProperty;
|
||||
d._parts=function(){return{protocol:null,username:null,password:null,hostname:null,urn:null,port:null,path:null,query:null,fragment:null,duplicateQueryParameters:d.duplicateQueryParameters,escapeQuerySpace:d.escapeQuerySpace}};d.duplicateQueryParameters=!1;d.escapeQuerySpace=!0;d.protocol_expression=/^[a-z][a-z0-9.+-]*$/i;d.idn_expression=/[^a-z0-9\.-]/i;d.punycode_expression=/(xn--)/i;d.ip4_expression=/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;d.ip6_expression=/^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
|
||||
d.find_uri_expression=/\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u2018\u2019]))/ig;d.findUri={start:/\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,end:/[\s\r\n]|$/,trim:/[`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u201e\u2018\u2019]+$/,parens:/(\([^\)]*\)|\[[^\]]*\]|\{[^}]*\}|<[^>]*>)/g};d.defaultPorts={http:"80",https:"443",ftp:"21",
|
||||
gopher:"70",ws:"80",wss:"443"};d.invalid_hostname_characters=/[^a-zA-Z0-9\.-]/;d.domAttributes={a:"href",blockquote:"cite",link:"href",base:"href",script:"src",form:"action",img:"src",area:"href",iframe:"src",embed:"src",source:"src",track:"src",input:"src",audio:"src",video:"src"};d.getDomAttribute=function(a){if(a&&a.nodeName){var b=a.nodeName.toLowerCase();if("input"!==b||"image"===a.type)return d.domAttributes[b]}};d.encode=C;d.decode=decodeURIComponent;d.iso8859=function(){d.encode=escape;d.decode=
|
||||
unescape};d.unicode=function(){d.encode=C;d.decode=decodeURIComponent};d.characters={pathname:{encode:{expression:/%(24|26|2B|2C|3B|3D|3A|40)/ig,map:{"%24":"$","%26":"&","%2B":"+","%2C":",","%3B":";","%3D":"=","%3A":":","%40":"@"}},decode:{expression:/[\/\?#]/g,map:{"/":"%2F","?":"%3F","#":"%23"}}},reserved:{encode:{expression:/%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,map:{"%3A":":","%2F":"/","%3F":"?","%23":"#","%5B":"[","%5D":"]","%40":"@","%21":"!","%24":"$","%26":"&","%27":"'",
|
||||
"%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"="}}},urnpath:{encode:{expression:/%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig,map:{"%21":"!","%24":"$","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"=","%40":"@"}},decode:{expression:/[\/\?#:]/g,map:{"/":"%2F","?":"%3F","#":"%23",":":"%3A"}}}};d.encodeQuery=function(a,b){var c=d.encode(a+"");void 0===b&&(b=d.escapeQuerySpace);return b?c.replace(/%20/g,"+"):c};d.decodeQuery=function(a,b){a+="";void 0===b&&
|
||||
(b=d.escapeQuerySpace);try{return d.decode(b?a.replace(/\+/g,"%20"):a)}catch(c){return a}};var t={encode:"encode",decode:"decode"},y,D=function(a,b){return function(c){try{return d[b](c+"").replace(d.characters[a][b].expression,function(c){return d.characters[a][b].map[c]})}catch(g){return c}}};for(y in t)d[y+"PathSegment"]=D("pathname",t[y]),d[y+"UrnPathSegment"]=D("urnpath",t[y]);t=function(a,b,c){return function(g){var f;f=c?function(a){return d[b](d[c](a))}:d[b];g=(g+"").split(a);for(var e=0,
|
||||
k=g.length;e<k;e++)g[e]=f(g[e]);return g.join(a)}};d.decodePath=t("/","decodePathSegment");d.decodeUrnPath=t(":","decodeUrnPathSegment");d.recodePath=t("/","encodePathSegment","decode");d.recodeUrnPath=t(":","encodeUrnPathSegment","decode");d.encodeReserved=D("reserved","encode");d.parse=function(a,b){var c;b||(b={});c=a.indexOf("#");-1<c&&(b.fragment=a.substring(c+1)||null,a=a.substring(0,c));c=a.indexOf("?");-1<c&&(b.query=a.substring(c+1)||null,a=a.substring(0,c));"//"===a.substring(0,2)?(b.protocol=
|
||||
null,a=a.substring(2),a=d.parseAuthority(a,b)):(c=a.indexOf(":"),-1<c&&(b.protocol=a.substring(0,c)||null,b.protocol&&!b.protocol.match(d.protocol_expression)?b.protocol=void 0:"//"===a.substring(c+1,c+3)?(a=a.substring(c+3),a=d.parseAuthority(a,b)):(a=a.substring(c+1),b.urn=!0)));b.path=a;return b};d.parseHost=function(a,b){a=a.replace(/\\/g,"/");var c=a.indexOf("/"),d;-1===c&&(c=a.length);if("["===a.charAt(0))d=a.indexOf("]"),b.hostname=a.substring(1,d)||null,b.port=a.substring(d+2,c)||null,"/"===
|
||||
b.port&&(b.port=null);else{var f=a.indexOf(":");d=a.indexOf("/");f=a.indexOf(":",f+1);-1!==f&&(-1===d||f<d)?(b.hostname=a.substring(0,c)||null,b.port=null):(d=a.substring(0,c).split(":"),b.hostname=d[0]||null,b.port=d[1]||null)}b.hostname&&"/"!==a.substring(c).charAt(0)&&(c++,a="/"+a);return a.substring(c)||"/"};d.parseAuthority=function(a,b){a=d.parseUserinfo(a,b);return d.parseHost(a,b)};d.parseUserinfo=function(a,b){var c=a.indexOf("/"),g=a.lastIndexOf("@",-1<c?c:a.length-1);-1<g&&(-1===c||g<c)?
|
||||
(c=a.substring(0,g).split(":"),b.username=c[0]?d.decode(c[0]):null,c.shift(),b.password=c[0]?d.decode(c.join(":")):null,a=a.substring(g+1)):(b.username=null,b.password=null);return a};d.parseQuery=function(a,b){if(!a)return{};a=a.replace(/&+/g,"&").replace(/^\?*&*|&+$/g,"");if(!a)return{};for(var c={},g=a.split("&"),f=g.length,e,k,l=0;l<f;l++)if(e=g[l].split("="),k=d.decodeQuery(e.shift(),b),e=e.length?d.decodeQuery(e.join("="),b):null,n.call(c,k)){if("string"===typeof c[k]||null===c[k])c[k]=[c[k]];
|
||||
c[k].push(e)}else c[k]=e;return c};d.build=function(a){var b="";a.protocol&&(b+=a.protocol+":");a.urn||!b&&!a.hostname||(b+="//");b+=d.buildAuthority(a)||"";"string"===typeof a.path&&("/"!==a.path.charAt(0)&&"string"===typeof a.hostname&&(b+="/"),b+=a.path);"string"===typeof a.query&&a.query&&(b+="?"+a.query);"string"===typeof a.fragment&&a.fragment&&(b+="#"+a.fragment);return b};d.buildHost=function(a){var b="";if(a.hostname)b=d.ip6_expression.test(a.hostname)?b+("["+a.hostname+"]"):b+a.hostname;
|
||||
else return"";a.port&&(b+=":"+a.port);return b};d.buildAuthority=function(a){return d.buildUserinfo(a)+d.buildHost(a)};d.buildUserinfo=function(a){var b="";a.username&&(b+=d.encode(a.username));a.password&&(b+=":"+d.encode(a.password));b&&(b+="@");return b};d.buildQuery=function(a,b,c){var g="",f,e,k,h;for(e in a)if(n.call(a,e)&&e)if(l(a[e]))for(f={},k=0,h=a[e].length;k<h;k++)void 0!==a[e][k]&&void 0===f[a[e][k]+""]&&(g+="&"+d.buildQueryParameter(e,a[e][k],c),!0!==b&&(f[a[e][k]+""]=!0));else void 0!==
|
||||
a[e]&&(g+="&"+d.buildQueryParameter(e,a[e],c));return g.substring(1)};d.buildQueryParameter=function(a,b,c){return d.encodeQuery(a,c)+(null!==b?"="+d.encodeQuery(b,c):"")};d.addQuery=function(a,b,c){if("object"===typeof b)for(var g in b)n.call(b,g)&&d.addQuery(a,g,b[g]);else if("string"===typeof b)void 0===a[b]?a[b]=c:("string"===typeof a[b]&&(a[b]=[a[b]]),l(c)||(c=[c]),a[b]=(a[b]||[]).concat(c));else throw new TypeError("URI.addQuery() accepts an object, string as the name parameter");};d.removeQuery=
|
||||
function(a,b,c){var g;if(l(b))for(c=0,g=b.length;c<g;c++)a[b[c]]=void 0;else if("RegExp"===v(b))for(g in a)b.test(g)&&(a[g]=void 0);else if("object"===typeof b)for(g in b)n.call(b,g)&&d.removeQuery(a,g,b[g]);else if("string"===typeof b)void 0!==c?"RegExp"===v(c)?!l(a[b])&&c.test(a[b])?a[b]=void 0:a[b]=E(a[b],c):a[b]!==String(c)||l(c)&&1!==c.length?l(a[b])&&(a[b]=E(a[b],c)):a[b]=void 0:a[b]=void 0;else throw new TypeError("URI.removeQuery() accepts an object, string, RegExp as the first parameter");
|
||||
};d.hasQuery=function(a,b,c,g){switch(v(b)){case "String":break;case "RegExp":for(var f in a)if(n.call(a,f)&&b.test(f)&&(void 0===c||d.hasQuery(a,f,c)))return!0;return!1;case "Object":for(var e in b)if(n.call(b,e)&&!d.hasQuery(a,e,b[e]))return!1;return!0;default:throw new TypeError("URI.hasQuery() accepts a string, regular expression or object as the name parameter");}switch(v(c)){case "Undefined":return b in a;case "Boolean":return a=!(l(a[b])?!a[b].length:!a[b]),c===a;case "Function":return!!c(a[b],
|
||||
b,a);case "Array":return l(a[b])?(g?A:F)(a[b],c):!1;case "RegExp":return l(a[b])?g?A(a[b],c):!1:!(!a[b]||!a[b].match(c));case "Number":c=String(c);case "String":return l(a[b])?g?A(a[b],c):!1:a[b]===c;default:throw new TypeError("URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter");}};d.joinPaths=function(){for(var a=[],b=[],c=0,g=0;g<arguments.length;g++){var f=new d(arguments[g]);a.push(f);for(var f=f.segment(),e=0;e<f.length;e++)"string"===typeof f[e]&&
|
||||
b.push(f[e]),f[e]&&c++}if(!b.length||!c)return new d("");b=(new d("")).segment(b);""!==a[0].path()&&"/"!==a[0].path().slice(0,1)||b.path("/"+b.path());return b.normalize()};d.commonPath=function(a,b){var c=Math.min(a.length,b.length),d;for(d=0;d<c;d++)if(a.charAt(d)!==b.charAt(d)){d--;break}if(1>d)return a.charAt(0)===b.charAt(0)&&"/"===a.charAt(0)?"/":"";if("/"!==a.charAt(d)||"/"!==b.charAt(d))d=a.substring(0,d).lastIndexOf("/");return a.substring(0,d+1)};d.withinString=function(a,b,c){c||(c={});
|
||||
var g=c.start||d.findUri.start,e=c.end||d.findUri.end,z=c.trim||d.findUri.trim,k=c.parens||d.findUri.parens,l=/[a-z0-9-]=["']?$/i;for(g.lastIndex=0;;){var h=g.exec(a);if(!h)break;var m=h.index;if(c.ignoreHtml){var p=a.slice(Math.max(m-3,0),m);if(p&&l.test(p))continue}for(var q=m+a.slice(m).search(e),p=a.slice(m,q),q=-1;;){var n=k.exec(p);if(!n)break;q=Math.max(q,n.index+n[0].length)}p=-1<q?p.slice(0,q)+p.slice(q).replace(z,""):p.replace(z,"");p.length<=h[0].length||c.ignore&&c.ignore.test(p)||(q=
|
||||
m+p.length,h=b(p,m,q,a),void 0===h?g.lastIndex=q:(h=String(h),a=a.slice(0,m)+h+a.slice(q),g.lastIndex=m+h.length))}g.lastIndex=0;return a};d.ensureValidHostname=function(a){if(a.match(d.invalid_hostname_characters)){if(!m)throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-] and Punycode.js is not available');if(m.toASCII(a).match(d.invalid_hostname_characters))throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-]');}};d.noConflict=function(a){if(a)return a=
|
||||
{URI:this.noConflict()},h.URITemplate&&"function"===typeof h.URITemplate.noConflict&&(a.URITemplate=h.URITemplate.noConflict()),h.IPv6&&"function"===typeof h.IPv6.noConflict&&(a.IPv6=h.IPv6.noConflict()),h.SecondLevelDomains&&"function"===typeof h.SecondLevelDomains.noConflict&&(a.SecondLevelDomains=h.SecondLevelDomains.noConflict()),a;h.URI===this&&(h.URI=I);return this};e.build=function(a){if(!0===a)this._deferred_build=!0;else if(void 0===a||this._deferred_build)this._string=d.build(this._parts),
|
||||
this._deferred_build=!1;return this};e.clone=function(){return new d(this)};e.valueOf=e.toString=function(){return this.build(!1)._string};e.protocol=x("protocol");e.username=x("username");e.password=x("password");e.hostname=x("hostname");e.port=x("port");e.query=G("query","?");e.fragment=G("fragment","#");e.search=function(a,b){var c=this.query(a,b);return"string"===typeof c&&c.length?"?"+c:c};e.hash=function(a,b){var c=this.fragment(a,b);return"string"===typeof c&&c.length?"#"+c:c};e.pathname=function(a,
|
||||
b){if(void 0===a||!0===a){var c=this._parts.path||(this._parts.hostname?"/":"");return a?(this._parts.urn?d.decodeUrnPath:d.decodePath)(c):c}this._parts.path=this._parts.urn?a?d.recodeUrnPath(a):"":a?d.recodePath(a):"/";this.build(!b);return this};e.path=e.pathname;e.href=function(a,b){var c;if(void 0===a)return this.toString();this._string="";this._parts=d._parts();var g=a instanceof d,e="object"===typeof a&&(a.hostname||a.path||a.pathname);a.nodeName&&(e=d.getDomAttribute(a),a=a[e]||"",e=!1);!g&&
|
||||
e&&void 0!==a.pathname&&(a=a.toString());if("string"===typeof a||a instanceof String)this._parts=d.parse(String(a),this._parts);else if(g||e)for(c in g=g?a._parts:a,g)n.call(this._parts,c)&&(this._parts[c]=g[c]);else throw new TypeError("invalid input");this.build(!b);return this};e.is=function(a){var b=!1,c=!1,g=!1,e=!1,z=!1,k=!1,h=!1,l=!this._parts.urn;this._parts.hostname&&(l=!1,c=d.ip4_expression.test(this._parts.hostname),g=d.ip6_expression.test(this._parts.hostname),b=c||g,z=(e=!b)&&u&&u.has(this._parts.hostname),
|
||||
k=e&&d.idn_expression.test(this._parts.hostname),h=e&&d.punycode_expression.test(this._parts.hostname));switch(a.toLowerCase()){case "relative":return l;case "absolute":return!l;case "domain":case "name":return e;case "sld":return z;case "ip":return b;case "ip4":case "ipv4":case "inet4":return c;case "ip6":case "ipv6":case "inet6":return g;case "idn":return k;case "url":return!this._parts.urn;case "urn":return!!this._parts.urn;case "punycode":return h}return null};var J=e.protocol,K=e.port,L=e.hostname;
|
||||
e.protocol=function(a,b){if(void 0!==a&&a&&(a=a.replace(/:(\/\/)?$/,""),!a.match(d.protocol_expression)))throw new TypeError('Protocol "'+a+"\" contains characters other than [A-Z0-9.+-] or doesn't start with [A-Z]");return J.call(this,a,b)};e.scheme=e.protocol;e.port=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0!==a&&(0===a&&(a=null),a&&(a+="",":"===a.charAt(0)&&(a=a.substring(1)),a.match(/[^0-9]/))))throw new TypeError('Port "'+a+'" contains characters other than [0-9]');
|
||||
return K.call(this,a,b)};e.hostname=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0!==a){var c={};if("/"!==d.parseHost(a,c))throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-]');a=c.hostname}return L.call(this,a,b)};e.origin=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){var c=this.protocol();return this.authority()?(c?c+"://":"")+this.authority():""}c=d(a);this.protocol(c.protocol()).authority(c.authority()).build(!b);return this};
|
||||
e.host=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a)return this._parts.hostname?d.buildHost(this._parts):"";if("/"!==d.parseHost(a,this._parts))throw new TypeError('Hostname "'+a+'" contains characters other than [A-Z0-9.-]');this.build(!b);return this};e.authority=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a)return this._parts.hostname?d.buildAuthority(this._parts):"";if("/"!==d.parseAuthority(a,this._parts))throw new TypeError('Hostname "'+
|
||||
a+'" contains characters other than [A-Z0-9.-]');this.build(!b);return this};e.userinfo=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){var c=d.buildUserinfo(this._parts);return c?c.substring(0,c.length-1):c}"@"!==a[a.length-1]&&(a+="@");d.parseUserinfo(a,this._parts);this.build(!b);return this};e.resource=function(a,b){var c;if(void 0===a)return this.path()+this.search()+this.hash();c=d.parse(a);this._parts.path=c.path;this._parts.query=c.query;this._parts.fragment=c.fragment;
|
||||
this.build(!b);return this};e.subdomain=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var c=this._parts.hostname.length-this.domain().length-1;return this._parts.hostname.substring(0,c)||""}c=this._parts.hostname.length-this.domain().length;c=this._parts.hostname.substring(0,c);c=new RegExp("^"+r(c));a&&"."!==a.charAt(a.length-1)&&(a+=".");a&&d.ensureValidHostname(a);this._parts.hostname=this._parts.hostname.replace(c,a);
|
||||
this.build(!b);return this};e.domain=function(a,b){if(this._parts.urn)return void 0===a?"":this;"boolean"===typeof a&&(b=a,a=void 0);if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var c=this._parts.hostname.match(/\./g);if(c&&2>c.length)return this._parts.hostname;c=this._parts.hostname.length-this.tld(b).length-1;c=this._parts.hostname.lastIndexOf(".",c-1)+1;return this._parts.hostname.substring(c)||""}if(!a)throw new TypeError("cannot set domain empty");d.ensureValidHostname(a);
|
||||
!this._parts.hostname||this.is("IP")?this._parts.hostname=a:(c=new RegExp(r(this.domain())+"$"),this._parts.hostname=this._parts.hostname.replace(c,a));this.build(!b);return this};e.tld=function(a,b){if(this._parts.urn)return void 0===a?"":this;"boolean"===typeof a&&(b=a,a=void 0);if(void 0===a){if(!this._parts.hostname||this.is("IP"))return"";var c=this._parts.hostname.lastIndexOf("."),c=this._parts.hostname.substring(c+1);return!0!==b&&u&&u.list[c.toLowerCase()]?u.get(this._parts.hostname)||c:c}if(a)if(a.match(/[^a-zA-Z0-9-]/))if(u&&
|
||||
u.is(a))c=new RegExp(r(this.tld())+"$"),this._parts.hostname=this._parts.hostname.replace(c,a);else throw new TypeError('TLD "'+a+'" contains characters other than [A-Z0-9]');else{if(!this._parts.hostname||this.is("IP"))throw new ReferenceError("cannot set TLD on non-domain host");c=new RegExp(r(this.tld())+"$");this._parts.hostname=this._parts.hostname.replace(c,a)}else throw new TypeError("cannot set TLD empty");this.build(!b);return this};e.directory=function(a,b){if(this._parts.urn)return void 0===
|
||||
a?"":this;if(void 0===a||!0===a){if(!this._parts.path&&!this._parts.hostname)return"";if("/"===this._parts.path)return"/";var c=this._parts.path.length-this.filename().length-1,c=this._parts.path.substring(0,c)||(this._parts.hostname?"/":"");return a?d.decodePath(c):c}c=this._parts.path.length-this.filename().length;c=this._parts.path.substring(0,c);c=new RegExp("^"+r(c));this.is("relative")||(a||(a="/"),"/"!==a.charAt(0)&&(a="/"+a));a&&"/"!==a.charAt(a.length-1)&&(a+="/");a=d.recodePath(a);this._parts.path=
|
||||
this._parts.path.replace(c,a);this.build(!b);return this};e.filename=function(a,b){if(this._parts.urn)return void 0===a?"":this;if("string"!==typeof a){if(!this._parts.path||"/"===this._parts.path)return"";var c=this._parts.path.lastIndexOf("/"),c=this._parts.path.substring(c+1);return a?d.decodePathSegment(c):c}c=!1;"/"===a.charAt(0)&&(a=a.substring(1));a.match(/\.?\//)&&(c=!0);var g=new RegExp(r(this.filename())+"$");a=d.recodePath(a);this._parts.path=this._parts.path.replace(g,a);c?this.normalizePath(b):
|
||||
this.build(!b);return this};e.suffix=function(a,b){if(this._parts.urn)return void 0===a?"":this;if(void 0===a||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";var c=this.filename(),g=c.lastIndexOf(".");if(-1===g)return"";c=c.substring(g+1);c=/^[a-z0-9%]+$/i.test(c)?c:"";return a?d.decodePathSegment(c):c}"."===a.charAt(0)&&(a=a.substring(1));if(c=this.suffix())g=a?new RegExp(r(c)+"$"):new RegExp(r("."+c)+"$");else{if(!a)return this;this._parts.path+="."+d.recodePath(a)}g&&(a=d.recodePath(a),
|
||||
this._parts.path=this._parts.path.replace(g,a));this.build(!b);return this};e.segment=function(a,b,c){var d=this._parts.urn?":":"/",e=this.path(),h="/"===e.substring(0,1),e=e.split(d);void 0!==a&&"number"!==typeof a&&(c=b,b=a,a=void 0);if(void 0!==a&&"number"!==typeof a)throw Error('Bad segment "'+a+'", must be 0-based integer');h&&e.shift();0>a&&(a=Math.max(e.length+a,0));if(void 0===b)return void 0===a?e:e[a];if(null===a||void 0===e[a])if(l(b)){e=[];a=0;for(var k=b.length;a<k;a++)if(b[a].length||
|
||||
e.length&&e[e.length-1].length)e.length&&!e[e.length-1].length&&e.pop(),e.push(B(b[a]))}else{if(b||"string"===typeof b)b=B(b),""===e[e.length-1]?e[e.length-1]=b:e.push(b)}else b?e[a]=B(b):e.splice(a,1);h&&e.unshift("");return this.path(e.join(d),c)};e.segmentCoded=function(a,b,c){var e,f;"number"!==typeof a&&(c=b,b=a,a=void 0);if(void 0===b){a=this.segment(a,b,c);if(l(a))for(e=0,f=a.length;e<f;e++)a[e]=d.decode(a[e]);else a=void 0!==a?d.decode(a):void 0;return a}if(l(b))for(e=0,f=b.length;e<f;e++)b[e]=
|
||||
d.encode(b[e]);else b="string"===typeof b||b instanceof String?d.encode(b):b;return this.segment(a,b,c)};var M=e.query;e.query=function(a,b){if(!0===a)return d.parseQuery(this._parts.query,this._parts.escapeQuerySpace);if("function"===typeof a){var c=d.parseQuery(this._parts.query,this._parts.escapeQuerySpace),e=a.call(this,c);this._parts.query=d.buildQuery(e||c,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace);this.build(!b);return this}return void 0!==a&&"string"!==typeof a?(this._parts.query=
|
||||
d.buildQuery(a,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace),this.build(!b),this):M.call(this,a,b)};e.setQuery=function(a,b,c){var e=d.parseQuery(this._parts.query,this._parts.escapeQuerySpace);if("string"===typeof a||a instanceof String)e[a]=void 0!==b?b:null;else if("object"===typeof a)for(var f in a)n.call(a,f)&&(e[f]=a[f]);else throw new TypeError("URI.addQuery() accepts an object, string as the name parameter");this._parts.query=d.buildQuery(e,this._parts.duplicateQueryParameters,
|
||||
this._parts.escapeQuerySpace);"string"!==typeof a&&(c=b);this.build(!c);return this};e.addQuery=function(a,b,c){var e=d.parseQuery(this._parts.query,this._parts.escapeQuerySpace);d.addQuery(e,a,void 0===b?null:b);this._parts.query=d.buildQuery(e,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace);"string"!==typeof a&&(c=b);this.build(!c);return this};e.removeQuery=function(a,b,c){var e=d.parseQuery(this._parts.query,this._parts.escapeQuerySpace);d.removeQuery(e,a,b);this._parts.query=
|
||||
d.buildQuery(e,this._parts.duplicateQueryParameters,this._parts.escapeQuerySpace);"string"!==typeof a&&(c=b);this.build(!c);return this};e.hasQuery=function(a,b,c){var e=d.parseQuery(this._parts.query,this._parts.escapeQuerySpace);return d.hasQuery(e,a,b,c)};e.setSearch=e.setQuery;e.addSearch=e.addQuery;e.removeSearch=e.removeQuery;e.hasSearch=e.hasQuery;e.normalize=function(){return this._parts.urn?this.normalizeProtocol(!1).normalizePath(!1).normalizeQuery(!1).normalizeFragment(!1).build():this.normalizeProtocol(!1).normalizeHostname(!1).normalizePort(!1).normalizePath(!1).normalizeQuery(!1).normalizeFragment(!1).build()};
|
||||
e.normalizeProtocol=function(a){"string"===typeof this._parts.protocol&&(this._parts.protocol=this._parts.protocol.toLowerCase(),this.build(!a));return this};e.normalizeHostname=function(a){this._parts.hostname&&(this.is("IDN")&&m?this._parts.hostname=m.toASCII(this._parts.hostname):this.is("IPv6")&&w&&(this._parts.hostname=w.best(this._parts.hostname)),this._parts.hostname=this._parts.hostname.toLowerCase(),this.build(!a));return this};e.normalizePort=function(a){"string"===typeof this._parts.protocol&&
|
||||
this._parts.port===d.defaultPorts[this._parts.protocol]&&(this._parts.port=null,this.build(!a));return this};e.normalizePath=function(a){var b=this._parts.path;if(!b)return this;if(this._parts.urn)return this._parts.path=d.recodeUrnPath(this._parts.path),this.build(!a),this;if("/"===this._parts.path)return this;var b=d.recodePath(b),c,e="",f,h;"/"!==b.charAt(0)&&(c=!0,b="/"+b);if("/.."===b.slice(-3)||"/."===b.slice(-2))b+="/";b=b.replace(/(\/(\.\/)+)|(\/\.$)/g,"/").replace(/\/{2,}/g,"/");c&&(e=b.substring(1).match(/^(\.\.\/)+/)||
|
||||
"")&&(e=e[0]);for(;;){f=b.search(/\/\.\.(\/|$)/);if(-1===f)break;else if(0===f){b=b.substring(3);continue}h=b.substring(0,f).lastIndexOf("/");-1===h&&(h=f);b=b.substring(0,h)+b.substring(f+3)}c&&this.is("relative")&&(b=e+b.substring(1));this._parts.path=b;this.build(!a);return this};e.normalizePathname=e.normalizePath;e.normalizeQuery=function(a){"string"===typeof this._parts.query&&(this._parts.query.length?this.query(d.parseQuery(this._parts.query,this._parts.escapeQuerySpace)):this._parts.query=
|
||||
null,this.build(!a));return this};e.normalizeFragment=function(a){this._parts.fragment||(this._parts.fragment=null,this.build(!a));return this};e.normalizeSearch=e.normalizeQuery;e.normalizeHash=e.normalizeFragment;e.iso8859=function(){var a=d.encode,b=d.decode;d.encode=escape;d.decode=decodeURIComponent;try{this.normalize()}finally{d.encode=a,d.decode=b}return this};e.unicode=function(){var a=d.encode,b=d.decode;d.encode=C;d.decode=unescape;try{this.normalize()}finally{d.encode=a,d.decode=b}return this};
|
||||
e.readable=function(){var a=this.clone();a.username("").password("").normalize();var b="";a._parts.protocol&&(b+=a._parts.protocol+"://");a._parts.hostname&&(a.is("punycode")&&m?(b+=m.toUnicode(a._parts.hostname),a._parts.port&&(b+=":"+a._parts.port)):b+=a.host());a._parts.hostname&&a._parts.path&&"/"!==a._parts.path.charAt(0)&&(b+="/");b+=a.path(!0);if(a._parts.query){for(var c="",e=0,f=a._parts.query.split("&"),h=f.length;e<h;e++){var k=(f[e]||"").split("="),c=c+("&"+d.decodeQuery(k[0],this._parts.escapeQuerySpace).replace(/&/g,
|
||||
"%26"));void 0!==k[1]&&(c+="="+d.decodeQuery(k[1],this._parts.escapeQuerySpace).replace(/&/g,"%26"))}b+="?"+c.substring(1)}return b+=d.decodeQuery(a.hash(),!0)};e.absoluteTo=function(a){var b=this.clone(),c=["protocol","username","password","hostname","port"],e,f;if(this._parts.urn)throw Error("URNs do not have any generally defined hierarchical components");a instanceof d||(a=new d(a));if(b._parts.protocol)return b;b._parts.protocol=a._parts.protocol;if(this._parts.hostname)return b;for(e=0;f=c[e];e++)b._parts[f]=
|
||||
a._parts[f];b._parts.path?(".."===b._parts.path.substring(-2)&&(b._parts.path+="/"),"/"!==b.path().charAt(0)&&(c=(c=a.directory())?c:0===a.path().indexOf("/")?"/":"",b._parts.path=(c?c+"/":"")+b._parts.path,b.normalizePath())):(b._parts.path=a._parts.path,b._parts.query||(b._parts.query=a._parts.query));b.build();return b};e.relativeTo=function(a){var b=this.clone().normalize(),c,e,f;if(b._parts.urn)throw Error("URNs do not have any generally defined hierarchical components");a=(new d(a)).normalize();
|
||||
c=b._parts;e=a._parts;f=b.path();a=a.path();if("/"!==f.charAt(0))throw Error("URI is already relative");if("/"!==a.charAt(0))throw Error("Cannot calculate a URI relative to another relative URI");c.protocol===e.protocol&&(c.protocol=null);if(c.username===e.username&&c.password===e.password&&null===c.protocol&&null===c.username&&null===c.password&&c.hostname===e.hostname&&c.port===e.port)c.hostname=null,c.port=null;else return b.build();if(f===a)return c.path="",b.build();f=d.commonPath(f,a);if(!f)return b.build();
|
||||
e=e.path.substring(f.length).replace(/[^\/]*$/,"").replace(/.*?\//g,"../");c.path=e+c.path.substring(f.length)||"./";return b.build()};e.equals=function(a){var b=this.clone(),c=new d(a),e;a={};var f,h;b.normalize();c.normalize();if(b.toString()===c.toString())return!0;f=b.query();e=c.query();b.query("");c.query("");if(b.toString()!==c.toString()||f.length!==e.length)return!1;b=d.parseQuery(f,this._parts.escapeQuerySpace);e=d.parseQuery(e,this._parts.escapeQuerySpace);for(h in b)if(n.call(b,h)){if(!l(b[h])){if(b[h]!==
|
||||
e[h])return!1}else if(!F(b[h],e[h]))return!1;a[h]=!0}for(h in e)if(n.call(e,h)&&!a[h])return!1;return!0};e.duplicateQueryParameters=function(a){this._parts.duplicateQueryParameters=!!a;return this};e.escapeQuerySpace=function(a){this._parts.escapeQuerySpace=!!a;return this};return d});
|
@ -1,16 +1,34 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest package="org.retroshare.android.qml_app" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
|
||||
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="RetroShare">
|
||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name=".RetroShareQmlActivity" android:label="RetroShare QML" android:screenOrientation="unspecified" android:launchMode="singleTop">
|
||||
<manifest package="org.retroshare.android.qml_app"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionName="1.0" android:versionCode="1"
|
||||
android:installLocation="auto">
|
||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication"
|
||||
android:hardwareAccelerated="true"
|
||||
android:label="RetroShare"
|
||||
android:icon="@drawable/retroshare06_128x128">
|
||||
<activity android:name=".RetroShareQmlActivity"
|
||||
android:label="RetroShare"
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||
android:screenOrientation="unspecified"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
<!-- Application to launch -->
|
||||
<meta-data android:name="android.app.lib_name" android:value="retroshare-qml-app"/>
|
||||
<!-- Application to launch -->
|
||||
<!-- Register for retroshare:// and rtsh:// link handling -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="retroshare" />
|
||||
<data android:scheme="rtsh" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Qt Application to launch -->
|
||||
<meta-data android:name="android.app.lib_name"
|
||||
android:value="retroshare-qml-app"/>
|
||||
<!-- Application arguments -->
|
||||
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
|
||||
<!-- Application arguments -->
|
||||
@ -55,28 +73,33 @@
|
||||
<!-- extract android style -->
|
||||
<!-- available android:values :
|
||||
* full - useful QWidget & Quick Controls 1 apps
|
||||
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
|
||||
* none - useful for apps that don't use any of the above Qt modules
|
||||
* minimal - useful for Quick Controls 2 apps, it is much faster
|
||||
than "full"
|
||||
* none - useful for apps that don't use any of the above Qt
|
||||
modules
|
||||
-->
|
||||
<meta-data android:name="android.app.extract_android_style" android:value="full"/>
|
||||
<meta-data android:name="android.app.extract_android_style"
|
||||
android:value="minimal"/>
|
||||
<!-- extract android style -->
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".BootCompletedReceiver" android:enabled="true" android:exported="false">
|
||||
<receiver android:name=".BootCompletedReceiver" android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".AppUpdatedReceiver" android:enabled="true" android:exported="false">
|
||||
<receiver android:name=".AppUpdatedReceiver" android:enabled="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
<service android:process=":rs" android:name=".RetroShareAndroidService" android:label="RetroShare Service" android:exported="true"> <!-- Added to be able to run the service from adb shell -->
|
||||
<!-- android:process=":qt" is needed to force the service to run on a separate process than the Activity -->
|
||||
<service android:name=".RetroShareAndroidService" android:process=":rs"
|
||||
android:label="RetroShare Service" android:exported="true">
|
||||
<!-- android:exported="true" Added to be able to run the service from adb shell -->
|
||||
<!-- android:process=":rs" is needed to force the service to run on a separate process than the Activity -->
|
||||
|
||||
|
||||
<!-- Application arguments -->
|
||||
@ -88,8 +111,58 @@
|
||||
one is which.
|
||||
-->
|
||||
|
||||
<!-- Qt Application to launch -->
|
||||
<meta-data android:name="android.app.lib_name"
|
||||
android:value="retroshare-android-service"/>
|
||||
<!-- Application to launch -->
|
||||
<meta-data android:name="android.app.lib_name" android:value="retroshare-android-service"/>
|
||||
|
||||
<!-- Ministro -->
|
||||
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
||||
<!-- Deploy Qt libs as part of package -->
|
||||
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
|
||||
<!-- Run with local libs -->
|
||||
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
|
||||
<meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
||||
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
|
||||
<!-- Messages maps -->
|
||||
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
|
||||
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
|
||||
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
|
||||
<!-- Messages maps -->
|
||||
|
||||
|
||||
<!-- Background running -->
|
||||
<meta-data android:name="android.app.background_running" android:value="true"/>
|
||||
<!-- Background running -->
|
||||
</service>
|
||||
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
<service android:name=".RetroShareAndroidNotifyService"
|
||||
android:label="RetroShare Notify Service"
|
||||
android:process=":nf"
|
||||
android:exported="true">
|
||||
<!-- android:exported="true" Added to be able to run the service from adb shell -->
|
||||
<!-- android:process=":nf" is needed to force the service to run on a separate process than the Activity -->
|
||||
|
||||
|
||||
<!-- Application arguments -->
|
||||
<!-- meta-data android:name="android.app.arguments" android:value="-service"/ -->
|
||||
<!-- Application arguments -->
|
||||
|
||||
<!-- If you are using the same application (.so file) for activity and also for service, then you
|
||||
need to use *android.app.arguments* to pass some arguments to your service in order to know which
|
||||
one is which.
|
||||
-->
|
||||
|
||||
<!-- Application to launch -->
|
||||
<meta-data android:name="android.app.lib_name" android:value="retroshare-android-notify-service"/>
|
||||
<!-- Application to launch -->
|
||||
|
||||
<!-- Ministro -->
|
||||
|
@ -9,88 +9,86 @@
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
||||
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
||||
<afterSyncTasks>
|
||||
<task>generateDebugSources</task>
|
||||
</afterSyncTasks>
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/../../../../qt5/qtbase/src/android/java/res" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/../../../../qt5/qtbase/src/android/java/res;file://$MODULE_DIR$/res" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/../../../../../../../opt/Qt5.8.0/5.8/android_armv7/src/android/java/res" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file:///opt/Qt5.8.0/5.8/android_armv7/src/android/java/res;file://$MODULE_DIR$/res" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/.build/intermediates/classes/debug" />
|
||||
<output-test url="file://$MODULE_DIR$/.build/intermediates/classes/test/debug" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$/../../../../qt5/qtbase/src/android/java/res">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../../../qt5/qtbase/src/android/java/res" type="java-resource" />
|
||||
</content>
|
||||
<content url="file://$MODULE_DIR$/../../../../qt5/qtbase/src/android/java/src">
|
||||
<sourceFolder url="file://$MODULE_DIR$/../../../../qt5/qtbase/src/android/java/src" isTestSource="false" />
|
||||
</content>
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/source/r/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/source/aidl/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/source/rs/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/source/apt/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/res/rs/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/res/generated/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/res/resValues/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/res/rs/androidTest/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/res/generated/androidTest/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/.build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/assets" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/assets" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/blame" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/mockable-android-18.jar" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/resources" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/rs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.build/outputs" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 18 Platform" jdkType="Android SDK" />
|
||||
<content url="file:///opt/Qt5.8.0/5.8/android_armv7/src/android/java/res">
|
||||
<sourceFolder url="file:///opt/Qt5.8.0/5.8/android_armv7/src/android/java/res" type="java-resource" />
|
||||
</content>
|
||||
<content url="file:///opt/Qt5.8.0/5.8/android_armv7/src/android/java/src">
|
||||
<sourceFolder url="file:///opt/Qt5.8.0/5.8/android_armv7/src/android/java/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" scope="TEST" name="mockable-android-18" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -4,7 +4,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.1.0'
|
||||
classpath 'com.android.tools.build:gradle:2.2.3'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,6 @@
|
||||
# as it contains information specific to your local configuration.
|
||||
|
||||
androidBuildToolsVersion=24.0.1
|
||||
androidCompileSdkVersion=18
|
||||
androidCompileSdkVersion=23
|
||||
buildDir=.build
|
||||
qt5AndroidDir=/opt/Qt5.7.0/5.7/android_armv7/src/android/java
|
||||
qt5AndroidDir=/opt/Qt5.8.0/5.8/android_armv7/src/android/java
|
||||
|
@ -1,6 +1,6 @@
|
||||
#Wed Apr 10 15:27:10 PDT 2013
|
||||
#Fri Apr 07 17:55:25 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
|
||||
|
@ -0,0 +1 @@
|
||||
../../../../../data/128x128/apps/retroshare06.png
|
@ -0,0 +1 @@
|
||||
../../../../../data/48x48/apps/retroshare06.png
|
43
retroshare-qml-app/src/android/src/NativeCalls.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
#include "NativeCalls.h"
|
||||
#include "rsqmlappengine.h"
|
||||
|
||||
#include <QMetaObject>
|
||||
#include <QDebug>
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_retroshare_android_qml_1app_jni_NativeCalls_notifyIntentUri
|
||||
(JNIEnv* env, jclass, jstring uri)
|
||||
{
|
||||
qDebug() << __PRETTY_FUNCTION__;
|
||||
|
||||
const char *uriBytes = env->GetStringUTFChars(uri, NULL);
|
||||
QString uriStr(uriBytes);
|
||||
env->ReleaseStringUTFChars(uri, uriBytes);
|
||||
|
||||
RsQmlAppEngine* engine = RsQmlAppEngine::mainInstance();
|
||||
if(engine)
|
||||
QMetaObject::invokeMethod(
|
||||
engine, "handleUri",
|
||||
Qt::QueuedConnection, // BlockingQueuedConnection, AutoConnection
|
||||
Q_ARG(QString, uriStr));
|
||||
else qCritical() << __PRETTY_FUNCTION__ << "RsQmlAppEngine::mainInstance()"
|
||||
<< "not initialized yet!";
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* RetroShare Android Service
|
||||
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
|
||||
* Copyright (C) 2016-2017 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
|
||||
@ -28,9 +28,16 @@ public class AppUpdatedReceiver extends BroadcastReceiver
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
Log.i("AppUpdatedReceiver", "onReceive() Stopping RetroShareAndroidNotifyService After Update");
|
||||
Intent nsIntent = new Intent(context, RetroShareAndroidNotifyService.class);
|
||||
context.stopService(nsIntent);
|
||||
|
||||
Log.i("AppUpdatedReceiver", "onReceive() Restarting RetroShare Android Service After Update");
|
||||
Intent myIntent = new Intent(context, RetroShareAndroidService.class);
|
||||
context.stopService(myIntent);
|
||||
context.startService(myIntent);
|
||||
Intent coreIntent = new Intent(context, RetroShareAndroidService.class);
|
||||
context.stopService(coreIntent);
|
||||
context.startService(coreIntent);
|
||||
|
||||
Log.i("AppUpdatedReceiver", "onReceive() Starting RetroShareAndroidNotifyService After Update");
|
||||
context.startService(nsIntent);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* RetroShare Android Service
|
||||
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
|
||||
* Copyright (C) 2016-2017 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
|
||||
@ -21,13 +21,19 @@ package org.retroshare.android.qml_app;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
public class BootCompletedReceiver extends BroadcastReceiver
|
||||
{
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
Intent myIntent = new Intent(context, RetroShareAndroidService.class);
|
||||
context.startService(myIntent);
|
||||
Log.i("BootCompletedReceiver", "onReceive() Starting RetroShareAndroidService on boot");
|
||||
Intent coreIntent = new Intent(context, RetroShareAndroidService.class);
|
||||
context.startService(coreIntent);
|
||||
|
||||
Log.i("BootCompletedReceiver", "onReceive() Starting RetroShareAndroidNotifyService on boot");
|
||||
Intent nsIntent = new Intent(context, RetroShareAndroidNotifyService.class);
|
||||
context.startService(nsIntent);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* RetroShare Android Service
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
package org.retroshare.android.qml_app;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.qtproject.qt5.android.bindings.QtService;
|
||||
|
||||
public class RetroShareAndroidNotifyService extends QtService
|
||||
{
|
||||
@UsedByNativeCode @SuppressWarnings("unused")
|
||||
public void notify(String title, String text, String uri)
|
||||
{
|
||||
Notification.Builder mBuilder = new Notification.Builder(this);
|
||||
mBuilder.setSmallIcon(R.drawable.retroshare06_48x48)
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setAutoCancel(true)
|
||||
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
|
||||
|
||||
Intent intent = new Intent(this, RetroShareQmlActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
|
||||
if(!uri.isEmpty()) intent.setData(Uri.parse(uri));
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(
|
||||
this, NOTIFY_REQ_CODE, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
mBuilder.setContentIntent(pendingIntent);
|
||||
NotificationManager mNotificationManager =
|
||||
(NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mNotificationManager.notify(0, mBuilder.build());
|
||||
}
|
||||
|
||||
/** Must not be 0 otherwise a new activity may be created when should not
|
||||
* (ex. the activity is already visible/on top) and deadlocks happens */
|
||||
private static final int NOTIFY_REQ_CODE = 2173;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
|
||||
* Copyright (C) 2016-2017 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
|
||||
@ -26,11 +26,15 @@ import android.util.Log;
|
||||
|
||||
import org.qtproject.qt5.android.bindings.QtActivity;
|
||||
|
||||
import org.retroshare.android.qml_app.jni.NativeCalls;
|
||||
|
||||
public class RetroShareQmlActivity extends QtActivity
|
||||
{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
Log.i("RetroShareQmlActivity", "onCreate()");
|
||||
|
||||
if (!isMyServiceRunning(RetroShareAndroidService.class))
|
||||
{
|
||||
Log.i("RetroShareQmlActivity", "onCreate(): RetroShareAndroidService is not running, let's start it by Intent");
|
||||
@ -39,9 +43,28 @@ public class RetroShareQmlActivity extends QtActivity
|
||||
}
|
||||
else Log.v("RetroShareQmlActivity", "onCreate(): RetroShareAndroidService already running");
|
||||
|
||||
if (!isMyServiceRunning(RetroShareAndroidNotifyService.class))
|
||||
{
|
||||
Log.i("RetroShareQmlActivity", "onCreate(): RetroShareAndroidNotifyService is not running, let's start it by Intent");
|
||||
Intent rsIntent = new Intent(this, RetroShareAndroidNotifyService.class);
|
||||
startService(rsIntent);
|
||||
}
|
||||
else Log.v("RetroShareQmlActivity", "onCreate(): RetroShareAndroidNotifyService already running");
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent)
|
||||
{
|
||||
Log.i("RetroShareQmlActivity", "onNewIntent(Intent intent)");
|
||||
|
||||
super.onNewIntent(intent);
|
||||
|
||||
String uri = intent.getDataString();
|
||||
if (uri != null) NativeCalls.notifyIntentUri(uri);
|
||||
}
|
||||
|
||||
private boolean isMyServiceRunning(Class<?> serviceClass)
|
||||
{
|
||||
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
|
||||
|
@ -0,0 +1,3 @@
|
||||
package org.retroshare.android.qml_app;
|
||||
|
||||
public @interface UsedByNativeCode {}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* RetroShare Android Service
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
package org.retroshare.android.qml_app.jni;
|
||||
|
||||
public class NativeCalls
|
||||
{
|
||||
public static native void notifyIntentUri(String uri);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/* DO NOT EDIT THIS FILE - it is machine generated */
|
||||
#include <jni.h>
|
||||
/* Header for class org_retroshare_android_qml_app_jni_NativeCalls */
|
||||
|
||||
#ifndef _Included_org_retroshare_android_qml_app_jni_NativeCalls
|
||||
#define _Included_org_retroshare_android_qml_app_jni_NativeCalls
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/*
|
||||
* Class: org_retroshare_android_qml_app_jni_NativeCalls
|
||||
* Method: notifyIntentUri
|
||||
* Signature: (Ljava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_org_retroshare_android_qml_1app_jni_NativeCalls_notifyIntentUri
|
||||
(JNIEnv *, jclass, jstring);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
BIN
retroshare-qml-app/src/icons/application-menu.png
Normal file
After Width: | Height: | Size: 387 B |
BIN
retroshare-qml-app/src/icons/edit-find.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
retroshare-qml-app/src/icons/edit-image-face-detect.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
retroshare-qml-app/src/icons/emblem-locked.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
retroshare-qml-app/src/icons/rating-unrated.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
retroshare-qml-app/src/icons/rating.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
1
retroshare-qml-app/src/icons/retroshare06.png
Symbolic link
@ -0,0 +1 @@
|
||||
../../../data/128x128/apps/retroshare06.png
|
BIN
retroshare-qml-app/src/icons/state-offline.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
retroshare-qml-app/src/icons/state-ok.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
|
||||
* Copyright (C) 2016-2017 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
|
||||
@ -20,46 +20,120 @@
|
||||
#include "libresapilocalclient.h"
|
||||
|
||||
#include <QJSEngine>
|
||||
#include <QtDebug>
|
||||
|
||||
|
||||
void LibresapiLocalClient::openConnection(QString socketPath)
|
||||
void LibresapiLocalClient::openConnection(const QString& socketPath)
|
||||
{
|
||||
connect(& mLocalSocket, SIGNAL(error(QLocalSocket::LocalSocketError)),
|
||||
this, SLOT(socketError(QLocalSocket::LocalSocketError)));
|
||||
connect(& mLocalSocket, SIGNAL(readyRead()),
|
||||
this, SLOT(read()));
|
||||
mLocalSocket.connectToServer(socketPath);
|
||||
mSocketPath = socketPath;
|
||||
socketConnectAttempt();
|
||||
}
|
||||
|
||||
int LibresapiLocalClient::request( const QString& path, const QString& jsonData,
|
||||
QJSValue callback )
|
||||
{
|
||||
#ifdef QT_DEBUG
|
||||
if(mDebug)
|
||||
qDebug() << reqCount++ << __PRETTY_FUNCTION__ << path << jsonData
|
||||
<< callback.toString();
|
||||
#endif // QT_DEBUG
|
||||
|
||||
QByteArray data;
|
||||
data.append(path); data.append('\n');
|
||||
data.append(jsonData); data.append('\n');
|
||||
callbackQueue.enqueue(callback);
|
||||
mLocalSocket.write(data);
|
||||
|
||||
return 1;
|
||||
processingQueue.enqueue(PQRecord(path, jsonData, callback));
|
||||
int ret = mLocalSocket.write(data);
|
||||
if(ret < 0) socketError(mLocalSocket.error());
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LibresapiLocalClient::socketError(QLocalSocket::LocalSocketError)
|
||||
void LibresapiLocalClient::socketError(QLocalSocket::LocalSocketError error)
|
||||
{
|
||||
qDebug() << "Socket Eerror!!" << mLocalSocket.errorString();
|
||||
qCritical() << __PRETTY_FUNCTION__ << "Socket error! " << error
|
||||
<< mLocalSocket.errorString();
|
||||
|
||||
if(mLocalSocket.state() == QLocalSocket::UnconnectedState &&
|
||||
!mConnectAttemptTimer.isActive())
|
||||
{
|
||||
qDebug() << __PRETTY_FUNCTION__ << "Socket:" << mSocketPath
|
||||
<< "is not connected, scheduling a connect attempt again";
|
||||
|
||||
mConnectAttemptTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
void LibresapiLocalClient::read()
|
||||
{
|
||||
QString receivedMsg(mLocalSocket.readLine());
|
||||
QJSValue callback(callbackQueue.dequeue());
|
||||
if(callback.isCallable())
|
||||
if(processingQueue.isEmpty())
|
||||
{
|
||||
QJSValue params = callback.engine()->newObject();
|
||||
params.setProperty("response", receivedMsg);
|
||||
|
||||
callback.call(QJSValueList { params });
|
||||
qCritical() << __PRETTY_FUNCTION__ << "callbackQueue is empty "
|
||||
<< "something really fishy is happening!";
|
||||
return;
|
||||
}
|
||||
|
||||
if(!mLocalSocket.canReadLine())
|
||||
{
|
||||
qWarning() << __PRETTY_FUNCTION__
|
||||
<< "Strange, can't read a complete line!";
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray&& ba(mLocalSocket.readLine());
|
||||
if(ba.size() < 2)
|
||||
{
|
||||
qWarning() << __PRETTY_FUNCTION__ << "Got answer of less then 2 bytes,"
|
||||
<< "something fishy is happening!";
|
||||
return;
|
||||
}
|
||||
|
||||
QString receivedMsg(ba);
|
||||
PQRecord&& p(processingQueue.dequeue());
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
if(mDebug)
|
||||
qDebug() << ansCount++ << __PRETTY_FUNCTION__ << receivedMsg << p.mPath
|
||||
<< p.mJsonData << p.mCallback.toString();
|
||||
#endif // QT_DEBUG
|
||||
|
||||
emit goodResponseReceived(receivedMsg); /// @deprecated
|
||||
emit responseReceived(receivedMsg);
|
||||
|
||||
if(p.mCallback.isCallable())
|
||||
{
|
||||
QJSValue&& params(p.mCallback.engine()->newObject());
|
||||
params.setProperty("response", receivedMsg);
|
||||
|
||||
p.mCallback.call(QJSValueList { params });
|
||||
}
|
||||
|
||||
// In case of multiple reply coaleshed in the same signal
|
||||
if(mLocalSocket.bytesAvailable() > 0) read();
|
||||
}
|
||||
|
||||
LibresapiLocalClient::PQRecord::PQRecord( const QString&
|
||||
#ifdef QT_DEBUG
|
||||
path
|
||||
#endif //QT_DEBUG
|
||||
, const QString&
|
||||
#ifdef QT_DEBUG
|
||||
jsonData
|
||||
#endif //QT_DEBUG
|
||||
, const QJSValue& callback) :
|
||||
#ifdef QT_DEBUG
|
||||
mPath(path), mJsonData(jsonData),
|
||||
#endif //QT_DEBUG
|
||||
mCallback(callback) {}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
void LibresapiLocalClient::setDebug(bool v)
|
||||
{
|
||||
if(v != mDebug)
|
||||
{
|
||||
mDebug = v;
|
||||
emit debugChanged();
|
||||
}
|
||||
}
|
||||
#endif // QT_DEBUG
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
/*
|
||||
* libresapi local socket client
|
||||
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
|
||||
* Copyright (C) 2016-2017 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
|
||||
@ -17,29 +18,64 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LIBRESAPILOCALCLIENT_H
|
||||
#define LIBRESAPILOCALCLIENT_H
|
||||
|
||||
#include <QLocalSocket>
|
||||
#include <QQueue>
|
||||
#include <QJSValue>
|
||||
#include <QTimer>
|
||||
|
||||
class LibresapiLocalClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LibresapiLocalClient() : mLocalSocket(this) {}
|
||||
LibresapiLocalClient() :
|
||||
#ifdef QT_DEBUG
|
||||
reqCount(0), ansCount(0), mDebug(true),
|
||||
#endif // QT_DEBUG
|
||||
mLocalSocket(this)
|
||||
{
|
||||
mConnectAttemptTimer.setSingleShot(true);
|
||||
mConnectAttemptTimer.setInterval(500);
|
||||
connect(&mConnectAttemptTimer, SIGNAL(timeout()),
|
||||
this, SLOT(socketConnectAttempt()));
|
||||
}
|
||||
|
||||
Q_INVOKABLE int request( const QString& path, const QString& jsonData = "",
|
||||
QJSValue callback = QJSValue::NullValue);
|
||||
Q_INVOKABLE void openConnection(QString socketPath);
|
||||
QJSValue callback = QJSValue::NullValue );
|
||||
Q_INVOKABLE void openConnection(const QString& socketPath);
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
Q_PROPERTY(bool debug READ debug WRITE setDebug NOTIFY debugChanged)
|
||||
|
||||
bool debug() const { return mDebug; }
|
||||
void setDebug(bool v);
|
||||
|
||||
uint64_t reqCount;
|
||||
uint64_t ansCount;
|
||||
bool mDebug;
|
||||
#endif // QT_DEBUG
|
||||
|
||||
private:
|
||||
QTimer mConnectAttemptTimer;
|
||||
QString mSocketPath;
|
||||
QLocalSocket mLocalSocket;
|
||||
QQueue<QJSValue> callbackQueue;
|
||||
|
||||
struct PQRecord
|
||||
{
|
||||
PQRecord( const QString& path, const QString& jsonData,
|
||||
const QJSValue& callback);
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
QString mPath;
|
||||
QString mJsonData;
|
||||
#endif //QT_DEBUG
|
||||
|
||||
QJSValue mCallback;
|
||||
};
|
||||
QQueue<PQRecord> processingQueue;
|
||||
|
||||
private slots:
|
||||
void socketConnectAttempt() { mLocalSocket.connectToServer(mSocketPath); }
|
||||
void socketError(QLocalSocket::LocalSocketError error);
|
||||
void read();
|
||||
|
||||
@ -52,6 +88,8 @@ signals:
|
||||
* @param msg
|
||||
*/
|
||||
void responseReceived(const QString & msg);
|
||||
};
|
||||
|
||||
#endif // LIBRESAPILOCALCLIENT_H
|
||||
#ifdef QT_DEBUG
|
||||
void debugChanged();
|
||||
#endif // QT_DEBUG
|
||||
};
|
||||
|
134
retroshare-qml-app/src/main-app.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2016-2017 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/>.
|
||||
*/
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlComponent>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QThread>
|
||||
#include <QVariant>
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
# include <QtAndroid>
|
||||
# include <QtAndroidExtras/QAndroidJniObject>
|
||||
# include <atomic>
|
||||
#endif // Q_OS_ANDROID
|
||||
|
||||
#include "libresapilocalclient.h"
|
||||
#include "rsqmlappengine.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
/** When possible it is better to use +rsApi+ object directly instead of
|
||||
* multiple instances of +LibresapiLocalClient+ in Qml */
|
||||
qmlRegisterType<LibresapiLocalClient>(
|
||||
"org.retroshare.qml_components.LibresapiLocalClient", 1, 0,
|
||||
"LibresapiLocalClient");
|
||||
|
||||
QString sockPath = QDir::homePath() + "/.retroshare";
|
||||
sockPath.append("/libresapi.sock");
|
||||
|
||||
LibresapiLocalClient rsApi;
|
||||
rsApi.openConnection(sockPath);
|
||||
|
||||
RsQmlAppEngine engine(true);
|
||||
QQmlContext& rootContext = *engine.rootContext();
|
||||
|
||||
QStringList mainArgs = app.arguments();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
rootContext.setContextProperty("Q_OS_ANDROID", QVariant(true));
|
||||
|
||||
/* Add Activity Intent data to args, because onNewIntent is called only if
|
||||
* the Intet was triggered when the Activity was already created, so only in
|
||||
* case onCreate is not called.
|
||||
* The solution exposed in http://stackoverflow.com/a/36942185 is not
|
||||
* adaptable to our case, because when onCreate is called the RsQmlAppEngine
|
||||
* is not ready yet.
|
||||
*/
|
||||
uint waitCount = 0;
|
||||
std::atomic<bool> waitIntent(true);
|
||||
QString uriStr;
|
||||
do
|
||||
{
|
||||
QtAndroid::runOnAndroidThread(
|
||||
[&waitIntent, &uriStr]()
|
||||
{
|
||||
QAndroidJniObject activity = QtAndroid::androidActivity();
|
||||
if(!activity.isValid())
|
||||
{
|
||||
qDebug() << "QtAndroid::runOnAndroidThread(...)"
|
||||
<< "activity not ready yet";
|
||||
return;
|
||||
}
|
||||
|
||||
QAndroidJniObject intent = activity.callObjectMethod(
|
||||
"getIntent", "()Landroid/content/Intent;");
|
||||
if(!intent.isValid())
|
||||
{
|
||||
qDebug() << "QtAndroid::runOnAndroidThread(...)"
|
||||
<< "intent not ready yet";
|
||||
return;
|
||||
}
|
||||
|
||||
QAndroidJniObject intentData = intent.callObjectMethod(
|
||||
"getDataString", "()Ljava/lang/String;");
|
||||
if(intentData.isValid()) uriStr = intentData.toString();
|
||||
|
||||
waitIntent = false;
|
||||
});
|
||||
|
||||
if(waitIntent)
|
||||
{
|
||||
qWarning() << "uriStr not ready yet after waiting"
|
||||
<< waitCount << "times";
|
||||
app.processEvents();
|
||||
++waitCount;
|
||||
QThread::msleep(10);
|
||||
}
|
||||
}
|
||||
while (waitIntent);
|
||||
|
||||
qDebug() << "Got uriStr:" << uriStr;
|
||||
|
||||
if(!uriStr.isEmpty()) mainArgs.append(uriStr);
|
||||
#else
|
||||
rootContext.setContextProperty("Q_OS_ANDROID", QVariant(false));
|
||||
#endif
|
||||
|
||||
rootContext.setContextProperty("mainArgs", mainArgs);
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
rootContext.setContextProperty("QT_DEBUG", QVariant(true));
|
||||
rsApi.setDebug(false);
|
||||
#else
|
||||
rootContext.setContextProperty("QT_DEBUG", QVariant(false));
|
||||
#endif // QT_DEBUG
|
||||
|
||||
rootContext.setContextProperty("apiSocketPath", sockPath);
|
||||
rootContext.setContextProperty("rsApi", &rsApi);
|
||||
engine.load(QUrl(QLatin1String("qrc:/main-app.qml")));
|
||||
|
||||
return app.exec();
|
||||
}
|
426
retroshare-qml-app/src/main-app.qml
Normal file
@ -0,0 +1,426 @@
|
||||
/*
|
||||
* RetroShare Android QML App
|
||||
* Copyright (C) 2016-2017 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.7
|
||||
import QtQuick.Controls 2.0
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
import "URI.js" as UriJs
|
||||
import "." //Needed for TokensManager and ClipboardWrapper singleton
|
||||
|
||||
ApplicationWindow
|
||||
{
|
||||
id: mainWindow
|
||||
visible: true
|
||||
title: "RetroShare"
|
||||
width: 400
|
||||
height: 400
|
||||
|
||||
property string user_name
|
||||
|
||||
property bool coreReady: stackView.state === "running_ok" ||
|
||||
stackView.state === "running_ok_no_full_control"
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
addUriHandler("/certificate", certificateLinkHandler)
|
||||
addUriHandler("/identity", contactLinkHandler)
|
||||
|
||||
var argc = mainArgs.length
|
||||
for(var i=0; i<argc; ++i)
|
||||
{
|
||||
var dump = UriJs.URI.parse(mainArgs[i])
|
||||
if(dump.protocol && (dump.query || dump.path))
|
||||
handleIntentUri(mainArgs[i])
|
||||
}
|
||||
}
|
||||
|
||||
property var uriHandlersRegister: ({})
|
||||
property var pendingUriRegister: []
|
||||
function addUriHandler(path, fun) { uriHandlersRegister[path] = fun }
|
||||
function delUriHandler(path, fun) { delete uriHandlersRegister[path] }
|
||||
|
||||
header: ToolBar
|
||||
{
|
||||
id: toolBar
|
||||
|
||||
Image
|
||||
{
|
||||
id: rsIcon
|
||||
fillMode: Image.PreserveAspectFit
|
||||
height: Math.max(30, parent.height - 4)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
source: "icons/retroshare06.png"
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: "RetroShare"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: rsIcon.right
|
||||
anchors.leftMargin: 20
|
||||
}
|
||||
MouseArea
|
||||
{
|
||||
height: parent.height
|
||||
width: parent.height
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
onClicked: menu.open()
|
||||
|
||||
Image
|
||||
{
|
||||
source: "qrc:/icons/application-menu.png"
|
||||
height: parent.height - 10
|
||||
width: parent.height - 10
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Menu
|
||||
{
|
||||
id: menu
|
||||
y: parent.y + parent.height
|
||||
|
||||
MenuItem
|
||||
{
|
||||
text: qsTr("Trusted Nodes")
|
||||
//iconSource: "qrc:/icons/document-share.png"
|
||||
onTriggered: stackView.push("qrc:/TrustedNodesView.qml")
|
||||
enabled: mainWindow.coreReady
|
||||
}
|
||||
MenuItem
|
||||
{
|
||||
text: qsTr("Search Contacts")
|
||||
onTriggered:
|
||||
stackView.push("qrc:/Contacts.qml",
|
||||
{'searching': true} )
|
||||
enabled: mainWindow.coreReady
|
||||
}
|
||||
MenuItem
|
||||
{
|
||||
text: "Paste Link"
|
||||
onTriggered: UriJs.URI.withinString(
|
||||
ClipboardWrapper.getFromClipBoard(),
|
||||
handleIntentUri)
|
||||
|
||||
enabled: mainWindow.coreReady
|
||||
}
|
||||
MenuItem
|
||||
{
|
||||
text: "Terminate Core"
|
||||
onTriggered: rsApi.request("/control/shutdown")
|
||||
visible: !Q_OS_ANDROID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StackView
|
||||
{
|
||||
id: stackView
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
onCurrentItemChanged: if (currentItem) currentItem.focus = true
|
||||
Keys.onReleased:
|
||||
if (event.key === Qt.Key_Back && stackView.depth > 1)
|
||||
{
|
||||
stackView.pop();
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
state: "core_down"
|
||||
initialItem: BusyOverlay { message: qsTr("Connecting to core...") }
|
||||
|
||||
states: [
|
||||
State
|
||||
{
|
||||
name: "core_down"
|
||||
PropertyChanges { target: stackView; enabled: false }
|
||||
},
|
||||
State
|
||||
{
|
||||
name: "waiting_account_select"
|
||||
PropertyChanges { target: stackView; enabled: true }
|
||||
StateChangeScript
|
||||
{
|
||||
script:
|
||||
{
|
||||
console.log("StateChangeScript waiting_account_select")
|
||||
stackView.clear()
|
||||
stackView.push("qrc:/Locations.qml")
|
||||
}
|
||||
}
|
||||
},
|
||||
State
|
||||
{
|
||||
name: "waiting_startup"
|
||||
PropertyChanges { target: stackView; enabled: false }
|
||||
StateChangeScript
|
||||
{
|
||||
script:
|
||||
{
|
||||
console.log("StateChangeScript waiting_startup")
|
||||
stackView.clear()
|
||||
stackView.push("qrc:/BusyOverlay.qml",
|
||||
{ message: "Core initializing..."})
|
||||
}
|
||||
}
|
||||
},
|
||||
State
|
||||
{
|
||||
name: "running_ok"
|
||||
PropertyChanges { target: stackView; enabled: true }
|
||||
StateChangeScript
|
||||
{
|
||||
script:
|
||||
{
|
||||
console.log("StateChangeScript running_ok")
|
||||
coreStateCheckTimer.stop()
|
||||
stackView.clear()
|
||||
stackView.push("qrc:/Contacts.qml")
|
||||
while(mainWindow.pendingUriRegister.length > 0)
|
||||
mainWindow.handleIntentUri(
|
||||
mainWindow.pendingUriRegister.shift())
|
||||
}
|
||||
}
|
||||
},
|
||||
State
|
||||
{
|
||||
name: "running_ok_no_full_control"
|
||||
PropertyChanges { target: stackView; state: "running_ok" }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Timer
|
||||
{
|
||||
id: coreStateCheckTimer
|
||||
interval: 1000
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered:
|
||||
{
|
||||
var ret = rsApi.request("/control/runstate/", "", runStateCallback)
|
||||
if ( ret < 1 )
|
||||
{
|
||||
console.log("checkCoreStatus() core is down")
|
||||
stackView.state = "core_down"
|
||||
}
|
||||
}
|
||||
Component.onCompleted: start()
|
||||
|
||||
function runStateCallback(par)
|
||||
{
|
||||
var jsonReponse = JSON.parse(par.response)
|
||||
var runState = jsonReponse.data.runstate
|
||||
if(typeof(runState) === 'string') stackView.state = runState
|
||||
else
|
||||
{
|
||||
stackView.state = "core_down"
|
||||
console.log("runStateCallback(...) core is down")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleIntentUri(uriStr)
|
||||
{
|
||||
console.log("handleIntentUri(uriStr)")
|
||||
|
||||
if(!Array.isArray(uriStr.match(/:\/\/[a-zA-Z.-]*\//g)))
|
||||
{
|
||||
/* RetroShare GUI produces links without hostname and only two
|
||||
* slashes after scheme causing the first piece of the path part
|
||||
* being interpreted as host, this is awckard and should be fixed in
|
||||
* the GUI, in the meantime we add a slash for easier parsing, in
|
||||
* case there is no hostname and just two slashes, we might consider
|
||||
* to use +hostname+ part for some trick in the future, for example
|
||||
* it could help other application to recognize retroshare link by
|
||||
* putting a domain name there that has no meaning for retroshare
|
||||
*/
|
||||
uriStr = uriStr.replace("://", ":///")
|
||||
}
|
||||
|
||||
var uri = new UriJs.URI(uriStr)
|
||||
var hPath = uri.path() // no nesting ATM segmentCoded()
|
||||
console.log(hPath)
|
||||
|
||||
if(typeof uriHandlersRegister[hPath] == "function")
|
||||
{
|
||||
console.log("handleIntentUri(uriStr)", "found handler for path",
|
||||
hPath, uriHandlersRegister[hPath])
|
||||
uriHandlersRegister[hPath](uriStr)
|
||||
}
|
||||
}
|
||||
|
||||
function certificateLinkHandler(uriStr)
|
||||
{
|
||||
console.log("certificateLinkHandler(uriStr)", coreReady)
|
||||
|
||||
if(!coreReady)
|
||||
{
|
||||
// Save cert uri for later processing as we need core to examine it
|
||||
pendingUriRegister.push(uriStr)
|
||||
return
|
||||
}
|
||||
|
||||
var uri = new UriJs.URI(uriStr)
|
||||
var uQuery = uri.search(true)
|
||||
if(uQuery.radix)
|
||||
{
|
||||
var certStr = UriJs.URI.decode(uQuery.radix)
|
||||
|
||||
// Workaround https://github.com/RetroShare/RetroShare/issues/772
|
||||
certStr = certStr.replace(/ /g, "+")
|
||||
|
||||
rsApi.request(
|
||||
"/peers/examine_cert/",
|
||||
JSON.stringify({cert_string: certStr}),
|
||||
function(par)
|
||||
{
|
||||
console.log("/peers/examine_cert/ CB", par)
|
||||
var jData = JSON.parse(par.response).data
|
||||
stackView.push(
|
||||
"qrc:/TrustedNodeDetails.qml",
|
||||
{
|
||||
nodeCert: certStr,
|
||||
pgpName: jData.name,
|
||||
pgpId: jData.pgp_id,
|
||||
locations:
|
||||
[{
|
||||
location: jData.location,
|
||||
peer_id: jData.peer_id
|
||||
}]
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function contactLinkHandler(uriStr)
|
||||
{
|
||||
console.log("contactLinkHandler(uriStr)", coreReady)
|
||||
|
||||
if(!coreReady)
|
||||
{
|
||||
// Save cert uri for later processing as we need core to examine it
|
||||
pendingUriRegister.push(uriStr)
|
||||
return
|
||||
}
|
||||
|
||||
var uri = new UriJs.URI(uriStr)
|
||||
var uQuery = uri.search(true)
|
||||
if(uQuery.groupdata)
|
||||
{
|
||||
contactImportPopup.expectedName = uQuery.name
|
||||
contactImportPopup.expectedGxsId = uQuery.gxsid
|
||||
|
||||
rsApi.request(
|
||||
"/identity/import_key",
|
||||
JSON.stringify({radix: uQuery.groupdata}),
|
||||
function(par)
|
||||
{
|
||||
var jD = JSON.parse(par.response).data
|
||||
contactImportPopup.realGxsId = jD.gxs_id
|
||||
contactImportPopup.open()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Popup
|
||||
{
|
||||
id: contactImportPopup
|
||||
property string expectedName
|
||||
property string expectedGxsId
|
||||
property string realGxsId
|
||||
|
||||
function idMatch() { return expectedGxsId === realGxsId }
|
||||
|
||||
visible: false
|
||||
onVisibleChanged: if(visible && idMatch()) contactImportTimer.start()
|
||||
|
||||
x: parent.x + parent.width/2 - width/2
|
||||
y: parent.y + parent.height/2 - height/2
|
||||
|
||||
Column
|
||||
{
|
||||
spacing: 3
|
||||
anchors.centerIn: parent
|
||||
|
||||
Text
|
||||
{
|
||||
text: qsTr("%1 key imported").arg(
|
||||
contactImportPopup.expectedName)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text
|
||||
{
|
||||
text: qsTr("Link malformed!")
|
||||
color: "red"
|
||||
visible: contactImportPopup.visible &&
|
||||
!contactImportPopup.idMatch()
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Text
|
||||
{
|
||||
text:
|
||||
qsTr("Expected id and real one differs:") +
|
||||
"<br/><pre>" + contactImportPopup.expectedGxsId +
|
||||
"<br/>" + contactImportPopup.realGxsId + "</pre>"
|
||||
visible: contactImportPopup.visible &&
|
||||
!contactImportPopup.idMatch()
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Timer
|
||||
{
|
||||
id: contactImportTimer
|
||||
interval: 1500
|
||||
onTriggered: contactImportPopup.close()
|
||||
}
|
||||
}
|
||||
|
||||
Popup
|
||||
{
|
||||
id: linkCopiedPopup
|
||||
property string itemName
|
||||
|
||||
visible: false
|
||||
onVisibleChanged: if(visible) contactLinkTimer.start()
|
||||
|
||||
x: parent.x + parent.width/2 - width/2
|
||||
y: parent.y + parent.height/2 - height/2
|
||||
|
||||
Text
|
||||
{
|
||||
text:
|
||||
qsTr("%1 link copied to clipboard").arg(
|
||||
linkCopiedPopup.itemName)
|
||||
}
|
||||
|
||||
Timer
|
||||
{
|
||||
id: contactLinkTimer
|
||||
interval: 1500
|
||||
onTriggered: linkCopiedPopup.close()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +1,33 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>qml/main.qml</file>
|
||||
<file>qml/icons/star-2-128.png</file>
|
||||
<file>qml/icons/settings-4-128.png</file>
|
||||
<file>qml/icons/email-128.png</file>
|
||||
<file>qml/icons/contacts-128.png</file>
|
||||
<file>qml/PostedMsgDelegate.qml</file>
|
||||
<file>qml/GxsService.qml</file>
|
||||
<file>qml/GxsIdDelegate.qml</file>
|
||||
<file>qml/GxsGroupDelegate.qml</file>
|
||||
<file>qml/ForumMsgDelegate.qml</file>
|
||||
<file>qml/ContactBox.qml</file>
|
||||
<file>qml/ChannelMsgDelegate.qml</file>
|
||||
<file>qml/ChannelGroupDelegate.qml</file>
|
||||
<file>qml/ApplicationBar.qml</file>
|
||||
<file>qml/AppButton.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>
|
||||
<file>qml/RsLoginPassView.qml</file>
|
||||
<file>qml/TrustedNodesView.qml</file>
|
||||
<file>qml/ChatView.qml</file>
|
||||
<file>main-app.qml</file>
|
||||
<file>Locations.qml</file>
|
||||
<file>jsonpath.js</file>
|
||||
<file>JSONListModel.qml</file>
|
||||
<file>Contacts.qml</file>
|
||||
<file>AddTrustedNode.qml</file>
|
||||
<file>RsLoginPassView.qml</file>
|
||||
<file>TrustedNodesView.qml</file>
|
||||
<file>ChatView.qml</file>
|
||||
<file>icons/retroshare06.png</file>
|
||||
<file>icons/state-offline.png</file>
|
||||
<file>icons/state-ok.png</file>
|
||||
<file>GxsIdentityDelegate.qml</file>
|
||||
<file>icons/edit-find.png</file>
|
||||
<file>icons/edit-image-face-detect.png</file>
|
||||
<file>icons/application-menu.png</file>
|
||||
<file>ContactSort.js</file>
|
||||
<file>icons/emblem-locked.png</file>
|
||||
<file>BusyOverlay.qml</file>
|
||||
<file>URI.js</file>
|
||||
<file>TokensManager.qml</file>
|
||||
<file>qmldir</file>
|
||||
<file>TrustedNodeDetails.qml</file>
|
||||
<file>ClipboardWrapper.qml</file>
|
||||
<file>ContactDetails.qml</file>
|
||||
<file>ColorHash.qml</file>
|
||||
<file>icons/rating-unrated.png</file>
|
||||
<file>icons/rating.png</file>
|
||||
<file>TimedPopup.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@ -1,78 +0,0 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
|
||||
Item
|
||||
{
|
||||
function refreshData() { rsApi.request("/peers/self/certificate/", "") }
|
||||
|
||||
Component.onCompleted:
|
||||
{
|
||||
rsApi.openConnection(apiSocketPath)
|
||||
refreshData()
|
||||
}
|
||||
onFocusChanged: focus && refreshData()
|
||||
|
||||
LibresapiLocalClient
|
||||
{
|
||||
id: rsApi
|
||||
onGoodResponseReceived:
|
||||
{
|
||||
var jsonData = JSON.parse(msg)
|
||||
if(jsonData && jsonData.data && jsonData.data.cert_string)
|
||||
myKeyField.text = jsonData.data.cert_string
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout
|
||||
{
|
||||
anchors.fill: parent
|
||||
|
||||
Button
|
||||
{
|
||||
id: bottomButton
|
||||
text: "Add trusted node"
|
||||
onClicked:
|
||||
{
|
||||
console.log("retroshare addtrusted: ", otherKeyField.text)
|
||||
var jsonData =
|
||||
{
|
||||
cert_string: otherKeyField.text,
|
||||
flags:
|
||||
{
|
||||
allow_direct_download: true,
|
||||
allow_push: false,
|
||||
require_whitelist: false,
|
||||
}
|
||||
}
|
||||
console.log("retroshare addtrusted jsonData: ", JSON.stringify(jsonData))
|
||||
//rsApi.request("/peers/examine_cert/", JSON.stringify({ cert_string: otherKeyField.text }))
|
||||
rsApi.request("PUT /peers", JSON.stringify(jsonData))
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
text: "Copy"
|
||||
onClicked:
|
||||
{
|
||||
myKeyField.selectAll()
|
||||
myKeyField.copy()
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
text: "Paste"
|
||||
onClicked:
|
||||
{
|
||||
otherKeyField.selectAll()
|
||||
otherKeyField.paste()
|
||||
}
|
||||
}
|
||||
|
||||
TextField { id: myKeyField }
|
||||
TextField { id: otherKeyField }
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import "."
|
||||
|
||||
Rectangle {
|
||||
id: appButton
|
||||
property alias icon: appIcon.source
|
||||
|
||||
signal buttonClicked
|
||||
|
||||
width: parent.height
|
||||
height: parent.height
|
||||
color: "#00000000"
|
||||
|
||||
Image {
|
||||
id: appIcon
|
||||
anchors.centerIn: parent
|
||||
width: 25
|
||||
height: 25
|
||||
}
|
||||
MouseArea {
|
||||
hoverEnabled: false
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
appButton.buttonClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,37 +0,0 @@
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import "."
|
||||
|
||||
Rectangle {
|
||||
id: status
|
||||
anchors.fill: parent
|
||||
color: "#336699" //"#FF7733"
|
||||
height: 50
|
||||
|
||||
default property alias contents: placeholder.children
|
||||
|
||||
RowLayout {
|
||||
id: placeholder
|
||||
spacing: 0
|
||||
width: 200
|
||||
height: parent.height
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
|
||||
}
|
||||
|
||||
ContactBox {
|
||||
|
||||
width: 200
|
||||
height: parent.height
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
|
||||
icon: "icons/contacts-128.png"
|
||||
name: "Vade Retro"
|
||||
status: "Away"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,33 +0,0 @@
|
||||
import QtQuick 2.2
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: item
|
||||
width: parent.width
|
||||
height: 50
|
||||
|
||||
Column {
|
||||
Text { text: '<b>' + model.GroupName + '</b>' }
|
||||
Text { text: GroupId }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
hoverEnabled: false
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
item.ListView.view.currentIndex = index
|
||||
channelMsgModel.updateEntries(model.GroupId)
|
||||
console.log("Clicked on Channel GroupId: " + model.GroupId)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: "#AAAAAA"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,42 +0,0 @@
|
||||
import QtQuick 2.2
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: msgDelegate
|
||||
|
||||
width: parent.width
|
||||
height: 150
|
||||
|
||||
Column {
|
||||
Text { text: '<b>MsgId:</b> ' + AuthorId }
|
||||
Text { text: '<b>AuthorId:</b> ' + AuthorId }
|
||||
Row {
|
||||
Text { text: '<b>Name:</b> ' + MsgName }
|
||||
Text { text: ' <b>PublishTs:</b> ' + PublishTs }
|
||||
}
|
||||
Text { text: '<b>Msg:</b> ' + Msg }
|
||||
Row {
|
||||
Text { text: '<b>NumberFiles:</b> ' + NumberFiles }
|
||||
Text { text: ' <b>TotalFileSize:</b> ' + TotalFileSize }
|
||||
}
|
||||
|
||||
Text { text: '<b>FileNames:</b> ' + FileNames }
|
||||
Text { text: '<b>FileSizes:</b> ' + FileSizes }
|
||||
Text { text: '<b>FileHashes:</b> ' + FileHashes }
|
||||
Row {
|
||||
Text { text: '<b>HaveVoted:</b> ' + HaveVoted }
|
||||
Text { text: ' <b>UpVotes:</b> ' + UpVotes }
|
||||
Text { text: ' <b>DownVotes:</b> ' + DownVotes }
|
||||
Text { text: ' <b>Comments:</b> ' + Comments }
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
hoverEnabled: false
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
item.ListView.view.currentIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,81 +0,0 @@
|
||||
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}
|
||||
rsApi.request("/chat/send_message", JSON.stringify(jsonData), function(par) { msgComposer.text = ""; console.log(msg) })
|
||||
}
|
||||
}
|
||||
|
||||
Timer
|
||||
{
|
||||
id: refreshTimer
|
||||
interval: 800
|
||||
repeat: true
|
||||
onTriggered: if(chatView.visible) chatView.refreshData()
|
||||
Component.onCompleted: start()
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
import QtQuick 2.2
|
||||
import "."
|
||||
|
||||
Item {
|
||||
|
||||
property alias icon: contactIcon.source
|
||||
property alias name: contactName.text
|
||||
property alias status: contactStatus.text
|
||||
|
||||
Rectangle {
|
||||
|
||||
anchors.fill: parent
|
||||
color: "#00000000"
|
||||
|
||||
Image {
|
||||
id: contactIcon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
width: 40
|
||||
height: 40
|
||||
source: "icons/contacts-128.png"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: contactIcon.height
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: contactIcon.right
|
||||
color: parent.color
|
||||
|
||||
Text {
|
||||
id: contactName
|
||||
text: "Username"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.bottom: contactStatus.top
|
||||
anchors.bottomMargin: 2
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: 14
|
||||
font.bold: false
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: contactStatus
|
||||
text: "Hello world!"
|
||||
anchors.left: parent.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 1
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: 10
|
||||
font.bold: false
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,130 +0,0 @@
|
||||
/*
|
||||
* 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.Dialogs 1.2
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
|
||||
Item
|
||||
{
|
||||
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()
|
||||
|
||||
JSONListModel
|
||||
{
|
||||
id: locationsModel
|
||||
query: "$.data[*]"
|
||||
}
|
||||
|
||||
ListView
|
||||
{
|
||||
id: locationsListView
|
||||
width: parent.width
|
||||
height: 300
|
||||
model: locationsModel.model
|
||||
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
|
||||
{
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import QtQuick 2.2
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: msgDelegate
|
||||
|
||||
width: parent.width
|
||||
height: col.height
|
||||
|
||||
Column {
|
||||
id: col
|
||||
Text { text: '<b>MsgId:</b> ' + AuthorId }
|
||||
Text { text: '<b>AuthorId:</b> ' + AuthorId }
|
||||
Row {
|
||||
Text { text: '<b>Name:</b> ' + MsgName }
|
||||
Text { text: ' <b>PublishTs:</b> ' + PublishTs }
|
||||
}
|
||||
Text {
|
||||
wrapMode: Text.Wrap
|
||||
text: '<b>Msg:</b> ' + Msg
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
hoverEnabled: false
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
item.ListView.view.currentIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 2
|
||||
color: "#AAAAAA"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.bottom
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
import QtQuick 2.2
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: item
|
||||
property var msgModel: {}
|
||||
|
||||
width: parent.width
|
||||
height: 50
|
||||
|
||||
Column {
|
||||
Text { text: '<b>Name:</b> ' + model.GroupName }
|
||||
Text { text: '<b>Number:</b> ' + GroupId }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
hoverEnabled: false
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
item.ListView.view.currentIndex = index
|
||||
item.msgModel.updateEntries(model.GroupId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,33 +0,0 @@
|
||||
import QtQuick 2.2
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: item
|
||||
width: parent.width
|
||||
height: 50
|
||||
|
||||
Column {
|
||||
Text { text: '<b>' + model.GroupName + '</b>' }
|
||||
Text { text: GroupId }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
hoverEnabled: false
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
item.ListView.view.currentIndex = index
|
||||
//channelMsgModel.updateEntries(model.GroupId)
|
||||
//console.log("Clicked on Channel GroupId: " + model.GroupId)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: "#AAAAAA"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,119 +0,0 @@
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 1.1
|
||||
import "."
|
||||
|
||||
|
||||
Item {
|
||||
id: gxsService
|
||||
|
||||
property alias icon: sideIcon.source
|
||||
property alias title: sideTitle.text
|
||||
|
||||
property alias groupDelegate: sideList.delegate
|
||||
property alias groupModel: sideList.model
|
||||
|
||||
property alias msgDelegate: mainList.delegate
|
||||
property alias msgModel: mainList.model
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
id: sideBar
|
||||
width: 200
|
||||
Layout.fillHeight: true
|
||||
|
||||
Rectangle {
|
||||
id: sideHeader
|
||||
width: parent.width
|
||||
height: 30
|
||||
|
||||
Text {
|
||||
id: sideTitle
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
width: 20
|
||||
height: 20
|
||||
text: "Service"
|
||||
color: "#333333"
|
||||
}
|
||||
|
||||
Image {
|
||||
id: sideIcon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
width: 20
|
||||
height: 20
|
||||
source: "icons/contacts-128.png"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: sideListBox
|
||||
width: parent.width
|
||||
|
||||
anchors.top: sideHeader.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
ListView {
|
||||
id: sideList
|
||||
anchors.fill: parent
|
||||
|
||||
delegate: GxsGroupDelegate {
|
||||
msgModel: mainList.model
|
||||
}
|
||||
|
||||
// section.
|
||||
section.property: "SubscribeStatus"
|
||||
section.criteria: ViewSection.FullString
|
||||
section.delegate: Rectangle {
|
||||
width: sideListBox.width
|
||||
height: childrenRect.height
|
||||
color: "blue"
|
||||
|
||||
Text {
|
||||
text: section
|
||||
font.bold: true
|
||||
font.pixelSize: 20
|
||||
}
|
||||
}
|
||||
|
||||
clip: true
|
||||
highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
|
||||
focus: true
|
||||
|
||||
onCurrentItemChanged: {
|
||||
console.log("SideBar Item Changed on " + gxsService.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ListView {
|
||||
id: mainList
|
||||
anchors.fill: parent
|
||||
|
||||
clip: true
|
||||
highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
|
||||
focus: true
|
||||
onCurrentItemChanged: {
|
||||
console.log("item changed")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,40 +0,0 @@
|
||||
import QtQuick 2.2
|
||||
import "."
|
||||
|
||||
Item {
|
||||
id: msgDelegate
|
||||
|
||||
width: parent.width
|
||||
height: 150
|
||||
|
||||
Column {
|
||||
Text { text: '<b>MsgId:</b> ' + AuthorId }
|
||||
Text { text: '<b>AuthorId:</b> ' + AuthorId }
|
||||
Row {
|
||||
Text { text: '<b>Name:</b> ' + MsgName }
|
||||
Text { text: ' <b>PublishTs:</b> ' + PublishTs }
|
||||
}
|
||||
Text { text: '<b>Link:</b> ' + Link }
|
||||
Text { text: '<b>Notes:</b> ' + Notes }
|
||||
Row {
|
||||
Text { text: '<b>Hot:</b> ' + HotScore }
|
||||
Text { text: ' <b>Top:</b> ' + HotScore }
|
||||
Text { text: ' <b>New:</b> ' + HotScore }
|
||||
}
|
||||
Row {
|
||||
Text { text: '<b>HaveVoted:</b> ' + HaveVoted }
|
||||
Text { text: ' <b>UpVotes:</b> ' + UpVotes }
|
||||
Text { text: ' <b>DownVotes:</b> ' + DownVotes }
|
||||
Text { text: ' <b>Comments:</b> ' + Comments }
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
hoverEnabled: false
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
item.ListView.view.currentIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
* 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 "jsonpath.js" as JSONPath
|
||||
|
||||
Item
|
||||
{
|
||||
function refreshData() { rsApi.request("/peers/*", "", function(par) { jsonModel.json = par.response }) }
|
||||
onFocusChanged: focus && refreshData()
|
||||
|
||||
JSONListModel
|
||||
{
|
||||
id: jsonModel
|
||||
query: "$.data[*]"
|
||||
}
|
||||
|
||||
ListView
|
||||
{
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: bottomButton.top
|
||||
model: jsonModel.model
|
||||
delegate: Item
|
||||
{
|
||||
height: 50
|
||||
Row
|
||||
{
|
||||
height: 30
|
||||
Text
|
||||
{
|
||||
text: model.name
|
||||
onTextChanged: color = JSONPath.jsonPath(JSON.parse(jsonModel.json), "$.data[?(@.pgp_id=='"+model.pgp_id+"')].locations[*].is_online").reduce(function(cur,acc){return cur || acc}, false) ? "lime" : "darkslategray"
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
height: parent.height
|
||||
width: parent.height
|
||||
color: "red"
|
||||
|
||||
MouseArea
|
||||
{
|
||||
height: parent.height
|
||||
width: parent.height
|
||||
onClicked: rsApi.request("/peers/"+model.pgp_id+"/delete")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: bottomButton
|
||||
text: "Add Trusted Node"
|
||||
anchors.bottom: parent.bottom
|
||||
onClicked: swipeView.currentIndex = 3
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.9 KiB |
@ -1,253 +0,0 @@
|
||||
/*
|
||||
* 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.Controls 1.4
|
||||
import org.retroshare.qml_components.LibresapiLocalClient 1.0
|
||||
|
||||
ApplicationWindow
|
||||
{
|
||||
id: mainWindow
|
||||
visible: true
|
||||
title: qsTr("RSChat")
|
||||
width: 400
|
||||
height: 400
|
||||
|
||||
property string activeChatId;
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: mainView
|
||||
anchors.fill: parent
|
||||
states:
|
||||
[
|
||||
State
|
||||
{
|
||||
name: "waiting_account_select"
|
||||
PropertyChanges { target: swipeView; currentIndex: 0 }
|
||||
PropertyChanges { target: locationsTab; enabled: true }
|
||||
},
|
||||
State
|
||||
{
|
||||
name: "running_ok"
|
||||
PropertyChanges { target: swipeView; currentIndex: 1 }
|
||||
PropertyChanges { target: locationsTab; enabled: false }
|
||||
},
|
||||
State
|
||||
{
|
||||
name: "running_ok_no_full_control"
|
||||
PropertyChanges { target: swipeView; currentIndex: 1 }
|
||||
PropertyChanges { target: locationsTab; enabled: false }
|
||||
}
|
||||
]
|
||||
|
||||
LibresapiLocalClient
|
||||
{
|
||||
onGoodResponseReceived:
|
||||
{
|
||||
var jsonReponse = JSON.parse(msg)
|
||||
mainView.state = jsonReponse.data.runstate
|
||||
}
|
||||
Component.onCompleted:
|
||||
{
|
||||
openConnection(apiSocketPath)
|
||||
request("/control/runstate/", "")
|
||||
}
|
||||
}
|
||||
|
||||
TabView
|
||||
{
|
||||
id: swipeView
|
||||
anchors.fill: parent
|
||||
visible: true
|
||||
currentIndex: 0
|
||||
|
||||
Tab
|
||||
{
|
||||
title:"Locations"
|
||||
id: locationsTab
|
||||
Locations { onVisibleChanged: focus = visible }
|
||||
}
|
||||
|
||||
Tab
|
||||
{
|
||||
title: "Trusted Nodes"
|
||||
TrustedNodesView { onVisibleChanged: focus = visible }
|
||||
}
|
||||
|
||||
Tab
|
||||
{
|
||||
title: "Contacts"
|
||||
Contacts { onVisibleChanged: focus = visible }
|
||||
}
|
||||
|
||||
Tab
|
||||
{
|
||||
title: "Add Node"
|
||||
AddTrustedNode { onVisibleChanged: focus = visible }
|
||||
}
|
||||
|
||||
Tab
|
||||
{
|
||||
title: "Chat"
|
||||
ChatView
|
||||
{
|
||||
id: chatView
|
||||
chatId: mainWindow.activeChatId
|
||||
onVisibleChanged: focus = visible
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
onSceneGraphInitialized: llc.openConnection()
|
||||
|
||||
Rectangle {
|
||||
id: page
|
||||
width: 600; height: 400
|
||||
color: "#336699" // "#FFFFFF"
|
||||
|
||||
Rectangle {
|
||||
id: header
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
height: 50
|
||||
|
||||
ApplicationBar {
|
||||
id: status
|
||||
|
||||
AppButton {
|
||||
icon: "icons/contacts-128.png"
|
||||
onButtonClicked : {
|
||||
tabView.currentIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
AppButton {
|
||||
icon: "icons/settings-4-128.png"
|
||||
onButtonClicked : {
|
||||
tabView.currentIndex = 1
|
||||
}
|
||||
}
|
||||
|
||||
AppButton {
|
||||
icon: "icons/email-128.png"
|
||||
onButtonClicked : {
|
||||
tabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
AppButton {
|
||||
icon: "icons/star-2-128.png"
|
||||
onButtonClicked : {
|
||||
tabView.currentIndex = 3
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TabView {
|
||||
id: tabView
|
||||
width: parent.width
|
||||
anchors.top: header.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
tabsVisible: false
|
||||
|
||||
Tab {
|
||||
id: gxsIds
|
||||
//onActiveChanged: llc.request("/identity/", "")
|
||||
|
||||
onVisibleChanged: llc.request("/identity/", "")
|
||||
|
||||
GxsService {
|
||||
id: gxss
|
||||
title: "Friends"
|
||||
// Button {
|
||||
// text: "buto"
|
||||
// anchors.left: gxss.right
|
||||
// onClicked: {
|
||||
// // gxss.title = "provaboba"
|
||||
// // gxss.title = llc.request("/identity/", "")
|
||||
// //llc.request("/identity/", "") // canviar per onVisibleChanged de Tab potser
|
||||
// }
|
||||
|
||||
// }
|
||||
Connections {
|
||||
target: llc
|
||||
onGoodResponseReceived: gxss.title = msg //console.log("Image has changed!")
|
||||
}
|
||||
//groupDelegate: GxsIdDelegate {}
|
||||
//groupModel: gxsIdModel
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
id: forum
|
||||
|
||||
GxsService {
|
||||
id: gxssforum
|
||||
title: "Forums"
|
||||
onVisibleChanged: llc.request("/control/locations/", "")
|
||||
Connections {
|
||||
target: llc
|
||||
onGoodResponseReceived: gxssforum.title = msg //console.log("Image has changed!")
|
||||
}
|
||||
// This one uses the default GxsGroupDelegate.
|
||||
// groupModel: forumGroupModel
|
||||
|
||||
// msgDelegate: ForumMsgDelegate {}
|
||||
// msgModel: forumMsgModel
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
id: channelLinks
|
||||
GxsService {
|
||||
title: "Channels"
|
||||
|
||||
// custom GroupDelegate.
|
||||
// groupDelegate: ChannelGroupDelegate {}
|
||||
// groupModel: channelGroupModel
|
||||
|
||||
// msgDelegate: ChannelMsgDelegate {}
|
||||
// msgModel: channelMsgModel
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
id: postedLinks
|
||||
|
||||
GxsService {
|
||||
title: "Posted"
|
||||
|
||||
// This one uses the default GxsGroupDelegate.
|
||||
// groupModel: postedGroupModel
|
||||
|
||||
// msgDelegate: PostedMsgDelegate {}
|
||||
// msgModel: postedMsgModel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
2
retroshare-qml-app/src/qmldir
Normal file
@ -0,0 +1,2 @@
|
||||
singleton TokensManager 1.0 TokensManager.qml
|
||||
singleton ClipboardWrapper 1.0 ClipboardWrapper.qml
|
@ -1,17 +1,27 @@
|
||||
!include("../../retroshare.pri"): error("Could not include file ../../retroshare.pri")
|
||||
|
||||
QT += qml quick
|
||||
QT += core network qml quick
|
||||
|
||||
CONFIG += c++11
|
||||
|
||||
HEADERS += libresapilocalclient.h
|
||||
SOURCES += main.cpp \
|
||||
libresapilocalclient.cpp
|
||||
HEADERS += libresapilocalclient.h \
|
||||
rsqmlappengine.h
|
||||
SOURCES += main-app.cpp \
|
||||
libresapilocalclient.cpp \
|
||||
rsqmlappengine.cpp
|
||||
|
||||
RESOURCES += qml.qrc
|
||||
|
||||
android-g++ {
|
||||
QT += androidextras
|
||||
SOURCES += NativeCalls.cpp
|
||||
HEADERS += NativeCalls.h
|
||||
}
|
||||
|
||||
# Additional import path used to resolve QML modules in Qt Creator's code model
|
||||
#QML_IMPORT_PATH =
|
||||
#QML2_IMPORT_PATH =
|
||||
|
||||
|
||||
# Default rules for deployment.
|
||||
include(deployment.pri)
|
||||
@ -23,7 +33,8 @@ DISTFILES += \
|
||||
android/res/values/libs.xml \
|
||||
android/build.gradle \
|
||||
android/gradle/wrapper/gradle-wrapper.properties \
|
||||
android/gradlew.bat
|
||||
android/gradlew.bat \
|
||||
icons/retroshare06.png
|
||||
|
||||
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
|
||||
|
||||
|
40
retroshare-qml-app/src/rsqmlappengine.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* RetroShare Qml App
|
||||
* Copyright (C) 2017 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/>.
|
||||
*/
|
||||
|
||||
#include "rsqmlappengine.h"
|
||||
|
||||
#include <QQuickWindow>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
/*static*/ RsQmlAppEngine* RsQmlAppEngine::mMainInstance = nullptr;
|
||||
|
||||
void RsQmlAppEngine::handleUri(QString uri)
|
||||
{
|
||||
QObject* rootObj = rootObjects()[0];
|
||||
QQuickWindow* mainWindow = qobject_cast<QQuickWindow*>(rootObj);
|
||||
|
||||
if(mainWindow)
|
||||
{
|
||||
QMetaObject::invokeMethod(mainWindow, "handleIntentUri",
|
||||
Qt::AutoConnection,
|
||||
Q_ARG(QVariant, uri));
|
||||
}
|
||||
else qCritical() << __PRETTY_FUNCTION__
|
||||
<< "Root object is not a window!";
|
||||
}
|