Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DcJsonrpcInstance class #2479

Merged
merged 25 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.journeyapps:zxing-android-embedded:3.4.0' // QR Code scanner
implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.1' // used as JSON library
implementation 'com.google.code.gson:gson:2.10.1' // used as JSON library
Comment on lines 38 to +39
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, so we need one more JSON library? jackson-databind doesn't work? Can we use gson instead of jackson-databind, in order to not increase the number of dependencies?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you grep for gson, you will find that we already use it in some part of the code, it seems it is a dependency of some of our dependencies already, I had to explicitly add this because it seems the version used by the dependency was old and didn't have a needed method.

in a follow-up PR we should try to only use one of gson, jackson and org.json (the 3 are used at the moment in existing code besides this PR)

implementation "me.leolin:ShortcutBadger:1.1.16" // display messagecount on the home screen icon.
implementation 'com.jpardogo.materialtabstrip:library:1.0.9' // used in the emoji selector for the tab selection.
implementation 'com.github.chrisbanes:PhotoView:2.1.3' // does the zooming on photos / media
Expand Down
43 changes: 43 additions & 0 deletions jni/dc_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ JNIEXPORT jlong Java_com_b44t_messenger_DcAccounts_getEventEmitterCPtr(JNIEnv *e
return (jlong)dc_accounts_get_event_emitter(get_dc_accounts(env, obj));
}

JNIEXPORT jlong Java_com_b44t_messenger_DcAccounts_getJsonrpcInstanceCPtr(JNIEnv *env, jobject obj)
{
return (jlong)dc_jsonrpc_init(get_dc_accounts(env, obj));
}


