Merge pull request #863 from G10h4ck/qmlapp_pex_alpha

Multiple improvements to android qml app and libreaspilocal
This commit is contained in:
csoler 2017-06-03 13:27:21 +02:00 committed by GitHub
commit 861dab32a5
102 changed files with 3668 additions and 1483 deletions

View File

@ -3,14 +3,28 @@ Compile Retroshare for Android
== Introduction == 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 == 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]. First of all setup your Qt for Android development environement following the
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. 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] [source,bash]
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@ -27,56 +41,56 @@ export ANDROID_NDK_ARCH="arm"
## The Android API level the Android device you want to target ## The Android API level the Android device you want to target
export ANDROID_PLATFORM_VER="19" 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 export HOST_NUM_CPU=1
./android-prepare-toolchain.sh ./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 == 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 Your Kit is now ready to use. Now you can open RetroShare as a Qt Creator
_Tools -> Options... -> Build & Run -> Compilers -> Android GCC (arm-4.9) -> Clone_ 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 and add the following configurations
[source,makefile] [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 == Quircks

View File

@ -34,15 +34,26 @@ retroshare_nogui {
retroshare_android_service { retroshare_android_service {
SUBDIRS += retroshare_android_service SUBDIRS += retroshare_android_service
retroshare_android_service.file = retroshare-android-service/src/retroshare-android-service.pro 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_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 { retroshare_qml_app {
SUBDIRS += retroshare_qml_app SUBDIRS += retroshare_qml_app
retroshare_qml_app.file = retroshare-qml-app/src/retroshare-qml-app.pro 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 retroshare_qml_app.target = retroshare_qml_app
android-g++ {
retroshare_qml_app.depends += retroshare_android_notify_service
}
} }
retroshare_plugins { retroshare_plugins {

View File

@ -105,9 +105,17 @@ readPath:
resource_api::Request req(reqJson); resource_api::Request req(reqJson);
req.mMethod = reqMeth; req.mMethod = reqMeth;
req.setPath(reqPath); 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"); mLocalSocket->write("\n\0");
mState = WAITING_PATH; mState = WAITING_PATH;
/* Because QLocalSocket is SOCK_STREAM some clients implementations /* Because QLocalSocket is SOCK_STREAM some clients implementations

View File

@ -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 "ChatHandler.h"
#include "Pagination.h" #include "Pagination.h"
#include "Operators.h" #include "Operators.h"
@ -936,7 +955,7 @@ ResponseTask* ChatHandler::handleLobbyParticipants(Request &req, Response &resp)
void ChatHandler::handleMessages(Request &req, Response &resp) void ChatHandler::handleMessages(Request &req, Response &resp)
{ {
/* G10h4ck: Whithout this the request processing won't happen, copied from /* 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? */ * doing it? */
tick(); tick();
@ -1132,24 +1151,28 @@ void ChatHandler::handleUnreadMsgs(Request &/*req*/, Response &resp)
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/ RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
resp.mDataStream.getStreamToMember(); 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; uint32_t count = 0;
for(std::list<Msg>::const_iterator lit = mit->second.begin(); lit != mit->second.end(); ++lit) for( std::list<Msg>::const_iterator lit = mit->second.begin();
if(!lit->read) lit != mit->second.end(); ++lit ) if(!lit->read) ++count;
count++;
std::map<ChatId, ChatInfo>::iterator mit2 = mChatInfo.find(mit->first); std::map<ChatId, ChatInfo>::iterator mit2 = mChatInfo.find(mit->first);
if(mit2 == mChatInfo.end()) 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; 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())) if(count && (mit2 != mChatInfo.end()))
{ {
resp.mDataStream.getStreamToMember() 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) << makeKeyValueReference("unread_count", count)
<< mit2->second; << mit2->second;
} }
} }
resp.mStateToken = mUnreadMsgsStateToken; resp.mStateToken = mUnreadMsgsStateToken;
resp.setOk();
} }
void ChatHandler::handleInitiateDistantChatConnexion(Request& req, Response& resp) void ChatHandler::handleInitiateDistantChatConnexion(Request& req, Response& resp)
@ -1176,7 +1199,8 @@ void ChatHandler::handleInitiateDistantChatConnexion(Request& req, Response& res
DistantChatPeerId distant_chat_id; DistantChatPeerId distant_chat_id;
uint32_t error_code; 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(); resp.setOk();
else resp.setFail("Failed to initiate distant chat"); else resp.setFail("Failed to initiate distant chat");

View File

@ -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 "IdentityHandler.h"
#include <retroshare/rsidentity.h> #include <retroshare/rsidentity.h>
@ -62,7 +82,9 @@ protected:
{ {
case BEGIN:{ case BEGIN:{
RsIdentityParameters params; 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 == "") if(params.nickname == "")
{ {
@ -127,17 +149,25 @@ private:
RsGxsId mId; RsGxsId mId;
}; };
IdentityHandler::IdentityHandler(StateTokenServer *sts, RsNotify *notify, RsIdentity *identity): IdentityHandler::IdentityHandler(StateTokenServer *sts, RsNotify *notify,
RsIdentity *identity) :
mStateTokenServer(sts), mNotify(notify), mRsIdentity(identity), mStateTokenServer(sts), mNotify(notify), mRsIdentity(identity),
mMtx("IdentityHandler Mtx"), mStateToken(sts->getNewToken()) mMtx("IdentityHandler Mtx"), mStateToken(sts->getNewToken())
{ {
mNotify->registerNotifyClient(this); mNotify->registerNotifyClient(this);
addResourceHandler("*", this, &IdentityHandler::handleWildcard); addResourceHandler("*", this, &IdentityHandler::handleWildcard);
addResourceHandler("own", this, &IdentityHandler::handleOwn); addResourceHandler("own", this, &IdentityHandler::handleOwn);
addResourceHandler("own_ids", this, &IdentityHandler::handleOwnIdsRequest); 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("add_contact", this, &IdentityHandler::handleAddContact);
addResourceHandler("remove_contact", this, &IdentityHandler::handleRemoveContact); addResourceHandler("remove_contact", this, &IdentityHandler::handleRemoveContact);
@ -158,12 +188,10 @@ IdentityHandler::~IdentityHandler()
void IdentityHandler::notifyGxsChange(const RsGxsChanges &changes) void IdentityHandler::notifyGxsChange(const RsGxsChanges &changes)
{ {
RS_STACK_MUTEX(mMtx); // ********** LOCKED ********** RS_STACK_MUTEX(mMtx);
// if changes come from identity service, invalidate own state token // if changes come from identity service, invalidate own state token
if(changes.mService == mRsIdentity->getTokenService()) if(changes.mService == mRsIdentity->getTokenService())
{ mStateTokenServer->replaceToken(mStateToken);
mStateTokenServer->replaceToken(mStateToken);
}
} }
void IdentityHandler::handleWildcard(Request & /*req*/, Response &resp) void IdentityHandler::handleWildcard(Request & /*req*/, Response &resp)
@ -177,39 +205,44 @@ void IdentityHandler::handleWildcard(Request & /*req*/, Response &resp)
RsTokReqOptions opts; RsTokReqOptions opts;
opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA; opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA;
uint32_t token; 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); time_t timeout = time(NULL)+10;
while((mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE) uint8_t rStatus = mRsIdentity->getTokenService()->requestStatus(token);
&&(mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_FAILED) while( rStatus != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE &&
&&((time(NULL) < (start+10))) rStatus != RsTokenService::GXS_REQUEST_V2_STATUS_FAILED &&
) time(NULL) < timeout )
{ {
#ifdef WINDOWS_SYS usleep(50*1000);
Sleep(500); rStatus = mRsIdentity->getTokenService()->requestStatus(token);
#else
usleep(500*1000);
#endif
} }
if(mRsIdentity->getTokenService()->requestStatus(token) == RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE) if(rStatus == RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
{ {
std::vector<RsGxsIdGroup> grps; std::vector<RsGxsIdGroup> grps;
ok &= mRsIdentity->getGroupData(token, grps); ok &= mRsIdentity->getGroupData(token, grps);
for(std::vector<RsGxsIdGroup>::iterator vit = grps.begin(); vit != grps.end(); vit++) for( std::vector<RsGxsIdGroup>::iterator vit = grps.begin();
vit != grps.end(); ++vit )
{ {
RsGxsIdGroup& grp = *vit; RsGxsIdGroup& grp = *vit;
//electron: not very happy about this, i think the flags should stay hidden in rsidentities /* electron: not very happy about this, i think the flags should
bool own = (grp.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_ADMIN); * stay hidden in rsidentities */
bool pgp_linked = (grp.mMeta.mGroupFlags & RSGXSID_GROUPFLAG_REALID_kept_for_compatibility ) ; bool own = (grp.mMeta.mSubscribeFlags &
resp.mDataStream.getStreamToMember() GXS_SERV::GROUP_SUBSCRIBE_ADMIN);
<< makeKeyValueReference("id", grp.mMeta.mGroupId) /// @deprecated using "id" as key can cause problems in some JS based languages like Qml @see gxs_id instead 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("gxs_id", grp.mMeta.mGroupId)
<< makeKeyValueReference("pgp_id",grp.mPgpId ) << makeKeyValueReference("pgp_id",grp.mPgpId )
<< makeKeyValueReference("name", grp.mMeta.mGroupName) << makeKeyValueReference("name", grp.mMeta.mGroupName)
<< makeKeyValueReference("contact", grp.mIsAContact) << makeKeyValueReference("contact", grp.mIsAContact)
<< makeKeyValueReference("own", own) << makeKeyValueReference("own", own)
<< makeKeyValueReference("pgp_linked", pgp_linked); << makeKeyValueReference("pgp_linked", pgp_linked)
<< makeKeyValueReference("is_contact", grp.mIsAContact);
} }
} }
else ok = false; else ok = false;
@ -218,7 +251,6 @@ void IdentityHandler::handleWildcard(Request & /*req*/, Response &resp)
else resp.setFail(); else resp.setFail();
} }
void IdentityHandler::handleNotOwnIdsRequest(Request & /*req*/, Response &resp) void IdentityHandler::handleNotOwnIdsRequest(Request & /*req*/, Response &resp)
{ {
bool ok = true; bool ok = true;
@ -230,38 +262,38 @@ void IdentityHandler::handleNotOwnIdsRequest(Request & /*req*/, Response &resp)
RsTokReqOptions opts; RsTokReqOptions opts;
opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA; opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA;
uint32_t token; 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); time_t timeout = time(NULL)+10;
while((mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE) uint8_t rStatus = mRsIdentity->getTokenService()->requestStatus(token);
&&(mRsIdentity->getTokenService()->requestStatus(token) != RsTokenService::GXS_REQUEST_V2_STATUS_FAILED) while( rStatus != RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE &&
&&((time(NULL) < (start+10))) rStatus != RsTokenService::GXS_REQUEST_V2_STATUS_FAILED &&
) time(NULL) < timeout )
{ {
#ifdef WINDOWS_SYS usleep(50*1000);
Sleep(500); rStatus = mRsIdentity->getTokenService()->requestStatus(token);
#else
usleep(500*1000);
#endif
} }
if(mRsIdentity->getTokenService()->requestStatus(token) == RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE) if(rStatus == RsTokenService::GXS_REQUEST_V2_STATUS_COMPLETE)
{ {
std::vector<RsGxsIdGroup> grps; std::vector<RsGxsIdGroup> grps;
ok &= mRsIdentity->getGroupData(token, grps); ok &= mRsIdentity->getGroupData(token, grps);
for(std::vector<RsGxsIdGroup>::iterator vit = grps.begin(); vit != grps.end(); vit++) for(std::vector<RsGxsIdGroup>::iterator vit = grps.begin();
vit != grps.end(); vit++)
{ {
RsGxsIdGroup& grp = *vit; RsGxsIdGroup& grp = *vit;
//electron: not very happy about this, i think the flags should stay hidden in rsidentities if(!(grp.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_ADMIN))
if(!(grp.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_ADMIN) && grp.mIsAContact)
{ {
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() 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("gxs_id", grp.mMeta.mGroupId)
<< makeKeyValueReference("pgp_id",grp.mPgpId ) << makeKeyValueReference("pgp_id",grp.mPgpId )
<< makeKeyValueReference("name", grp.mMeta.mGroupName) << 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); 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 } // namespace resource_api

View File

@ -1,4 +1,23 @@
#pragma once #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 <retroshare/rsnotify.h>
#include <util/rsthreads.h> #include <util/rsthreads.h>
@ -22,10 +41,13 @@ public:
virtual void notifyGxsChange(const RsGxsChanges &changes); virtual void notifyGxsChange(const RsGxsChanges &changes);
private: private:
void handleWildcard(Request& req, Response& resp); void handleWildcard(Request& req, Response& resp);
void handleNotOwnIdsRequest(Request& req, Response& resp); void handleNotOwnIdsRequest(Request& req, Response& resp);
void handleOwnIdsRequest(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 handleAddContact(Request& req, Response& resp);
void handleRemoveContact(Request& req, Response& resp); void handleRemoveContact(Request& req, Response& resp);

View File

@ -1,4 +1,22 @@
#pragma once #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" #include "ApiTypes.h"
@ -13,13 +31,6 @@ namespace resource_api
template<class C> template<class C>
void handlePaginationRequest(Request& req, Response& resp, C& data) 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()){ if(data.begin() == data.end()){
// set result type to list // set result type to list
resp.mDataStream.getStreamToMember(); resp.mDataStream.getStreamToMember();
@ -30,7 +41,8 @@ void handlePaginationRequest(Request& req, Response& resp, C& data)
std::string begin_after; std::string begin_after;
std::string last; 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(); typename C::iterator it_first = data.begin();
if(begin_after != "begin" && begin_after != "") 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 ++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) for(typename C::iterator it = it_first; it != it_last; ++it)
{ {
StreamBase& stream = resp.mDataStream.getStreamToMember(); 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 // 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 // then want to return the elements near the specified element
count++;
if(count > 20){ // G10h4ck: @see first comment about JSON_API_LIMIT_CHAT_MSG_COUNT_BY_DEFAULT
resp.mDebug << "limited the number of returned items to 20" << std::endl; #ifdef JSON_API_LIMIT_CHAT_MSG_COUNT_BY_DEFAULT
break; ++count;
} if(count > 20)
{
resp.mDebug << "limited the number of returned items to 20";
break;
}
#endif
} }
resp.setOk(); resp.setOk();
} }

View File

@ -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 "PeersHandler.h"
#include <retroshare/rspeers.h> #include <retroshare/rspeers.h>
@ -194,6 +214,7 @@ PeersHandler::PeersHandler(StateTokenServer* sts, RsNotify* notify, RsPeers *pee
mNotify->registerNotifyClient(this); mNotify->registerNotifyClient(this);
mStateTokenServer->registerTickClient(this); mStateTokenServer->registerTickClient(this);
addResourceHandler("*", this, &PeersHandler::handleWildcard); addResourceHandler("*", this, &PeersHandler::handleWildcard);
addResourceHandler("attempt_connection", this, &PeersHandler::handleAttemptConnection);
addResourceHandler("get_state_string", this, &PeersHandler::handleGetStateString); addResourceHandler("get_state_string", this, &PeersHandler::handleGetStateString);
addResourceHandler("set_state_string", this, &PeersHandler::handleSetStateString); addResourceHandler("set_state_string", this, &PeersHandler::handleSetStateString);
addResourceHandler("get_custom_state_string", this, &PeersHandler::handleGetCustomStateString); 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("get_node_options", this, &PeersHandler::handleGetNodeOptions);
addResourceHandler("set_node_options", this, &PeersHandler::handleSetNodeOptions); addResourceHandler("set_node_options", this, &PeersHandler::handleSetNodeOptions);
addResourceHandler("examine_cert", this, &PeersHandler::handleExamineCert); addResourceHandler("examine_cert", this, &PeersHandler::handleExamineCert);
} }
PeersHandler::~PeersHandler() 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) void PeersHandler::handleExamineCert(Request &req, Response &resp)
{ {
std::string cert_string; std::string cert_string;

View File

@ -1,4 +1,23 @@
#pragma once #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 "ResourceRouter.h"
#include "StateTokenServer.h" #include "StateTokenServer.h"
@ -33,8 +52,11 @@ public:
virtual void notifyUnreadMsgCountChanged(const RsPeerId& peer, uint32_t count); virtual void notifyUnreadMsgCountChanged(const RsPeerId& peer, uint32_t count);
private: private:
void handleWildcard(Request& req, Response& resp); void handleWildcard(Request& req, Response& resp);
void handleExamineCert(Request& req, Response& resp);
void handleAttemptConnection(Request& req, Response& resp);
void handleExamineCert(Request& req, Response& resp);
void handleGetStateString(Request& req, Response& resp); void handleGetStateString(Request& req, Response& resp);
void handleSetStateString(Request& req, Response& resp); void handleSetStateString(Request& req, Response& resp);

View File

@ -1271,7 +1271,9 @@ bool RsGenExchange::getMsgRelatedMeta(const uint32_t &token, GxsMsgRelatedMetaMa
return ok; 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) ; RS_STACK_MUTEX(mGenMtx) ;
@ -1304,24 +1306,30 @@ bool RsGenExchange::getSerializedGroupData(const uint32_t &token, RsGxsGroupId&
return RsNxsSerialiser(mServType).serialise(nxs_grp,data,&size) ; 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) ; RS_STACK_MUTEX(mGenMtx) ;
RsItem *item = RsNxsSerialiser(mServType).deserialise(data, &size); RsItem *item = RsNxsSerialiser(mServType).deserialise(data, &size);
RsNxsGrp *nxs_grp = dynamic_cast<RsNxsGrp*>(item) ; RsNxsGrp *nxs_grp = dynamic_cast<RsNxsGrp*>(item);
if(item == NULL) if(item == NULL)
{ {
std::cerr << "(EE) RsGenExchange::deserializeGroupData(): cannot deserialise this data. Something's wrong." << std::endl; std::cerr << "(EE) RsGenExchange::deserializeGroupData(): cannot "
delete item ; << "deserialise this data. Something's wrong." << std::endl;
return false ; 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) bool RsGenExchange::getGroupData(const uint32_t &token, std::vector<RsGxsGrpItem *>& grpItem)

View File

@ -299,8 +299,10 @@ protected:
* \return * \return
*/ */
bool getSerializedGroupData(const uint32_t &token, RsGxsGroupId &id, unsigned char *& data, uint32_t& size); bool getSerializedGroupData(uint32_t token, RsGxsGroupId &id,
bool deserializeGroupData(unsigned char *data, uint32_t size); unsigned char *& data, uint32_t& size);
bool deserializeGroupData(unsigned char *data, uint32_t size,
RsGxsGroupId* gId = nullptr);
template<class GrpType> template<class GrpType>
bool getGroupDataT(const uint32_t &token, std::vector<GrpType*>& grpItem) bool getGroupDataT(const uint32_t &token, std::vector<GrpType*>& grpItem)

View File

@ -922,15 +922,15 @@ android-g++ {
## Add this here and not in retroshare.pri because static library are very ## 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 ## sensible to order in command line, has to be in the end of file for the
## same reason ## same reason
LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl LIBS += -L$$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl
INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include INCLUDEPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include DEPENDPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a PRE_TARGETDEPS += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a
LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto LIBS += -L$$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto
INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include INCLUDEPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include DEPENDPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a PRE_TARGETDEPS += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a
HEADERS += util/androiddebug.h HEADERS += util/androiddebug.h
} }

View File

@ -91,11 +91,11 @@ class GxsReputation
}; };
class RsGxsIdGroup struct RsGxsIdGroup
{ {
public: RsGxsIdGroup() :
RsGxsIdGroup(): mLastUsageTS(0), mPgpKnown(false),mIsAContact(false) { return; } mLastUsageTS(0), mPgpKnown(false), mIsAContact(false) {}
~RsGxsIdGroup() { return; } ~RsGxsIdGroup() {}
RsGroupMetaData mMeta; RsGroupMetaData mMeta;
@ -305,8 +305,10 @@ public:
virtual bool setAsRegularContact(const RsGxsId& id,bool is_a_contact) = 0 ; virtual bool setAsRegularContact(const RsGxsId& id,bool is_a_contact) = 0 ;
virtual bool isARegularContact(const RsGxsId& id) = 0 ; virtual bool isARegularContact(const RsGxsId& id) = 0 ;
virtual bool serialiseIdentityToMemory(const RsGxsId& id,std::string& radix_string)=0; virtual bool serialiseIdentityToMemory( const RsGxsId& id,
virtual bool deserialiseIdentityFromMemory(const std::string& radix_string)=0; std::string& radix_string ) = 0;
virtual bool deserialiseIdentityFromMemory( const std::string& radix_string,
RsGxsId* id = nullptr ) = 0;
/*! /*!
* \brief overallReputationLevel * \brief overallReputationLevel

View File

@ -698,9 +698,10 @@ bool p3IdService::getOwnIds(std::list<RsGxsId> &ownIds)
return true ; 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. // 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 ; 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; std::cerr << __PRETTY_FUNCTION__ << "Cannot decode radix string \""
return false ; << radix_string << "\"" << std::endl;
return false;
} }
if(!RsGenExchange::deserializeGroupData(mem.data(),mem.size())) if( !RsGenExchange::deserializeGroupData(
{ mem.data(), mem.size(), reinterpret_cast<RsGxsGroupId*>(id)) )
std::cerr << "Cannot load identity from radix string \"" << radix_string << "\"" << std::endl; {
return false ; 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 &params) bool p3IdService::createIdentity(uint32_t& token, RsIdentityParameters &params)

View File

@ -350,8 +350,10 @@ public:
const RsIdentityUsage &use_info ); const RsIdentityUsage &use_info );
virtual bool requestPrivateKey(const RsGxsId &id); virtual bool requestPrivateKey(const RsGxsId &id);
virtual bool serialiseIdentityToMemory(const RsGxsId& id,std::string& radix_string); virtual bool serialiseIdentityToMemory(const RsGxsId& id,
virtual bool deserialiseIdentityFromMemory(const std::string& radix_string); std::string& radix_string);
virtual bool deserialiseIdentityFromMemory(const std::string& radix_string,
RsGxsId* id = nullptr);
/**************** RsGixsReputation Implementation ****************/ /**************** RsGixsReputation Implementation ****************/

View File

@ -126,14 +126,14 @@ android-g++ {
## Add this here and not in retroshare.pri because static library are very ## Add this here and not in retroshare.pri because static library are very
## sensible to order in command line ## sensible to order in command line
LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl LIBS += -L$$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl
INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include INCLUDEPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include DEPENDPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a PRE_TARGETDEPS += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a
LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto LIBS += -L$$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto
INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include INCLUDEPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include DEPENDPATH += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/include
PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a PRE_TARGETDEPS += $$NATIVE_LIBS_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a
} }

View 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()
})
}
}
}

View File

@ -0,0 +1 @@
../../retroshare-qml-app/src/TokensManager.qml

View File

@ -0,0 +1 @@
../../retroshare-qml-app/src/libresapilocalclient.cpp

View File

@ -0,0 +1 @@
../../retroshare-qml-app/src/libresapilocalclient.h

View File

@ -1,5 +1,5 @@
/* /*
* RetroShare Android QML App * RetroShare Android Service
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org> * Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
* *
* This program is free software: you can redistribute it and/or modify * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QGuiApplication> #include <QCoreApplication>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQmlContext> #include <QQmlContext>
#include <QQmlComponent> #include <QFileInfo>
#include <QDebug> #include <QDebug>
#include <QDir>
#include <QThread>
#ifdef __ANDROID__ #ifdef __ANDROID__
# include <QtAndroidExtras> # include "util/androiddebug.h"
#endif #endif
#include <QFileInfo>
#include <QDateTime>
#include "libresapilocalclient.h" #include "libresapilocalclient.h"
#include "retroshare/rsinit.h" #include "notificationsbridge.h"
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #ifdef __ANDROID__
QGuiApplication app(argc, argv); AndroidStdIOCatcher dbg; (void) dbg;
#endif
QCoreApplication app(argc, argv);
QString sockPath = QDir::homePath() + "/.retroshare";
sockPath.append("/libresapi.sock");
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
/// @deprecated qmlRegisterType<NotificationsBridge>(
"org.retroshare.qml_components.NotificationsBridge", 1, 0,
"NotificationsBridge");
NotificationsBridge notificationsBridge;
engine.rootContext()->setContextProperty("notificationsBridge",
&notificationsBridge);
#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>( qmlRegisterType<LibresapiLocalClient>(
"org.retroshare.qml_components.LibresapiLocalClient", 1, 0, "org.retroshare.qml_components.LibresapiLocalClient", 1, 0,
"LibresapiLocalClient"); "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.rootContext()->setContextProperty("rsApi", &rsApi);
engine.load(QUrl(QLatin1String("qrc:/qml/main.qml")));
QFileInfo fileInfo(sockPath); engine.load(QUrl(QLatin1String("qrc:/main.qml")));
qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString();
return app.exec(); return app.exec();
} }

View 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)
}
}

View 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__
}
};

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

View File

@ -0,0 +1 @@
singleton TokensManager 1.0 TokensManager.qml

View File

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

View File

@ -1,6 +1,6 @@
/* /*
* RetroShare Android Service * 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 * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@ -18,9 +18,10 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include <QMetaObject>
#include <QDir>
#ifdef __ANDROID__ #ifdef __ANDROID__
# include <QtAndroidExtras>
# include "util/androiddebug.h" # include "util/androiddebug.h"
#endif #endif
@ -36,32 +37,36 @@ int main(int argc, char *argv[])
AndroidStdIOCatcher dbg; (void) dbg; AndroidStdIOCatcher dbg; (void) dbg;
#endif #endif
QCoreApplication a(argc, argv); QCoreApplication app(argc, argv);
ApiServer api; ApiServer api;
RsControlModule ctrl_mod(argc, argv, api.getStateTokenServer(), &api, true); 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/"; QString sockPath = "RS/";
#else #elif defined(Q_OS_WIN)
QString sockPath = QCoreApplication::applicationDirPath(); QString sockPath = QCoreApplication::applicationDirPath();
#else
QString sockPath = QDir::homePath() + "/.retroshare";
#endif #endif
sockPath.append("/libresapi.sock"); sockPath.append("/libresapi.sock");
qDebug() << "Listening on:" << sockPath; qDebug() << "Listening on:" << sockPath;
ApiServerLocal apiServerLocal(&api, sockPath); (void) apiServerLocal; 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()) while (!ctrl_mod.processShouldExit())
{ {
a.processEvents(); app.processEvents();
usleep(20000); 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();
} }

View 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
}
}
}

View 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
}
}

View 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
}
}

View 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 = ""; } )
}
}
}

View 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 }
}

View File

@ -1,6 +1,6 @@
/* /*
* RetroShare Android QML App * 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 * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.0 import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.4
import QtQml 2.2
import org.retroshare.qml_components.LibresapiLocalClient 1.0
Item Rectangle
{ {
id: loginView property string hash
property string buttonText: "Login"
property string login
property string password
signal submit(string login, string password)
ColumnLayout width: height
property int childHeight : height/2
color: "white"
Image
{ {
id: inputView source: "qrc:/icons/edit-image-face-detect.png"
width: parent.width anchors.fill: parent
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 } }
} }
Button Rectangle
{ {
id: bottomButton color: '#' + hash.substring(1, 9)
text: loginView.buttonText 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.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
onClicked: loginView.submit(nameField.text, passwordField.text)
} }
} }

View 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
}
}
}

View 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)
}

View 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
}
}

View 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)})
}
}

View File

@ -4,7 +4,7 @@
* Licensed under the MIT licence (http://opensource.org/licenses/mit-license.php) * 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 import "jsonpath.js" as JSONPath
Item { Item {

View File

@ -16,15 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.0 import QtQuick 2.7
import QtQuick.Controls 1.4 import QtQuick.Controls 2.0
import org.retroshare.qml_components.LibresapiLocalClient 1.0 import org.retroshare.qml_components.LibresapiLocalClient 1.0
Item Item
{ {
id: locationView id: locationView
state: "selectLocation" state: "selectLocation"
property var qParent
property bool attemptLogin: false property bool attemptLogin: false
property string password property string password
property string sslid property string sslid
@ -42,17 +41,26 @@ Item
{ {
name: "createLocation" name: "createLocation"
PropertyChanges { target: locationsListView; visible: false } PropertyChanges { target: locationsListView; visible: false }
PropertyChanges { target: bottomButton; visible: false } PropertyChanges { target: bottomButton; visible: false }
PropertyChanges PropertyChanges
{ {
target: loginView target: loginView
visible: true visible: true
buttonText: "Save" buttonText: qsTr("Save")
iconUrl: "qrc:/icons/edit-image-face-detect.png"
suggestionText: qsTr("Create your profile")
onSubmit: onSubmit:
{ {
var jsonData = { pgp_name: login, ssl_name: login, pgp_password: password } busyIndicator.running = true
rsApi.request("/control/create_location/", JSON.stringify(jsonData)) 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" locationView.state = "selectLocation"
bottomButton.enabled = false
bottomButton.text = "Creating profile..."
} }
} }
}, },
@ -60,86 +68,59 @@ Item
{ {
name: "login" name: "login"
PropertyChanges { target: locationsListView; visible: false } PropertyChanges { target: locationsListView; visible: false }
PropertyChanges { target: bottomButton; visible: false } PropertyChanges { target: bottomButton; visible: false }
PropertyChanges PropertyChanges
{ {
target: loginView target: loginView
visible: true visible: true
advancedMode: true
onSubmit: onSubmit:
{ {
locationView.password = password locationView.password = password
console.log("locationView.sslid: ", locationView.sslid) 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 locationView.attemptLogin = true
busyIndicator.running = true
attemptTimer.start() attemptTimer.start()
} }
} }
} }
] ]
function requestLocationsList() { rsApi.request("/control/locations/", "") } function requestLocationsListCB(par)
onFocusChanged: focus && requestLocationsList()
LibresapiLocalClient
{ {
id: rsApi var jsonData = JSON.parse(par.response).data
Component.onCompleted: if(jsonData.length === 1)
{ {
openConnection(apiSocketPath) // There is only one location so we can jump selecting location
locationView.requestLocationsList() 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) // The user haven't created a location yet
locationView.state = "createLocation"
}
if(jsonData) else
{ {
if(jsonData.data) // There is more then one location to choose from
{ locationsModel.json = par.response
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")
}
}
}
}
} }
} }
function requestLocationsList()
{ rsApi.request("/control/locations/", "", requestLocationsListCB) }
BusyIndicator { id: busyIndicator; anchors.centerIn: parent } onFocusChanged: focus && requestLocationsList()
Component.onCompleted: requestLocationsList()
JSONListModel JSONListModel
{ {
id: locationsModel id: locationsModel
query: "$.data[*]" query: "$.data[*]"
} }
ListView ListView
{ {
id: locationsListView id: locationsListView
@ -149,22 +130,24 @@ Item
model: locationsModel.model model: locationsModel.model
delegate: Button delegate: Button
{ {
text: model.name text: model.name
onClicked: onClicked:
{ {
loginView.login = text loginView.login = text
locationView.sslid = model.id locationView.sslid = model.id
locationView.state = "login" locationView.state = "login"
mainWindow.user_name = model.name
} }
} }
visible: false visible: false
} }
Button Button
{ {
id: bottomButton id: bottomButton
text: "Create new location" text: "Create new location"
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
onClicked: locationView.state = "createLocation" onClicked: locationView.state = "createLocation"
} }
@ -175,18 +158,60 @@ Item
anchors.fill: parent 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 Timer
{ {
id: attemptTimer id: attemptTimer
interval: 500 interval: 1000
repeat: true repeat: true
triggeredOnStart: true
onTriggered: onTriggered:
{ {
if(locationView.focus)
locationView.requestLocationsList()
if (locationView.attemptLogin) if (locationView.attemptLogin)
rsApi.request("/control/password/", "") loginApi.request("/control/password/", "")
} }
} }
} }

View File

@ -0,0 +1 @@
android/src/NativeCalls.cpp

View File

@ -0,0 +1 @@
android/src/org_retroshare_android_qml_app_jni_NativeCalls.h

View 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)
}
}
}
}

View 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()
}
}

View 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()
}
}

View 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") })
}
}
}
}

View 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
}
}

View 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});

View File

@ -1,16 +1,34 @@
<?xml version="1.0"?> <?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"> <manifest package="org.retroshare.android.qml_app"
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="RetroShare"> xmlns:android="http://schemas.android.com/apk/res/android"
<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"> 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> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<!-- Application to launch --> <!-- Register for retroshare:// and rtsh:// link handling -->
<meta-data android:name="android.app.lib_name" android:value="retroshare-qml-app"/> <intent-filter>
<!-- Application to launch --> <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 --> <!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ --> <!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments --> <!-- Application arguments -->
@ -55,28 +73,33 @@
<!-- extract android style --> <!-- extract android style -->
<!-- available android:values : <!-- available android:values :
* full - useful QWidget & Quick Controls 1 apps * full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full" * minimal - useful for Quick Controls 2 apps, it is much faster
* none - useful for apps that don't use any of the above Qt modules 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 --> <!-- extract android style -->
</activity> </activity>
<receiver android:name=".BootCompletedReceiver" android:enabled="true" android:exported="false"> <receiver android:name=".BootCompletedReceiver" android:enabled="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".AppUpdatedReceiver" android:enabled="true" android:exported="false"> <receiver android:name=".AppUpdatedReceiver" android:enabled="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/> <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices --> <!-- 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 --> <service android:name=".RetroShareAndroidService" android:process=":rs"
<!-- android:process=":qt" is needed to force the service to run on a separate process than the Activity --> 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 --> <!-- Application arguments -->
@ -88,8 +111,58 @@
one is which. one is which.
--> -->
<!-- Qt Application to launch -->
<meta-data android:name="android.app.lib_name"
android:value="retroshare-android-service"/>
<!-- Application to launch --> <!-- 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 --> <!-- Application to launch -->
<!-- Ministro --> <!-- Ministro -->

View File

@ -9,88 +9,86 @@
<facet type="android" name="Android"> <facet type="android" name="Android">
<configuration> <configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" /> <option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" /> <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<afterSyncTasks> <afterSyncTasks>
<task>generateDebugSources</task> <task>generateDebugSources</task>
</afterSyncTasks> </afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" /> <option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/../../../../qt5/qtbase/src/android/java/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://$MODULE_DIR$/../../../../qt5/qtbase/src/android/java/res;file://$MODULE_DIR$/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> </configuration>
</facet> </facet>
</component> </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 url="file://$MODULE_DIR$/.build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/.build/intermediates/classes/test/debug" /> <output-test url="file://$MODULE_DIR$/.build/intermediates/classes/test/debug" />
<exclude-output /> <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$"> <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/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/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/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/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/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/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/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/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/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/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/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" 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/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" /> <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/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/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/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" 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/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" /> <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/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/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$/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" 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$/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/aidl" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/java" 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/main/shaders" 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/androidTest/res" type="java-test-resource" /> <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/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" 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/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" 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" /> <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/incremental" />
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/manifests" /> <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/res" />
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/resources" />
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/rs" /> <excludeFolder url="file://$MODULE_DIR$/.build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/.build/intermediates/symbols" /> <excludeFolder url="file://$MODULE_DIR$/.build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/.build/outputs" /> <excludeFolder url="file://$MODULE_DIR$/.build/outputs" />
</content> </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="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" scope="TEST" name="mockable-android-18" level="project" />
</component> </component>
</module> </module>

View File

@ -4,7 +4,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:1.1.0' classpath 'com.android.tools.build:gradle:2.2.3'
} }
} }

View File

@ -4,6 +4,6 @@
# as it contains information specific to your local configuration. # as it contains information specific to your local configuration.
androidBuildToolsVersion=24.0.1 androidBuildToolsVersion=24.0.1
androidCompileSdkVersion=18 androidCompileSdkVersion=23
buildDir=.build 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

View File

@ -1,6 +1,6 @@
#Wed Apr 10 15:27:10 PDT 2013 #Fri Apr 07 17:55:25 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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

View File

@ -0,0 +1 @@
../../../../../data/128x128/apps/retroshare06.png

View File

@ -0,0 +1 @@
../../../../../data/48x48/apps/retroshare06.png

View 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!";
}

View File

@ -1,6 +1,6 @@
/* /*
* RetroShare Android Service * 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 * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@ -28,9 +28,16 @@ public class AppUpdatedReceiver extends BroadcastReceiver
@Override @Override
public void onReceive(Context context, Intent intent) 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"); Log.i("AppUpdatedReceiver", "onReceive() Restarting RetroShare Android Service After Update");
Intent myIntent = new Intent(context, RetroShareAndroidService.class); Intent coreIntent = new Intent(context, RetroShareAndroidService.class);
context.stopService(myIntent); context.stopService(coreIntent);
context.startService(myIntent); context.startService(coreIntent);
Log.i("AppUpdatedReceiver", "onReceive() Starting RetroShareAndroidNotifyService After Update");
context.startService(nsIntent);
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
* RetroShare Android Service * 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 * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * 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.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.util.Log;
public class BootCompletedReceiver extends BroadcastReceiver public class BootCompletedReceiver extends BroadcastReceiver
{ {
@Override @Override
public void onReceive(Context context, Intent intent) public void onReceive(Context context, Intent intent)
{ {
Intent myIntent = new Intent(context, RetroShareAndroidService.class); Log.i("BootCompletedReceiver", "onReceive() Starting RetroShareAndroidService on boot");
context.startService(myIntent); 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);
} }
} }

View File

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

View File

@ -1,6 +1,6 @@
/* /*
* RetroShare Android QML App * 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 * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * 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.qtproject.qt5.android.bindings.QtActivity;
import org.retroshare.android.qml_app.jni.NativeCalls;
public class RetroShareQmlActivity extends QtActivity public class RetroShareQmlActivity extends QtActivity
{ {
@Override @Override
public void onCreate(Bundle savedInstanceState) public void onCreate(Bundle savedInstanceState)
{ {
Log.i("RetroShareQmlActivity", "onCreate()");
if (!isMyServiceRunning(RetroShareAndroidService.class)) if (!isMyServiceRunning(RetroShareAndroidService.class))
{ {
Log.i("RetroShareQmlActivity", "onCreate(): RetroShareAndroidService is not running, let's start it by Intent"); 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"); 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); 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) private boolean isMyServiceRunning(Class<?> serviceClass)
{ {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

View File

@ -0,0 +1,3 @@
package org.retroshare.android.qml_app;
public @interface UsedByNativeCode {}

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1 @@
../../../data/128x128/apps/retroshare06.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,6 +1,6 @@
/* /*
* RetroShare Android QML App * 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> * Copyright (C) 2016 Manu Pineda <manu@cooperativa.cat>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -20,46 +20,120 @@
#include "libresapilocalclient.h" #include "libresapilocalclient.h"
#include <QJSEngine> #include <QJSEngine>
#include <QtDebug>
void LibresapiLocalClient::openConnection(const QString& socketPath)
void LibresapiLocalClient::openConnection(QString socketPath)
{ {
connect(& mLocalSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), connect(& mLocalSocket, SIGNAL(error(QLocalSocket::LocalSocketError)),
this, SLOT(socketError(QLocalSocket::LocalSocketError))); this, SLOT(socketError(QLocalSocket::LocalSocketError)));
connect(& mLocalSocket, SIGNAL(readyRead()), connect(& mLocalSocket, SIGNAL(readyRead()),
this, SLOT(read())); this, SLOT(read()));
mLocalSocket.connectToServer(socketPath); mSocketPath = socketPath;
socketConnectAttempt();
} }
int LibresapiLocalClient::request( const QString& path, const QString& jsonData, int LibresapiLocalClient::request( const QString& path, const QString& jsonData,
QJSValue callback ) QJSValue callback )
{ {
#ifdef QT_DEBUG
if(mDebug)
qDebug() << reqCount++ << __PRETTY_FUNCTION__ << path << jsonData
<< callback.toString();
#endif // QT_DEBUG
QByteArray data; QByteArray data;
data.append(path); data.append('\n'); data.append(path); data.append('\n');
data.append(jsonData); data.append('\n'); data.append(jsonData); data.append('\n');
callbackQueue.enqueue(callback); processingQueue.enqueue(PQRecord(path, jsonData, callback));
mLocalSocket.write(data); int ret = mLocalSocket.write(data);
if(ret < 0) socketError(mLocalSocket.error());
return 1; 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() void LibresapiLocalClient::read()
{ {
QString receivedMsg(mLocalSocket.readLine()); if(processingQueue.isEmpty())
QJSValue callback(callbackQueue.dequeue());
if(callback.isCallable())
{ {
QJSValue params = callback.engine()->newObject(); qCritical() << __PRETTY_FUNCTION__ << "callbackQueue is empty "
params.setProperty("response", receivedMsg); << "something really fishy is happening!";
return;
callback.call(QJSValueList { params });
} }
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 goodResponseReceived(receivedMsg); /// @deprecated
emit responseReceived(receivedMsg); 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

View File

@ -1,6 +1,7 @@
#pragma once
/* /*
* libresapi local socket client * 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> * Copyright (C) 2016 Manu Pineda <manu@cooperativa.cat>
* *
* This program is free software: you can redistribute it and/or modify * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef LIBRESAPILOCALCLIENT_H
#define LIBRESAPILOCALCLIENT_H
#include <QLocalSocket> #include <QLocalSocket>
#include <QQueue> #include <QQueue>
#include <QJSValue> #include <QJSValue>
#include <QTimer>
class LibresapiLocalClient : public QObject class LibresapiLocalClient : public QObject
{ {
Q_OBJECT Q_OBJECT
public: 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 = "", Q_INVOKABLE int request( const QString& path, const QString& jsonData = "",
QJSValue callback = QJSValue::NullValue); QJSValue callback = QJSValue::NullValue );
Q_INVOKABLE void openConnection(QString socketPath); 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: private:
QTimer mConnectAttemptTimer;
QString mSocketPath;
QLocalSocket mLocalSocket; 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: private slots:
void socketConnectAttempt() { mLocalSocket.connectToServer(mSocketPath); }
void socketError(QLocalSocket::LocalSocketError error); void socketError(QLocalSocket::LocalSocketError error);
void read(); void read();
@ -52,6 +88,8 @@ signals:
* @param msg * @param msg
*/ */
void responseReceived(const QString & msg); void responseReceived(const QString & msg);
};
#endif // LIBRESAPILOCALCLIENT_H #ifdef QT_DEBUG
void debugChanged();
#endif // QT_DEBUG
};

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

