From 9a980def2c35f87a25e5e2d1207af990a7297739 Mon Sep 17 00:00:00 2001 From: Gio Date: Wed, 3 Aug 2016 00:48:19 +0200 Subject: [PATCH 01/15] libretroshare now compiles for Android, instruction on how to do it included --- .travis.yml | 4 +- README-Android.asciidoc | 98 +++ RetroShare.pro | 47 +- android-prepare-toolchain.sh | 130 ++++ libresapi/src/api/RsControlModule.cpp | 4 +- libretroshare/src/libretroshare.pro | 18 +- libretroshare/src/pqi/pqinetwork.cc | 4 + libretroshare/src/rsserver/p3peers.h | 3 +- libretroshare/src/rsserver/rsloginhandler.cc | 4 + libretroshare/src/util/ifaddrs.c | 600 +++++++++++++++++++ libretroshare/src/util/ifaddrs.h | 54 ++ openpgpsdk/src/openpgpsdk/readerwriter.c | 5 + retroshare-gui/src/gui/settings/ServerPage.h | 2 +- retroshare-gui/src/util/RsNetUtil.h | 1 + retroshare.pri | 63 +- tests/unittests/unittests.pro | 2 +- 16 files changed, 994 insertions(+), 45 deletions(-) create mode 100644 README-Android.asciidoc create mode 100755 android-prepare-toolchain.sh create mode 100644 libretroshare/src/util/ifaddrs.c create mode 100644 libretroshare/src/util/ifaddrs.h diff --git a/.travis.yml b/.travis.yml index 80b719c06..8ec4c7ac7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,12 +24,12 @@ addons: project: name: "RetroShare/RetroShare" description: "RetroShare Build submitted via Travis CI" - build_command_prepend: "qmake CONFIG+=NO_SQLCIPHER; make clean" + build_command_prepend: "qmake CONFIG+=no_sqlcipher; make clean" build_command: "make -j 4" branch_pattern: coverity_scan before_script: - - qmake QMAKE_CC=$CC QMAKE_CXX=$CXX CONFIG+=NO_SQLCIPHER CONFIG+=tests + - qmake QMAKE_CC=$CC QMAKE_CXX=$CXX CONFIG+=no_sqlcipher CONFIG+=tests #script: make script: if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then make && tests/unittests/unittests >/dev/null 2>&1 ; fi diff --git a/README-Android.asciidoc b/README-Android.asciidoc new file mode 100644 index 000000000..18f744384 --- /dev/null +++ b/README-Android.asciidoc @@ -0,0 +1,98 @@ +Compile Retroshare for Android +============================== + +== Introduction + +Compiling an application for Android is not as easy as one would imagine, expecially one like RetroShare that has a big codebase and is not well documented. This document is aimed to empower the reader so she can hopefully succed or at least have a significant help in compiling her own RetroShare APK package installable on Android. + +== Preparing The Environement + +First of all setup your Qt for Android development environement following the guide on the link:http://doc.qt.io/qt-5/androidgs.html[Qt for android web site]. +At this point you should have Android SDK, Android NDK, and Qt for Android working fine, and you should be capable of execute on a Android emulator on on your Android phone Qt for Android examples. + +But RetroShare is not as simple to compile as those examples, in particular 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 provide the +android-prepare-toolchain.sh+ script, before you execute it you should define some variable the script cannot determine in an easy and reliable manner by itself in your terminal. + +[source,bash] +------------------------------------------------------------------------------- +## The path where Android NDK is installed in your system +export ANDROID_NDK_PATH="/opt/android-ndk/" + +## The path where your fresh compiled toolchain will be installed, take care +## the parent exists +export NDK_TOOLCHAIN_PATH="/home/$(whoami)/Development/android-toolchains/retroshare-android/" + +## The CPU architecture of the Android device you want to target +export ANDROID_NDK_ARCH="arm" + +## The Android API level the Android device you want to target +export ANDROID_PLATFORM_VER="23" + +## The number of core that yout host CPU have (just to speed up compilation) set it to 1 if unsure +export HOST_NUM_CPU=1 + +./android-prepare-toolchain.sh +------------------------------------------------------------------------------- + +Now is time for the bad news: as of today Qt assume 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 a little Qt internals 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 +/opt/Qt5.7.0/5.7/android_armv7/mkspecs+ and then modify qmake configuration for 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 advantege of the variables I set before. + +[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|^ANDROID_PLATFORM =.*|ANDROID_PLATFORM = android-${ANDROID_PLATFORM_VER}|" qmake.conf +sed -i "s|^NDK_TOOLCHAIN_PATH =.*|NDK_TOOLCHAIN_PATH = ${NDK_TOOLCHAIN_PATH}|" qmake.conf +sed -i "s|^QMAKE_LFLAGS \+=.*|QMAKE_LFLAGS = --sysroot=${NDK_TOOLCHAIN_PATH}/sysroot/|" qmake.conf +------------------------------------------------------------------------------- + +== Preparing Qt Creator + +Now that your environement is set up you should configure Qt Creator for Android following the link:http://doc.qt.io/qtcreator/creator-developing-android.html[official guide], at the end of this step your Qt Creator should recognize Android compiler and Qt for Android kit, because we use a custom toolchain some step more is needed. + +From the top menu click Tools -> Options... -> Build & Run -> Compilers -> Android GCC (arm-4.9) -> Clone + +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+ then press Apply. + +Now go to the Kits tab, select +Android for armeabi-v7a (GCC 4.9, Qt 5.7.0)+ and press Clone button a new kit is created and it is 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. + +Your Kit is now ready to use, now you can open retroshare as 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. + +== Final tweaks + +RetroShare GUI, plugins and WebUI are yet not supported on android so to be able to compile RetroShare without error you will have to open RetroShare.pro file with Qt Creator and change the following assignation + +[source,makefile] +------------------------------------------------------------------------------- +SUBDIRS += \ + openpgpsdk \ + libbitdht \ + libretroshare \ + libresapi \ + retroshare_gui \ + retroshare_nogui \ + plugins +------------------------------------------------------------------------------- + +into + +[source,makefile] +------------------------------------------------------------------------------- +SUBDIRS += \ + openpgpsdk \ + libbitdht \ + libretroshare \ + libresapi \ + retroshare_nogui +------------------------------------------------------------------------------- + + +== Furter Readings + +- link:http://doc.qt.io/qt-5/android-support.html[] +- link:https://developer.android.com/ndk/guides/libs.html[] +- link:retroshare://forum?name=Compiling%20nogui%20for%20android&id=8fd22bd8f99754461e7ba1ca8a727995&msgid=4e0f92330600bba9cf978f384f4b7b2f2ca64eff[] +- link:retroshare://file?name=Android%20Native%20Development%20Kit%20Cookbook.pdf&size=29214468&hash=0123361c1b14366ce36118e82b90faf7c7b1b136[] diff --git a/RetroShare.pro b/RetroShare.pro index 4f7e1934d..ba8f0ebfa 100644 --- a/RetroShare.pro +++ b/RetroShare.pro @@ -3,36 +3,47 @@ TEMPLATE = subdirs #CONFIG += tests -SUBDIRS += \ - openpgpsdk \ - libbitdht \ - libretroshare \ - libresapi \ - retroshare_gui \ - retroshare_nogui \ - plugins - +SUBDIRS += openpgpsdk openpgpsdk.file = openpgpsdk/src/openpgpsdk.pro +SUBDIRS += libbitdht libbitdht.file = libbitdht/src/libbitdht.pro +SUBDIRS += libretroshare libretroshare.file = libretroshare/src/libretroshare.pro libretroshare.depends = openpgpsdk libbitdht +SUBDIRS += libresapi libresapi.file = libresapi/src/libresapi.pro libresapi.depends = libretroshare -retroshare_gui.file = retroshare-gui/src/retroshare-gui.pro -retroshare_gui.depends = libretroshare libresapi -retroshare_gui.target = retroshare-gui +retroshare_gui { + SUBDIRS += retroshare_gui + retroshare_gui.file = retroshare-gui/src/retroshare-gui.pro + retroshare_gui.depends = libretroshare libresapi + retroshare_gui.target = retroshare_gui +} -retroshare_nogui.file = retroshare-nogui/src/retroshare-nogui.pro -retroshare_nogui.depends = libretroshare libresapi -retroshare_nogui.target = retroshare-nogui +retroshare_nogui { + SUBDIRS += retroshare_nogui + retroshare_nogui.file = retroshare-nogui/src/retroshare-nogui.pro + retroshare_nogui.depends = libretroshare libresapi + retroshare_nogui.target = retroshare_nogui +} -plugins.file = plugins/plugins.pro -plugins.depends = retroshare_gui -plugins.target = plugins +retroshare_android_service { + SUBDIRS += retroshare_android_service + retroshare_android_service.file = retroshare-android-service/src/retroshare-android-service.pro + retroshare_android_service.depends = libretroshare libresapi + retroshare_android_service.target = retroshare_android_service +} + +retroshare_plugins { + SUBDIRS += plugins + plugins.file = plugins/plugins.pro + plugins.depends = retroshare_gui + plugins.target = plugins +} wikipoos { SUBDIRS += pegmarkdown diff --git a/android-prepare-toolchain.sh b/android-prepare-toolchain.sh new file mode 100755 index 000000000..c496aafad --- /dev/null +++ b/android-prepare-toolchain.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +## You are supposed to provide the following variables according to your system setup +[ -z ${ANDROID_NDK_PATH+x} ] && export ANDROID_NDK_PATH="/opt/android-ndk/" +[ -z ${NDK_TOOLCHAIN_PATH+x} ] && export NDK_TOOLCHAIN_PATH="/home/$(whoami)/Development/android-toolchains/retroshare-android/" +[ -z ${ANDROID_NDK_ARCH+x} ] && export ANDROID_NDK_ARCH="arm" +[ -z ${ANDROID_PLATFORM_VER+x} ] && export ANDROID_PLATFORM_VER="23" +[ -z ${HOST_NUM_CPU+x} ] && export HOST_NUM_CPU=4 + + +## You should not edit the following variables +export SYSROOT="${NDK_TOOLCHAIN_PATH}/sysroot" +export PREFIX="${SYSROOT}" +export CC="${NDK_TOOLCHAIN_PATH}/bin/arm-linux-androideabi-gcc" +export CXX="${NDK_TOOLCHAIN_PATH}/bin/arm-linux-androideabi-g++" +export AR="${NDK_TOOLCHAIN_PATH}/bin/arm-linux-androideabi-ar" +export RANLIB="${NDK_TOOLCHAIN_PATH}/bin/arm-linux-androideabi-gcc-ranlib" +export ANDROID_DEV="${ANDROID_NDK_PATH}/platforms/android-${ANDROID_PLATFORM_VER}/arch-${ANDROID_NDK_ARCH}/usr" + + +## More information available at https://android.googlesource.com/platform/ndk/+/ics-mr0/docs/STANDALONE-TOOLCHAIN.html +build_toolchain() +{ + rm -rf ${NDK_TOOLCHAIN_PATH} + ${ANDROID_NDK_PATH}/build/tools/make-standalone-toolchain.sh --ndk-dir=${ANDROID_NDK_PATH} --arch=${ANDROID_NDK_ARCH} --install-dir=${NDK_TOOLCHAIN_PATH} --platform=android-${ANDROID_PLATFORM_VER} +} + +## More information available at retroshare://file?name=Android%20Native%20Development%20Kit%20Cookbook.pdf&size=29214468&hash=0123361c1b14366ce36118e82b90faf7c7b1b136 +build_bzlib() +{ + B_dir="bzip2-1.0.6" + rm -rf $B_dir + [ -f $B_dir.tar.gz ] || wget http://www.bzip.org/1.0.6/bzip2-1.0.6.tar.gz + tar -xf $B_dir.tar.gz + cd $B_dir + sed -i "/^CC=.*/d" Makefile + sed -i "/^AR=.*/d" Makefile + sed -i "/^RANLIB=.*/d" Makefile + sed -i "/^LDFLAGS=.*/d" Makefile + sed -i "s/^all: libbz2.a bzip2 bzip2recover test/all: libbz2.a bzip2 bzip2recover/" Makefile + make -j${HOST_NUM_CPU} + make install PREFIX=${SYSROOT}/usr + sed -i "/^CC=.*/d" Makefile-libbz2_so + make -f Makefile-libbz2_so -j${HOST_NUM_CPU} + cp libbz2.so.1.0.6 ${SYSROOT}/usr/lib/libbz2.so + cd .. +} + +## More information available at http://doc.qt.io/qt-5/opensslsupport.html +build_openssl() +{ + B_dir="openssl-1.0.2h" + rm -rf $B_dir + [ -f $B_dir.tar.gz ] || wget https://www.openssl.org/source/$B_dir.tar.gz + tar -xf $B_dir.tar.gz + cd $B_dir + ANDROID_NDK_ROOT="${ANDROID_NDK_PATH}" ./Configure shared android-armv7 --prefix="${SYSROOT}/usr" --openssldir="${SYSROOT}/etc/ssl" + sed -i 's/LIBNAME=$$i LIBVERSION=$(SHLIB_MAJOR).$(SHLIB_MINOR) \\/LIBNAME=$$i \\/g' Makefile + sed -i '/LIBCOMPATVERSIONS=";$(SHLIB_VERSION_HISTORY)" \\/d' Makefile + make -j${HOST_NUM_CPU} + make install + cp *.so "${SYSROOT}/usr/lib" + cd .. +} + +build_sqlite() +{ + B_dir="sqlite-autoconf-3130000" + [ -f $B_dir.tar.gz ] || wget https://www.sqlite.org/2016/$B_dir.tar.gz + tar -xf $B_dir.tar.gz + cd $B_dir + ./configure --prefix="${SYSROOT}/usr" --host=${ANDROID_NDK_ARCH}-linux + make -j${HOST_NUM_CPU} + make install + ${CC} -shared -o libsqlite3.so -fPIC sqlite3.o -ldl + cp libsqlite3.so "${SYSROOT}/usr/lib" + cd .. +} + +build_sqlcipher() +{ + echo "sqlcipher not supported yet on android" + return 0 + + cd sqlcipher + ./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" LDFLAGS="${SYSROOT}/usr/lib/libcrypto.a" + make -j${HOST_NUM_CPU} + make install + cd .. +} + +build_libupnp() +{ + B_dir="libupnp-1.6.20" + rm -rf $B_dir + [ -f $B_dir.tar.bz2 ] || wget https://sourceforge.net/projects/pupnp/files/pupnp/libUPnP%201.6.20/$B_dir.tar.bz2 + tar -xf $B_dir.tar.bz2 + cd $B_dir +## liupnp must be configured as static library because if not the linker will +## look for libthreadutils.so.6 at runtime that cannot be packaged on android +## as it supports only libname.so format for libraries, thus resulting in a +## crash at startup. + ./configure --enable-static --disable-shared --disable-samples --prefix="${SYSROOT}/usr" --host=${ANDROID_NDK_ARCH}-linux + make -j${HOST_NUM_CPU} + make install + cd .. +} + +build_libmicrohttpd() +{ + echo "libmicrohttpd not supported yet on android" + return 0 + + B_dir="libmicrohttpd-0.9.50" + rm -rf $B_dir + [ -f $B_dir.tar.gz ] || wget ftp://ftp.gnu.org/gnu/libmicrohttpd/$B_dir.tar.gz + tar -xf $B_dir.tar.gz + cd $B_dir + ./configure --prefix="${SYSROOT}/usr" --host=${ANDROID_NDK_ARCH}-linux + #make -e ? + make -j${HOST_NUM_CPU} + make install + cd .. +} + +build_toolchain +build_bzlib +build_openssl +build_sqlite +build_libupnp diff --git a/libresapi/src/api/RsControlModule.cpp b/libresapi/src/api/RsControlModule.cpp index 8bf8fd7ea..9430df170 100644 --- a/libresapi/src/api/RsControlModule.cpp +++ b/libresapi/src/api/RsControlModule.cpp @@ -59,7 +59,7 @@ bool RsControlModule::askForPassword(const std::string &title, const std::string { cancelled = false ; { - RsStackMutex stack(mDataMtx); // ********** LOCKED ********** + RS_STACK_MUTEX(mDataMtx); // ********** LOCKED ********** if(mFixedPassword != "") { password = mFixedPassword; @@ -78,7 +78,7 @@ bool RsControlModule::askForPassword(const std::string &title, const std::string { usleep(5*1000); - RsStackMutex stack(mDataMtx); // ********** LOCKED ********** + RS_STACK_MUTEX(mDataMtx); // ********** LOCKED ********** wait = mWantPassword; if(!wait && mPassword != "") { diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index 6499243b8..6c89e6f05 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -134,8 +134,7 @@ linux-* { QMAKE_CXXFLAGS *= -Wall -D_FILE_OFFSET_BITS=64 QMAKE_CC = $${QMAKE_CXX} - contains(CONFIG, NO_SQLCIPHER) { - DEFINES *= NO_SQLCIPHER + no_sqlcipher { PKGCONFIG *= sqlite3 } else { SQLCIPHER_OK = $$system(pkg-config --exists sqlcipher && echo yes) @@ -146,7 +145,7 @@ linux-* { DEPENDPATH += ../../../lib/ INCLUDEPATH += ../../../lib/ } else { - error("libsqlcipher is not installed and libsqlcipher.a not found. SQLCIPHER is necessary for encrypted database, to build with unencrypted database, run: qmake CONFIG+=NO_SQLCIPHER") + error("libsqlcipher is not installed and libsqlcipher.a not found. SQLCIPHER is necessary for encrypted database, to build with unencrypted database, run: qmake CONFIG+=no_sqlcipher") } } else { # Workaround for broken sqlcipher packages, e.g. Ubuntu 14.04 @@ -160,7 +159,7 @@ linux-* { # linux/bsd can use either - libupnp is more complete and packaged. #CONFIG += upnp_miniupnpc - CONFIG += upnp_libupnp + CONFIG += upnp_libupnp # Check if the systems libupnp has been Debian-patched system(grep -E 'char[[:space:]]+PublisherUrl' /usr/include/upnp/upnp.h >/dev/null 2>&1) { @@ -309,7 +308,7 @@ freebsd-* { # linux/bsd can use either - libupnp is more complete and packaged. #CONFIG += upnp_miniupnpc - CONFIG += upnp_libupnp + CONFIG += upnp_libupnp } ################################# OpenBSD ########################################## @@ -336,6 +335,15 @@ haiku-* { DESTDIR = lib } +################################# Android ##################################### + +android-g++ { + ## ifaddrs is missing on Android add them + ## taken from https://github.com/morristech/android-ifaddrs + HEADERS *= util/ifaddrs.h + SOURCES *= util/ifaddrs.c +} + ################################### COMMON stuff ################################## # openpgpsdk diff --git a/libretroshare/src/pqi/pqinetwork.cc b/libretroshare/src/pqi/pqinetwork.cc index 4937aef64..794cb5bca 100644 --- a/libretroshare/src/pqi/pqinetwork.cc +++ b/libretroshare/src/pqi/pqinetwork.cc @@ -275,7 +275,11 @@ int inet_aton(const char *name, struct in_addr *addr) #include #pragma comment(lib, "IPHLPAPI.lib") #else // WINDOWS_SYS +#ifdef __ANDROID__ +#include "util/ifaddrs.h" +#else // __ANDROID__ #include +#endif //__ANDROID__ #include #endif // WINDOWS_SYS diff --git a/libretroshare/src/rsserver/p3peers.h b/libretroshare/src/rsserver/p3peers.h index 2edae4abc..9bdf4283e 100644 --- a/libretroshare/src/rsserver/p3peers.h +++ b/libretroshare/src/rsserver/p3peers.h @@ -26,11 +26,12 @@ * */ +#include #include "retroshare/rspeers.h" + class p3LinkMgr; class p3PeerMgr; class p3NetMgr; -struct sockaddr_storage; class p3Peers: public RsPeers diff --git a/libretroshare/src/rsserver/rsloginhandler.cc b/libretroshare/src/rsserver/rsloginhandler.cc index a62381432..fcd5965e4 100644 --- a/libretroshare/src/rsserver/rsloginhandler.cc +++ b/libretroshare/src/rsserver/rsloginhandler.cc @@ -200,6 +200,7 @@ bool RsLoginHandler::tryAutoLogin(const RsPeerId& ssl_id,std::string& ssl_passwd /******************** OSX KeyChain stuff *****************************/ #else /* UNIX, but not HAS_GNOME_KEYRING or APPLE */ +#ifdef TODO_CODE_ROTTEN FILE* helpFile = RsDirUtil::rs_fopen(getAutologinFileName(ssl_id).c_str(), "r"); if(helpFile == NULL){ @@ -248,6 +249,7 @@ bool RsLoginHandler::tryAutoLogin(const RsPeerId& ssl_id,std::string& ssl_passwd delete key; return true; +#endif //TODO_CODE_ROTTEN #endif // APPLE #endif // HAS_GNOME_KEYRING /******* WINDOWS BELOW *****/ @@ -419,6 +421,7 @@ bool RsLoginHandler::enableAutoLogin(const RsPeerId& ssl_id,const std::string& s /***************** OSX KEYCHAIN ****************/ #else +#ifdef TODO_CODE_ROTTEN /* WARNING: Autologin is inherently unsafe */ FILE* helpFile = RsDirUtil::rs_fopen(getAutologinFileName.c_str(), "w"); @@ -450,6 +453,7 @@ bool RsLoginHandler::enableAutoLogin(const RsPeerId& ssl_id,const std::string& s return true; +#endif // TODO_CODE_ROTTEN #endif // __APPLE__ #endif // HAS_GNOME_KEYRING. #else /* windows */ diff --git a/libretroshare/src/util/ifaddrs.c b/libretroshare/src/util/ifaddrs.c new file mode 100644 index 000000000..338fff887 --- /dev/null +++ b/libretroshare/src/util/ifaddrs.c @@ -0,0 +1,600 @@ +/* +Copyright (c) 2013, Kenneth MacKay +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ifaddrs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct NetlinkList +{ + struct NetlinkList *m_next; + struct nlmsghdr *m_data; + unsigned int m_size; +} NetlinkList; + +static int netlink_socket(void) +{ + int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if(l_socket < 0) + { + return -1; + } + + struct sockaddr_nl l_addr; + memset(&l_addr, 0, sizeof(l_addr)); + l_addr.nl_family = AF_NETLINK; + if(bind(l_socket, (struct sockaddr *)&l_addr, sizeof(l_addr)) < 0) + { + close(l_socket); + return -1; + } + + return l_socket; +} + +static int netlink_send(int p_socket, int p_request) +{ + char l_buffer[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))]; + memset(l_buffer, 0, sizeof(l_buffer)); + struct nlmsghdr *l_hdr = (struct nlmsghdr *)l_buffer; + struct rtgenmsg *l_msg = (struct rtgenmsg *)NLMSG_DATA(l_hdr); + + l_hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*l_msg)); + l_hdr->nlmsg_type = p_request; + l_hdr->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; + l_hdr->nlmsg_pid = 0; + l_hdr->nlmsg_seq = p_socket; + l_msg->rtgen_family = AF_UNSPEC; + + struct sockaddr_nl l_addr; + memset(&l_addr, 0, sizeof(l_addr)); + l_addr.nl_family = AF_NETLINK; + return (sendto(p_socket, l_hdr, l_hdr->nlmsg_len, 0, (struct sockaddr *)&l_addr, sizeof(l_addr))); +} + +static int netlink_recv(int p_socket, void *p_buffer, size_t p_len) +{ + struct msghdr l_msg; + struct iovec l_iov = { p_buffer, p_len }; + struct sockaddr_nl l_addr; + int l_result; + + for(;;) + { + l_msg.msg_name = (void *)&l_addr; + l_msg.msg_namelen = sizeof(l_addr); + l_msg.msg_iov = &l_iov; + l_msg.msg_iovlen = 1; + l_msg.msg_control = NULL; + l_msg.msg_controllen = 0; + l_msg.msg_flags = 0; + int l_result = recvmsg(p_socket, &l_msg, 0); + + if(l_result < 0) + { + if(errno == EINTR) + { + continue; + } + return -2; + } + + if(l_msg.msg_flags & MSG_TRUNC) + { // buffer was too small + return -1; + } + return l_result; + } +} + +static struct nlmsghdr *getNetlinkResponse(int p_socket, int *p_size, int *p_done) +{ + size_t l_size = 4096; + void *l_buffer = NULL; + + for(;;) + { + free(l_buffer); + l_buffer = malloc(l_size); + + int l_read = netlink_recv(p_socket, l_buffer, l_size); + *p_size = l_read; + if(l_read == -2) + { + free(l_buffer); + return NULL; + } + if(l_read >= 0) + { + pid_t l_pid = getpid(); + struct nlmsghdr *l_hdr; + for(l_hdr = (struct nlmsghdr *)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); l_hdr = (struct nlmsghdr *)NLMSG_NEXT(l_hdr, l_read)) + { + if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if(l_hdr->nlmsg_type == NLMSG_DONE) + { + *p_done = 1; + break; + } + + if(l_hdr->nlmsg_type == NLMSG_ERROR) + { + free(l_buffer); + return NULL; + } + } + return l_buffer; + } + + l_size *= 2; + } +} + +static NetlinkList *newListItem(struct nlmsghdr *p_data, unsigned int p_size) +{ + NetlinkList *l_item = malloc(sizeof(NetlinkList)); + l_item->m_next = NULL; + l_item->m_data = p_data; + l_item->m_size = p_size; + return l_item; +} + +static void freeResultList(NetlinkList *p_list) +{ + NetlinkList *l_cur; + while(p_list) + { + l_cur = p_list; + p_list = p_list->m_next; + free(l_cur->m_data); + free(l_cur); + } +} + +static NetlinkList *getResultList(int p_socket, int p_request) +{ + if(netlink_send(p_socket, p_request) < 0) + { + return NULL; + } + + NetlinkList *l_list = NULL; + NetlinkList *l_end = NULL; + int l_size; + int l_done = 0; + while(!l_done) + { + struct nlmsghdr *l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done); + if(!l_hdr) + { // error + freeResultList(l_list); + return NULL; + } + + NetlinkList *l_item = newListItem(l_hdr, l_size); + if(!l_list) + { + l_list = l_item; + } + else + { + l_end->m_next = l_item; + } + l_end = l_item; + } + return l_list; +} + +static size_t maxSize(size_t a, size_t b) +{ + return (a > b ? a : b); +} + +static size_t calcAddrLen(sa_family_t p_family, int p_dataSize) +{ + switch(p_family) + { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + case AF_PACKET: + return maxSize(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize); + default: + return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize); + } +} + +static void makeSockaddr(sa_family_t p_family, struct sockaddr *p_dest, void *p_data, size_t p_size) +{ + switch(p_family) + { + case AF_INET: + memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size); + break; + case AF_INET6: + memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size); + break; + case AF_PACKET: + memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size); + ((struct sockaddr_ll*)p_dest)->sll_halen = p_size; + break; + default: + memcpy(p_dest->sa_data, p_data, p_size); + break; + } + p_dest->sa_family = p_family; +} + +static void addToEnd(struct ifaddrs **p_resultList, struct ifaddrs *p_entry) +{ + if(!*p_resultList) + { + *p_resultList = p_entry; + } + else + { + struct ifaddrs *l_cur = *p_resultList; + while(l_cur->ifa_next) + { + l_cur = l_cur->ifa_next; + } + l_cur->ifa_next = p_entry; + } +} + +static void interpretLink(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList) +{ + struct ifinfomsg *l_info = (struct ifinfomsg *)NLMSG_DATA(p_hdr); + + size_t l_nameSize = 0; + size_t l_addrSize = 0; + size_t l_dataSize = 0; + + size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); + struct rtattr *l_rta; + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch(l_rta->rta_type) + { + case IFLA_ADDRESS: + case IFLA_BROADCAST: + l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize)); + break; + case IFLA_IFNAME: + l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); + break; + case IFLA_STATS: + l_dataSize += NLMSG_ALIGN(l_rtaSize); + break; + default: + break; + } + } + + struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize + l_dataSize); + memset(l_entry, 0, sizeof(struct ifaddrs)); + l_entry->ifa_name = ""; + + char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs); + char *l_addr = l_name + l_nameSize; + char *l_data = l_addr + l_addrSize; + + l_entry->ifa_flags = l_info->ifi_flags; + + l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch(l_rta->rta_type) + { + case IFLA_ADDRESS: + case IFLA_BROADCAST: + { + size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize); + makeSockaddr(AF_PACKET, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize); + ((struct sockaddr_ll *)l_addr)->sll_ifindex = l_info->ifi_index; + ((struct sockaddr_ll *)l_addr)->sll_hatype = l_info->ifi_type; + if(l_rta->rta_type == IFLA_ADDRESS) + { + l_entry->ifa_addr = (struct sockaddr *)l_addr; + } + else + { + l_entry->ifa_broadaddr = (struct sockaddr *)l_addr; + } + l_addr += NLMSG_ALIGN(l_addrLen); + break; + } + case IFLA_IFNAME: + strncpy(l_name, l_rtaData, l_rtaDataSize); + l_name[l_rtaDataSize] = '\0'; + l_entry->ifa_name = l_name; + break; + case IFLA_STATS: + memcpy(l_data, l_rtaData, l_rtaDataSize); + l_entry->ifa_data = l_data; + break; + default: + break; + } + } + + addToEnd(p_resultList, l_entry); + p_links[l_info->ifi_index - 1] = l_entry; +} + +static void interpretAddr(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList) +{ + struct ifaddrmsg *l_info = (struct ifaddrmsg *)NLMSG_DATA(p_hdr); + + size_t l_nameSize = 0; + size_t l_addrSize = 0; + + int l_addedNetmask = 0; + + size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); + struct rtattr *l_rta; + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + if(l_info->ifa_family == AF_PACKET) + { + continue; + } + + switch(l_rta->rta_type) + { + case IFA_ADDRESS: + case IFA_LOCAL: + if((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask) + { // make room for netmask + l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); + l_addedNetmask = 1; + } + case IFA_BROADCAST: + l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); + break; + case IFA_LABEL: + l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); + break; + default: + break; + } + } + + struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize); + memset(l_entry, 0, sizeof(struct ifaddrs)); + l_entry->ifa_name = p_links[l_info->ifa_index - 1]->ifa_name; + + char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs); + char *l_addr = l_name + l_nameSize; + + l_entry->ifa_flags = l_info->ifa_flags | p_links[l_info->ifa_index - 1]->ifa_flags; + + l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); + for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) + { + void *l_rtaData = RTA_DATA(l_rta); + size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); + switch(l_rta->rta_type) + { + case IFA_ADDRESS: + case IFA_BROADCAST: + case IFA_LOCAL: + { + size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize); + makeSockaddr(l_info->ifa_family, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize); + if(l_info->ifa_family == AF_INET6) + { + if(IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)l_rtaData) || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr *)l_rtaData)) + { + ((struct sockaddr_in6 *)l_addr)->sin6_scope_id = l_info->ifa_index; + } + } + + if(l_rta->rta_type == IFA_ADDRESS) + { // apparently in a point-to-point network IFA_ADDRESS contains the dest address and IFA_LOCAL contains the local address + if(l_entry->ifa_addr) + { + l_entry->ifa_dstaddr = (struct sockaddr *)l_addr; + } + else + { + l_entry->ifa_addr = (struct sockaddr *)l_addr; + } + } + else if(l_rta->rta_type == IFA_LOCAL) + { + if(l_entry->ifa_addr) + { + l_entry->ifa_dstaddr = l_entry->ifa_addr; + } + l_entry->ifa_addr = (struct sockaddr *)l_addr; + } + else + { + l_entry->ifa_broadaddr = (struct sockaddr *)l_addr; + } + l_addr += NLMSG_ALIGN(l_addrLen); + break; + } + case IFA_LABEL: + strncpy(l_name, l_rtaData, l_rtaDataSize); + l_name[l_rtaDataSize] = '\0'; + l_entry->ifa_name = l_name; + break; + default: + break; + } + } + + if(l_entry->ifa_addr && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6)) + { + unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128); + unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen); + char l_mask[16] = {0}; + unsigned i; + for(i=0; i<(l_prefix/8); ++i) + { + l_mask[i] = 0xff; + } + l_mask[i] = 0xff << (8 - (l_prefix % 8)); + + makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr *)l_addr, l_mask, l_maxPrefix / 8); + l_entry->ifa_netmask = (struct sockaddr *)l_addr; + } + + addToEnd(p_resultList, l_entry); +} + +static void interpret(int p_socket, NetlinkList *p_netlinkList, struct ifaddrs **p_links, struct ifaddrs **p_resultList) +{ + pid_t l_pid = getpid(); + for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next) + { + unsigned int l_nlsize = p_netlinkList->m_size; + struct nlmsghdr *l_hdr; + for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) + { + if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if(l_hdr->nlmsg_type == NLMSG_DONE) + { + break; + } + + if(l_hdr->nlmsg_type == RTM_NEWLINK) + { + interpretLink(l_hdr, p_links, p_resultList); + } + else if(l_hdr->nlmsg_type == RTM_NEWADDR) + { + interpretAddr(l_hdr, p_links, p_resultList); + } + } + } +} + +static unsigned countLinks(int p_socket, NetlinkList *p_netlinkList) +{ + unsigned l_links = 0; + pid_t l_pid = getpid(); + for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next) + { + unsigned int l_nlsize = p_netlinkList->m_size; + struct nlmsghdr *l_hdr; + for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) + { + if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) + { + continue; + } + + if(l_hdr->nlmsg_type == NLMSG_DONE) + { + break; + } + + if(l_hdr->nlmsg_type == RTM_NEWLINK) + { + ++l_links; + } + } + } + + return l_links; +} + +int getifaddrs(struct ifaddrs **ifap) +{ + if(!ifap) + { + return -1; + } + *ifap = NULL; + + int l_socket = netlink_socket(); + if(l_socket < 0) + { + return -1; + } + + NetlinkList *l_linkResults = getResultList(l_socket, RTM_GETLINK); + if(!l_linkResults) + { + close(l_socket); + return -1; + } + + NetlinkList *l_addrResults = getResultList(l_socket, RTM_GETADDR); + if(!l_addrResults) + { + close(l_socket); + freeResultList(l_linkResults); + return -1; + } + + unsigned l_numLinks = countLinks(l_socket, l_linkResults) + countLinks(l_socket, l_addrResults); + struct ifaddrs *l_links[l_numLinks]; + memset(l_links, 0, l_numLinks * sizeof(struct ifaddrs *)); + + interpret(l_socket, l_linkResults, l_links, ifap); + interpret(l_socket, l_addrResults, l_links, ifap); + + freeResultList(l_linkResults); + freeResultList(l_addrResults); + close(l_socket); + return 0; +} + +void freeifaddrs(struct ifaddrs *ifa) +{ + struct ifaddrs *l_cur; + while(ifa) + { + l_cur = ifa; + ifa = ifa->ifa_next; + free(l_cur); + } +} diff --git a/libretroshare/src/util/ifaddrs.h b/libretroshare/src/util/ifaddrs.h new file mode 100644 index 000000000..9cd19fec1 --- /dev/null +++ b/libretroshare/src/util/ifaddrs.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 1995, 1999 + * Berkeley Software Design, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * BSDI ifaddrs.h,v 2.5 2000/02/23 14:51:59 dab Exp + */ + +#ifndef _IFADDRS_H_ +#define _IFADDRS_H_ + +struct ifaddrs { + struct ifaddrs *ifa_next; + char *ifa_name; + unsigned int ifa_flags; + struct sockaddr *ifa_addr; + struct sockaddr *ifa_netmask; + struct sockaddr *ifa_dstaddr; + void *ifa_data; +}; + +/* + * This may have been defined in . Note that if is + * to be included it must be included before this header file. + */ +#ifndef ifa_broadaddr +#define ifa_broadaddr ifa_dstaddr /* broadcast address interface */ +#endif + +#include + +__BEGIN_DECLS +extern int getifaddrs(struct ifaddrs **ifap); +extern void freeifaddrs(struct ifaddrs *ifa); +__END_DECLS + +#endif diff --git a/openpgpsdk/src/openpgpsdk/readerwriter.c b/openpgpsdk/src/openpgpsdk/readerwriter.c index a80fede82..f3a16c364 100644 --- a/openpgpsdk/src/openpgpsdk/readerwriter.c +++ b/openpgpsdk/src/openpgpsdk/readerwriter.c @@ -460,7 +460,12 @@ callback_cmd_get_secret_key(const ops_parser_content_t *content_,ops_parse_cb_in char *ops_get_passphrase(void) { +#ifndef __ANDROID__ return ops_malloc_passphrase(getpass("Passphrase: ")); +#else // __ANDROID__ + return ops_malloc_passphrase("getpass not supported on android"); +#warning "getpass not supported on android" +#endif // __ANDROID__ } char *ops_malloc_passphrase(char *pp) diff --git a/retroshare-gui/src/gui/settings/ServerPage.h b/retroshare-gui/src/gui/settings/ServerPage.h index 6d8399974..51a9bb9f4 100755 --- a/retroshare-gui/src/gui/settings/ServerPage.h +++ b/retroshare-gui/src/gui/settings/ServerPage.h @@ -26,11 +26,11 @@ #include "ui_ServerPage.h" #include "RsAutoUpdatePage.h" #include +#include class QNetworkReply; class QNetworkAccessManager; class BanListPeer; -struct sockaddr_storage; class ServerPage: public ConfigPage { diff --git a/retroshare-gui/src/util/RsNetUtil.h b/retroshare-gui/src/util/RsNetUtil.h index dded451a8..8fa897425 100644 --- a/retroshare-gui/src/util/RsNetUtil.h +++ b/retroshare-gui/src/util/RsNetUtil.h @@ -24,6 +24,7 @@ #include #include +#include class RsNetUtil { diff --git a/retroshare.pri b/retroshare.pri index 95c784512..331c461b6 100644 --- a/retroshare.pri +++ b/retroshare.pri @@ -1,12 +1,44 @@ -# To {dis,en}able libresapi via local socket (unix domain socket or windows named pipes) -# {,un}comment the following line -#CONFIG *= libresapilocalserver +# To disable RetroShare-gui append the following +# assignation to qmake command line "CONFIG+=no_retroshare_gui" +CONFIG *= retroshare_gui +no_retroshare_gui:CONFIG -= retroshare_gui -# To {dis,en}able libresapi via HTTP (libmicrohttpd) {,un}comment the following line +# To disable RetroShare-nogui append the following +# assignation to qmake command line "CONFIG+=no_retroshare_nogui" +CONFIG *= retroshare_nogui +no_retroshare_nogui:CONFIG -= retroshare_nogui + +# To disable RetroShare plugins append the following +# assignation to qmake command line "CONFIG+=no_retroshare_plugins" +CONFIG *= retroshare_plugins +no_retroshare_plugins:CONFIG -= retroshare_plugins + +# To enable RetroShare-android-service append the following assignation to +# qmake command line "CONFIG+=retroshare_android_service" +CONFIG *= no_retroshare_android_service +retroshare_android_service:CONFIG -= no_retroshare_android_service + +# To enable libresapi via local socket (unix domain socket or windows named +# pipes) append the following assignation to qmake command line +#"CONFIG+=libresapilocalserver" +CONFIG *= no_libresapilocalserver +libresapilocalserver:CONFIG -= no_libresapilocalserver + +# To disable libresapi via HTTP (based on libmicrohttpd) append the following +# assignation to qmake command line "CONFIG+=no_libresapihttpserver" CONFIG *= libresapihttpserver +no_libresapihttpserver:CONFIG -= libresapihttpserver + +# To disable SQLCipher support append the following assignation to qmake +# command line "CONFIG+=no_sqlcipher" +CONFIG *= sqlcipher +no_sqlcipher:CONFIG -= sqlcipher + +# To disable GXS (General eXchange System) append the following +# assignation to qmake command line "CONFIG+=no_rs_gxs" +CONFIG *= rs_gxs +no_rs_gxs:CONFIG -= rs_gxs -# Gxs is always enabled now. -DEFINES *= RS_ENABLE_GXS unix { isEmpty(PREFIX) { PREFIX = "/usr" } @@ -18,22 +50,21 @@ unix { } android-g++ { - DEFINES *= NO_SQLCIPHER + CONFIG *= no_libresapihttpserver no_sqlcipher upnp_libupnp + CONFIG -= libresapihttpserver sqlcipher upnp_miniupnpc DEFINES *= "fopen64=fopen" DEFINES *= "fseeko64=fseeko" DEFINES *= "ftello64=ftello" INCLUDEPATH *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/include/ LIBS *= -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ - LIBS *= -lssl -lcrypto -lsqlite3 -lupnp -lixml + LIBS *= -lssl -lcrypto -lsqlite3 -lupnp -lixml -lthreadutil ANDROID_EXTRA_LIBS *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.so ANDROID_EXTRA_LIBS *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.so ANDROID_EXTRA_LIBS *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libbz2.so ANDROID_EXTRA_LIBS *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libsqlite3.so - ANDROID_EXTRA_LIBS *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libupnp.so - ANDROID_EXTRA_LIBS *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libixml.so - message(NDK_TOOLCHAIN_PATH: $$NDK_TOOLCHAIN_PATH) - message(LIBS: $$LIBS) - message(ANDROID_EXTRA_LIBS: $$ANDROID_EXTRA_LIBS) +# message(NDK_TOOLCHAIN_PATH: $$NDK_TOOLCHAIN_PATH) +# message(LIBS: $$LIBS) +# message(ANDROID_EXTRA_LIBS: $$ANDROID_EXTRA_LIBS) } win32 { @@ -95,6 +126,8 @@ unfinished { } wikipoos:DEFINES *= RS_USE_WIKI - +rs_gxs:DEFINES *= RS_ENABLE_GXS libresapilocalserver:DEFINES *= LIBRESAPI_LOCAL_SERVER -libresapihttpserver::DEFINES *= ENABLE_WEBUI +libresapihttpserver:DEFINES *= ENABLE_WEBUI +sqlcipher:DEFINES -= NO_SQLCIPHER +no_sqlcipher:DEFINES *= NO_SQLCIPHER diff --git a/tests/unittests/unittests.pro b/tests/unittests/unittests.pro index 2409b8845..7179a998c 100644 --- a/tests/unittests/unittests.pro +++ b/tests/unittests/unittests.pro @@ -47,7 +47,7 @@ linux-* { #LIBS += ../../supportlibs/pegmarkdown/lib/libpegmarkdown.a - contains(CONFIG, NO_SQLCIPHER) { + no_sqlcipher { DEFINES *= NO_SQLCIPHER PKGCONFIG *= sqlite3 } else { From 68a00138d232b6f6086bc6b53264422f456f2481 Mon Sep 17 00:00:00 2001 From: Gio Date: Tue, 23 Aug 2016 03:11:49 +0200 Subject: [PATCH 02/15] Avoid usage of deprecated function RSA_generate_key that make android compiler mad --- libretroshare/src/gxs/gxssecurity.cc | 5 ++++- libretroshare/src/pqi/sslfns.cc | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libretroshare/src/gxs/gxssecurity.cc b/libretroshare/src/gxs/gxssecurity.cc index a1de27d89..eb5f38219 100644 --- a/libretroshare/src/gxs/gxssecurity.cc +++ b/libretroshare/src/gxs/gxssecurity.cc @@ -243,7 +243,10 @@ bool GxsSecurity::checkPublicKey(const RsTlvPublicRSAKey &key) bool GxsSecurity::generateKeyPair(RsTlvPublicRSAKey& public_key,RsTlvPrivateRSAKey& private_key) { // admin keys - RSA *rsa = RSA_generate_key(2048, 65537, NULL, NULL); + BIGNUM *ebn = BN_new(); + BN_set_word(ebn, 65537); + RSA *rsa = RSA_new(); + RSA_generate_key_ex(rsa, 2048, ebn, NULL); RSA *rsa_pub = RSAPublicKey_dup(rsa); public_key.keyFlags = RSTLV_KEY_TYPE_PUBLIC_ONLY ; diff --git a/libretroshare/src/pqi/sslfns.cc b/libretroshare/src/pqi/sslfns.cc index 9e5813b75..07ec804a2 100644 --- a/libretroshare/src/pqi/sslfns.cc +++ b/libretroshare/src/pqi/sslfns.cc @@ -105,11 +105,15 @@ X509_REQ *GenerateX509Req( fprintf(stderr,"GenerateX509Req: reverting to %d\n", nbits); } - rsa = RSA_generate_key(nbits, e, NULL, NULL); + rsa = RSA_new(); if ((rsa == NULL) || !EVP_PKEY_assign_RSA(pkey, rsa)) throw std::runtime_error("Couldn't generate RSA Key"); + BIGNUM *ebn = BN_new(); + BN_set_word(ebn, e); + RSA_generate_key_ex(rsa, nbits, ebn, NULL); + // open the file. FILE *out; if (NULL == (out = RsDirUtil::rs_fopen(pkey_file.c_str(), "w"))) From 279551fe8d17ffa19a8b6b461ce5a177e7036c65 Mon Sep 17 00:00:00 2001 From: Gio Date: Tue, 23 Aug 2016 03:19:33 +0200 Subject: [PATCH 03/15] Retroshare android service now run and start at boot on android, the qml app is still disfunctional --- README-Android.asciidoc | 30 +--- RetroShare.pro | 7 + android-prepare-toolchain.sh | 46 +++-- libresapi/src/api/ApiServerLocal.cpp | 12 +- libresapi/src/api/ApiServerLocal.h | 25 ++- libretroshare/src/libretroshare.pro | 13 ++ libretroshare/src/retroshare/rsinit.h | 12 +- libretroshare/src/rsserver/rsaccounts.cc | 20 +-- libretroshare/src/rsserver/rsaccounts.h | 12 +- libretroshare/src/rsserver/rsinit.cc | 2 +- libretroshare/src/util/rsdiscspace.cc | 15 +- .../src/retroshare-android-service.pro | 23 +++ retroshare-android-service/src/service.cpp | 52 ++++++ retroshare-gui/src/gui/settings/WebuiPage.cpp | 2 +- retroshare-qml-app/src/Page1.qml | 10 ++ retroshare-qml-app/src/Page1Form.ui.qml | 22 +++ retroshare-qml-app/src/android/.gitignore | 5 + .../src/android/AndroidManifest.xml | 139 +++++++++++++++ retroshare-qml-app/src/android/android.iml | 96 ++++++++++ retroshare-qml-app/src/android/build.gradle | 57 ++++++ ...share.android.qml_app_2016.08.22_16.56.txt | 36 ++++ .../src/android/gradle.properties | 9 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + retroshare-qml-app/src/android/gradlew | 164 ++++++++++++++++++ retroshare-qml-app/src/android/gradlew.bat | 90 ++++++++++ .../src/android/res/values/libs.xml | 25 +++ .../qml_app/BootCompletedReceiver.java | 33 ++++ .../qml_app/RetroShareAndroidService.java | 23 +++ .../qml_app/RetroShareQmlActivity.java | 23 +++ retroshare-qml-app/src/deployment.pri | 13 ++ retroshare-qml-app/src/main.cpp | 47 +++++ retroshare-qml-app/src/main.qml | 37 ++++ retroshare-qml-app/src/qml.qrc | 7 + retroshare-qml-app/src/retroshare-qml-app.pro | 31 ++++ retroshare.pri | 11 +- 36 files changed, 1076 insertions(+), 79 deletions(-) create mode 100644 retroshare-android-service/src/retroshare-android-service.pro create mode 100644 retroshare-android-service/src/service.cpp create mode 100644 retroshare-qml-app/src/Page1.qml create mode 100644 retroshare-qml-app/src/Page1Form.ui.qml create mode 100644 retroshare-qml-app/src/android/.gitignore create mode 100644 retroshare-qml-app/src/android/AndroidManifest.xml create mode 100644 retroshare-qml-app/src/android/android.iml create mode 100644 retroshare-qml-app/src/android/build.gradle create mode 100644 retroshare-qml-app/src/android/captures/org.retroshare.android.qml_app_2016.08.22_16.56.txt create mode 100644 retroshare-qml-app/src/android/gradle.properties create mode 100644 retroshare-qml-app/src/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 retroshare-qml-app/src/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 retroshare-qml-app/src/android/gradlew create mode 100644 retroshare-qml-app/src/android/gradlew.bat create mode 100644 retroshare-qml-app/src/android/res/values/libs.xml create mode 100644 retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/BootCompletedReceiver.java create mode 100644 retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareAndroidService.java create mode 100644 retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java create mode 100644 retroshare-qml-app/src/deployment.pri create mode 100644 retroshare-qml-app/src/main.cpp create mode 100644 retroshare-qml-app/src/main.qml create mode 100644 retroshare-qml-app/src/qml.qrc create mode 100644 retroshare-qml-app/src/retroshare-qml-app.pro diff --git a/README-Android.asciidoc b/README-Android.asciidoc index 18f744384..5c126b059 100644 --- a/README-Android.asciidoc +++ b/README-Android.asciidoc @@ -35,6 +35,8 @@ export HOST_NUM_CPU=1 Now is time for the bad news: as of today Qt assume 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 a little Qt internals 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 +/opt/Qt5.7.0/5.7/android_armv7/mkspecs+ and then modify qmake configuration for 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 advantege of the variables I set before. +WARNING: This may need slightly modification if you have a different Qt version. + [source,bash] ------------------------------------------------------------------------------- export QT_PATH="/opt/Qt5.7.0/" @@ -44,11 +46,10 @@ 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|^ANDROID_PLATFORM =.*|ANDROID_PLATFORM = android-${ANDROID_PLATFORM_VER}|" qmake.conf sed -i "s|^NDK_TOOLCHAIN_PATH =.*|NDK_TOOLCHAIN_PATH = ${NDK_TOOLCHAIN_PATH}|" qmake.conf -sed -i "s|^QMAKE_LFLAGS \+=.*|QMAKE_LFLAGS = --sysroot=${NDK_TOOLCHAIN_PATH}/sysroot/|" qmake.conf ------------------------------------------------------------------------------- + == Preparing Qt Creator Now that your environement is set up you should configure Qt Creator for Android following the link:http://doc.qt.io/qtcreator/creator-developing-android.html[official guide], at the end of this step your Qt Creator should recognize Android compiler and Qt for Android kit, because we use a custom toolchain some step more is needed. @@ -61,33 +62,16 @@ Now go to the Kits tab, select +Android for armeabi-v7a (GCC 4.9, Qt 5.7.0)+ and Your Kit is now ready to use, now you can open retroshare as 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. -== Final tweaks +Some of RetroShare modules like +retroshare-gui+, +WebUI+ and +sqlcipher+ are not supported yet on android so to be able to compile RetroShare without error you will have to go to + -RetroShare GUI, plugins and WebUI are yet not supported on android so to be able to compile RetroShare without error you will have to open RetroShare.pro file with Qt Creator and change the following assignation +Qt Creator left pane -> Projects -> Build and Run -> Android SOMESTUFF kit -> Build Steps -> qmake -> Additional arguments, and add the following configurations [source,makefile] ------------------------------------------------------------------------------- -SUBDIRS += \ - openpgpsdk \ - libbitdht \ - libretroshare \ - libresapi \ - retroshare_gui \ - retroshare_nogui \ - plugins +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 ------------------------------------------------------------------------------- -into - -[source,makefile] -------------------------------------------------------------------------------- -SUBDIRS += \ - openpgpsdk \ - libbitdht \ - libretroshare \ - libresapi \ - retroshare_nogui -------------------------------------------------------------------------------- +WARNING: SQLCipher is not supported yet on RetroShare for android this pose a mayor security concern, we are working to fix this ASAP. == Furter Readings diff --git a/RetroShare.pro b/RetroShare.pro index ba8f0ebfa..3d71f892c 100644 --- a/RetroShare.pro +++ b/RetroShare.pro @@ -38,6 +38,13 @@ retroshare_android_service { retroshare_android_service.target = retroshare_android_service } +retroshare_qml_app { + SUBDIRS += retroshare_qml_app + retroshare_qml_app.file = retroshare-qml-app/src/retroshare-qml-app.pro + retroshare_qml_app.depends = libretroshare retroshare_android_service + retroshare_qml_app.target = retroshare_qml_app +} + retroshare_plugins { SUBDIRS += plugins plugins.file = plugins/plugins.pro diff --git a/android-prepare-toolchain.sh b/android-prepare-toolchain.sh index c496aafad..a0e37a054 100755 --- a/android-prepare-toolchain.sh +++ b/android-prepare-toolchain.sh @@ -2,19 +2,28 @@ ## You are supposed to provide the following variables according to your system setup [ -z ${ANDROID_NDK_PATH+x} ] && export ANDROID_NDK_PATH="/opt/android-ndk/" -[ -z ${NDK_TOOLCHAIN_PATH+x} ] && export NDK_TOOLCHAIN_PATH="/home/$(whoami)/Development/android-toolchains/retroshare-android/" [ -z ${ANDROID_NDK_ARCH+x} ] && export ANDROID_NDK_ARCH="arm" -[ -z ${ANDROID_PLATFORM_VER+x} ] && export ANDROID_PLATFORM_VER="23" +[ -z ${ANDROID_NDK_ABI_VER+x} ] && export ANDROID_NDK_ABI_VER="4.9" +[ -z ${ANDROID_PLATFORM_VER+x} ] && export ANDROID_PLATFORM_VER="18" +[ -z ${NDK_TOOLCHAIN_PATH+x} ] && export NDK_TOOLCHAIN_PATH="/home/$(whoami)/Development/android-toolchains/retroshare-android-${ANDROID_PLATFORM_VER}-${ANDROID_NDK_ARCH}-abi${ANDROID_NDK_ABI_VER}/" [ -z ${HOST_NUM_CPU+x} ] && export HOST_NUM_CPU=4 +runDir="$(pwd)" ## You should not edit the following variables +if [ "${ANDROID_NDK_ARCH}" == "x86" ]; then + cArch="i686" + eABI="" +else + cArch="${ANDROID_NDK_ARCH}" + eABI="eabi" +fi export SYSROOT="${NDK_TOOLCHAIN_PATH}/sysroot" export PREFIX="${SYSROOT}" -export CC="${NDK_TOOLCHAIN_PATH}/bin/arm-linux-androideabi-gcc" -export CXX="${NDK_TOOLCHAIN_PATH}/bin/arm-linux-androideabi-g++" -export AR="${NDK_TOOLCHAIN_PATH}/bin/arm-linux-androideabi-ar" -export RANLIB="${NDK_TOOLCHAIN_PATH}/bin/arm-linux-androideabi-gcc-ranlib" +export CC="${NDK_TOOLCHAIN_PATH}/bin/${cArch}-linux-android${eABI}-gcc" +export CXX="${NDK_TOOLCHAIN_PATH}/bin/${cArch}-linux-android${eABI}-g++" +export AR="${NDK_TOOLCHAIN_PATH}/bin/${cArch}-linux-android${eABI}-ar" +export RANLIB="${NDK_TOOLCHAIN_PATH}/bin/${cArch}-linux-android${eABI}-ranlib" export ANDROID_DEV="${ANDROID_NDK_PATH}/platforms/android-${ANDROID_PLATFORM_VER}/arch-${ANDROID_NDK_ARCH}/usr" @@ -22,7 +31,8 @@ export ANDROID_DEV="${ANDROID_NDK_PATH}/platforms/android-${ANDROID_PLATFORM_VER build_toolchain() { rm -rf ${NDK_TOOLCHAIN_PATH} - ${ANDROID_NDK_PATH}/build/tools/make-standalone-toolchain.sh --ndk-dir=${ANDROID_NDK_PATH} --arch=${ANDROID_NDK_ARCH} --install-dir=${NDK_TOOLCHAIN_PATH} --platform=android-${ANDROID_PLATFORM_VER} + [ "${ANDROID_NDK_ARCH}" == "x86" ] && toolchainName="${ANDROID_NDK_ARCH}-${ANDROID_NDK_ABI_VER}" || toolchainName="${ANDROID_NDK_ARCH}-linux-androideabi-${ANDROID_NDK_ABI_VER}" + ${ANDROID_NDK_PATH}/build/tools/make-standalone-toolchain.sh --ndk-dir=${ANDROID_NDK_PATH} --arch=${ANDROID_NDK_ARCH} --install-dir=${NDK_TOOLCHAIN_PATH} --platform=android-${ANDROID_PLATFORM_VER} --toolchain=${toolchainName} --verbose } ## More information available at retroshare://file?name=Android%20Native%20Development%20Kit%20Cookbook.pdf&size=29214468&hash=0123361c1b14366ce36118e82b90faf7c7b1b136 @@ -40,9 +50,9 @@ build_bzlib() sed -i "s/^all: libbz2.a bzip2 bzip2recover test/all: libbz2.a bzip2 bzip2recover/" Makefile make -j${HOST_NUM_CPU} make install PREFIX=${SYSROOT}/usr - sed -i "/^CC=.*/d" Makefile-libbz2_so - make -f Makefile-libbz2_so -j${HOST_NUM_CPU} - cp libbz2.so.1.0.6 ${SYSROOT}/usr/lib/libbz2.so +# sed -i "/^CC=.*/d" Makefile-libbz2_so +# make -f Makefile-libbz2_so -j${HOST_NUM_CPU} +# cp libbz2.so.1.0.6 ${SYSROOT}/usr/lib/libbz2.so cd .. } @@ -54,12 +64,23 @@ build_openssl() [ -f $B_dir.tar.gz ] || wget https://www.openssl.org/source/$B_dir.tar.gz tar -xf $B_dir.tar.gz cd $B_dir - ANDROID_NDK_ROOT="${ANDROID_NDK_PATH}" ./Configure shared android-armv7 --prefix="${SYSROOT}/usr" --openssldir="${SYSROOT}/etc/ssl" + if [ "${ANDROID_NDK_ARCH}" == "arm" ]; then + oArch="armv7" + else + oArch="${ANDROID_NDK_ARCH}" + fi +# ANDROID_NDK_ROOT="${ANDROID_NDK_PATH}" ./Configure android-${oArch} shared --prefix="${SYSROOT}/usr" --openssldir="${SYSROOT}/etc/ssl" +## We link openssl statically to avoid android silently sneaking in his own +## version of libssl.so (we noticed this because it had some missing symbol +## that made RS crash), the crash in some android version is only one of the +## possible problems the fact that android insert his own binary libssl.so pose +## non neglegible security concerns. + ANDROID_NDK_ROOT="${ANDROID_NDK_PATH}" ./Configure android-${oArch} --prefix="${SYSROOT}/usr" --openssldir="${SYSROOT}/etc/ssl" sed -i 's/LIBNAME=$$i LIBVERSION=$(SHLIB_MAJOR).$(SHLIB_MINOR) \\/LIBNAME=$$i \\/g' Makefile sed -i '/LIBCOMPATVERSIONS=";$(SHLIB_VERSION_HISTORY)" \\/d' Makefile make -j${HOST_NUM_CPU} make install - cp *.so "${SYSROOT}/usr/lib" +# cp *.so "${SYSROOT}/usr/lib" cd .. } @@ -72,6 +93,7 @@ build_sqlite() ./configure --prefix="${SYSROOT}/usr" --host=${ANDROID_NDK_ARCH}-linux make -j${HOST_NUM_CPU} make install + rm -f ${SYSROOT}/usr/lib/libsqlite3.so* ${CC} -shared -o libsqlite3.so -fPIC sqlite3.o -ldl cp libsqlite3.so "${SYSROOT}/usr/lib" cd .. diff --git a/libresapi/src/api/ApiServerLocal.cpp b/libresapi/src/api/ApiServerLocal.cpp index 4e3f50e65..1755a2d9c 100644 --- a/libresapi/src/api/ApiServerLocal.cpp +++ b/libresapi/src/api/ApiServerLocal.cpp @@ -21,9 +21,9 @@ namespace resource_api{ -ApiServerLocal::ApiServerLocal(ApiServer* server, QObject *parent) : +ApiServerLocal::ApiServerLocal(ApiServer* server, const QString &listenPath, QObject *parent) : QObject(parent), serverThread(this), - localListener(server) // Must have no parent to be movable to other thread + localListener(server, listenPath) // Must have no parent to be movable to other thread { localListener.moveToThread(&serverThread); serverThread.start(); @@ -31,15 +31,17 @@ ApiServerLocal::ApiServerLocal(ApiServer* server, QObject *parent) : ApiServerLocal::~ApiServerLocal() { serverThread.quit(); } -ApiLocalListener::ApiLocalListener(ApiServer *server, QObject *parent) : +ApiLocalListener::ApiLocalListener(ApiServer *server, + const QString &listenPath, + QObject *parent) : QObject(parent), mApiServer(server), mLocalServer(this) { - mLocalServer.removeServer(serverName()); + mLocalServer.removeServer(listenPath); #if QT_VERSION >= 0x050000 mLocalServer.setSocketOptions(QLocalServer::UserAccessOption); #endif connect(&mLocalServer, SIGNAL(newConnection()), this, SLOT(handleConnection())); - mLocalServer.listen(serverName()); + mLocalServer.listen(listenPath); } void ApiLocalListener::handleConnection() diff --git a/libresapi/src/api/ApiServerLocal.h b/libresapi/src/api/ApiServerLocal.h index 8835f712c..576190a71 100644 --- a/libresapi/src/api/ApiServerLocal.h +++ b/libresapi/src/api/ApiServerLocal.h @@ -34,16 +34,9 @@ class ApiLocalListener : public QObject Q_OBJECT public: - ApiLocalListener(ApiServer* server, QObject *parent=0); + ApiLocalListener(ApiServer* server, const QString &listenPath, QObject *parent=0); ~ApiLocalListener() { mLocalServer.close(); } - const static QString& serverName() - { - const static QString sockPath(RsAccounts::AccountDirectory() - .append("/libresapi.sock").c_str()); - return sockPath; - } - public slots: void handleConnection(); @@ -57,9 +50,23 @@ class ApiServerLocal : public QObject Q_OBJECT public: - ApiServerLocal(ApiServer* server, QObject *parent=0); + ApiServerLocal(ApiServer* server, const QString& listenPath, QObject *parent=0); ~ApiServerLocal(); + const static QString& loginServerPath() + { + const static QString sockPath(RsAccounts::ConfigDirectory() + .append("/libresapi.sock").c_str()); + return sockPath; + } + + const static QString& serverPath() + { + const static QString sockPath(RsAccounts::AccountDirectory() + .append("/libresapi.sock").c_str()); + return sockPath; + } + private: QThread serverThread; ApiLocalListener localListener; diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index 6c89e6f05..a1b2b9f47 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -881,3 +881,16 @@ test_bitdht { # ENABLED UDP NOW. } +android-g++ { +## Add this here and not in retroshare.pri because static library are very +## sensible to order in command line + LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl + INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a + + LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto + INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a +} diff --git a/libretroshare/src/retroshare/rsinit.h b/libretroshare/src/retroshare/rsinit.h index 1555be893..c7abc7bf6 100644 --- a/libretroshare/src/retroshare/rsinit.h +++ b/libretroshare/src/retroshare/rsinit.h @@ -129,15 +129,21 @@ class RsInit namespace RsAccounts { - // Directories. - std::string ConfigDirectory(); // aka Base Directory. (normally ~/.retroshare) /** + * @brief ConfigDirectory (normally ~/.retroshare) you can call this method + * even before initialisation (you can't with some other methods) + * @see RsAccountsDetail::PathBaseDirectory() + */ + std::string ConfigDirectory(); + + /** * @brief DataDirectory - * you can call this method even before initialisation (you can't with the other methods) + * you can call this method even before initialisation (you can't with some other methods) * @param check if set to true and directory does not exist, return empty string * @return path where global platform independent files are stored, like bdboot.txt or webinterface files */ std::string DataDirectory(bool check = true); + std::string PGPDirectory(); std::string AccountDirectory(); diff --git a/libretroshare/src/rsserver/rsaccounts.cc b/libretroshare/src/rsserver/rsaccounts.cc index ad9e1306e..0626ea46c 100644 --- a/libretroshare/src/rsserver/rsaccounts.cc +++ b/libretroshare/src/rsserver/rsaccounts.cc @@ -65,14 +65,8 @@ AccountDetails::AccountDetails() return; } -RsAccountsDetail::RsAccountsDetail() -:mAccountsLocked(false), mPreferredId(""), mBaseDirectory("") -{ - mAccounts.clear(); - mUnsupportedKeys.clear(); - return; -} - +RsAccountsDetail::RsAccountsDetail() : mAccountsLocked(false), mPreferredId("") +{} bool RsAccountsDetail::loadAccounts() { @@ -214,6 +208,7 @@ std::string RsAccountsDetail::PathPGPDirectory() std::string RsAccountsDetail::PathBaseDirectory() { + if(mBaseDirectory.empty()) defaultBaseDirectory(); return mBaseDirectory; } @@ -326,8 +321,6 @@ bool RsAccountsDetail::setupBaseDirectory(std::string alt_basedir) } - - bool RsAccountsDetail::defaultBaseDirectory() { std::string basedir; @@ -339,8 +332,8 @@ bool RsAccountsDetail::defaultBaseDirectory() char *h = getenv("HOME"); if (h == NULL) { - std::cerr << "defaultBaseDirectory() Error: "; - std::cerr << "cannot determine $HOME dir" <PathBaseDirectory(); } +std::string RsAccounts::ConfigDirectory() { return RsAccountsDetail::PathBaseDirectory(); } std::string RsAccounts::DataDirectory(bool check) { return RsAccountsDetail::PathDataDirectory(check); } std::string RsAccounts::PGPDirectory() { return rsAccounts->PathPGPDirectory(); } std::string RsAccounts::AccountDirectory() { return rsAccounts->PathAccountDirectory(); } @@ -1333,3 +1326,4 @@ bool RsAccounts::GenerateSSLCertificate(const RsPgpId& pgp_id, const std::str * END OF: PUBLIC INTERFACE FUNCTIONS ********************************************************************************/ +std::string RsAccountsDetail::mBaseDirectory; diff --git a/libretroshare/src/rsserver/rsaccounts.h b/libretroshare/src/rsserver/rsaccounts.h index 7f1fa9d3a..5832dfa00 100644 --- a/libretroshare/src/rsserver/rsaccounts.h +++ b/libretroshare/src/rsserver/rsaccounts.h @@ -80,7 +80,13 @@ class RsAccountsDetail * @return path where global platform independent files are stored, like bdboot.txt or webinterface files */ static std::string PathDataDirectory(bool check = true); - std::string PathBaseDirectory(); + + /** + * @brief PathBaseDirectory + * @return path where user data is stored ( on Linux and similar + * systems it is usually something like /home/USERNAME/.retroshare ). + */ + static std::string PathBaseDirectory(); // PGP Path is only dependent on BaseDirectory. std::string PathPGPDirectory(); @@ -134,7 +140,7 @@ class RsAccountsDetail private: bool checkPreferredId(); - bool defaultBaseDirectory(); + static bool defaultBaseDirectory(); bool getAvailableAccounts(std::map &accounts, int& failing_accounts, @@ -148,7 +154,7 @@ class RsAccountsDetail std::map mAccounts; RsPeerId mPreferredId; - std::string mBaseDirectory; + static std::string mBaseDirectory; std::map > mUnsupportedKeys ; }; diff --git a/libretroshare/src/rsserver/rsinit.cc b/libretroshare/src/rsserver/rsinit.cc index 1ccafd3c5..59397ddc5 100644 --- a/libretroshare/src/rsserver/rsinit.cc +++ b/libretroshare/src/rsserver/rsinit.cc @@ -507,7 +507,7 @@ int RsInit::InitRetroShare(int argcIgnored, char **argvIgnored, bool strictCheck AuthSSL::AuthSSLInit(); AuthSSL::getAuthSSL() -> InitAuth(NULL, NULL, NULL, ""); - rsAccounts = new RsAccountsDetail() ; + rsAccounts = new RsAccountsDetail(); // first check config directories, and set bootstrap values. if(!rsAccounts->setupBaseDirectory(opt_base_dir)) diff --git a/libretroshare/src/util/rsdiscspace.cc b/libretroshare/src/util/rsdiscspace.cc index 12fea3969..25a764b14 100644 --- a/libretroshare/src/util/rsdiscspace.cc +++ b/libretroshare/src/util/rsdiscspace.cc @@ -32,10 +32,19 @@ #include "rsserver/rsaccounts.h" #include "rsdiscspace.h" #include -#ifndef WIN32 -#include + +#ifdef __ANDROID__ +# include +#endif + +#ifdef WIN32 +# include +#elif defined(__ANDROID__) && (__ANDROID_API__ < 21) +# include +# define statvfs64 statfs +# warning statvfs64 is not supported with android platform < 21 falling back to statfs that is untested (may misbehave) #else -#include +# include #endif #define DELAY_BETWEEN_CHECKS 2 diff --git a/retroshare-android-service/src/retroshare-android-service.pro b/retroshare-android-service/src/retroshare-android-service.pro new file mode 100644 index 000000000..d327ace4f --- /dev/null +++ b/retroshare-android-service/src/retroshare-android-service.pro @@ -0,0 +1,23 @@ +!include("../../retroshare.pri"): error("Could not include file ../../retroshare.pri") + +TARGET = retroshare-android-service + +QT += core network androidextras +QT -= gui + +CONFIG += c++11 +CONFIG += dll + +TEMPLATE = lib + +SOURCES += service.cpp + +DEPENDPATH *= ../../libresapi/src +INCLUDEPATH *= ../../libresapi/src +PRE_TARGETDEPS *= ../../libresapi/src/lib/libresapi.a +LIBS *= ../../libresapi/src/lib/libresapi.a + +DEPENDPATH *= ../../libretroshare/src +INCLUDEPATH *= ../../libretroshare/src +PRE_TARGETDEPS *= ../../libretroshare/src/lib/libretroshare.a +LIBS *= ../../libretroshare/src/lib/libretroshare.a diff --git a/retroshare-android-service/src/service.cpp b/retroshare-android-service/src/service.cpp new file mode 100644 index 000000000..5cea31e5f --- /dev/null +++ b/retroshare-android-service/src/service.cpp @@ -0,0 +1,52 @@ +/* + * RetroShare Android Service + * Copyright (C) 2016 Gioacchino Mazzurco + * + * 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 . + */ + +#include +#include +#include + +#include "retroshare/rsinit.h" +#include "api/ApiServer.h" +#include "api/ApiServerLocal.h" +#include "api/RsControlModule.h" + +using namespace resource_api; + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + ApiServer api; + RsControlModule ctrl_mod(argc, argv, api.getStateTokenServer(), &api, true); + api.addResourceHandler("control", dynamic_cast(&ctrl_mod), &resource_api::RsControlModule::handleRequest); + + QString sockPath = QString::fromStdString(RsAccounts::ConfigDirectory()); + sockPath.append("/libresapi.sock"); + qDebug() << "Listening on:" << sockPath; + ApiServerLocal apiServerLocal(&api, sockPath); (void) apiServerLocal; + + qDebug() << "Is service.cpp running as a service?" << QtAndroid::androidService().isValid(); + qDebug() << "Is service.cpp running as an activity?" << QtAndroid::androidActivity().isValid(); + + while (!ctrl_mod.processShouldExit()) + { + a.processEvents(); + usleep(20000); + } + + return 0; +} diff --git a/retroshare-gui/src/gui/settings/WebuiPage.cpp b/retroshare-gui/src/gui/settings/WebuiPage.cpp index cb7121746..f8923a174 100644 --- a/retroshare-gui/src/gui/settings/WebuiPage.cpp +++ b/retroshare-gui/src/gui/settings/WebuiPage.cpp @@ -100,7 +100,7 @@ QString WebuiPage::helpText() const // TODO: LIBRESAPI_LOCAL_SERVER Move in appropriate place #ifdef LIBRESAPI_LOCAL_SERVER - apiServerLocal = new resource_api::ApiServerLocal(apiServer); + apiServerLocal = new resource_api::ApiServerLocal(apiServer, resource_api::ApiServerLocal::serverPath()); #endif return ok; } diff --git a/retroshare-qml-app/src/Page1.qml b/retroshare-qml-app/src/Page1.qml new file mode 100644 index 000000000..d5b808f9b --- /dev/null +++ b/retroshare-qml-app/src/Page1.qml @@ -0,0 +1,10 @@ +import QtQuick 2.7 + +Page1Form { + button1.onClicked: { + console.log("Button 1 clicked."); + } + button2.onClicked: { + console.log("Button 2 clicked."); + } +} diff --git a/retroshare-qml-app/src/Page1Form.ui.qml b/retroshare-qml-app/src/Page1Form.ui.qml new file mode 100644 index 000000000..806ad2190 --- /dev/null +++ b/retroshare-qml-app/src/Page1Form.ui.qml @@ -0,0 +1,22 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.0 + +Item { + property alias button1: button1 + property alias button2: button2 + + RowLayout { + anchors.centerIn: parent + + Button { + id: button1 + text: qsTr("Press Me 1") + } + + Button { + id: button2 + text: qsTr("Press Me 2") + } + } +} diff --git a/retroshare-qml-app/src/android/.gitignore b/retroshare-qml-app/src/android/.gitignore new file mode 100644 index 000000000..e8dd8d7de --- /dev/null +++ b/retroshare-qml-app/src/android/.gitignore @@ -0,0 +1,5 @@ +.build +.gradle +.idea +captures +local.properties diff --git a/retroshare-qml-app/src/android/AndroidManifest.xml b/retroshare-qml-app/src/android/AndroidManifest.xml new file mode 100644 index 000000000..54eb4f894 --- /dev/null +++ b/retroshare-qml-app/src/android/AndroidManifest.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/retroshare-qml-app/src/android/android.iml b/retroshare-qml-app/src/android/android.iml new file mode 100644 index 000000000..4dde2c8b9 --- /dev/null +++ b/retroshare-qml-app/src/android/android.iml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/retroshare-qml-app/src/android/build.gradle b/retroshare-qml-app/src/android/build.gradle new file mode 100644 index 000000000..ef416b0b8 --- /dev/null +++ b/retroshare-qml-app/src/android/build.gradle @@ -0,0 +1,57 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:1.1.0' + } +} + +allprojects { + repositories { + jcenter() + } +} + +apply plugin: 'com.android.application' + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} + +android { + /******************************************************* + * The following variables: + * - androidBuildToolsVersion, + * - androidCompileSdkVersion + * - qt5AndroidDir - holds the path to qt android files + * needed to build any Qt application + * on Android. + * + * are defined in gradle.properties file. This file is + * updated by QtCreator and androiddeployqt tools. + * Changing them manually might break the compilation! + *******************************************************/ + + compileSdkVersion androidCompileSdkVersion.toInteger() + + buildToolsVersion androidBuildToolsVersion + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] + aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] + res.srcDirs = [qt5AndroidDir + '/res', 'res'] + resources.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } + + lintOptions { + abortOnError false + } +} diff --git a/retroshare-qml-app/src/android/captures/org.retroshare.android.qml_app_2016.08.22_16.56.txt b/retroshare-qml-app/src/android/captures/org.retroshare.android.qml_app_2016.08.22_16.56.txt new file mode 100644 index 000000000..f55ee311f --- /dev/null +++ b/retroshare-qml-app/src/android/captures/org.retroshare.android.qml_app_2016.08.22_16.56.txt @@ -0,0 +1,36 @@ +Activity Resolver Table: + Non-Data Actions: + android.intent.action.MAIN: + 42420338 org.retroshare.android.qml_app/.RetroShareQmlActivity filter 42424830 + +Receiver Resolver Table: + Non-Data Actions: + android.intent.action.BOOT_COMPLETED: + 4245c230 org.retroshare.android.qml_app/.BootCompletedReceiver filter 4245c360 + +Packages: + Package [org.retroshare.android.qml_app] (4289b2f8): + userId=10168 gids=[3003, 1015, 1028] + pkg=Package{42244e18 org.retroshare.android.qml_app} + codePath=/data/app/org.retroshare.android.qml_app-2.apk + resourcePath=/data/app/org.retroshare.android.qml_app-2.apk + nativeLibraryPath=/data/app-lib/org.retroshare.android.qml_app-2 + versionCode=1 targetSdk=18 + versionName=1.0 + applicationInfo=ApplicationInfo{4232af50 org.retroshare.android.qml_app} + flags=[ DEBUGGABLE HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ] + dataDir=/data/data/org.retroshare.android.qml_app + supportsScreens=[small, medium, large, xlarge, resizeable, anyDensity] + timeStamp=2016-08-22 16:53:12 + firstInstallTime=2016-08-22 16:47:19 + lastUpdateTime=2016-08-22 16:53:30 + signatures=PackageSignatures{4233b1e0 [4265b018]} + permissionsFixed=true haveGids=true installStatus=1 + pkgFlags=[ DEBUGGABLE HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ] + User 0: installed=true stopped=false notLaunched=false enabled=0 + grantedPermissions: + android.permission.READ_EXTERNAL_STORAGE + android.permission.RECEIVE_BOOT_COMPLETED + android.permission.ACCESS_NETWORK_STATE + android.permission.WRITE_EXTERNAL_STORAGE + android.permission.INTERNET diff --git a/retroshare-qml-app/src/android/gradle.properties b/retroshare-qml-app/src/android/gradle.properties new file mode 100644 index 000000000..0675a3cf4 --- /dev/null +++ b/retroshare-qml-app/src/android/gradle.properties @@ -0,0 +1,9 @@ +## This file is automatically generated by QtCreator. +# +# This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. + +androidBuildToolsVersion=24.0.1 +androidCompileSdkVersion=18 +buildDir=.build +qt5AndroidDir=/opt/Qt5.7.0/5.7/android_armv7/src/android/java diff --git a/retroshare-qml-app/src/android/gradle/wrapper/gradle-wrapper.jar b/retroshare-qml-app/src/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd GIT binary patch literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/retroshare-qml-app/src/android/gradlew.bat b/retroshare-qml-app/src/android/gradlew.bat new file mode 100644 index 000000000..aec99730b --- /dev/null +++ b/retroshare-qml-app/src/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/retroshare-qml-app/src/android/res/values/libs.xml b/retroshare-qml-app/src/android/res/values/libs.xml new file mode 100644 index 000000000..43296f2e7 --- /dev/null +++ b/retroshare-qml-app/src/android/res/values/libs.xml @@ -0,0 +1,25 @@ + + + + https://download.qt.io/ministro/android/qt5/qt-5.7 + + + + + + + + + + + + + + + + + + + + diff --git a/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/BootCompletedReceiver.java b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/BootCompletedReceiver.java new file mode 100644 index 000000000..22324158d --- /dev/null +++ b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/BootCompletedReceiver.java @@ -0,0 +1,33 @@ +/* + * RetroShare Android Service + * Copyright (C) 2016 Gioacchino Mazzurco + * + * 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 . + */ + +package org.retroshare.android.qml_app; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class BootCompletedReceiver extends BroadcastReceiver +{ + @Override + public void onReceive(Context context, Intent intent) + { + Intent myIntent = new Intent(context, RetroShareAndroidService.class); + context.startService(myIntent); + } +} diff --git a/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareAndroidService.java b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareAndroidService.java new file mode 100644 index 000000000..f7c21a7d8 --- /dev/null +++ b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareAndroidService.java @@ -0,0 +1,23 @@ +/* + * RetroShare Android Service + * Copyright (C) 2016 Gioacchino Mazzurco + * + * 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 . + */ + +package org.retroshare.android.qml_app; + +import org.qtproject.qt5.android.bindings.QtService; + +public class RetroShareAndroidService extends QtService {} diff --git a/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java new file mode 100644 index 000000000..8e47563c8 --- /dev/null +++ b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java @@ -0,0 +1,23 @@ +/* + * RetroShare Android QML App + * Copyright (C) 2016 Gioacchino Mazzurco + * + * 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 . + */ + +package org.retroshare.android.qml_app; + +import org.qtproject.qt5.android.bindings.QtActivity; + +public class RetroShareQmlActivity extends QtActivity {} diff --git a/retroshare-qml-app/src/deployment.pri b/retroshare-qml-app/src/deployment.pri new file mode 100644 index 000000000..265ce71f3 --- /dev/null +++ b/retroshare-qml-app/src/deployment.pri @@ -0,0 +1,13 @@ +unix:!android { + isEmpty(target.path) { + qnx { + target.path = /tmp/$${TARGET}/bin + } else { + target.path = /opt/$${TARGET}/bin + } + export(target.path) + } + INSTALLS += target +} + +export(INSTALLS) diff --git a/retroshare-qml-app/src/main.cpp b/retroshare-qml-app/src/main.cpp new file mode 100644 index 000000000..2834d7908 --- /dev/null +++ b/retroshare-qml-app/src/main.cpp @@ -0,0 +1,47 @@ +/* + * RetroShare Android QML App + * Copyright (C) 2016 Gioacchino Mazzurco + * + * 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 . + */ + +#include +#include +#include + +#include +#include +#include + +#include "retroshare/rsinit.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.load(QUrl(QLatin1String("qrc:/main.qml"))); + + QString sockPath = QString::fromStdString(RsAccounts::ConfigDirectory()); + sockPath.append("/libresapi.sock"); + + QFileInfo fileInfo(sockPath); + + qDebug() << "Is service.cpp running as a service?" << QtAndroid::androidService().isValid(); + qDebug() << "Is service.cpp running as an activity?" << QtAndroid::androidActivity().isValid(); + qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString(); + + return app.exec(); +} diff --git a/retroshare-qml-app/src/main.qml b/retroshare-qml-app/src/main.qml new file mode 100644 index 000000000..9e0707ad6 --- /dev/null +++ b/retroshare-qml-app/src/main.qml @@ -0,0 +1,37 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.0 + +ApplicationWindow { + visible: true + width: 640 + height: 480 + title: qsTr("Hello World") + + SwipeView { + id: swipeView + anchors.fill: parent + currentIndex: tabBar.currentIndex + + Page1 { + } + + Page { + Label { + text: qsTr("Second page") + anchors.centerIn: parent + } + } + } + + footer: TabBar { + id: tabBar + currentIndex: swipeView.currentIndex + TabButton { + text: qsTr("First") + } + TabButton { + text: qsTr("Second") + } + } +} diff --git a/retroshare-qml-app/src/qml.qrc b/retroshare-qml-app/src/qml.qrc new file mode 100644 index 000000000..67415af31 --- /dev/null +++ b/retroshare-qml-app/src/qml.qrc @@ -0,0 +1,7 @@ + + + main.qml + Page1.qml + Page1Form.ui.qml + + diff --git a/retroshare-qml-app/src/retroshare-qml-app.pro b/retroshare-qml-app/src/retroshare-qml-app.pro new file mode 100644 index 000000000..4d3ca438b --- /dev/null +++ b/retroshare-qml-app/src/retroshare-qml-app.pro @@ -0,0 +1,31 @@ +!include("../../retroshare.pri"): error("Could not include file ../../retroshare.pri") + +QT += qml quick androidextras + +CONFIG += c++11 + +SOURCES += main.cpp + +RESOURCES += qml.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = + +# Default rules for deployment. +include(deployment.pri) + +DISTFILES += \ + android/AndroidManifest.xml \ + android/gradle/wrapper/gradle-wrapper.jar \ + android/gradlew \ + android/res/values/libs.xml \ + android/build.gradle \ + android/gradle/wrapper/gradle-wrapper.properties \ + android/gradlew.bat + +ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + +DEPENDPATH *= ../../libretroshare/src +INCLUDEPATH *= ../../libretroshare/src +PRE_TARGETDEPS *= ../../libretroshare/src/lib/libretroshare.a +LIBS *= ../../libretroshare/src/lib/libretroshare.a diff --git a/retroshare.pri b/retroshare.pri index 331c461b6..f880d3a90 100644 --- a/retroshare.pri +++ b/retroshare.pri @@ -55,16 +55,15 @@ android-g++ { DEFINES *= "fopen64=fopen" DEFINES *= "fseeko64=fseeko" DEFINES *= "ftello64=ftello" - INCLUDEPATH *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/include/ + INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include LIBS *= -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ - LIBS *= -lssl -lcrypto -lsqlite3 -lupnp -lixml -lthreadutil - ANDROID_EXTRA_LIBS *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.so - ANDROID_EXTRA_LIBS *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.so - ANDROID_EXTRA_LIBS *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libbz2.so + LIBS *= -lbz2 -lupnp -lixml -lthreadutil -lsqlite3 ANDROID_EXTRA_LIBS *= $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libsqlite3.so -# message(NDK_TOOLCHAIN_PATH: $$NDK_TOOLCHAIN_PATH) # message(LIBS: $$LIBS) # message(ANDROID_EXTRA_LIBS: $$ANDROID_EXTRA_LIBS) +# message(ANDROID_PLATFORM: $$ANDROID_PLATFORM) +# message(ANDROID_PLATFORM_ROOT_PATH: $$ANDROID_PLATFORM_ROOT_PATH) +# message(NDK_TOOLCHAIN_PATH: $$NDK_TOOLCHAIN_PATH) } win32 { From 85461825ceec40defdf65d3a47da830bdbe8f3af Mon Sep 17 00:00:00 2001 From: Gio Date: Tue, 23 Aug 2016 12:14:42 +0200 Subject: [PATCH 04/15] Ignore all .pro.user files --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 85b1c28a6..ae38cd091 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -/RetroShare.pro.user *.o *.sw? *.so @@ -9,3 +8,4 @@ ui_*.h Makefile.* *~ Thumbs.db +*.pro.user From 47944b30e6fbc39a912137acc6bbe74094d45c9d Mon Sep 17 00:00:00 2001 From: Gio Date: Tue, 23 Aug 2016 12:27:04 +0200 Subject: [PATCH 05/15] libretroshare android move openssl static linking in more appropriated place --- libretroshare/src/libretroshare.pro | 30 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index a1b2b9f47..1b41ec215 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -338,10 +338,22 @@ haiku-* { ################################# Android ##################################### android-g++ { - ## ifaddrs is missing on Android add them - ## taken from https://github.com/morristech/android-ifaddrs +## ifaddrs is missing on Android add them +## taken from https://github.com/morristech/android-ifaddrs HEADERS *= util/ifaddrs.h SOURCES *= util/ifaddrs.c + +## Add this here and not in retroshare.pri because static library are very +## sensible to order in command line + LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl + INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a + + LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto + INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a } ################################### COMMON stuff ################################## @@ -880,17 +892,3 @@ test_bitdht { # ENABLED UDP NOW. } - -android-g++ { -## Add this here and not in retroshare.pri because static library are very -## sensible to order in command line - LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl - INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include - DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include - PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a - - LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto - INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include - DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include - PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a -} From 50fe3dd7118c41238d6c5bd57bf2df69358d7872 Mon Sep 17 00:00:00 2001 From: manu Date: Sun, 4 Sep 2016 15:01:44 +0200 Subject: [PATCH 06/15] QML activity interacts with backend. Test QML activity changed for a simplified version of drbob's rsqml-models. It requests JSON documents from the libresapilocalserver and shows them raw. Updated Android documentation. Moved Android qmake section to the end of libretroshare.pro and openpgpsdk.pro to avoid static linking errors. --- README-Android.asciidoc | 32 ++-- libretroshare/src/libretroshare.pro | 43 ++--- openpgpsdk/src/openpgpsdk.pro | 18 ++ retroshare-qml-app/src/Page1.qml | 10 -- retroshare-qml-app/src/Page1Form.ui.qml | 22 --- .../src/android/AndroidManifest.xml | 15 +- retroshare-qml-app/src/debugutils.h | 15 ++ .../src/libresapilocalclient.cpp | 79 +++++++++ retroshare-qml-app/src/libresapilocalclient.h | 64 +++++++ retroshare-qml-app/src/main.cpp | 29 ++-- retroshare-qml-app/src/main.qml | 37 ---- retroshare-qml-app/src/qml.qrc | 16 ++ retroshare-qml-app/src/qml/AppButton.qml | 30 ++++ retroshare-qml-app/src/qml/ApplicationBar.qml | 37 ++++ .../src/qml/ChannelGroupDelegate.qml | 33 ++++ .../src/qml/ChannelMsgDelegate.qml | 42 +++++ retroshare-qml-app/src/qml/ContactBox.qml | 61 +++++++ .../src/qml/ForumMsgDelegate.qml | 41 +++++ .../src/qml/GxsGroupDelegate.qml | 26 +++ retroshare-qml-app/src/qml/GxsIdDelegate.qml | 33 ++++ retroshare-qml-app/src/qml/GxsService.qml | 119 +++++++++++++ .../src/qml/LibresapiLocalClientComm.qml | 5 + .../src/qml/PostedMsgDelegate.qml | 40 +++++ .../src/qml/icons/contacts-128.png | Bin 0 -> 2399 bytes .../src/qml/icons/email-128.png | Bin 0 -> 2915 bytes .../src/qml/icons/settings-4-128.png | Bin 0 -> 3162 bytes .../src/qml/icons/star-2-128.png | Bin 0 -> 1973 bytes retroshare-qml-app/src/qml/main.qml | 160 ++++++++++++++++++ retroshare-qml-app/src/retroshare-qml-app.pro | 7 +- 29 files changed, 886 insertions(+), 128 deletions(-) delete mode 100644 retroshare-qml-app/src/Page1.qml delete mode 100644 retroshare-qml-app/src/Page1Form.ui.qml create mode 100644 retroshare-qml-app/src/debugutils.h create mode 100644 retroshare-qml-app/src/libresapilocalclient.cpp create mode 100644 retroshare-qml-app/src/libresapilocalclient.h delete mode 100644 retroshare-qml-app/src/main.qml create mode 100644 retroshare-qml-app/src/qml/AppButton.qml create mode 100644 retroshare-qml-app/src/qml/ApplicationBar.qml create mode 100644 retroshare-qml-app/src/qml/ChannelGroupDelegate.qml create mode 100644 retroshare-qml-app/src/qml/ChannelMsgDelegate.qml create mode 100644 retroshare-qml-app/src/qml/ContactBox.qml create mode 100644 retroshare-qml-app/src/qml/ForumMsgDelegate.qml create mode 100644 retroshare-qml-app/src/qml/GxsGroupDelegate.qml create mode 100644 retroshare-qml-app/src/qml/GxsIdDelegate.qml create mode 100644 retroshare-qml-app/src/qml/GxsService.qml create mode 100644 retroshare-qml-app/src/qml/LibresapiLocalClientComm.qml create mode 100644 retroshare-qml-app/src/qml/PostedMsgDelegate.qml create mode 100755 retroshare-qml-app/src/qml/icons/contacts-128.png create mode 100755 retroshare-qml-app/src/qml/icons/email-128.png create mode 100755 retroshare-qml-app/src/qml/icons/settings-4-128.png create mode 100755 retroshare-qml-app/src/qml/icons/star-2-128.png create mode 100644 retroshare-qml-app/src/qml/main.qml diff --git a/README-Android.asciidoc b/README-Android.asciidoc index 5c126b059..b84d4a482 100644 --- a/README-Android.asciidoc +++ b/README-Android.asciidoc @@ -8,16 +8,16 @@ Compiling an application for Android is not as easy as one would imagine, expeci == Preparing The Environement First of all setup your Qt for Android development environement following the guide on the link:http://doc.qt.io/qt-5/androidgs.html[Qt for android web site]. -At this point you should have Android SDK, Android NDK, and Qt for Android working fine, and you should be capable of execute on a Android emulator on on your Android phone Qt for Android examples. +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 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 provide the +android-prepare-toolchain.sh+ script, before you execute it you should define some variable 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. 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. [source,bash] ------------------------------------------------------------------------------- ## The path where Android NDK is installed in your system export ANDROID_NDK_PATH="/opt/android-ndk/" -## The path where your fresh compiled toolchain will be installed, take care +## The path where your fresh compiled toolchain will be installed, make sure ## the parent exists export NDK_TOOLCHAIN_PATH="/home/$(whoami)/Development/android-toolchains/retroshare-android/" @@ -25,7 +25,7 @@ export NDK_TOOLCHAIN_PATH="/home/$(whoami)/Development/android-toolchains/retros export ANDROID_NDK_ARCH="arm" ## The Android API level the Android device you want to target -export ANDROID_PLATFORM_VER="23" +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 export HOST_NUM_CPU=1 @@ -33,9 +33,9 @@ export HOST_NUM_CPU=1 ./android-prepare-toolchain.sh ------------------------------------------------------------------------------- -Now is time for the bad news: as of today Qt assume 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 a little Qt internals 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 +/opt/Qt5.7.0/5.7/android_armv7/mkspecs+ and then modify qmake configuration for 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 advantege of the variables I set before. +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 slightly modification if you have a different Qt version. +WARNING: This may need a slight modification if you have a different Qt version. [source,bash] ------------------------------------------------------------------------------- @@ -52,27 +52,31 @@ sed -i "s|^NDK_TOOLCHAIN_PATH =.*|NDK_TOOLCHAIN_PATH = ${NDK_TOOLCHAIN_PATH}|" q == 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 Android compiler and Qt for Android kit, because we use a custom toolchain some step more 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. As we use a custom toolchain one more step is needed. -From the top menu click Tools -> Options... -> Build & Run -> Compilers -> Android GCC (arm-4.9) -> Clone +From the top menu click +_Tools -> Options... -> Build & Run -> Compilers -> Android GCC (arm-4.9) -> Clone_ -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+ then press Apply. +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. -Now go to the Kits tab, select +Android for armeabi-v7a (GCC 4.9, Qt 5.7.0)+ and press Clone button a new kit is created and it is 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. +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. -Your Kit is now ready to use, now you can open retroshare as 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. +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. -Some of RetroShare modules like +retroshare-gui+, +WebUI+ and +sqlcipher+ are not supported yet on android so to be able to compile RetroShare without error you will have to go to + +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 + -Qt Creator left pane -> Projects -> Build and Run -> Android SOMESTUFF kit -> Build Steps -> qmake -> Additional arguments, and add the following configurations +_Qt Creator left pane -> Projects -> Build and Run -> Android SOMESTUFF kit -> Build Steps -> qmake -> Additional arguments_ + +and add the following configurations [source,makefile] ------------------------------------------------------------------------------- CONFIG+=no_retroshare_gui CONFIG+=no_retroshare_nogui CONFIG+=no_retroshare_plugins CONFIG+=retroshare_android_service CONFIG+=libresapilocalserver CONFIG+=no_libresapihttpserver CONFIG+=no_sqlcipher CONFIG+=retroshare_qml_app ------------------------------------------------------------------------------- -WARNING: SQLCipher is not supported yet on RetroShare for android this pose a mayor 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+ == Furter Readings diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index 1b41ec215..5cb1d0781 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -335,27 +335,6 @@ haiku-* { DESTDIR = lib } -################################# Android ##################################### - -android-g++ { -## ifaddrs is missing on Android add them -## taken from https://github.com/morristech/android-ifaddrs - HEADERS *= util/ifaddrs.h - SOURCES *= util/ifaddrs.c - -## Add this here and not in retroshare.pri because static library are very -## sensible to order in command line - LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl - INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include - DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include - PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a - - LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto - INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include - DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include - PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a -} - ################################### COMMON stuff ################################## # openpgpsdk @@ -892,3 +871,25 @@ test_bitdht { # ENABLED UDP NOW. } + +################################# Android ##################################### + +android-g++ { +## ifaddrs is missing on Android add them +## taken from https://github.com/morristech/android-ifaddrs + HEADERS *= util/ifaddrs.h + SOURCES *= util/ifaddrs.c + +## Add this here and not in retroshare.pri because static library are very +## sensible to order in command line, has to be in the end of file for the +## same reason + LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl + INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a + + LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto + INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a +} diff --git a/openpgpsdk/src/openpgpsdk.pro b/openpgpsdk/src/openpgpsdk.pro index 2cee7847d..6847c195c 100644 --- a/openpgpsdk/src/openpgpsdk.pro +++ b/openpgpsdk/src/openpgpsdk.pro @@ -115,3 +115,21 @@ SOURCES += openpgpsdk/accumulate.c \ openpgpsdk/writer_stream_encrypt_se_ip.c \ openpgpsdk/opsdir.c \ openpgpsdk/opsstring.c + +################################# Android ##################################### + +android-g++ { + +## Add this here and not in retroshare.pri because static library are very +## sensible to order in command line + LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lssl + INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libssl.a + + LIBS += -L$$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/ -lcrypto + INCLUDEPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + DEPENDPATH += $$NDK_TOOLCHAIN_PATH/sysroot/usr/include + PRE_TARGETDEPS += $$NDK_TOOLCHAIN_PATH/sysroot/usr/lib/libcrypto.a + +} diff --git a/retroshare-qml-app/src/Page1.qml b/retroshare-qml-app/src/Page1.qml deleted file mode 100644 index d5b808f9b..000000000 --- a/retroshare-qml-app/src/Page1.qml +++ /dev/null @@ -1,10 +0,0 @@ -import QtQuick 2.7 - -Page1Form { - button1.onClicked: { - console.log("Button 1 clicked."); - } - button2.onClicked: { - console.log("Button 2 clicked."); - } -} diff --git a/retroshare-qml-app/src/Page1Form.ui.qml b/retroshare-qml-app/src/Page1Form.ui.qml deleted file mode 100644 index 806ad2190..000000000 --- a/retroshare-qml-app/src/Page1Form.ui.qml +++ /dev/null @@ -1,22 +0,0 @@ -import QtQuick 2.7 -import QtQuick.Controls 2.0 -import QtQuick.Layouts 1.0 - -Item { - property alias button1: button1 - property alias button2: button2 - - RowLayout { - anchors.centerIn: parent - - Button { - id: button1 - text: qsTr("Press Me 1") - } - - Button { - id: button2 - text: qsTr("Press Me 2") - } - } -} diff --git a/retroshare-qml-app/src/android/AndroidManifest.xml b/retroshare-qml-app/src/android/AndroidManifest.xml index 54eb4f894..c5552d619 100644 --- a/retroshare-qml-app/src/android/AndroidManifest.xml +++ b/retroshare-qml-app/src/android/AndroidManifest.xml @@ -1,12 +1,7 @@ - + @@ -74,11 +69,7 @@ - + @@ -135,5 +126,5 @@ - + diff --git a/retroshare-qml-app/src/debugutils.h b/retroshare-qml-app/src/debugutils.h new file mode 100644 index 000000000..a9a2f39ff --- /dev/null +++ b/retroshare-qml-app/src/debugutils.h @@ -0,0 +1,15 @@ +#ifndef DEBUGUTILS_H +#define DEBUGUTILS_H + +#include + +//To switch between debugging and normal mode, un-/comment next line +#define DEBUGGING +#ifdef DEBUGGING + #define myDebug(line) qDebug() << "| FILE:" << __FILE__ << " | LINE_NUMBER:"\ + << __LINE__ << " | FUNCTION:" << __FUNCTION__ << " | CONTENT:" << line +#else + #define myDebug(line) +#endif + +#endif // DEBUGUTILS_H diff --git a/retroshare-qml-app/src/libresapilocalclient.cpp b/retroshare-qml-app/src/libresapilocalclient.cpp new file mode 100644 index 000000000..6b7e1d1f1 --- /dev/null +++ b/retroshare-qml-app/src/libresapilocalclient.cpp @@ -0,0 +1,79 @@ +#include "libresapilocalclient.h" +#include "debugutils.h" +#include + +/* Constructor de còpia per proves, no s'ha d'usar. +LibresapiLocalClient::LibresapiLocalClient(const LibresapiLocalClient & l) +{ + //mLocalSocket = l.mLocalSocket; + receivedBytes = l.receivedBytes; + json = l.json; +}*/ + +LibresapiLocalClient::LibresapiLocalClient(const QString & socketPath) : + mLocalSocket(this) +{ + myDebug(this); + mSocketPath = socketPath; + connect(& mLocalSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), + this, SLOT(socketError(QLocalSocket::LocalSocketError))); + connect(& mLocalSocket, SIGNAL(readyRead()), + this, SLOT(read())); + //openConnection(); +} + + +void LibresapiLocalClient::openConnection() +{ + mLocalSocket.connectToServer(mSocketPath); +} + +int LibresapiLocalClient::request(const QString & path, const QString & jsonData) +{ + QByteArray data; + data.append(path); data.append('\n'); + data.append(jsonData); data.append('\n'); + mLocalSocket.write(data); + + return 1; +} + +void LibresapiLocalClient::socketError(QLocalSocket::LocalSocketError error) +{ + myDebug("error!!!!\n" + mLocalSocket.errorString());//error.errorString()); +} + +void LibresapiLocalClient::read() +{ + receivedBytes = mLocalSocket.readAll(); + + if(parseResponse()){ // pensar en fer un buffer per parsejar, per evitar errors. + emit goodResponseReceived(QString(receivedBytes)); + return; + } + + QString errMess = "The message was not understood!\n" + "It should be a JSON formatted text file\n" + "Its contents were:\n" + receivedBytes; + myDebug(errMess.replace(QChar('\n'), QChar::LineSeparator)); + +} + +bool LibresapiLocalClient::parseResponse() +{ + QJsonParseError error; + json = QJsonDocument::fromJson(receivedBytes, &error); + myDebug(QString(json.toJson()).replace(QChar('\n'), QChar::LineSeparator)); + + if(error.error == QJsonParseError::NoError){ + return true; + } + myDebug(error.errorString()); + + return false; +} + +const QJsonDocument & LibresapiLocalClient::getJson() +{ + return json; +} diff --git a/retroshare-qml-app/src/libresapilocalclient.h b/retroshare-qml-app/src/libresapilocalclient.h new file mode 100644 index 000000000..da2917359 --- /dev/null +++ b/retroshare-qml-app/src/libresapilocalclient.h @@ -0,0 +1,64 @@ +/* + * libresapi local socket client + * Copyright (C) 2016 Manu Pineda + * + * 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 . + */ + +#ifndef LIBRESAPILOCALCLIENT_H +#define LIBRESAPILOCALCLIENT_H + +#include +#include +#include +#include + +class LibresapiLocalClient : public QObject +{ + Q_OBJECT + + public: + + LibresapiLocalClient() {} + // LibresapiLocalClient(const LibresapiLocalClient & l); + LibresapiLocalClient(const QString & socketPath); + // potser abstreure el següent amb QUrl urlPath (path) i amb QJson jsonData. + Q_INVOKABLE int request(const QString & path, const QString & jsonData); + const QJsonDocument & getJson(); + Q_INVOKABLE void openConnection(); + + private: + + QString mSocketPath; + QLocalSocket mLocalSocket; + QByteArray receivedBytes; + QJsonDocument json; + //QVector responses; + + bool parseResponse(); //std::string msg); + + private slots: + + void socketError(QLocalSocket::LocalSocketError error); + void read(); + + signals: + + void goodResponseReceived(const QString & msg);//, int requestId); + + public slots: + +}; + +#endif // LIBRESAPILOCALCLIENT_H diff --git a/retroshare-qml-app/src/main.cpp b/retroshare-qml-app/src/main.cpp index 2834d7908..05ada4369 100644 --- a/retroshare-qml-app/src/main.cpp +++ b/retroshare-qml-app/src/main.cpp @@ -18,30 +18,37 @@ #include #include +#include +#include #include #include #include #include +#include "libresapilocalclient.h" #include "retroshare/rsinit.h" int main(int argc, char *argv[]) { - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - QGuiApplication app(argc, argv); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); - QQmlApplicationEngine engine; - engine.load(QUrl(QLatin1String("qrc:/main.qml"))); + QQmlApplicationEngine engine; - QString sockPath = QString::fromStdString(RsAccounts::ConfigDirectory()); - sockPath.append("/libresapi.sock"); + QString sockPath = QString::fromStdString(RsAccounts::ConfigDirectory()); + sockPath.append("/libresapi.sock"); + LibresapiLocalClient llc(sockPath); + qmlRegisterType("LibresapiLocalClientQml", 1, 0, "LibresapiLocalClientComm"); - QFileInfo fileInfo(sockPath); + engine.rootContext()->setContextProperty("llc", &llc); + engine.load(QUrl(QLatin1String("qrc:/qml/main.qml"))); - qDebug() << "Is service.cpp running as a service?" << QtAndroid::androidService().isValid(); - qDebug() << "Is service.cpp running as an activity?" << QtAndroid::androidActivity().isValid(); - qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString(); + QFileInfo fileInfo(sockPath); - return app.exec(); + qDebug() << "Is main.cpp running as a service?" << QtAndroid::androidService().isValid(); + qDebug() << "Is main.cpp running as an activity?" << QtAndroid::androidActivity().isValid(); + qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString(); + + return app.exec(); } diff --git a/retroshare-qml-app/src/main.qml b/retroshare-qml-app/src/main.qml deleted file mode 100644 index 9e0707ad6..000000000 --- a/retroshare-qml-app/src/main.qml +++ /dev/null @@ -1,37 +0,0 @@ -import QtQuick 2.7 -import QtQuick.Controls 2.0 -import QtQuick.Layouts 1.0 - -ApplicationWindow { - visible: true - width: 640 - height: 480 - title: qsTr("Hello World") - - SwipeView { - id: swipeView - anchors.fill: parent - currentIndex: tabBar.currentIndex - - Page1 { - } - - Page { - Label { - text: qsTr("Second page") - anchors.centerIn: parent - } - } - } - - footer: TabBar { - id: tabBar - currentIndex: swipeView.currentIndex - TabButton { - text: qsTr("First") - } - TabButton { - text: qsTr("Second") - } - } -} diff --git a/retroshare-qml-app/src/qml.qrc b/retroshare-qml-app/src/qml.qrc index 67415af31..c2fd19fdc 100644 --- a/retroshare-qml-app/src/qml.qrc +++ b/retroshare-qml-app/src/qml.qrc @@ -3,5 +3,21 @@ main.qml Page1.qml Page1Form.ui.qml + qml/main.qml + qml/icons/star-2-128.png + qml/icons/settings-4-128.png + qml/icons/email-128.png + qml/icons/contacts-128.png + qml/PostedMsgDelegate.qml + qml/GxsService.qml + qml/GxsIdDelegate.qml + qml/GxsGroupDelegate.qml + qml/ForumMsgDelegate.qml + qml/ContactBox.qml + qml/ChannelMsgDelegate.qml + qml/ChannelGroupDelegate.qml + qml/ApplicationBar.qml + qml/AppButton.qml + qml/LibresapiLocalClientComm.qml diff --git a/retroshare-qml-app/src/qml/AppButton.qml b/retroshare-qml-app/src/qml/AppButton.qml new file mode 100644 index 000000000..6b3f9a881 --- /dev/null +++ b/retroshare-qml-app/src/qml/AppButton.qml @@ -0,0 +1,30 @@ +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() + } + } +} + + diff --git a/retroshare-qml-app/src/qml/ApplicationBar.qml b/retroshare-qml-app/src/qml/ApplicationBar.qml new file mode 100644 index 000000000..3ec44349d --- /dev/null +++ b/retroshare-qml-app/src/qml/ApplicationBar.qml @@ -0,0 +1,37 @@ +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" + } +} + + + diff --git a/retroshare-qml-app/src/qml/ChannelGroupDelegate.qml b/retroshare-qml-app/src/qml/ChannelGroupDelegate.qml new file mode 100644 index 000000000..a92cab36c --- /dev/null +++ b/retroshare-qml-app/src/qml/ChannelGroupDelegate.qml @@ -0,0 +1,33 @@ +import QtQuick 2.2 +import "." + +Item { + id: item + width: parent.width + height: 50 + + Column { + Text { text: '' + model.GroupName + '' } + 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 + } +} + + diff --git a/retroshare-qml-app/src/qml/ChannelMsgDelegate.qml b/retroshare-qml-app/src/qml/ChannelMsgDelegate.qml new file mode 100644 index 000000000..333d0fab0 --- /dev/null +++ b/retroshare-qml-app/src/qml/ChannelMsgDelegate.qml @@ -0,0 +1,42 @@ +import QtQuick 2.2 +import "." + +Item { + id: msgDelegate + + width: parent.width + height: 150 + + Column { + Text { text: 'MsgId: ' + AuthorId } + Text { text: 'AuthorId: ' + AuthorId } + Row { + Text { text: 'Name: ' + MsgName } + Text { text: ' PublishTs: ' + PublishTs } + } + Text { text: 'Msg: ' + Msg } + Row { + Text { text: 'NumberFiles: ' + NumberFiles } + Text { text: ' TotalFileSize: ' + TotalFileSize } + } + + Text { text: 'FileNames: ' + FileNames } + Text { text: 'FileSizes: ' + FileSizes } + Text { text: 'FileHashes: ' + FileHashes } + Row { + Text { text: 'HaveVoted: ' + HaveVoted } + Text { text: ' UpVotes: ' + UpVotes } + Text { text: ' DownVotes: ' + DownVotes } + Text { text: ' Comments: ' + Comments } + } + } + + MouseArea { + hoverEnabled: false + anchors.fill: parent + onClicked: { + item.ListView.view.currentIndex = index + } + } +} + diff --git a/retroshare-qml-app/src/qml/ContactBox.qml b/retroshare-qml-app/src/qml/ContactBox.qml new file mode 100644 index 000000000..9790ed677 --- /dev/null +++ b/retroshare-qml-app/src/qml/ContactBox.qml @@ -0,0 +1,61 @@ +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" + } + } + } +} + + diff --git a/retroshare-qml-app/src/qml/ForumMsgDelegate.qml b/retroshare-qml-app/src/qml/ForumMsgDelegate.qml new file mode 100644 index 000000000..bdb8d9138 --- /dev/null +++ b/retroshare-qml-app/src/qml/ForumMsgDelegate.qml @@ -0,0 +1,41 @@ +import QtQuick 2.2 +import "." + +Item { + id: msgDelegate + + width: parent.width + height: col.height + + Column { + id: col + Text { text: 'MsgId: ' + AuthorId } + Text { text: 'AuthorId: ' + AuthorId } + Row { + Text { text: 'Name: ' + MsgName } + Text { text: ' PublishTs: ' + PublishTs } + } + Text { + wrapMode: Text.Wrap + text: 'Msg: ' + 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 + } + +} + diff --git a/retroshare-qml-app/src/qml/GxsGroupDelegate.qml b/retroshare-qml-app/src/qml/GxsGroupDelegate.qml new file mode 100644 index 000000000..0a0b14f73 --- /dev/null +++ b/retroshare-qml-app/src/qml/GxsGroupDelegate.qml @@ -0,0 +1,26 @@ +import QtQuick 2.2 +import "." + +Item { + id: item + property var msgModel: {} + + width: parent.width + height: 50 + + Column { + Text { text: 'Name: ' + model.GroupName } + Text { text: 'Number: ' + GroupId } + } + + MouseArea { + hoverEnabled: false + anchors.fill: parent + onClicked: { + item.ListView.view.currentIndex = index + item.msgModel.updateEntries(model.GroupId) + } + } +} + + diff --git a/retroshare-qml-app/src/qml/GxsIdDelegate.qml b/retroshare-qml-app/src/qml/GxsIdDelegate.qml new file mode 100644 index 000000000..8fd7d738d --- /dev/null +++ b/retroshare-qml-app/src/qml/GxsIdDelegate.qml @@ -0,0 +1,33 @@ +import QtQuick 2.2 +import "." + +Item { + id: item + width: parent.width + height: 50 + + Column { + Text { text: '' + model.GroupName + '' } + 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 + } +} + + diff --git a/retroshare-qml-app/src/qml/GxsService.qml b/retroshare-qml-app/src/qml/GxsService.qml new file mode 100644 index 000000000..7c2a4798d --- /dev/null +++ b/retroshare-qml-app/src/qml/GxsService.qml @@ -0,0 +1,119 @@ +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") + } + } + + } + } +} + + diff --git a/retroshare-qml-app/src/qml/LibresapiLocalClientComm.qml b/retroshare-qml-app/src/qml/LibresapiLocalClientComm.qml new file mode 100644 index 000000000..75233c4ab --- /dev/null +++ b/retroshare-qml-app/src/qml/LibresapiLocalClientComm.qml @@ -0,0 +1,5 @@ +import LibresapiLocalClientQml 1.0 + +LibresapiLocalClientComm { + id: llc +} diff --git a/retroshare-qml-app/src/qml/PostedMsgDelegate.qml b/retroshare-qml-app/src/qml/PostedMsgDelegate.qml new file mode 100644 index 000000000..f700054aa --- /dev/null +++ b/retroshare-qml-app/src/qml/PostedMsgDelegate.qml @@ -0,0 +1,40 @@ +import QtQuick 2.2 +import "." + +Item { + id: msgDelegate + + width: parent.width + height: 150 + + Column { + Text { text: 'MsgId: ' + AuthorId } + Text { text: 'AuthorId: ' + AuthorId } + Row { + Text { text: 'Name: ' + MsgName } + Text { text: ' PublishTs: ' + PublishTs } + } + Text { text: 'Link: ' + Link } + Text { text: 'Notes: ' + Notes } + Row { + Text { text: 'Hot: ' + HotScore } + Text { text: ' Top: ' + HotScore } + Text { text: ' New: ' + HotScore } + } + Row { + Text { text: 'HaveVoted: ' + HaveVoted } + Text { text: ' UpVotes: ' + UpVotes } + Text { text: ' DownVotes: ' + DownVotes } + Text { text: ' Comments: ' + Comments } + } + } + + MouseArea { + hoverEnabled: false + anchors.fill: parent + onClicked: { + item.ListView.view.currentIndex = index + } + } +} + diff --git a/retroshare-qml-app/src/qml/icons/contacts-128.png b/retroshare-qml-app/src/qml/icons/contacts-128.png new file mode 100755 index 0000000000000000000000000000000000000000..41d04a8ae8504e0bcc18e8e1961f3d792a69e89b GIT binary patch literal 2399 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 N002ovPDHLkV1k^olt};p literal 0 HcmV?d00001 diff --git a/retroshare-qml-app/src/qml/icons/settings-4-128.png b/retroshare-qml-app/src/qml/icons/settings-4-128.png new file mode 100755 index 0000000000000000000000000000000000000000..44a65b18c9100670836845d49dd00f104f739fb0 GIT binary patch literal 3162 zcmV-g45jmlP) zd#qel6^Fld(~v@%rWR5WN`nocfH6=}NCj+b!CJ6Fc!&kDVpCs8h?x3_!4P6hLx^b# znkt}(JYo?Aq~+ZQAkR`B7OJJ10v5ufEwn&sd+*Ob)(o9{JNM4ax#yg9&fNL_y04kF z?%wO{*^jk;8`=a(ZD}iOhDAG&6iK6iDZpglc;FG>TRE9uB55?R0$2(xGqZPdvORQ> zG+xpVB|RW%hkEmDPCj%=+SJGPdy?*w^j%5&<>AMWLDJ|-!#=9UjLX5F;~V(UE$Kc< zvm}kk+Ml*X(lL^*t2|xJa!C$;UE6{`Z%O)%q)%k+S6d(-UQdzqa0`uG%j@#FL00}9 zDCzwKes#+~BBo~LTd9$Rq|+s>PT29a%+JuzTT}4wVM!-u75K3 zw3bbhCZ*u-naZZ~Yk5=BY_-t_Q@=<$KjX%%Wrw7TCGC;G&k>TYls~0;aaI;>UMS=< z{iQiJswAzGbe^Q)P53ub(uI=#m$Q#|DPRP!+%4iedcbO+VTYv5X5en%Az%&g2G9kJ z0S*I>1EvCdygHz z1E51N(E*V6K0p!qaHR3*02m)^zX%)_X*@arjt;h81SU!f>xVGIl7<1(qU;xlF~G4= z#v@07lktDP1AH&aaMWQCz~jJC5%vp64{)TJJ(IWLs1aZmozAbN3s_kBSj^pUu1Gor z$SiTQ#2f=$mbcMd13=PP;0|D9o_0&bg_7pxX|SmPkTh|y*(#MMe<^U#px&tcnI~z1 z5~Q(Jk*Bkzv$BwMkapF+wOo27&-tGvuafD|JgX#4OwisZN?I!~lG&%#*VlW)B{fdH zBWa#|C~x$-E=e=gTuonx`gF_Ne})FgXOutjGv!6IwJdDZPXjIk-?<^L&09}<- zqh~1J4EJr|G&9>8>4cfx1Y8m6Lz8R={tSEtUk0Ak>$mdc&7&kORQ4*KX)r6G5$hsp z52Yh?odrAOf|vWY@(+zub~?Ukuv`U`RaN7QBqI2)q+@H3I_Vv+y|)3v3N z#Zm%}2(UR;(kEMeQA+_JSDlIsfX@ZmZVI`8R@6w<@}XU;BfxB8kAjjH%2#3g|{R%bs=ZN5kS&3;MzgAPI`3F;xLy7l8(T|wTi$< z;4VoGN237)K+<`@trRC8u*%6wdzc#p@`>p}inX&k3b;qoT(x=+fuymL76lDo&GL#` z$#u^VNk5Kmj0UWhbh_#ULSKcDqwuxlEL=9F=qv-i5LFrmAZaSFgmOshyb9a~EXQ*e zo;0~G{f+pI75fD`my{cTv(56q{z$&_ww!VuR8`pue5kAP^c+DsJ0D!a<@d^YgGkZ@ z;2z4^`2bIv+0JUCg2E>KWW^}pXTW;-y*qOxH9V%3w3nnwk}kt5 ze3k%{vUN3gZmoXN&O(v>u@QI<&!XLoYy6C2msIQ_lw(bwlwLxSeK&%hr+pMLJJhGw z9YTHA&P6)(>5vWA)qc}=1lSK)PkRr6Lz&I^@c)6dU9S8-J|re5ikX zxgaNJI^;_ab<|br1;G8dzJ7=HVzHU+R<*oa08~!Zewl|y9dhNedOz%3962;-?czu? z+X-BnsY@O5i?s<`BwZlS*KDP&$4UB2O{G=wgr|$fW55Y{ zs-fkcOD$<8@p`&N zFV`i`gDvtaXWJin0^^RH8g{K*{4%MpC(0E`^Wbs$$k^UU8Ybzd@C&zE zJ5JKSGWK+$|$f!((jbuKEDnXB<&+FlUmuJvENe;J8JSh zf`RCkv|Q48%1OL>$RJ-i%$4+zcHc`_nxh}<`q+M7(mnE`*;2{w=YPvWO32svQ-R6& zZKijdSuXb)N?L@giav-dtuHgPt$En)&>>a+4{7b=b~#0Z7ytkO07*qoM6N<$g75+l AAOHXW literal 0 HcmV?d00001 diff --git a/retroshare-qml-app/src/qml/icons/star-2-128.png b/retroshare-qml-app/src/qml/icons/star-2-128.png new file mode 100755 index 0000000000000000000000000000000000000000..3409abc13a77ec066ea0f1862b4c9e32b8123174 GIT binary patch literal 1973 zcmV;m2TJ&fP)^pr{Cz zNBS0AupOLm~Al_8*550sdUrazP_0AZ_+ip{agtfdl}!$~(SL7;xtj zoe%J^cYL8BU=46=nFIiMuXmiG5a4TX(D?uldB+(F0H%SXLnOc%;62{)hRlFFhUol( z`@Q2083C(-!y_cX8Q?PSxI-qu?IU#lz%RVx4jBNefJ0*>z$sv}cl@PIc?VFzHc8`j z6KpUsl-r2mT4H_O5ddy%+&Vi#Kkr zSmXvtpQ>11F}+9trK#H5b=d{1s$6DvD?R~8i#E176un8(#~sSeYDH#ncl@1ITIlf^ z`PE}La9q;7q^Hg7zfN(C&AbCx3TeV+lTs&dlJwT|e&3)rllMPL$uKYJsqW{~lIG3q zInThB;+g=YJ8Xka-7M)X=l#8J_uyX}DY~DB&inltGdt@MRM7+=y?JAUF1#0SmbA8H z`76i?rDPZ+#9;K8m-G)aJ6)>ai4&kZ6P{J-(8YW4CZz=xVSJ}09nsQ@O#o$K&ckH;5_f9qUA+-GLLALT!f+;L`h1URgi4sFFu_vd*@kB$1D$36rAJ0;z% z9?T{5z+Oq89OqnN?9MZ@A4}Sybc_mqekSR*aZeO;oCI!@v`29s;Nf0LJIu!K#)*e- zGutca7D*>3-BuXM_cdYXOPP+}X7+%jk4l>8k|Yf6+h(?F($1GMPXqziNZKdqlAPNO zue00CzLRTX*=GWQD zq+87F*Ch+7vL2wjfi$P_Ue$6jdPUL)&1`?^f~w5po7oXbS4%orqr4UFxTG6ubiP!1 zMNqc_Xin0Wn&qw_$0c2BW{=k@u=FVy}xti*?a?R|xq-!Pp zp?28~J0ge9mmC*`0+%RF8(-rULn8*AKkrev+hUGxE!z!}ieBb4ZeG&+ygFZUUd>}> zCzb9Z{>n2BCx2Di^Ia#(ns_h-n3D8!`8?v~;h<9IyFV<_#CpE&p@`d*R=D)tY zfP2)R7c%`tY1nUW$y`$-z#=ES1ITEq#m<-7PJnlL2awTat-ehp5}=N0HF_(&$pio{ zm2|0h9GNwl&`lCtEBvH=9VRQhvYzyk1H&if89unM>x_*aGU0WSA!>m~uSg&X}8 z@Dt!o%fv7Ze1r6%=#sWqY}#eDbOZ-Tu&NhCXsD~ zTJeC($85xxfICUw7_1%h6tE3=YRuTbTs!7w0ozAxyanI~q_>WF$G#f)9PsRjb=cz_ zdozF^4%ujDfct=r-Z3u*tRam?!%k)$@Q%3|z{5i{*n^}OP&W-&-zD^`Z&05F8hu`{ zI^ftc4fP1{e(xAZ1#ATNu>7TCUspd5513h^ah?Ef^p0;*z!qTN5_P=QJ3e>7b&DG1 zFz^Y|i=%o2xB_@|QC;uyj>{eJ)vhs)0biujwY(SLCg91g&ilOMaR=N3e2WGjn<|Ag zhp-)Z#5)#uec$7kb1~OFgb+dqA%qY@2qA Date: Mon, 5 Sep 2016 02:12:00 +0200 Subject: [PATCH 07/15] Start RetroShare Android Service when needed RetroShareQmlActivity checks if the service is running, and start it if not The service is restarted on package update Remove non existents qml files from qrc --- .../src/android/AndroidManifest.xml | 6 ++++ .../android/qml_app/AppUpdatedReceiver.java | 36 +++++++++++++++++++ .../qml_app/RetroShareQmlActivity.java | 32 ++++++++++++++++- retroshare-qml-app/src/qml.qrc | 3 -- 4 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/AppUpdatedReceiver.java diff --git a/retroshare-qml-app/src/android/AndroidManifest.xml b/retroshare-qml-app/src/android/AndroidManifest.xml index c5552d619..64b2bca6a 100644 --- a/retroshare-qml-app/src/android/AndroidManifest.xml +++ b/retroshare-qml-app/src/android/AndroidManifest.xml @@ -68,6 +68,12 @@ + + + + + + diff --git a/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/AppUpdatedReceiver.java b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/AppUpdatedReceiver.java new file mode 100644 index 000000000..d9f87f650 --- /dev/null +++ b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/AppUpdatedReceiver.java @@ -0,0 +1,36 @@ +/* + * RetroShare Android Service + * Copyright (C) 2016 Gioacchino Mazzurco + * + * 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 . + */ + +package org.retroshare.android.qml_app; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +public class AppUpdatedReceiver extends BroadcastReceiver +{ + @Override + public void onReceive(Context context, Intent intent) + { + Log.i("AppUpdatedReceiver", "onReceive() Restarting RetroShare Android Service After Update"); + Intent myIntent = new Intent(context, RetroShareAndroidService.class); + context.stopService(myIntent); + context.startService(myIntent); + } +} diff --git a/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java index 8e47563c8..faabc266b 100644 --- a/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java +++ b/retroshare-qml-app/src/android/src/org/retroshare/android/qml_app/RetroShareQmlActivity.java @@ -18,6 +18,36 @@ package org.retroshare.android.qml_app; +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + import org.qtproject.qt5.android.bindings.QtActivity; -public class RetroShareQmlActivity extends QtActivity {} +public class RetroShareQmlActivity extends QtActivity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + if (!isMyServiceRunning(RetroShareAndroidService.class)) + { + Log.i("RetroShareQmlActivity", "onCreate(): RetroShareAndroidService is not running, let's start it by Intent"); + Intent rsIntent = new Intent(this, RetroShareAndroidService.class); + startService(rsIntent); + } + else Log.v("RetroShareQmlActivity", "onCreate(): RetroShareAndroidService already running"); + + super.onCreate(savedInstanceState); + } + + private boolean isMyServiceRunning(Class serviceClass) + { + ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) + if (serviceClass.getName().equals(service.service.getClassName())) + return true; + return false; + } +} diff --git a/retroshare-qml-app/src/qml.qrc b/retroshare-qml-app/src/qml.qrc index c2fd19fdc..a0211e789 100644 --- a/retroshare-qml-app/src/qml.qrc +++ b/retroshare-qml-app/src/qml.qrc @@ -1,8 +1,5 @@ - main.qml - Page1.qml - Page1Form.ui.qml qml/main.qml qml/icons/star-2-128.png qml/icons/settings-4-128.png From 8d6d3d1894aab417837cabd3cc7723d3c9141a81 Mon Sep 17 00:00:00 2001 From: Gio Date: Thu, 15 Sep 2016 13:07:13 +0200 Subject: [PATCH 08/15] Retroshare QML App: Implemesh some basic stuff Implement location creation, selection and login Implement people listing Implement firends adding (not working yet) Depend on androidextra qt module only if compiling for android LibresapiLocalClient parse one line at time to avoid error if two requests are sent rapidly one after another LibresapiLocalClient socket path now is a parameter of openConnection() to use it as qml type constructor without parameter must be useful Added JSONListModel for JASON based MVC pattern --- .../src/retroshare-android-service.pro | 2 +- retroshare-android-service/src/service.cpp | 7 +- .../src/libresapilocalclient.cpp | 73 +++++---- retroshare-qml-app/src/libresapilocalclient.h | 46 +++--- retroshare-qml-app/src/main.cpp | 25 ++- retroshare-qml-app/src/qml.qrc | 6 +- retroshare-qml-app/src/qml/AddTrustedNode.qml | 39 +++++ retroshare-qml-app/src/qml/Contacts.qml | 56 +++++++ retroshare-qml-app/src/qml/JSONListModel.qml | 51 ++++++ .../src/qml/LibresapiLocalClientComm.qml | 5 - retroshare-qml-app/src/qml/Locations.qml | 152 ++++++++++++++++++ retroshare-qml-app/src/qml/jsonpath.js | 88 ++++++++++ retroshare-qml-app/src/qml/main.qml | 111 ++++++++++--- retroshare-qml-app/src/retroshare-qml-app.pro | 2 +- retroshare.pri | 1 + 15 files changed, 565 insertions(+), 99 deletions(-) create mode 100644 retroshare-qml-app/src/qml/AddTrustedNode.qml create mode 100644 retroshare-qml-app/src/qml/Contacts.qml create mode 100644 retroshare-qml-app/src/qml/JSONListModel.qml delete mode 100644 retroshare-qml-app/src/qml/LibresapiLocalClientComm.qml create mode 100644 retroshare-qml-app/src/qml/Locations.qml create mode 100644 retroshare-qml-app/src/qml/jsonpath.js diff --git a/retroshare-android-service/src/retroshare-android-service.pro b/retroshare-android-service/src/retroshare-android-service.pro index d327ace4f..bede07450 100644 --- a/retroshare-android-service/src/retroshare-android-service.pro +++ b/retroshare-android-service/src/retroshare-android-service.pro @@ -2,7 +2,7 @@ TARGET = retroshare-android-service -QT += core network androidextras +QT += core network QT -= gui CONFIG += c++11 diff --git a/retroshare-android-service/src/service.cpp b/retroshare-android-service/src/service.cpp index 5cea31e5f..9359af904 100644 --- a/retroshare-android-service/src/service.cpp +++ b/retroshare-android-service/src/service.cpp @@ -18,7 +18,10 @@ #include #include -#include + +#ifdef __ANDROID__ +# include +#endif #include "retroshare/rsinit.h" #include "api/ApiServer.h" @@ -39,8 +42,10 @@ int main(int argc, char *argv[]) qDebug() << "Listening on:" << sockPath; ApiServerLocal apiServerLocal(&api, sockPath); (void) apiServerLocal; +#ifdef __ANDROID__ qDebug() << "Is service.cpp running as a service?" << QtAndroid::androidService().isValid(); qDebug() << "Is service.cpp running as an activity?" << QtAndroid::androidActivity().isValid(); +#endif while (!ctrl_mod.processShouldExit()) { diff --git a/retroshare-qml-app/src/libresapilocalclient.cpp b/retroshare-qml-app/src/libresapilocalclient.cpp index 6b7e1d1f1..aa74feab7 100644 --- a/retroshare-qml-app/src/libresapilocalclient.cpp +++ b/retroshare-qml-app/src/libresapilocalclient.cpp @@ -1,35 +1,39 @@ +/* + * RetroShare Android QML App + * Copyright (C) 2016 Gioacchino Mazzurco + * Copyright (C) 2016 Manu Pineda + * + * 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 . + */ + #include "libresapilocalclient.h" #include "debugutils.h" #include -/* Constructor de còpia per proves, no s'ha d'usar. -LibresapiLocalClient::LibresapiLocalClient(const LibresapiLocalClient & l) -{ - //mLocalSocket = l.mLocalSocket; - receivedBytes = l.receivedBytes; - json = l.json; -}*/ -LibresapiLocalClient::LibresapiLocalClient(const QString & socketPath) : - mLocalSocket(this) +void LibresapiLocalClient::openConnection(QString socketPath) { - myDebug(this); - mSocketPath = socketPath; - connect(& mLocalSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), - this, SLOT(socketError(QLocalSocket::LocalSocketError))); - connect(& mLocalSocket, SIGNAL(readyRead()), - this, SLOT(read())); - //openConnection(); -} - - -void LibresapiLocalClient::openConnection() -{ - mLocalSocket.connectToServer(mSocketPath); + connect(& mLocalSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), + this, SLOT(socketError(QLocalSocket::LocalSocketError))); + connect(& mLocalSocket, SIGNAL(readyRead()), + this, SLOT(read())); + mLocalSocket.connectToServer(socketPath); } int LibresapiLocalClient::request(const QString & path, const QString & jsonData) { + qDebug() << "LibresapiLocalClient::request()" << path << jsonData; QByteArray data; data.append(path); data.append('\n'); data.append(jsonData); data.append('\n'); @@ -38,25 +42,24 @@ int LibresapiLocalClient::request(const QString & path, const QString & jsonData return 1; } -void LibresapiLocalClient::socketError(QLocalSocket::LocalSocketError error) +void LibresapiLocalClient::socketError(QLocalSocket::LocalSocketError) { - myDebug("error!!!!\n" + mLocalSocket.errorString());//error.errorString()); + myDebug("error!!!!\n" + mLocalSocket.errorString()); } void LibresapiLocalClient::read() { - receivedBytes = mLocalSocket.readAll(); - - if(parseResponse()){ // pensar en fer un buffer per parsejar, per evitar errors. - emit goodResponseReceived(QString(receivedBytes)); - return; - } - - QString errMess = "The message was not understood!\n" - "It should be a JSON formatted text file\n" - "Its contents were:\n" + receivedBytes; - myDebug(errMess.replace(QChar('\n'), QChar::LineSeparator)); + receivedBytes = mLocalSocket.readLine(); + if(parseResponse()) // pensar en fer un buffer per parsejar, per evitar errors. + emit goodResponseReceived(QString(receivedBytes)); + else + { + QString errMess = "The message was not understood!\n" + "It should be a JSON formatted text file\n" + "Its contents were:\n" + receivedBytes; + myDebug(errMess.replace(QChar('\n'), QChar::LineSeparator)); + } } bool LibresapiLocalClient::parseResponse() diff --git a/retroshare-qml-app/src/libresapilocalclient.h b/retroshare-qml-app/src/libresapilocalclient.h index da2917359..cfbfb6dec 100644 --- a/retroshare-qml-app/src/libresapilocalclient.h +++ b/retroshare-qml-app/src/libresapilocalclient.h @@ -1,5 +1,6 @@ /* * libresapi local socket client + * Copyright (C) 2016 Gioacchino Mazzurco * Copyright (C) 2016 Manu Pineda * * This program is free software: you can redistribute it and/or modify @@ -26,39 +27,30 @@ class LibresapiLocalClient : public QObject { - Q_OBJECT + Q_OBJECT - public: +public: + LibresapiLocalClient() : mLocalSocket(this) {} - LibresapiLocalClient() {} - // LibresapiLocalClient(const LibresapiLocalClient & l); - LibresapiLocalClient(const QString & socketPath); - // potser abstreure el següent amb QUrl urlPath (path) i amb QJson jsonData. - Q_INVOKABLE int request(const QString & path, const QString & jsonData); - const QJsonDocument & getJson(); - Q_INVOKABLE void openConnection(); + // potser abstreure el següent amb QUrl urlPath (path) i amb QJson jsonData. + Q_INVOKABLE int request(const QString & path, const QString & jsonData); + const QJsonDocument & getJson(); + Q_INVOKABLE void openConnection(QString socketPath); - private: +private: + QLocalSocket mLocalSocket; + QByteArray receivedBytes; + QJsonDocument json; + //QVector responses; - QString mSocketPath; - QLocalSocket mLocalSocket; - QByteArray receivedBytes; - QJsonDocument json; - //QVector responses; + bool parseResponse(); //std::string msg); - bool parseResponse(); //std::string msg); - - private slots: - - void socketError(QLocalSocket::LocalSocketError error); - void read(); - - signals: - - void goodResponseReceived(const QString & msg);//, int requestId); - - public slots: +private slots: + void socketError(QLocalSocket::LocalSocketError error); + void read(); +signals: + void goodResponseReceived(const QString & msg);//, int requestId); }; #endif // LIBRESAPILOCALCLIENT_H diff --git a/retroshare-qml-app/src/main.cpp b/retroshare-qml-app/src/main.cpp index 05ada4369..1418ec3fb 100644 --- a/retroshare-qml-app/src/main.cpp +++ b/retroshare-qml-app/src/main.cpp @@ -22,7 +22,10 @@ #include #include -#include +#ifdef __ANDROID__ +# include +#endif + #include #include @@ -35,20 +38,28 @@ int main(int argc, char *argv[]) QGuiApplication app(argc, argv); QQmlApplicationEngine engine; + qmlRegisterType( + "org.retroshare.qml_components.LibresapiLocalClient", 1, 0, + "LibresapiLocalClient"); QString sockPath = QString::fromStdString(RsAccounts::ConfigDirectory()); sockPath.append("/libresapi.sock"); - LibresapiLocalClient llc(sockPath); - qmlRegisterType("LibresapiLocalClientQml", 1, 0, "LibresapiLocalClientComm"); - engine.rootContext()->setContextProperty("llc", &llc); - engine.load(QUrl(QLatin1String("qrc:/qml/main.qml"))); +#ifndef __ANDROID__ + sockPath = "/home/gio/.retroshare/LOC06_8730499b55bb946424d537b180bee10a/libresapi.sock"; +#endif - QFileInfo fileInfo(sockPath); + engine.rootContext()->setContextProperty("apiSocketPath", sockPath); + engine.load(QUrl(QLatin1String("qrc:/qml/main.qml"))); + QFileInfo fileInfo(sockPath); + +#ifdef __ANDROID__ qDebug() << "Is main.cpp running as a service?" << QtAndroid::androidService().isValid(); qDebug() << "Is main.cpp running as an activity?" << QtAndroid::androidActivity().isValid(); - qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString(); +#endif + + qDebug() << "QML APP:" << sockPath << fileInfo.exists() << fileInfo.lastModified().toString(); return app.exec(); } diff --git a/retroshare-qml-app/src/qml.qrc b/retroshare-qml-app/src/qml.qrc index a0211e789..4bb111bc1 100644 --- a/retroshare-qml-app/src/qml.qrc +++ b/retroshare-qml-app/src/qml.qrc @@ -15,6 +15,10 @@ qml/ChannelGroupDelegate.qml qml/ApplicationBar.qml qml/AppButton.qml - qml/LibresapiLocalClientComm.qml + qml/Locations.qml + qml/jsonpath.js + qml/JSONListModel.qml + qml/Contacts.qml + qml/AddTrustedNode.qml diff --git a/retroshare-qml-app/src/qml/AddTrustedNode.qml b/retroshare-qml-app/src/qml/AddTrustedNode.qml new file mode 100644 index 000000000..80a0e094f --- /dev/null +++ b/retroshare-qml-app/src/qml/AddTrustedNode.qml @@ -0,0 +1,39 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 +import org.retroshare.qml_components.LibresapiLocalClient 1.0 + +Item +{ + Component.onCompleted: + { + rsApi.openConnection(apiSocketPath) + rsApi.request("/peers/self/certificate/", "") + } + + LibresapiLocalClient + { + id: rsApi + onGoodResponseReceived: myKeyField.text = JSON.parse(msg).data.cert_string + } + + ColumnLayout + { + anchors.top: parent.top + anchors.bottom: bottomButton.top + + TextField { id: myKeyField } + TextField { id: otherKeyField } + } + + Button + { + id: bottomButton + text: "Add trusted node" + anchors.bottom: parent.bottom + onClicked: + { + rsApi.request("/peers/examine_cert/", JSON.stringify({ cert_string: otherKeyField.text })) + } + } +} diff --git a/retroshare-qml-app/src/qml/Contacts.qml b/retroshare-qml-app/src/qml/Contacts.qml new file mode 100644 index 000000000..ef4fe3a13 --- /dev/null +++ b/retroshare-qml-app/src/qml/Contacts.qml @@ -0,0 +1,56 @@ +/* + * RetroShare Android QML App + * Copyright (C) 2016 Gioacchino Mazzurco + * + * 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 . + */ + +import QtQuick 2.0 +import QtQuick.Controls 1.4 +import org.retroshare.qml_components.LibresapiLocalClient 1.0 + +Item +{ + function refreshData() + { + rsApi.openConnection(apiSocketPath) + rsApi.request("/identity/*/", "") + } + + Component.onCompleted: refreshData() + onFocusChanged: focus && refreshData() + + LibresapiLocalClient + { + id: rsApi + onGoodResponseReceived: locationsModel.json = msg + } + + JSONListModel + { + id: locationsModel + query: "$.data[*]" + } + + ListView + { + id: locationsListView + width: parent.width + height: 300 + model: locationsModel.model + delegate: Text { text: model.name } + } + + Text { text: "Contacts View"; anchors.bottom: parent.bottom } +} diff --git a/retroshare-qml-app/src/qml/JSONListModel.qml b/retroshare-qml-app/src/qml/JSONListModel.qml new file mode 100644 index 000000000..cee905d75 --- /dev/null +++ b/retroshare-qml-app/src/qml/JSONListModel.qml @@ -0,0 +1,51 @@ +/* JSONListModel - a QML ListModel with JSON and JSONPath support + * + * Copyright (c) 2012 Romain Pokrzywka (KDAB) (romain@kdab.com) + * Licensed under the MIT licence (http://opensource.org/licenses/mit-license.php) + */ + +import QtQuick 2.0 +import "jsonpath.js" as JSONPath + +Item { + property string source: "" + property string json: "" + property string query: "" + + property ListModel model : ListModel { id: jsonModel } + property alias count: jsonModel.count + + onSourceChanged: { + var xhr = new XMLHttpRequest; + xhr.open("GET", source); + xhr.onreadystatechange = function() { + if (xhr.readyState == XMLHttpRequest.DONE) + json = xhr.responseText; + } + xhr.send(); + } + + onJsonChanged: updateJSONModel() + onQueryChanged: updateJSONModel() + + function updateJSONModel() { + jsonModel.clear(); + + if ( json === "" ) + return; + + var objectArray = parseJSONString(json, query); + for ( var key in objectArray ) { + var jo = objectArray[key]; + jsonModel.append( jo ); + } + } + + function parseJSONString(jsonString, jsonPathQuery) { + var objectArray = JSON.parse(jsonString); + if ( jsonPathQuery !== "" ) + objectArray = JSONPath.jsonPath(objectArray, jsonPathQuery); + + return objectArray; + } +} diff --git a/retroshare-qml-app/src/qml/LibresapiLocalClientComm.qml b/retroshare-qml-app/src/qml/LibresapiLocalClientComm.qml deleted file mode 100644 index 75233c4ab..000000000 --- a/retroshare-qml-app/src/qml/LibresapiLocalClientComm.qml +++ /dev/null @@ -1,5 +0,0 @@ -import LibresapiLocalClientQml 1.0 - -LibresapiLocalClientComm { - id: llc -} diff --git a/retroshare-qml-app/src/qml/Locations.qml b/retroshare-qml-app/src/qml/Locations.qml new file mode 100644 index 000000000..3e3c5e62e --- /dev/null +++ b/retroshare-qml-app/src/qml/Locations.qml @@ -0,0 +1,152 @@ +/* + * RetroShare Android QML App + * Copyright (C) 2016 Gioacchino Mazzurco + * + * 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 . + */ + +import QtQuick 2.0 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 +import org.retroshare.qml_components.LibresapiLocalClient 1.0 + +Item +{ + id: locationView + state: "selectLocation" + + states: + [ + State + { + name: "selectLocation" + PropertyChanges { target: locationsListView; visible: true } + PropertyChanges { target: createLocationView; visible: false } + PropertyChanges + { + target: bottomButton + text: "Create new location" + onClicked: locationView.state = "createLocation" + } + }, + State + { + name: "createLocation" + PropertyChanges { target: locationsListView; visible: false } + PropertyChanges { target: createLocationView; visible: true } + PropertyChanges + { + target: bottomButton + text: "Save" + onClicked: + { + var jsonData = { pgp_name: nameField.text, ssl_name: nameField.text, pgp_password: passwordField.text } + rsApi.request("/control/create_location/", JSON.stringify(jsonData)) + onClicked: locationView.state = "savingLocation" + } + } + }, + State + { + name: "savingLocation" + PropertyChanges { target: locationsListView; visible: false } + PropertyChanges { target: createLocationView; color: "grey" } + PropertyChanges + { + target: bottomButton + text: "Saving..." + enabled: false + } + }, + State + { + name: "loggingIn" + PropertyChanges { target: locationsListView; visible: false } + PropertyChanges { target: createLocationView; visible: true } + PropertyChanges { target: nameField; enabled: false} + PropertyChanges + { + target: bottomButton + text: "Login" + enabled: true + onClicked: + { + var jsonData = { id: nameField.sslid, autologin: false } + rsApi.request("/control/login/", JSON.stringify(jsonData)) + jsonData = { password: passwordField.text } + rsApi.request("/control/password/", JSON.stringify(jsonData)) + } + } + } + ] + + Component.onCompleted: + { + rsApi.openConnection(apiSocketPath) + rsApi.request("/control/locations/", "") + } + + LibresapiLocalClient + { + id: rsApi + onGoodResponseReceived: locationsModel.json = msg + } + + JSONListModel + { + id: locationsModel + query: "$.data[*]" + } + + ListView + { + id: locationsListView + width: parent.width + anchors.top: parent.top + anchors.bottom: bottomButton.top + model: locationsModel.model + delegate: Button + { + text: model.name + property string sslid: model.id + onClicked: + { + locationView.state = "loggingIn" + nameField.text = text + } + } + visible: false + } + + ColumnLayout + { + id: createLocationView + width: parent.width + anchors.top: parent.top + anchors.bottom: bottomButton.top + visible: false + + Row { Text {text: "Name:" } TextField { id: nameField; property string sslid } } + Row { Text {text: "Password:" } TextField { id: passwordField; echoMode: PasswordEchoOnEdit } } + } + + Text { text: "Locations View"; anchors.bottom: bottomButton.top } + + Button + { + id: bottomButton + text: "Create new location" + anchors.bottom: parent.bottom + } +} diff --git a/retroshare-qml-app/src/qml/jsonpath.js b/retroshare-qml-app/src/qml/jsonpath.js new file mode 100644 index 000000000..2cb6bb07e --- /dev/null +++ b/retroshare-qml-app/src/qml/jsonpath.js @@ -0,0 +1,88 @@ +/* JSONPath 0.8.5 - XPath for JSON + * + * Copyright (c) 2007 Stefan Goessner (goessner.net) + * Licensed under the MIT (MIT-LICENSE.txt) licence. + * + */ +function jsonPath(obj, expr, arg) { + var P = { + resultType: arg && arg.resultType || "VALUE", + result: [], + normalize: function(expr) { + var subx = []; + return expr.replace(/[\['](\??\(.*?\))[\]']|\['(.*?)'\]/g, function($0,$1,$2){return "[#"+(subx.push($1||$2)-1)+"]";}) /* http://code.google.com/p/jsonpath/issues/detail?id=4 */ + .replace(/'?\.'?|\['?/g, ";") + .replace(/;;;|;;/g, ";..;") + .replace(/;$|'?\]|'$/g, "") + .replace(/#([0-9]+)/g, function($0,$1){return subx[$1];}); + }, + asPath: function(path) { + var x = path.split(";"), p = "$"; + for (var i=1,n=x.length; i + * + * 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 . + */ import QtQuick 2.2 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 1.1 // millor fer servir 2.0 o més -import LibresapiLocalClientQml 1.0 +import QtQuick.Controls 2.0 +import org.retroshare.qml_components.LibresapiLocalClient 1.0 -ApplicationWindow { - visible: true - width: 640 - height: 480 - title: qsTr("RSChat") +ApplicationWindow +{ + id: mainWindow + visible: true + title: qsTr("RSChat") + Rectangle + { + id: mainView + anchors.fill: parent; + + states: + [ + State + { + name: "waiting_account_select"; + PropertyChanges { target: swipeView; currentIndex: 1 } + }, + State + { + name: "running_ok" + PropertyChanges { target: swipeView; currentIndex: 2 } + }, + State + { + name: "running_ok_no_full_control" + PropertyChanges { target: swipeView; currentIndex: 2 } + } + ] + + LibresapiLocalClient + { + onGoodResponseReceived: + { + var jsonReponse = JSON.parse(msg) + mainView.state = jsonReponse.data.runstate + } + Component.onCompleted: + { + openConnection(apiSocketPath) + request("/control/runstate/", "") + } + } + + SwipeView + { + id: swipeView + anchors.fill: parent + visible: true + currentIndex: 1 + + Locations + { + id: locationsView + visible: true + } + + AddTrustedNode + { + id: addTrustedNodeView + visible: true + } + + Contacts + { + id: contactsView + visible: true + } + } + } /* - LibresapiLocalClientComm{ - id: llc - onGoodResponseReceived: gxss.title = msg - - }*/ - onSceneGraphInitialized: llc.openConnection() + onSceneGraphInitialized: llc.openConnection() Rectangle { id: page @@ -154,7 +225,5 @@ ApplicationWindow { } } } + */ } - - - diff --git a/retroshare-qml-app/src/retroshare-qml-app.pro b/retroshare-qml-app/src/retroshare-qml-app.pro index b9c79f8ec..63e34a404 100644 --- a/retroshare-qml-app/src/retroshare-qml-app.pro +++ b/retroshare-qml-app/src/retroshare-qml-app.pro @@ -1,6 +1,6 @@ !include("../../retroshare.pri"): error("Could not include file ../../retroshare.pri") -QT += qml quick androidextras +QT += qml quick CONFIG += c++11 diff --git a/retroshare.pri b/retroshare.pri index 08bb84a7d..aaee2e406 100644 --- a/retroshare.pri +++ b/retroshare.pri @@ -52,6 +52,7 @@ unix { android-g++ { CONFIG *= no_libresapihttpserver no_sqlcipher upnp_libupnp CONFIG -= libresapihttpserver sqlcipher upnp_miniupnpc + QT *= androidextras DEFINES *= "fopen64=fopen" DEFINES *= "fseeko64=fseeko" DEFINES *= "ftello64=ftello" From c12cfd60b8dfb48c654b824d2f84f3da19f5b955 Mon Sep 17 00:00:00 2001 From: Gio Date: Fri, 16 Sep 2016 12:04:49 +0200 Subject: [PATCH 09/15] Implemented login on Retroshare Android QML App Login works fine but soon after retroshare-android-service crashes probably when trying to determine local address (getLocalAddresses) Added RsLoginPassView.qml generic login/pass/button view retroshare-android-service can be build as a separate app on non-android systems --- .../src/retroshare-android-service.pro | 3 +- .../src/libresapilocalclient.cpp | 2 + retroshare-qml-app/src/main.cpp | 4 - retroshare-qml-app/src/qml.qrc | 1 + retroshare-qml-app/src/qml/AddTrustedNode.qml | 14 +- retroshare-qml-app/src/qml/Contacts.qml | 1 - retroshare-qml-app/src/qml/Locations.qml | 158 +++++++++++------- .../src/qml/RsLoginPassView.qml | 52 ++++++ retroshare-qml-app/src/qml/main.qml | 34 +++- 9 files changed, 195 insertions(+), 74 deletions(-) create mode 100644 retroshare-qml-app/src/qml/RsLoginPassView.qml diff --git a/retroshare-android-service/src/retroshare-android-service.pro b/retroshare-android-service/src/retroshare-android-service.pro index bede07450..c2caa12a0 100644 --- a/retroshare-android-service/src/retroshare-android-service.pro +++ b/retroshare-android-service/src/retroshare-android-service.pro @@ -8,7 +8,8 @@ QT -= gui CONFIG += c++11 CONFIG += dll -TEMPLATE = lib +android-g++:TEMPLATE = lib +!android-g++:TEMPLATE = app SOURCES += service.cpp diff --git a/retroshare-qml-app/src/libresapilocalclient.cpp b/retroshare-qml-app/src/libresapilocalclient.cpp index aa74feab7..34831753d 100644 --- a/retroshare-qml-app/src/libresapilocalclient.cpp +++ b/retroshare-qml-app/src/libresapilocalclient.cpp @@ -51,6 +51,8 @@ void LibresapiLocalClient::read() { receivedBytes = mLocalSocket.readLine(); + qDebug() << receivedBytes; + if(parseResponse()) // pensar en fer un buffer per parsejar, per evitar errors. emit goodResponseReceived(QString(receivedBytes)); else diff --git a/retroshare-qml-app/src/main.cpp b/retroshare-qml-app/src/main.cpp index 1418ec3fb..2f03478c9 100644 --- a/retroshare-qml-app/src/main.cpp +++ b/retroshare-qml-app/src/main.cpp @@ -45,10 +45,6 @@ int main(int argc, char *argv[]) QString sockPath = QString::fromStdString(RsAccounts::ConfigDirectory()); sockPath.append("/libresapi.sock"); -#ifndef __ANDROID__ - sockPath = "/home/gio/.retroshare/LOC06_8730499b55bb946424d537b180bee10a/libresapi.sock"; -#endif - engine.rootContext()->setContextProperty("apiSocketPath", sockPath); engine.load(QUrl(QLatin1String("qrc:/qml/main.qml"))); diff --git a/retroshare-qml-app/src/qml.qrc b/retroshare-qml-app/src/qml.qrc index 4bb111bc1..0a2b127ca 100644 --- a/retroshare-qml-app/src/qml.qrc +++ b/retroshare-qml-app/src/qml.qrc @@ -20,5 +20,6 @@ qml/JSONListModel.qml qml/Contacts.qml qml/AddTrustedNode.qml + qml/RsLoginPassView.qml diff --git a/retroshare-qml-app/src/qml/AddTrustedNode.qml b/retroshare-qml-app/src/qml/AddTrustedNode.qml index 80a0e094f..37b8efc9c 100644 --- a/retroshare-qml-app/src/qml/AddTrustedNode.qml +++ b/retroshare-qml-app/src/qml/AddTrustedNode.qml @@ -5,16 +5,24 @@ import org.retroshare.qml_components.LibresapiLocalClient 1.0 Item { + function refreshData() { rsApi.request("/peers/self/certificate/", "") } + Component.onCompleted: { rsApi.openConnection(apiSocketPath) - rsApi.request("/peers/self/certificate/", "") + refreshData() } + onFocusChanged: focus && refreshData() LibresapiLocalClient { id: rsApi - onGoodResponseReceived: myKeyField.text = JSON.parse(msg).data.cert_string + onGoodResponseReceived: + { + var jsonData = JSON.parse(msg) + if(jsonData && jsonData.data && jsonData.data.cert_string) + myKeyField.text = jsonData.data.cert_string + } } ColumnLayout @@ -22,7 +30,7 @@ Item anchors.top: parent.top anchors.bottom: bottomButton.top - TextField { id: myKeyField } + Text { id: myKeyField } TextField { id: otherKeyField } } diff --git a/retroshare-qml-app/src/qml/Contacts.qml b/retroshare-qml-app/src/qml/Contacts.qml index ef4fe3a13..1a405c873 100644 --- a/retroshare-qml-app/src/qml/Contacts.qml +++ b/retroshare-qml-app/src/qml/Contacts.qml @@ -28,7 +28,6 @@ Item rsApi.request("/identity/*/", "") } - Component.onCompleted: refreshData() onFocusChanged: focus && refreshData() LibresapiLocalClient diff --git a/retroshare-qml-app/src/qml/Locations.qml b/retroshare-qml-app/src/qml/Locations.qml index 3e3c5e62e..99feffae6 100644 --- a/retroshare-qml-app/src/qml/Locations.qml +++ b/retroshare-qml-app/src/qml/Locations.qml @@ -18,13 +18,15 @@ import QtQuick 2.0 import QtQuick.Controls 1.4 -import QtQuick.Layouts 1.3 import org.retroshare.qml_components.LibresapiLocalClient 1.0 Item { id: locationView state: "selectLocation" + property var qParent + property bool attemptLogin: false + property string password states: [ @@ -32,77 +34,104 @@ Item { name: "selectLocation" PropertyChanges { target: locationsListView; visible: true } - PropertyChanges { target: createLocationView; visible: false } - PropertyChanges - { - target: bottomButton - text: "Create new location" - onClicked: locationView.state = "createLocation" - } + PropertyChanges { target: bottomButton; visible: true } + PropertyChanges { target: loginView; visible: false } }, State { name: "createLocation" PropertyChanges { target: locationsListView; visible: false } - PropertyChanges { target: createLocationView; visible: true } + PropertyChanges { target: bottomButton; visible: false } PropertyChanges { - target: bottomButton - text: "Save" - onClicked: + target: loginView + visible: true + buttonText: "Save" + onSubmit: { - var jsonData = { pgp_name: nameField.text, ssl_name: nameField.text, pgp_password: passwordField.text } + var jsonData = { pgp_name: login, ssl_name: login, pgp_password: password } rsApi.request("/control/create_location/", JSON.stringify(jsonData)) - onClicked: locationView.state = "savingLocation" + locationView.state = "selectLocation" } } }, State { - name: "savingLocation" + name: "login" PropertyChanges { target: locationsListView; visible: false } - PropertyChanges { target: createLocationView; color: "grey" } + PropertyChanges { target: bottomButton; visible: false } PropertyChanges { - target: bottomButton - text: "Saving..." - enabled: false - } - }, - State - { - name: "loggingIn" - PropertyChanges { target: locationsListView; visible: false } - PropertyChanges { target: createLocationView; visible: true } - PropertyChanges { target: nameField; enabled: false} - PropertyChanges - { - target: bottomButton - text: "Login" - enabled: true - onClicked: + target: loginView + visible: true + onSubmit: { - var jsonData = { id: nameField.sslid, autologin: false } - rsApi.request("/control/login/", JSON.stringify(jsonData)) - jsonData = { password: passwordField.text } - rsApi.request("/control/password/", JSON.stringify(jsonData)) + locationView.password = password + rsApi.request("/control/login/", JSON.stringify({id: locationsListView.currentItem.sslid})) + locationView.attemptLogin = true + busyIndicator.running = true + attemptTimer.start() } } } ] - Component.onCompleted: - { - rsApi.openConnection(apiSocketPath) - rsApi.request("/control/locations/", "") - } + function requestLocationsList() { rsApi.request("/control/locations/", "") } + + onFocusChanged: focus && requestLocationsList() LibresapiLocalClient { id: rsApi - onGoodResponseReceived: locationsModel.json = msg + Component.onCompleted: + { + openConnection(apiSocketPath) + locationView.requestLocationsList() + } + onGoodResponseReceived: + { + var jsonData = JSON.parse(msg) + + + if(jsonData) + { + if(jsonData.data) + { + if(jsonData.data[0] && jsonData.data[0].pgp_id) + { + // if location list update + locationsModel.json = msg + busyIndicator.running = false + } + if (jsonData.data.key_name) + { + if(jsonData.data.want_password) + { + // if Server requested password + var jsonPass = { password: locationView.password } + rsApi.request("/control/password/", JSON.stringify(jsonPass)) + locationView.attemptLogin = false + console.debug("RS core asked for password") + } + else + { + // if Already logged in + bottomButton.enabled = false + bottomButton.text = "Already logged in" + locationView.attemptLogin = false + busyIndicator.running = false + locationView.state = "selectLocation" + locationsListView.enabled = false + console.debug("Already logged in") + } + } + } + } + } } + BusyIndicator { id: busyIndicator; anchors.centerIn: parent } + JSONListModel { id: locationsModel @@ -122,31 +151,40 @@ Item property string sslid: model.id onClicked: { - locationView.state = "loggingIn" - nameField.text = text + loginView.login = text + locationView.state = "login" } } visible: false } - ColumnLayout + Button { - id: createLocationView - width: parent.width - anchors.top: parent.top - anchors.bottom: bottomButton.top - visible: false - - Row { Text {text: "Name:" } TextField { id: nameField; property string sslid } } - Row { Text {text: "Password:" } TextField { id: passwordField; echoMode: PasswordEchoOnEdit } } - } - - Text { text: "Locations View"; anchors.bottom: bottomButton.top } - - Button - { id: bottomButton text: "Create new location" anchors.bottom: parent.bottom + onClicked: locationView.state = "createLocation" + } + + RsLoginPassView + { + id: loginView + visible: false + anchors.fill: parent + } + + Timer + { + id: attemptTimer + interval: 500 + repeat: true + onTriggered: + { + if(locationView.focus) + locationView.requestLocationsList() + + if (locationView.attemptLogin) + rsApi.request("/control/password/", "") + } } } diff --git a/retroshare-qml-app/src/qml/RsLoginPassView.qml b/retroshare-qml-app/src/qml/RsLoginPassView.qml new file mode 100644 index 000000000..228635fee --- /dev/null +++ b/retroshare-qml-app/src/qml/RsLoginPassView.qml @@ -0,0 +1,52 @@ +/* + * RetroShare Android QML App + * Copyright (C) 2016 Gioacchino Mazzurco + * + * 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 . + */ + +import QtQuick 2.0 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 1.4 +import QtQml 2.2 +import org.retroshare.qml_components.LibresapiLocalClient 1.0 + +Item +{ + id: loginView + property string buttonText: "Login" + property string login + property string password + signal submit(string login, string password) + + ColumnLayout + { + id: inputView + width: parent.width + anchors.top: parent.top + anchors.bottom: bottomButton.top + + Row { Text {text: "Name:" } TextField { id: nameField; text: loginView.login } } + Row { Text {text: "Password:" } TextField { id: passwordField; text: loginView.password } } + } + + Button + { + id: bottomButton + text: loginView.buttonText + anchors.bottom: parent.bottom + anchors.right: parent.right + onClicked: loginView.submit(nameField.text, passwordField.text) + } +} diff --git a/retroshare-qml-app/src/qml/main.qml b/retroshare-qml-app/src/qml/main.qml index 2f9f33a99..d34cfe4eb 100644 --- a/retroshare-qml-app/src/qml/main.qml +++ b/retroshare-qml-app/src/qml/main.qml @@ -36,17 +36,17 @@ ApplicationWindow State { name: "waiting_account_select"; - PropertyChanges { target: swipeView; currentIndex: 1 } + PropertyChanges { target: swipeView; currentIndex: locationsView.SwipeView.index } }, State { name: "running_ok" - PropertyChanges { target: swipeView; currentIndex: 2 } + //PropertyChanges { target: swipeView; currentIndex: contactsView.SwipeView.index } }, State { name: "running_ok_no_full_control" - PropertyChanges { target: swipeView; currentIndex: 2 } + //PropertyChanges { target: swipeView; currentIndex: contactsView.SwipeView.index } } ] @@ -69,12 +69,13 @@ ApplicationWindow id: swipeView anchors.fill: parent visible: true - currentIndex: 1 + currentIndex: 0 Locations { id: locationsView visible: true + qParent: swipeView } AddTrustedNode @@ -83,11 +84,34 @@ ApplicationWindow visible: true } + Rectangle + { + color: "red" + width: 100 + height: 100 + } + + Rectangle + { + color: "green" + width: 100 + height: 100 + } + + Rectangle + { + color: "blue" + width: 100 + height: 100 + } + +/* + Contacts { id: contactsView visible: true - } + }*/ } } From 48a9be0ccc9a8d2486b7e2ef016e6d9d1db7d1eb Mon Sep 17 00:00:00 2001 From: Gio Date: Wed, 21 Sep 2016 12:46:55 +0200 Subject: [PATCH 10/15] Use Qt to read the local addresses on android getifaddrs is not present on android and an alternative implementation crashed, use QNetworkInterface::allAddresses() to retrieve local addresses list instead This --- libretroshare/src/libretroshare.pro | 9 +- libretroshare/src/pqi/pqinetwork.cc | 37 +- libretroshare/src/util/ifaddrs.c | 600 ---------------------------- libretroshare/src/util/ifaddrs.h | 54 --- 4 files changed, 28 insertions(+), 672 deletions(-) delete mode 100644 libretroshare/src/util/ifaddrs.c delete mode 100644 libretroshare/src/util/ifaddrs.h diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index 5cb1d0781..c8be4e996 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -875,10 +875,11 @@ test_bitdht { ################################# Android ##################################### android-g++ { -## ifaddrs is missing on Android add them -## taken from https://github.com/morristech/android-ifaddrs - HEADERS *= util/ifaddrs.h - SOURCES *= util/ifaddrs.c +## ifaddrs is missing on Android add them don't use the one from +## https://github.com/morristech/android-ifaddrs +## because they crash, use QNetworkInterface from Qt instead + CONFIG *= qt + QT *= network ## 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 diff --git a/libretroshare/src/pqi/pqinetwork.cc b/libretroshare/src/pqi/pqinetwork.cc index 794cb5bca..ed9298aee 100644 --- a/libretroshare/src/pqi/pqinetwork.cc +++ b/libretroshare/src/pqi/pqinetwork.cc @@ -24,9 +24,9 @@ */ #ifdef WINDOWS_SYS -#include "util/rswin.h" -#include "util/rsmemory.h" -#include +# include "util/rswin.h" +# include "util/rsmemory.h" +# include #endif // WINDOWS_SYS #include "pqi/pqinetwork.h" @@ -271,16 +271,17 @@ int inet_aton(const char *name, struct in_addr *addr) #include #ifdef WINDOWS_SYS -#include -#include -#pragma comment(lib, "IPHLPAPI.lib") -#else // WINDOWS_SYS -#ifdef __ANDROID__ -#include "util/ifaddrs.h" -#else // __ANDROID__ -#include -#endif //__ANDROID__ -#include +# include +# include +# pragma comment(lib, "IPHLPAPI.lib") +#elif defined(__ANDROID__) +# include +# include +# include +# include +#else // not __ANDROID__ nor WINDOWS => Linux and other unixes +# include +# include #endif // WINDOWS_SYS void getLocalAddressesFailed() @@ -323,7 +324,15 @@ bool getLocalAddresses(std::list & addrs) } } free(adapter_addresses); -#else // WINDOWS_SYS +#elif defined(__ANDROID__) + foreach(QHostAddress qAddr, QNetworkInterface::allAddresses()) + { + sockaddr_storage tmpAddr; + sockaddr_storage_clear(tmpAddr); + if(sockaddr_storage_ipv4_aton(tmpAddr, qAddr.toString().toStdString().c_str())) + addrs.push_back(tmpAddr); + } +#else // not WINDOWS_SYS not ANDROID => Linux and other unixes struct ifaddrs *ifsaddrs, *ifa; if(getifaddrs(&ifsaddrs) != 0) getLocalAddressesFailed(); for ( ifa = ifsaddrs; ifa; ifa = ifa->ifa_next ) diff --git a/libretroshare/src/util/ifaddrs.c b/libretroshare/src/util/ifaddrs.c deleted file mode 100644 index 338fff887..000000000 --- a/libretroshare/src/util/ifaddrs.c +++ /dev/null @@ -1,600 +0,0 @@ -/* -Copyright (c) 2013, Kenneth MacKay -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "ifaddrs.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -typedef struct NetlinkList -{ - struct NetlinkList *m_next; - struct nlmsghdr *m_data; - unsigned int m_size; -} NetlinkList; - -static int netlink_socket(void) -{ - int l_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if(l_socket < 0) - { - return -1; - } - - struct sockaddr_nl l_addr; - memset(&l_addr, 0, sizeof(l_addr)); - l_addr.nl_family = AF_NETLINK; - if(bind(l_socket, (struct sockaddr *)&l_addr, sizeof(l_addr)) < 0) - { - close(l_socket); - return -1; - } - - return l_socket; -} - -static int netlink_send(int p_socket, int p_request) -{ - char l_buffer[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))]; - memset(l_buffer, 0, sizeof(l_buffer)); - struct nlmsghdr *l_hdr = (struct nlmsghdr *)l_buffer; - struct rtgenmsg *l_msg = (struct rtgenmsg *)NLMSG_DATA(l_hdr); - - l_hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*l_msg)); - l_hdr->nlmsg_type = p_request; - l_hdr->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; - l_hdr->nlmsg_pid = 0; - l_hdr->nlmsg_seq = p_socket; - l_msg->rtgen_family = AF_UNSPEC; - - struct sockaddr_nl l_addr; - memset(&l_addr, 0, sizeof(l_addr)); - l_addr.nl_family = AF_NETLINK; - return (sendto(p_socket, l_hdr, l_hdr->nlmsg_len, 0, (struct sockaddr *)&l_addr, sizeof(l_addr))); -} - -static int netlink_recv(int p_socket, void *p_buffer, size_t p_len) -{ - struct msghdr l_msg; - struct iovec l_iov = { p_buffer, p_len }; - struct sockaddr_nl l_addr; - int l_result; - - for(;;) - { - l_msg.msg_name = (void *)&l_addr; - l_msg.msg_namelen = sizeof(l_addr); - l_msg.msg_iov = &l_iov; - l_msg.msg_iovlen = 1; - l_msg.msg_control = NULL; - l_msg.msg_controllen = 0; - l_msg.msg_flags = 0; - int l_result = recvmsg(p_socket, &l_msg, 0); - - if(l_result < 0) - { - if(errno == EINTR) - { - continue; - } - return -2; - } - - if(l_msg.msg_flags & MSG_TRUNC) - { // buffer was too small - return -1; - } - return l_result; - } -} - -static struct nlmsghdr *getNetlinkResponse(int p_socket, int *p_size, int *p_done) -{ - size_t l_size = 4096; - void *l_buffer = NULL; - - for(;;) - { - free(l_buffer); - l_buffer = malloc(l_size); - - int l_read = netlink_recv(p_socket, l_buffer, l_size); - *p_size = l_read; - if(l_read == -2) - { - free(l_buffer); - return NULL; - } - if(l_read >= 0) - { - pid_t l_pid = getpid(); - struct nlmsghdr *l_hdr; - for(l_hdr = (struct nlmsghdr *)l_buffer; NLMSG_OK(l_hdr, (unsigned int)l_read); l_hdr = (struct nlmsghdr *)NLMSG_NEXT(l_hdr, l_read)) - { - if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) - { - continue; - } - - if(l_hdr->nlmsg_type == NLMSG_DONE) - { - *p_done = 1; - break; - } - - if(l_hdr->nlmsg_type == NLMSG_ERROR) - { - free(l_buffer); - return NULL; - } - } - return l_buffer; - } - - l_size *= 2; - } -} - -static NetlinkList *newListItem(struct nlmsghdr *p_data, unsigned int p_size) -{ - NetlinkList *l_item = malloc(sizeof(NetlinkList)); - l_item->m_next = NULL; - l_item->m_data = p_data; - l_item->m_size = p_size; - return l_item; -} - -static void freeResultList(NetlinkList *p_list) -{ - NetlinkList *l_cur; - while(p_list) - { - l_cur = p_list; - p_list = p_list->m_next; - free(l_cur->m_data); - free(l_cur); - } -} - -static NetlinkList *getResultList(int p_socket, int p_request) -{ - if(netlink_send(p_socket, p_request) < 0) - { - return NULL; - } - - NetlinkList *l_list = NULL; - NetlinkList *l_end = NULL; - int l_size; - int l_done = 0; - while(!l_done) - { - struct nlmsghdr *l_hdr = getNetlinkResponse(p_socket, &l_size, &l_done); - if(!l_hdr) - { // error - freeResultList(l_list); - return NULL; - } - - NetlinkList *l_item = newListItem(l_hdr, l_size); - if(!l_list) - { - l_list = l_item; - } - else - { - l_end->m_next = l_item; - } - l_end = l_item; - } - return l_list; -} - -static size_t maxSize(size_t a, size_t b) -{ - return (a > b ? a : b); -} - -static size_t calcAddrLen(sa_family_t p_family, int p_dataSize) -{ - switch(p_family) - { - case AF_INET: - return sizeof(struct sockaddr_in); - case AF_INET6: - return sizeof(struct sockaddr_in6); - case AF_PACKET: - return maxSize(sizeof(struct sockaddr_ll), offsetof(struct sockaddr_ll, sll_addr) + p_dataSize); - default: - return maxSize(sizeof(struct sockaddr), offsetof(struct sockaddr, sa_data) + p_dataSize); - } -} - -static void makeSockaddr(sa_family_t p_family, struct sockaddr *p_dest, void *p_data, size_t p_size) -{ - switch(p_family) - { - case AF_INET: - memcpy(&((struct sockaddr_in*)p_dest)->sin_addr, p_data, p_size); - break; - case AF_INET6: - memcpy(&((struct sockaddr_in6*)p_dest)->sin6_addr, p_data, p_size); - break; - case AF_PACKET: - memcpy(((struct sockaddr_ll*)p_dest)->sll_addr, p_data, p_size); - ((struct sockaddr_ll*)p_dest)->sll_halen = p_size; - break; - default: - memcpy(p_dest->sa_data, p_data, p_size); - break; - } - p_dest->sa_family = p_family; -} - -static void addToEnd(struct ifaddrs **p_resultList, struct ifaddrs *p_entry) -{ - if(!*p_resultList) - { - *p_resultList = p_entry; - } - else - { - struct ifaddrs *l_cur = *p_resultList; - while(l_cur->ifa_next) - { - l_cur = l_cur->ifa_next; - } - l_cur->ifa_next = p_entry; - } -} - -static void interpretLink(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList) -{ - struct ifinfomsg *l_info = (struct ifinfomsg *)NLMSG_DATA(p_hdr); - - size_t l_nameSize = 0; - size_t l_addrSize = 0; - size_t l_dataSize = 0; - - size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); - struct rtattr *l_rta; - for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) - { - void *l_rtaData = RTA_DATA(l_rta); - size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); - switch(l_rta->rta_type) - { - case IFLA_ADDRESS: - case IFLA_BROADCAST: - l_addrSize += NLMSG_ALIGN(calcAddrLen(AF_PACKET, l_rtaDataSize)); - break; - case IFLA_IFNAME: - l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); - break; - case IFLA_STATS: - l_dataSize += NLMSG_ALIGN(l_rtaSize); - break; - default: - break; - } - } - - struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize + l_dataSize); - memset(l_entry, 0, sizeof(struct ifaddrs)); - l_entry->ifa_name = ""; - - char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs); - char *l_addr = l_name + l_nameSize; - char *l_data = l_addr + l_addrSize; - - l_entry->ifa_flags = l_info->ifi_flags; - - l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifinfomsg)); - for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifinfomsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) - { - void *l_rtaData = RTA_DATA(l_rta); - size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); - switch(l_rta->rta_type) - { - case IFLA_ADDRESS: - case IFLA_BROADCAST: - { - size_t l_addrLen = calcAddrLen(AF_PACKET, l_rtaDataSize); - makeSockaddr(AF_PACKET, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize); - ((struct sockaddr_ll *)l_addr)->sll_ifindex = l_info->ifi_index; - ((struct sockaddr_ll *)l_addr)->sll_hatype = l_info->ifi_type; - if(l_rta->rta_type == IFLA_ADDRESS) - { - l_entry->ifa_addr = (struct sockaddr *)l_addr; - } - else - { - l_entry->ifa_broadaddr = (struct sockaddr *)l_addr; - } - l_addr += NLMSG_ALIGN(l_addrLen); - break; - } - case IFLA_IFNAME: - strncpy(l_name, l_rtaData, l_rtaDataSize); - l_name[l_rtaDataSize] = '\0'; - l_entry->ifa_name = l_name; - break; - case IFLA_STATS: - memcpy(l_data, l_rtaData, l_rtaDataSize); - l_entry->ifa_data = l_data; - break; - default: - break; - } - } - - addToEnd(p_resultList, l_entry); - p_links[l_info->ifi_index - 1] = l_entry; -} - -static void interpretAddr(struct nlmsghdr *p_hdr, struct ifaddrs **p_links, struct ifaddrs **p_resultList) -{ - struct ifaddrmsg *l_info = (struct ifaddrmsg *)NLMSG_DATA(p_hdr); - - size_t l_nameSize = 0; - size_t l_addrSize = 0; - - int l_addedNetmask = 0; - - size_t l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); - struct rtattr *l_rta; - for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) - { - void *l_rtaData = RTA_DATA(l_rta); - size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); - if(l_info->ifa_family == AF_PACKET) - { - continue; - } - - switch(l_rta->rta_type) - { - case IFA_ADDRESS: - case IFA_LOCAL: - if((l_info->ifa_family == AF_INET || l_info->ifa_family == AF_INET6) && !l_addedNetmask) - { // make room for netmask - l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); - l_addedNetmask = 1; - } - case IFA_BROADCAST: - l_addrSize += NLMSG_ALIGN(calcAddrLen(l_info->ifa_family, l_rtaDataSize)); - break; - case IFA_LABEL: - l_nameSize += NLMSG_ALIGN(l_rtaSize + 1); - break; - default: - break; - } - } - - struct ifaddrs *l_entry = malloc(sizeof(struct ifaddrs) + l_nameSize + l_addrSize); - memset(l_entry, 0, sizeof(struct ifaddrs)); - l_entry->ifa_name = p_links[l_info->ifa_index - 1]->ifa_name; - - char *l_name = ((char *)l_entry) + sizeof(struct ifaddrs); - char *l_addr = l_name + l_nameSize; - - l_entry->ifa_flags = l_info->ifa_flags | p_links[l_info->ifa_index - 1]->ifa_flags; - - l_rtaSize = NLMSG_PAYLOAD(p_hdr, sizeof(struct ifaddrmsg)); - for(l_rta = (struct rtattr *)(((char *)l_info) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))); RTA_OK(l_rta, l_rtaSize); l_rta = RTA_NEXT(l_rta, l_rtaSize)) - { - void *l_rtaData = RTA_DATA(l_rta); - size_t l_rtaDataSize = RTA_PAYLOAD(l_rta); - switch(l_rta->rta_type) - { - case IFA_ADDRESS: - case IFA_BROADCAST: - case IFA_LOCAL: - { - size_t l_addrLen = calcAddrLen(l_info->ifa_family, l_rtaDataSize); - makeSockaddr(l_info->ifa_family, (struct sockaddr *)l_addr, l_rtaData, l_rtaDataSize); - if(l_info->ifa_family == AF_INET6) - { - if(IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)l_rtaData) || IN6_IS_ADDR_MC_LINKLOCAL((struct in6_addr *)l_rtaData)) - { - ((struct sockaddr_in6 *)l_addr)->sin6_scope_id = l_info->ifa_index; - } - } - - if(l_rta->rta_type == IFA_ADDRESS) - { // apparently in a point-to-point network IFA_ADDRESS contains the dest address and IFA_LOCAL contains the local address - if(l_entry->ifa_addr) - { - l_entry->ifa_dstaddr = (struct sockaddr *)l_addr; - } - else - { - l_entry->ifa_addr = (struct sockaddr *)l_addr; - } - } - else if(l_rta->rta_type == IFA_LOCAL) - { - if(l_entry->ifa_addr) - { - l_entry->ifa_dstaddr = l_entry->ifa_addr; - } - l_entry->ifa_addr = (struct sockaddr *)l_addr; - } - else - { - l_entry->ifa_broadaddr = (struct sockaddr *)l_addr; - } - l_addr += NLMSG_ALIGN(l_addrLen); - break; - } - case IFA_LABEL: - strncpy(l_name, l_rtaData, l_rtaDataSize); - l_name[l_rtaDataSize] = '\0'; - l_entry->ifa_name = l_name; - break; - default: - break; - } - } - - if(l_entry->ifa_addr && (l_entry->ifa_addr->sa_family == AF_INET || l_entry->ifa_addr->sa_family == AF_INET6)) - { - unsigned l_maxPrefix = (l_entry->ifa_addr->sa_family == AF_INET ? 32 : 128); - unsigned l_prefix = (l_info->ifa_prefixlen > l_maxPrefix ? l_maxPrefix : l_info->ifa_prefixlen); - char l_mask[16] = {0}; - unsigned i; - for(i=0; i<(l_prefix/8); ++i) - { - l_mask[i] = 0xff; - } - l_mask[i] = 0xff << (8 - (l_prefix % 8)); - - makeSockaddr(l_entry->ifa_addr->sa_family, (struct sockaddr *)l_addr, l_mask, l_maxPrefix / 8); - l_entry->ifa_netmask = (struct sockaddr *)l_addr; - } - - addToEnd(p_resultList, l_entry); -} - -static void interpret(int p_socket, NetlinkList *p_netlinkList, struct ifaddrs **p_links, struct ifaddrs **p_resultList) -{ - pid_t l_pid = getpid(); - for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next) - { - unsigned int l_nlsize = p_netlinkList->m_size; - struct nlmsghdr *l_hdr; - for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) - { - if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) - { - continue; - } - - if(l_hdr->nlmsg_type == NLMSG_DONE) - { - break; - } - - if(l_hdr->nlmsg_type == RTM_NEWLINK) - { - interpretLink(l_hdr, p_links, p_resultList); - } - else if(l_hdr->nlmsg_type == RTM_NEWADDR) - { - interpretAddr(l_hdr, p_links, p_resultList); - } - } - } -} - -static unsigned countLinks(int p_socket, NetlinkList *p_netlinkList) -{ - unsigned l_links = 0; - pid_t l_pid = getpid(); - for(; p_netlinkList; p_netlinkList = p_netlinkList->m_next) - { - unsigned int l_nlsize = p_netlinkList->m_size; - struct nlmsghdr *l_hdr; - for(l_hdr = p_netlinkList->m_data; NLMSG_OK(l_hdr, l_nlsize); l_hdr = NLMSG_NEXT(l_hdr, l_nlsize)) - { - if((pid_t)l_hdr->nlmsg_pid != l_pid || (int)l_hdr->nlmsg_seq != p_socket) - { - continue; - } - - if(l_hdr->nlmsg_type == NLMSG_DONE) - { - break; - } - - if(l_hdr->nlmsg_type == RTM_NEWLINK) - { - ++l_links; - } - } - } - - return l_links; -} - -int getifaddrs(struct ifaddrs **ifap) -{ - if(!ifap) - { - return -1; - } - *ifap = NULL; - - int l_socket = netlink_socket(); - if(l_socket < 0) - { - return -1; - } - - NetlinkList *l_linkResults = getResultList(l_socket, RTM_GETLINK); - if(!l_linkResults) - { - close(l_socket); - return -1; - } - - NetlinkList *l_addrResults = getResultList(l_socket, RTM_GETADDR); - if(!l_addrResults) - { - close(l_socket); - freeResultList(l_linkResults); - return -1; - } - - unsigned l_numLinks = countLinks(l_socket, l_linkResults) + countLinks(l_socket, l_addrResults); - struct ifaddrs *l_links[l_numLinks]; - memset(l_links, 0, l_numLinks * sizeof(struct ifaddrs *)); - - interpret(l_socket, l_linkResults, l_links, ifap); - interpret(l_socket, l_addrResults, l_links, ifap); - - freeResultList(l_linkResults); - freeResultList(l_addrResults); - close(l_socket); - return 0; -} - -void freeifaddrs(struct ifaddrs *ifa) -{ - struct ifaddrs *l_cur; - while(ifa) - { - l_cur = ifa; - ifa = ifa->ifa_next; - free(l_cur); - } -} diff --git a/libretroshare/src/util/ifaddrs.h b/libretroshare/src/util/ifaddrs.h deleted file mode 100644 index 9cd19fec1..000000000 --- a/libretroshare/src/util/ifaddrs.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 1995, 1999 - * Berkeley Software Design, Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * BSDI ifaddrs.h,v 2.5 2000/02/23 14:51:59 dab Exp - */ - -#ifndef _IFADDRS_H_ -#define _IFADDRS_H_ - -struct ifaddrs { - struct ifaddrs *ifa_next; - char *ifa_name; - unsigned int ifa_flags; - struct sockaddr *ifa_addr; - struct sockaddr *ifa_netmask; - struct sockaddr *ifa_dstaddr; - void *ifa_data; -}; - -/* - * This may have been defined in . Note that if is - * to be included it must be included before this header file. - */ -#ifndef ifa_broadaddr -#define ifa_broadaddr ifa_dstaddr /* broadcast address interface */ -#endif - -#include - -__BEGIN_DECLS -extern int getifaddrs(struct ifaddrs **ifap); -extern void freeifaddrs(struct ifaddrs *ifa); -__END_DECLS - -#endif From cf1c49aa3aa064de5b1759efe2890ea80cfb6a55 Mon Sep 17 00:00:00 2001 From: Gio Date: Thu, 22 Sep 2016 12:48:08 +0200 Subject: [PATCH 11/15] Advances on trusted node addings ApiServerLocal trim method/path line to avoid white spaces parsing ApiServerLocal add support for passing METHOD in request AddTrustedNode.qml add ability to copy and paste keys --- libresapi/src/api/ApiServerLocal.cpp | 27 +++++++---- libresapi/src/api/ApiServerLocal.h | 2 + libresapi/src/api/ApiServerMHD.cpp | 25 +--------- libresapi/src/api/ApiTypes.h | 29 ++++-------- retroshare-qml-app/src/qml/AddTrustedNode.qml | 47 +++++++++++++++++-- 5 files changed, 76 insertions(+), 54 deletions(-) diff --git a/libresapi/src/api/ApiServerLocal.cpp b/libresapi/src/api/ApiServerLocal.cpp index 1755a2d9c..d77cd7633 100644 --- a/libresapi/src/api/ApiServerLocal.cpp +++ b/libresapi/src/api/ApiServerLocal.cpp @@ -74,15 +74,25 @@ void ApiLocalConnectionHandler::handlePendingRequests() if(mLocalSocket->canReadLine()) { readPath: - reqPath = mLocalSocket->readLine().constData(); - mState = WAITING_DATA; + QString rString(mLocalSocket->readLine()); + rString = rString.simplified(); + if (!rString.isEmpty()) + { + if(rString.startsWith("PUT", Qt::CaseInsensitive)) reqMeth = resource_api::Request::PUT; + else if (rString.startsWith("DELETE", Qt::CaseInsensitive)) reqMeth = resource_api::Request::DELETE_AA; + if(rString.contains(' ')) rString = rString.split(' ')[1]; - /* Because QLocalSocket is SOCK_STREAM some clients implementations - * like the one based on QLocalSocket feel free to send the whole - * request (PATH + DATA) in a single write(), causing readyRead() - * signal being emitted only once, in that case we should continue - * processing without waiting for readyRead() being fired again, so - * we don't break here as there may be more lines to read */ + reqPath = rString.toStdString(); + mState = WAITING_DATA; + + /* Because QLocalSocket is SOCK_STREAM some clients implementations + * like the one based on QLocalSocket feel free to send the whole + * request (PATH + DATA) in a single write(), causing readyRead() + * signal being emitted only once, in that case we should continue + * processing without waiting for readyRead() being fired again, so + * we don't break here as there may be more lines to read */ + } + else break; } } case WAITING_DATA: @@ -92,6 +102,7 @@ void ApiLocalConnectionHandler::handlePendingRequests() resource_api::JsonStream reqJson; reqJson.setJsonString(std::string(mLocalSocket->readLine().constData())); resource_api::Request req(reqJson); + req.mMethod = reqMeth; req.setPath(reqPath); std::string resultString = mApiServer->handleRequest(req); mLocalSocket->write(resultString.c_str(), resultString.length()); diff --git a/libresapi/src/api/ApiServerLocal.h b/libresapi/src/api/ApiServerLocal.h index 576190a71..1d6c87ae5 100644 --- a/libresapi/src/api/ApiServerLocal.h +++ b/libresapi/src/api/ApiServerLocal.h @@ -24,6 +24,7 @@ #include #include +#include "ApiTypes.h" #include "ApiServer.h" namespace resource_api @@ -89,6 +90,7 @@ private: QLocalSocket* mLocalSocket; State mState; std::string reqPath; + resource_api::Request::Method reqMeth; }; } // namespace resource_api diff --git a/libresapi/src/api/ApiServerMHD.cpp b/libresapi/src/api/ApiServerMHD.cpp index 9522c66be..273010d8b 100644 --- a/libresapi/src/api/ApiServerMHD.cpp +++ b/libresapi/src/api/ApiServerMHD.cpp @@ -211,30 +211,7 @@ public: req.mMethod = resource_api::Request::DELETE_AA; } - std::stack stack; - std::string str; - for(std::string::reverse_iterator sit = path2.rbegin(); sit != path2.rend(); sit++) - { - if((*sit) != '/') - { - // add to front because we are traveling in reverse order - str = *sit + str; - } - else - { - if(str != "") - { - stack.push(str); - str.clear(); - } - } - } - if(str != "") - { - stack.push(str); - } - req.mPath = stack; - req.mFullPath = path2; + req.setPath(path2); std::string result = mApiServer->handleRequest(req); diff --git a/libresapi/src/api/ApiTypes.h b/libresapi/src/api/ApiTypes.h index f5ab6094b..98ad44cd9 100644 --- a/libresapi/src/api/ApiTypes.h +++ b/libresapi/src/api/ApiTypes.h @@ -193,29 +193,21 @@ public: // then each handler should pop the top element std::stack mPath; std::string mFullPath; - bool setPath(std::string reqPath) + bool setPath(const std::string &reqPath) { std::string str; - for(std::string::reverse_iterator sit = reqPath.rbegin(); sit != reqPath.rend(); sit++) + std::string::const_reverse_iterator sit; + for( sit = reqPath.rbegin(); sit != reqPath.rend(); ++sit ) { - if((*sit) != '/') + // add to front because we are traveling in reverse order + if((*sit) != '/') str = *sit + str; + else if(!str.empty()) { - // add to front because we are traveling in reverse order - str = *sit + str; - } - else - { - if(str != "") - { - mPath.push(str); - str.clear(); - } + mPath.push(str); + str.clear(); } } - if(str != "") - { - mPath.push(str); - } + if(!str.empty()) mPath.push(str); mFullPath = reqPath; return true; @@ -231,8 +223,7 @@ public: // contains data for new resources StreamBase& mStream; - // use the is*() methods to query the method type -//private: + // use the is*() methods to query the method type: enum Method { GET, PUT, DELETE_AA, EXEC};// something is wrong with DELETE, it won't compile with it Method mMethod; }; diff --git a/retroshare-qml-app/src/qml/AddTrustedNode.qml b/retroshare-qml-app/src/qml/AddTrustedNode.qml index 37b8efc9c..0d44ab46d 100644 --- a/retroshare-qml-app/src/qml/AddTrustedNode.qml +++ b/retroshare-qml-app/src/qml/AddTrustedNode.qml @@ -27,13 +27,41 @@ Item ColumnLayout { + id: colLayout anchors.top: parent.top - anchors.bottom: bottomButton.top + anchors.bottom: rowLayout.top - Text { id: myKeyField } + + TextField { id: myKeyField } TextField { id: otherKeyField } } + RowLayout + { + id: rowLayout + anchors.top: colLayout.bottom + + Button + { + text: "Copy" + onClicked: + { + myKeyField.selectAll() + myKeyField.copy() + } + } + + Button + { + text: "Paste" + onClicked: + { + otherKeyField.selectAll() + otherKeyField.paste() + } + } + } + Button { id: bottomButton @@ -41,7 +69,20 @@ Item anchors.bottom: parent.bottom onClicked: { - rsApi.request("/peers/examine_cert/", JSON.stringify({ cert_string: otherKeyField.text })) + 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("POST /peers", JSON.stringify(jsonData)) } } } From 148c04e85e7624e29737260a48d56a762979d13a Mon Sep 17 00:00:00 2001 From: Gio Date: Thu, 22 Sep 2016 16:01:46 +0200 Subject: [PATCH 12/15] Trusted nodes adding now works First connection happened! --- retroshare-qml-app/src/qml/AddTrustedNode.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/retroshare-qml-app/src/qml/AddTrustedNode.qml b/retroshare-qml-app/src/qml/AddTrustedNode.qml index 0d44ab46d..6a642a31b 100644 --- a/retroshare-qml-app/src/qml/AddTrustedNode.qml +++ b/retroshare-qml-app/src/qml/AddTrustedNode.qml @@ -82,7 +82,7 @@ Item } console.log("retroshare addtrusted jsonData: ", JSON.stringify(jsonData)) //rsApi.request("/peers/examine_cert/", JSON.stringify({ cert_string: otherKeyField.text })) - rsApi.request("POST /peers", JSON.stringify(jsonData)) + rsApi.request("PUT /peers", JSON.stringify(jsonData)) } } } From 80f5e3bd7410fae2ce60a14ebe1dff2ac5256bb5 Mon Sep 17 00:00:00 2001 From: Gio Date: Tue, 27 Sep 2016 14:05:14 +0200 Subject: [PATCH 13/15] Add view to list trusted nodes --- retroshare-qml-app/src/qml.qrc | 1 + retroshare-qml-app/src/qml/Contacts.qml | 7 +-- .../src/qml/TrustedNodesView.qml | 58 +++++++++++++++++++ retroshare-qml-app/src/qml/main.qml | 14 ++--- 4 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 retroshare-qml-app/src/qml/TrustedNodesView.qml diff --git a/retroshare-qml-app/src/qml.qrc b/retroshare-qml-app/src/qml.qrc index 0a2b127ca..54ce78395 100644 --- a/retroshare-qml-app/src/qml.qrc +++ b/retroshare-qml-app/src/qml.qrc @@ -21,5 +21,6 @@ qml/Contacts.qml qml/AddTrustedNode.qml qml/RsLoginPassView.qml + qml/TrustedNodesView.qml diff --git a/retroshare-qml-app/src/qml/Contacts.qml b/retroshare-qml-app/src/qml/Contacts.qml index 1a405c873..63364d516 100644 --- a/retroshare-qml-app/src/qml/Contacts.qml +++ b/retroshare-qml-app/src/qml/Contacts.qml @@ -22,11 +22,7 @@ import org.retroshare.qml_components.LibresapiLocalClient 1.0 Item { - function refreshData() - { - rsApi.openConnection(apiSocketPath) - rsApi.request("/identity/*/", "") - } + function refreshData() { rsApi.request("/identity/*/", "") } onFocusChanged: focus && refreshData() @@ -34,6 +30,7 @@ Item { id: rsApi onGoodResponseReceived: locationsModel.json = msg + Component.onCompleted: { openConnection(apiSocketPath) } } JSONListModel diff --git a/retroshare-qml-app/src/qml/TrustedNodesView.qml b/retroshare-qml-app/src/qml/TrustedNodesView.qml new file mode 100644 index 000000000..7464948f8 --- /dev/null +++ b/retroshare-qml-app/src/qml/TrustedNodesView.qml @@ -0,0 +1,58 @@ +/* + * RetroShare Android QML App + * Copyright (C) 2016 Gioacchino Mazzurco + * + * 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 . + */ + +import QtQuick 2.0 +import QtQuick.Controls 1.4 +import org.retroshare.qml_components.LibresapiLocalClient 1.0 + +Item +{ + function refreshData() { rsApi.request("/peers", "") } + + onFocusChanged: focus && refreshData() + + LibresapiLocalClient + { + id: rsApi + onGoodResponseReceived: jsonModel.json = msg + Component.onCompleted: { openConnection(apiSocketPath) } + } + + JSONListModel + { + id: jsonModel + query: "$.data[*]" + } + + ListView + { + width: parent.width + anchors.top: parent.top + anchors.bottom: bottomButton.top + model: jsonModel.model + delegate: Text { text: model.name } + } + + Button + { + id: bottomButton + text: "Add Trusted Node" + anchors.bottom: parent.bottom + onClicked: swipeView.currentIndex = addTrustedNodeView.SwipeView.index + } +} diff --git a/retroshare-qml-app/src/qml/main.qml b/retroshare-qml-app/src/qml/main.qml index d34cfe4eb..716ea5550 100644 --- a/retroshare-qml-app/src/qml/main.qml +++ b/retroshare-qml-app/src/qml/main.qml @@ -78,9 +78,11 @@ ApplicationWindow qParent: swipeView } - AddTrustedNode + TrustedNodesView { id: trustedNodesView } + + Contacts { - id: addTrustedNodeView + id: contactsView visible: true } @@ -105,13 +107,11 @@ ApplicationWindow height: 100 } -/* - - Contacts + AddTrustedNode { - id: contactsView + id: addTrustedNodeView visible: true - }*/ + } } } From 10ec1144eb46cff4338838c78563993c8382d05f Mon Sep 17 00:00:00 2001 From: Gio Date: Wed, 26 Oct 2016 20:37:28 +0200 Subject: [PATCH 14/15] Simplify QML to work better on both Desktop and Phone - Set request method to GET by default in ApiLocalConnectionHandler to avoid /peers request fail randomly - Use TabView instead of SwipeView to not depend on Qt 5.7.0 (latest version) - AddTrustedNode simplified layout to avoid unexpected behaviour on some phones - Locations add sslid property to root Item to avoid unexpected behaviour when multiple locations are available - TrustedNodesView color connected friends with green and offline with grey - Ignore kdevelop 4 files --- .gitignore | 2 + libresapi/src/api/ApiServerLocal.cpp | 3 +- retroshare-qml-app/src/qml/AddTrustedNode.qml | 60 ++++++------- retroshare-qml-app/src/qml/Locations.qml | 8 +- .../src/qml/TrustedNodesView.qml | 11 ++- retroshare-qml-app/src/qml/main.qml | 87 +++++++++---------- 6 files changed, 82 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index 47bb4bb22..ecf9f7099 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ Makefile* *~ Thumbs.db *.pro.user +.kdev4 +*.kdev4 diff --git a/libresapi/src/api/ApiServerLocal.cpp b/libresapi/src/api/ApiServerLocal.cpp index d77cd7633..d55d1e5cc 100644 --- a/libresapi/src/api/ApiServerLocal.cpp +++ b/libresapi/src/api/ApiServerLocal.cpp @@ -73,13 +73,14 @@ void ApiLocalConnectionHandler::handlePendingRequests() { if(mLocalSocket->canReadLine()) { - readPath: +readPath: QString rString(mLocalSocket->readLine()); rString = rString.simplified(); if (!rString.isEmpty()) { if(rString.startsWith("PUT", Qt::CaseInsensitive)) reqMeth = resource_api::Request::PUT; else if (rString.startsWith("DELETE", Qt::CaseInsensitive)) reqMeth = resource_api::Request::DELETE_AA; + else reqMeth = resource_api::Request::GET; if(rString.contains(' ')) rString = rString.split(' ')[1]; reqPath = rString.toStdString(); diff --git a/retroshare-qml-app/src/qml/AddTrustedNode.qml b/retroshare-qml-app/src/qml/AddTrustedNode.qml index 6a642a31b..6af454f8d 100644 --- a/retroshare-qml-app/src/qml/AddTrustedNode.qml +++ b/retroshare-qml-app/src/qml/AddTrustedNode.qml @@ -27,19 +27,30 @@ Item ColumnLayout { - id: colLayout - anchors.top: parent.top - anchors.bottom: rowLayout.top + anchors.fill: parent - - TextField { id: myKeyField } - TextField { id: otherKeyField } - } - - RowLayout - { - id: rowLayout - anchors.top: colLayout.bottom + 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 { @@ -60,29 +71,8 @@ Item otherKeyField.paste() } } - } - Button - { - id: bottomButton - text: "Add trusted node" - anchors.bottom: parent.bottom - 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)) - } + TextField { id: myKeyField } + TextField { id: otherKeyField } } } diff --git a/retroshare-qml-app/src/qml/Locations.qml b/retroshare-qml-app/src/qml/Locations.qml index 99feffae6..f81ae4c42 100644 --- a/retroshare-qml-app/src/qml/Locations.qml +++ b/retroshare-qml-app/src/qml/Locations.qml @@ -27,6 +27,7 @@ Item property var qParent property bool attemptLogin: false property string password + property string sslid states: [ @@ -34,7 +35,7 @@ Item { name: "selectLocation" PropertyChanges { target: locationsListView; visible: true } - PropertyChanges { target: bottomButton; visible: true } + PropertyChanges { target: bottomButton; visible: true } PropertyChanges { target: loginView; visible: false } }, State @@ -67,7 +68,8 @@ Item onSubmit: { locationView.password = password - rsApi.request("/control/login/", JSON.stringify({id: locationsListView.currentItem.sslid})) + console.log("locationView.sslid: ", locationView.sslid) + rsApi.request("/control/login/", JSON.stringify({id: locationView.sslid})) locationView.attemptLogin = true busyIndicator.running = true attemptTimer.start() @@ -148,10 +150,10 @@ Item delegate: Button { text: model.name - property string sslid: model.id onClicked: { loginView.login = text + locationView.sslid = model.id locationView.state = "login" } } diff --git a/retroshare-qml-app/src/qml/TrustedNodesView.qml b/retroshare-qml-app/src/qml/TrustedNodesView.qml index 7464948f8..59a536cc8 100644 --- a/retroshare-qml-app/src/qml/TrustedNodesView.qml +++ b/retroshare-qml-app/src/qml/TrustedNodesView.qml @@ -19,10 +19,11 @@ import QtQuick 2.0 import QtQuick.Controls 1.4 import org.retroshare.qml_components.LibresapiLocalClient 1.0 +import "jsonpath.js" as JSONPath Item { - function refreshData() { rsApi.request("/peers", "") } + function refreshData() { rsApi.request("/peers/*", "") } onFocusChanged: focus && refreshData() @@ -45,7 +46,11 @@ Item anchors.top: parent.top anchors.bottom: bottomButton.top model: jsonModel.model - delegate: Text { text: model.name } + delegate: 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" + } } Button @@ -53,6 +58,6 @@ Item id: bottomButton text: "Add Trusted Node" anchors.bottom: parent.bottom - onClicked: swipeView.currentIndex = addTrustedNodeView.SwipeView.index + onClicked: swipeView.currentIndex = 3 } } diff --git a/retroshare-qml-app/src/qml/main.qml b/retroshare-qml-app/src/qml/main.qml index 716ea5550..e2cffc811 100644 --- a/retroshare-qml-app/src/qml/main.qml +++ b/retroshare-qml-app/src/qml/main.qml @@ -17,7 +17,7 @@ */ import QtQuick 2.2 -import QtQuick.Controls 2.0 +import QtQuick.Controls 1.4 import org.retroshare.qml_components.LibresapiLocalClient 1.0 ApplicationWindow @@ -25,30 +25,34 @@ ApplicationWindow id: mainWindow visible: true title: qsTr("RSChat") + width: 400 + height: 400 Rectangle { id: mainView - anchors.fill: parent; - + anchors.fill: parent states: [ - State - { - name: "waiting_account_select"; - PropertyChanges { target: swipeView; currentIndex: locationsView.SwipeView.index } - }, - State - { - name: "running_ok" - //PropertyChanges { target: swipeView; currentIndex: contactsView.SwipeView.index } - }, - State - { - name: "running_ok_no_full_control" - //PropertyChanges { target: swipeView; currentIndex: contactsView.SwipeView.index } - } - ] + 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 { @@ -64,53 +68,42 @@ ApplicationWindow } } - SwipeView + TabView { id: swipeView anchors.fill: parent visible: true currentIndex: 0 - Locations + Tab { - id: locationsView - visible: true - qParent: swipeView + title:"Locations" + id: locationsTab + Locations { onVisibleChanged: focus = visible } } - TrustedNodesView { id: trustedNodesView } - - Contacts + Tab { - id: contactsView - visible: true + title: "Trusted Nodes" + TrustedNodesView { onVisibleChanged: focus = visible } } - Rectangle + Tab { - color: "red" - width: 100 - height: 100 + title: "Contacts" + Contacts { onVisibleChanged: focus = visible } } - Rectangle + Tab { - color: "green" - width: 100 - height: 100 + title: "Add Node" + AddTrustedNode { onVisibleChanged: focus = visible } } - Rectangle + Tab { - color: "blue" - width: 100 - height: 100 - } - - AddTrustedNode - { - id: addTrustedNodeView - visible: true + title: "Blue" + Rectangle { color: "blue"; anchors.fill: parent } } } } From beaf4797322f303f2c496fab2f4deab8f3435f85 Mon Sep 17 00:00:00 2001 From: Gio Date: Mon, 7 Nov 2016 14:14:28 +0100 Subject: [PATCH 15/15] Remove rotten code in rsloginhandler autologin --- libretroshare/src/rsserver/rsloginhandler.cc | 57 +------------------- 1 file changed, 2 insertions(+), 55 deletions(-) diff --git a/libretroshare/src/rsserver/rsloginhandler.cc b/libretroshare/src/rsserver/rsloginhandler.cc index fcd5965e4..b06021ce2 100644 --- a/libretroshare/src/rsserver/rsloginhandler.cc +++ b/libretroshare/src/rsserver/rsloginhandler.cc @@ -198,62 +198,9 @@ bool RsLoginHandler::tryAutoLogin(const RsPeerId& ssl_id,std::string& ssl_passwd return (status == 0); /******************** OSX KeyChain stuff *****************************/ -#else /* UNIX, but not HAS_GNOME_KEYRING or APPLE */ - -#ifdef TODO_CODE_ROTTEN - FILE* helpFile = RsDirUtil::rs_fopen(getAutologinFileName(ssl_id).c_str(), "r"); - - if(helpFile == NULL){ - std::cerr << "\nFailed to open help file\n" << std::endl; - return false; - } - - /* decrypt help */ - - int c ; - std::string passwd ; - while( (c = getc(helpFile)) != EOF ) - passwd += (char)c ; - - const int DAT_LEN = passwd.length(); - const int KEY_DAT_LEN = RsInitConfig::load_cert.length(); - unsigned char* key_data = (unsigned char*)RsInitConfig::load_cert.c_str(); - unsigned char* indata = new unsigned char[DAT_LEN]; - unsigned char* outdata = new unsigned char[DAT_LEN]; - - for(int i=0;i