JNIEXPORT void Java_com_b44t_messenger_DcAccounts_startIo(JNIEnv *env, jobject obj)
{
Expand Down Expand Up @@ -2137,3 +2142,41 @@ JNIEXPORT jbyteArray Java_com_b44t_messenger_DcHttpResponse_getBlob(JNIEnv *env,
dc_str_unref((char*)ptr);
return ret;
}

/*******************************************************************************
* DcJsonrpcInstance
******************************************************************************/

static dc_jsonrpc_instance_t* get_dc_jsonrpc_instance(JNIEnv *env, jobject obj)
{
static jfieldID fid = 0;
if (fid==0) {
jclass cls = (*env)->GetObjectClass(env, obj);
fid = (*env)->GetFieldID(env, cls, "jsonrpcInstanceCPtr", "J" /*Signature, J=long*/);
}
if (fid) {
return (dc_jsonrpc_instance_t*)(*env)->GetLongField(env, obj, fid);
}
return NULL;
}


JNIEXPORT void Java_com_b44t_messenger_DcJsonrpcInstance_unrefJsonrpcInstanceCPtr(JNIEnv *env, jobject obj)
{
dc_jsonrpc_unref(get_dc_jsonrpc_instance(env, obj));
}

JNIEXPORT void Java_com_b44t_messenger_DcJsonrpcInstance_request(JNIEnv *env, jobject obj, jstring request)
{
CHAR_REF(request);
dc_jsonrpc_request(get_dc_jsonrpc_instance(env, obj), requestPtr);
CHAR_UNREF(request);
}

JNIEXPORT jstring Java_com_b44t_messenger_DcJsonrpcInstance_getNextResponse(JNIEnv *env, jobject obj)
{
char* temp = dc_jsonrpc_next_response(get_dc_jsonrpc_instance(env, obj));
jstring ret = JSTRING_NEW(temp);
dc_str_unref(temp);
return ret;
}
8 changes: 4 additions & 4 deletions scripts/ndk-make.sh
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ if test -z $1 || test $1 = armeabi-v7a; then
TARGET_CC="$TOOLCHAIN/bin/armv7a-linux-androideabi16-clang" \
TARGET_AR="$TOOLCHAIN/bin/llvm-ar" \
TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \
cargo build $RELEASEFLAG --target armv7-linux-androideabi -p deltachat_ffi
cargo build $RELEASEFLAG --target armv7-linux-androideabi -p deltachat_ffi --features jsonrpc
cp target/armv7-linux-androideabi/$RELEASE/libdeltachat.a $jnidir/armeabi-v7a
fi

Expand All @@ -124,7 +124,7 @@ if test -z $1 || test $1 = arm64-v8a; then
TARGET_CC="$TOOLCHAIN/bin/aarch64-linux-android21-clang" \
TARGET_AR="$TOOLCHAIN/bin/llvm-ar" \
TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \
cargo build $RELEASEFLAG --target aarch64-linux-android -p deltachat_ffi
cargo build $RELEASEFLAG --target aarch64-linux-android -p deltachat_ffi --features jsonrpc
cp target/aarch64-linux-android/$RELEASE/libdeltachat.a $jnidir/arm64-v8a
fi

Expand All @@ -133,7 +133,7 @@ if test -z $1 || test $1 = x86; then
TARGET_CC="$TOOLCHAIN/bin/i686-linux-android16-clang" \
TARGET_AR="$TOOLCHAIN/bin/llvm-ar" \
TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \
cargo build $RELEASEFLAG --target i686-linux-android -p deltachat_ffi
cargo build $RELEASEFLAG --target i686-linux-android -p deltachat_ffi --features jsonrpc
cp target/i686-linux-android/$RELEASE/libdeltachat.a $jnidir/x86
fi

Expand All @@ -142,7 +142,7 @@ if test -z $1 || test $1 = x86_64; then
TARGET_CC="$TOOLCHAIN/bin/x86_64-linux-android21-clang" \
TARGET_AR="$TOOLCHAIN/bin/llvm-ar" \
TARGET_RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \
cargo build $RELEASEFLAG --target x86_64-linux-android -p deltachat_ffi
cargo build $RELEASEFLAG --target x86_64-linux-android -p deltachat_ffi --features jsonrpc
cp target/x86_64-linux-android/$RELEASE/libdeltachat.a $jnidir/x86_64
fi

Expand Down
2 changes: 2 additions & 0 deletions src/com/b44t/messenger/DcAccounts.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public void unref() {
}

public DcEventEmitter getEventEmitter () { return new DcEventEmitter(getEventEmitterCPtr()); }
public DcJsonrpcInstance getJsonrpcInstance () { return new DcJsonrpcInstance(getJsonrpcInstanceCPtr()); }
public native void startIo ();
public native void stopIo ();
public native void maybeNetwork ();
Expand All @@ -38,6 +39,7 @@ public void unref() {
private native long createAccountsCPtr (String osName, String dir);
private native void unrefAccountsCPtr ();
private native long getEventEmitterCPtr ();
private native long getJsonrpcInstanceCPtr ();
private native long getAccountCPtr (int accountId);
private native long getSelectedAccountCPtr ();
}
21 changes: 21 additions & 0 deletions src/com/b44t/messenger/DcJsonrpcInstance.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.b44t.messenger;

public class DcJsonrpcInstance {

public DcJsonrpcInstance(long jsonrpcInstanceCPtr) {
this.jsonrpcInstanceCPtr = jsonrpcInstanceCPtr;
}

@Override protected void finalize() throws Throwable {
super.finalize();
unrefJsonrpcInstanceCPtr();
jsonrpcInstanceCPtr = 0;
}

public native void request(String request);
public native String getNextResponse();

// working with raw c-data
private long jsonrpcInstanceCPtr; // CAVE: the name is referenced in the JNI
private native void unrefJsonrpcInstanceCPtr();
}
33 changes: 33 additions & 0 deletions src/com/b44t/messenger/rpc/HttpResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.b44t.messenger.rpc;

import android.util.Base64;

public class HttpResponse {
// base64-encoded response body.
private String blob;
// MIME type, e.g. "text/plain" or "text/html".
private String mimetype;
// Encoding, e.g. "utf-8".
private String encoding;

public HttpResponse(String blob, String mimetype, String encoding) {
this.blob = blob;
this.mimetype = mimetype;
this.encoding = encoding;
}

public byte[] getBlob() {
if (blob == null) {
return null;
}
return Base64.decode(blob, Base64.NO_WRAP | Base64.NO_PADDING);
}

public String getMimetype() {
return mimetype;
}

public String getEncoding() {
return encoding;
}
}
128 changes: 128 additions & 0 deletions src/com/b44t/messenger/rpc/Rpc.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package com.b44t.messenger.rpc;

import com.b44t.messenger.DcJsonrpcInstance;
import com.b44t.messenger.util.concurrent.SettableFuture;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;

public class Rpc {
private final Map<Integer, SettableFuture<JsonElement>> requestFutures = new ConcurrentHashMap<>();
private final DcJsonrpcInstance dcJsonrpcInstance;
private int requestId = 0;
private final Gson gson = new GsonBuilder().serializeNulls().create();

public Rpc(DcJsonrpcInstance dcJsonrpcInstance) {
this.dcJsonrpcInstance = dcJsonrpcInstance;
}

private void processResponse() throws JsonSyntaxException {
String jsonResponse = dcJsonrpcInstance.getNextResponse();
Response response = gson.fromJson(jsonResponse, Response.class);

if (response.id == 0) { // Got JSON-RPC notification/event, ignore
return;
}

SettableFuture<JsonElement> future = requestFutures.remove(response.id);
if (future == null) { // Got a response with unknown ID, ignore
return;
}

if (response.error != null) {
future.setException(new RpcException(response.error.toString()));
} else if (response.result != null) {
future.set(response.result);
} else {
future.setException(new RpcException("Got JSON-RPC response witout result or error: " + jsonResponse));
}
}

public void start() {
new Thread(() -> {
while (true) {
try {
processResponse();
} catch (JsonSyntaxException e) {
e.printStackTrace();
}
}
}, "jsonrpcThread").start();
}

public SettableFuture<JsonElement> call(String method, Object... params) {
int id;
synchronized (this) {
id = ++requestId;
}
String jsonRequest = gson.toJson(new Request(method, params, id));
SettableFuture<JsonElement> future = new SettableFuture<>();
requestFutures.put(id, future);
dcJsonrpcInstance.request(jsonRequest);
return future;
}

public JsonElement getResult(String method, Object... params) throws RpcException {
try {
return call(method, params).get();
} catch (ExecutionException e) {
throw (RpcException)e.getCause();
} catch (InterruptedException e) {
throw new RpcException(e.getMessage());
}
}

public int addAccount() throws RpcException {
return gson.fromJson(getResult("add_account"), int.class);
}

public void startIO() throws RpcException {
getResult("start_io_for_all_accounts");
}

public void stopIO() throws RpcException {
getResult("stop_io_for_all_accounts");
}

public Map<String, String> getSystemInfo() throws RpcException {
TypeToken<Map<String, String>> mapType = new TypeToken<Map<String, String>>(){};
return gson.fromJson(getResult("get_system_info"), mapType);
}

public HttpResponse getHttpResponse(int accountId, String url) throws RpcException {
return gson.fromJson(getResult("get_http_response", accountId, url), HttpResponse.class);
}


private static class Request {
public String jsonrpc = "2.0";
public String method;
public Object[] params;
public int id;

public Request(String method, Object[] params, int id) {
this.method = method;
this.params = params;
this.id = id;
}
}

private static class Response {
public int id = 0;
public JsonElement result;
public JsonElement error;

public Response(int id, JsonElement result, JsonElement error) {
this.id = id;
this.result = result;
this.error = error;
}
}
}
11 changes: 11 additions & 0 deletions src/com/b44t/messenger/rpc/RpcException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.b44t.messenger.rpc;

/**
* An exception occurred while processing a request in Delta Chat core.
**/
public class RpcException extends Exception {

public RpcException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.util.concurrent;
package com.b44t.messenger.util.concurrent;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.thoughtcrime.securesms.util.concurrent;
package com.b44t.messenger.util.concurrent;

import java.util.LinkedList;
import java.util.List;
Expand Down
5 changes: 5 additions & 0 deletions src/org/thoughtcrime/securesms/ApplicationContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcEventEmitter;
import com.b44t.messenger.rpc.Rpc;

import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.connect.AccountManager;
Expand Down Expand Up @@ -52,6 +53,7 @@ public class ApplicationContext extends MultiDexApplication {
private static final String TAG = ApplicationContext.class.getSimpleName();

public DcAccounts dcAccounts;
public Rpc rpc;
public DcContext dcContext;
public DcLocationManager dcLocationManager;
public DcEventCenter eventCenter;
Expand Down Expand Up @@ -88,6 +90,7 @@ public void onCreate() {
System.loadLibrary("native-utils");

dcAccounts = new DcAccounts("Android "+BuildConfig.VERSION_NAME, new File(getFilesDir(), "accounts").getAbsolutePath());
rpc = new Rpc(dcAccounts.getJsonrpcInstance());
AccountManager.getInstance().migrateToDcAccounts(this);
int[] allAccounts = dcAccounts.getAll();
for (int accountId : allAccounts) {
Expand Down Expand Up @@ -122,6 +125,8 @@ public void onCreate() {
Log.i("DeltaChat", "shutting down event handler");
}, "eventThread").start();

rpc.start();

// set translations before starting I/O to avoid sending untranslated MDNs (issue #2288)
DcHelper.setStockTranslations(this);

Expand Down
4 changes: 2 additions & 2 deletions src/org/thoughtcrime/securesms/ConversationActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcMsg;
import com.b44t.messenger.util.concurrent.ListenableFuture;
import com.b44t.messenger.util.concurrent.SettableFuture;

import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
Expand Down Expand Up @@ -114,8 +116,6 @@
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
import org.thoughtcrime.securesms.util.guava.Optional;
import org.thoughtcrime.securesms.util.views.Stub;
import org.thoughtcrime.securesms.video.recode.VideoRecoder;
Expand Down
Loading