View 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()
}
}
}

View File

@ -1,27 +1,33 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>qml/main.qml</file> <file>main-app.qml</file>
<file>qml/icons/star-2-128.png</file> <file>Locations.qml</file>
<file>qml/icons/settings-4-128.png</file> <file>jsonpath.js</file>
<file>qml/icons/email-128.png</file> <file>JSONListModel.qml</file>
<file>qml/icons/contacts-128.png</file> <file>Contacts.qml</file>
<file>qml/PostedMsgDelegate.qml</file> <file>AddTrustedNode.qml</file>
<file>qml/GxsService.qml</file> <file>RsLoginPassView.qml</file>
<file>qml/GxsIdDelegate.qml</file> <file>TrustedNodesView.qml</file>
<file>qml/GxsGroupDelegate.qml</file> <file>ChatView.qml</file>
<file>qml/ForumMsgDelegate.qml</file> <file>icons/retroshare06.png</file>
<file>qml/ContactBox.qml</file> <file>icons/state-offline.png</file>
<file>qml/ChannelMsgDelegate.qml</file> <file>icons/state-ok.png</file>
<file>qml/ChannelGroupDelegate.qml</file> <file>GxsIdentityDelegate.qml</file>
<file>qml/ApplicationBar.qml</file> <file>icons/edit-find.png</file>
<file>qml/AppButton.qml</file> <file>icons/edit-image-face-detect.png</file>
<file>qml/Locations.qml</file> <file>icons/application-menu.png</file>
<file>qml/jsonpath.js</file> <file>ContactSort.js</file>
<file>qml/JSONListModel.qml</file> <file>icons/emblem-locked.png</file>
<file>qml/Contacts.qml</file> <file>BusyOverlay.qml</file>
<file>qml/AddTrustedNode.qml</file> <file>URI.js</file>
<file>qml/RsLoginPassView.qml</file> <file>TokensManager.qml</file>
<file>qml/TrustedNodesView.qml</file> <file>qmldir</file>
<file>qml/ChatView.qml</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> </qresource>
</RCC> </RCC>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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")
}
}
}
}
}

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -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
}
}
}
}
*/
}

View File

@ -0,0 +1,2 @@
singleton TokensManager 1.0 TokensManager.qml
singleton ClipboardWrapper 1.0 ClipboardWrapper.qml

View File

@ -1,17 +1,27 @@
!include("../../retroshare.pri"): error("Could not include file ../../retroshare.pri") !include("../../retroshare.pri"): error("Could not include file ../../retroshare.pri")
QT += qml quick QT += core network qml quick
CONFIG += c++11 CONFIG += c++11
HEADERS += libresapilocalclient.h HEADERS += libresapilocalclient.h \
SOURCES += main.cpp \ rsqmlappengine.h
libresapilocalclient.cpp SOURCES += main-app.cpp \
libresapilocalclient.cpp \
rsqmlappengine.cpp
RESOURCES += qml.qrc 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 # Additional import path used to resolve QML modules in Qt Creator's code model
#QML_IMPORT_PATH = #QML_IMPORT_PATH =
#QML2_IMPORT_PATH =
# Default rules for deployment. # Default rules for deployment.
include(deployment.pri) include(deployment.pri)
@ -23,7 +33,8 @@ DISTFILES += \
android/res/values/libs.xml \ android/res/values/libs.xml \
android/build.gradle \ android/build.gradle \
android/gradle/wrapper/gradle-wrapper.properties \ android/gradle/wrapper/gradle-wrapper.properties \
android/gradlew.bat android/gradlew.bat \
icons/retroshare06.png
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android

View 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!";
}

Some files were not shown because too many files have changed in this diff Show More