mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-07 23:03:24 -04:00
sparate android + ios build targets
This commit is contained in:
BIN
Cargo.lock
generated
BIN
Cargo.lock
generated
Binary file not shown.
@@ -7,7 +7,7 @@ members = [
|
||||
# "crates/p2p/tunnel/utils",
|
||||
"apps/cli",
|
||||
"apps/desktop/src-tauri",
|
||||
"apps/mobile/rust",
|
||||
"apps/mobile/rust/*",
|
||||
"apps/server",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
[package]
|
||||
name = "sd-core-mobile"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.64.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "cdylib"] # staticlib for IOS and cdylib for Android
|
||||
|
||||
[dependencies]
|
||||
once_cell = "1.15.0"
|
||||
sd-core = { path = "../../../core", features = [
|
||||
"mobile",
|
||||
"p2p",
|
||||
], default-features = false }
|
||||
rspc = { workspace = true }
|
||||
serde_json = "1.0.85"
|
||||
tokio = "1.21.2"
|
||||
openssl = { version = "0.10.42", features = [
|
||||
"vendored",
|
||||
] } # Override features of transitive dependencies
|
||||
openssl-sys = { version = "0.9.76", features = [
|
||||
"vendored",
|
||||
] } # Override features of transitive dependencies to support IOS Simulator on M1
|
||||
futures = "0.3.24"
|
||||
tracing = "0.1.37"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
objc = "0.2.7"
|
||||
objc_id = "0.1.1"
|
||||
objc-foundation = "0.1.1"
|
||||
|
||||
# This is `not(ios)` instead of `android` because of https://github.com/mozilla/rust-android-gradle/issues/93
|
||||
[target.'cfg(not(target_os = "ios"))'.dependencies]
|
||||
jni = "0.19.0"
|
||||
|
||||
[target.'cfg(not(target_os = "ios"))'.features]
|
||||
default = ["sd-core/android"]
|
||||
|
||||
19
apps/mobile/rust/android/Cargo.toml
Normal file
19
apps/mobile/rust/android/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "sd-core-android"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.64.0"
|
||||
|
||||
[lib]
|
||||
# Android can use dynamic linking since all FFI is done via JNI
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
# FFI
|
||||
jni = "0.19.0"
|
||||
|
||||
# Core mobile handling stuff
|
||||
sd-core-mobile = { path = "../mobile", features = ["android"] }
|
||||
|
||||
# Other
|
||||
tracing = "0.1.37"
|
||||
105
apps/mobile/rust/android/src/lib.rs
Normal file
105
apps/mobile/rust/android/src/lib.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use std::panic;
|
||||
|
||||
use jni::{
|
||||
objects::{JClass, JObject, JString},
|
||||
JNIEnv,
|
||||
};
|
||||
|
||||
use sd_core_mobile::*;
|
||||
|
||||
use tracing::error;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_spacedrive_app_SDCore_registerCoreEventListener(
|
||||
env: JNIEnv,
|
||||
class: JClass,
|
||||
) {
|
||||
let result = panic::catch_unwind(|| {
|
||||
let jvm = env.get_java_vm().unwrap();
|
||||
let class = env.new_global_ref(class).unwrap();
|
||||
|
||||
spawn_core_event_listener(move |data| {
|
||||
let env = jvm.attach_current_thread().unwrap();
|
||||
env.call_method(
|
||||
&class,
|
||||
"sendCoreEvent",
|
||||
"(Ljava/lang/String;)V",
|
||||
&[env
|
||||
.new_string(data)
|
||||
.expect("Couldn't create java string!")
|
||||
.into()],
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
// TODO: Send rspc error or something here so we can show this in the UI.
|
||||
// TODO: Maybe reinitialise the core cause it could be in an invalid state?
|
||||
println!(
|
||||
"Error in Java_com_spacedrive_app_SDCore_registerCoreEventListener: {:?}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_spacedrive_app_SDCore_handleCoreMsg(
|
||||
env: JNIEnv,
|
||||
class: JClass,
|
||||
query: JString,
|
||||
callback: JObject,
|
||||
) {
|
||||
let result = panic::catch_unwind(|| {
|
||||
let jvm = env.get_java_vm().unwrap();
|
||||
|
||||
let query: String = env
|
||||
.get_string(query)
|
||||
.expect("Couldn't get java string!")
|
||||
.into();
|
||||
|
||||
let class = env.new_global_ref(class).unwrap();
|
||||
let callback = env.new_global_ref(callback).unwrap();
|
||||
|
||||
let data_directory = {
|
||||
let env = jvm.attach_current_thread().unwrap();
|
||||
let data_dir = env
|
||||
.call_method(&class, "getDataDirectory", "()Ljava/lang/String;", &[])
|
||||
.unwrap()
|
||||
.l()
|
||||
.unwrap();
|
||||
|
||||
env.get_string(data_dir.into()).unwrap().into()
|
||||
};
|
||||
|
||||
handle_core_msg(query, data_directory, move |result| match result {
|
||||
Ok(data) => {
|
||||
let env = jvm.attach_current_thread().unwrap();
|
||||
env.call_method(
|
||||
&callback,
|
||||
"resolve",
|
||||
"(Ljava/lang/Object;)V",
|
||||
&[env
|
||||
.new_string(data)
|
||||
.expect("Couldn't create java string!")
|
||||
.into()],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Err(_) => {
|
||||
// TODO: handle error
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
// TODO: Send rspc error or something here so we can show this in the UI.
|
||||
// TODO: Maybe reinitialise the core cause it could be in an invalid state?
|
||||
|
||||
// TODO: This log statement doesn't work. I recon the JNI env is being dropped before it's called.
|
||||
error!(
|
||||
"Error in Java_com_spacedrive_app_SDCore_registerCoreEventListener: {:?}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
21
apps/mobile/rust/ios/Cargo.toml
Normal file
21
apps/mobile/rust/ios/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "sd-core-ios"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
rust-version = "1.64.0"
|
||||
|
||||
[lib]
|
||||
# iOS requires static linking
|
||||
# Makes sense considering this lib needs to link against call_resolve and get_data_directory,
|
||||
# which are only available when linking against the app's ObjC
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
# FFI
|
||||
objc = "0.2.7"
|
||||
objc_id = "0.1.1"
|
||||
objc-foundation = "0.1.1"
|
||||
|
||||
# Core mobile handling stuff
|
||||
sd-core-mobile = { path = "../mobile" }
|
||||
79
apps/mobile/rust/ios/src/lib.rs
Normal file
79
apps/mobile/rust/ios/src/lib.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
os::raw::{c_char, c_void},
|
||||
panic,
|
||||
};
|
||||
|
||||
use objc::{msg_send, runtime::Object, sel, sel_impl};
|
||||
use objc_foundation::{INSString, NSString};
|
||||
use objc_id::Id;
|
||||
|
||||
use sd_core_mobile::*;
|
||||
|
||||
extern "C" {
|
||||
fn get_data_directory() -> *const c_char;
|
||||
fn call_resolve(resolve: *const c_void, result: *const c_char);
|
||||
}
|
||||
|
||||
// This struct wraps the function pointer which represent a Javascript Promise. We wrap the
|
||||
// function pointers in a struct so we can unsafely assert to Rust that they are `Send`.
|
||||
// We know they are send as we have ensured Objective-C won't deallocate the function pointer
|
||||
// until `call_resolve` is called.
|
||||
struct RNPromise(*const c_void);
|
||||
|
||||
unsafe impl Send for RNPromise {}
|
||||
|
||||
impl RNPromise {
|
||||
// resolve the promise
|
||||
unsafe fn resolve(self, result: CString) {
|
||||
call_resolve(self.0, result.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn register_core_event_listener(id: *mut Object) {
|
||||
let result = panic::catch_unwind(|| {
|
||||
let id = Id::<Object>::from_ptr(id);
|
||||
|
||||
spawn_core_event_listener(move |data| {
|
||||
let data = NSString::from_str(&data);
|
||||
let _: () = msg_send![id, sendCoreEvent: data];
|
||||
});
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
// TODO: Send rspc error or something here so we can show this in the UI.
|
||||
// TODO: Maybe reinitialise the core cause it could be in an invalid state?
|
||||
println!("Error in register_core_event_listener: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sd_core_msg(query: *const c_char, resolve: *const c_void) {
|
||||
let result = panic::catch_unwind(|| {
|
||||
// This string is cloned to the Rust heap. This is important as Objective-C may remove the query once this function completions but prior to the async block finishing.
|
||||
let query = CStr::from_ptr(query).to_str().unwrap().to_string();
|
||||
|
||||
let resolve = RNPromise(resolve);
|
||||
|
||||
let data_directory = CStr::from_ptr(get_data_directory())
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
handle_core_msg(query, data_directory, |result| {
|
||||
match result {
|
||||
Ok(data) => resolve.resolve(CString::new(data).unwrap()),
|
||||
Err(_) => {
|
||||
// TODO: handle error
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
// TODO: Send rspc error or something here so we can show this in the UI.
|
||||
// TODO: Maybe reinitialise the core cause it could be in an invalid state?
|
||||
println!("Error in sd_core_msg: {:?}", err);
|
||||
}
|
||||
}
|
||||
26
apps/mobile/rust/mobile/Cargo.toml
Normal file
26
apps/mobile/rust/mobile/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "sd-core-mobile"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.64.0"
|
||||
|
||||
[features]
|
||||
android = ["sd-core/android"]
|
||||
|
||||
[dependencies]
|
||||
once_cell = "1.15.0"
|
||||
sd-core = { path = "../../../../core", features = [
|
||||
"mobile",
|
||||
"p2p",
|
||||
], default-features = false }
|
||||
rspc.workspace= true
|
||||
serde_json = "1.0.85"
|
||||
tokio = "1.21.2"
|
||||
openssl = { version = "0.10.42", features = [
|
||||
"vendored",
|
||||
] } # Override features of transitive dependencies
|
||||
openssl-sys = { version = "0.9.76", features = [
|
||||
"vendored",
|
||||
] } # Override features of transitive dependencies to support IOS Simulator on M1
|
||||
futures = "0.3.24"
|
||||
tracing = "0.1.37"
|
||||
106
apps/mobile/rust/mobile/src/lib.rs
Normal file
106
apps/mobile/rust/mobile/src/lib.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use futures::future::join_all;
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use rspc::internal::jsonrpc::*;
|
||||
use sd_core::{api::Router, Node};
|
||||
use serde_json::{from_str, from_value, to_string, Value};
|
||||
use std::{collections::HashMap, marker::Send, sync::Arc};
|
||||
use tokio::{
|
||||
runtime::Runtime,
|
||||
sync::{
|
||||
mpsc::{unbounded_channel, UnboundedSender},
|
||||
oneshot, Mutex,
|
||||
},
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
pub static RUNTIME: Lazy<Runtime> = Lazy::new(|| Runtime::new().unwrap());
|
||||
|
||||
pub type NodeType = Lazy<Mutex<Option<(Arc<Node>, Arc<Router>)>>>;
|
||||
|
||||
pub static NODE: NodeType = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
pub static SUBSCRIPTIONS: Lazy<Mutex<HashMap<RequestId, oneshot::Sender<()>>>> =
|
||||
Lazy::new(Default::default);
|
||||
|
||||
pub static EVENT_SENDER: OnceCell<UnboundedSender<Response>> = OnceCell::new();
|
||||
|
||||
pub fn handle_core_msg(
|
||||
query: String,
|
||||
data_dir: String,
|
||||
callback: impl FnOnce(Result<String, String>) + Send + 'static,
|
||||
) {
|
||||
RUNTIME.spawn(async move {
|
||||
let (node, router) = {
|
||||
let node = &mut *NODE.lock().await;
|
||||
match node {
|
||||
Some(node) => node.clone(),
|
||||
None => {
|
||||
// TODO: probably don't unwrap
|
||||
let new_node = Node::new(data_dir).await.unwrap();
|
||||
node.replace(new_node.clone());
|
||||
new_node
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let reqs = match from_str::<Value>(&query).and_then(|v| match v.is_array() {
|
||||
true => from_value::<Vec<Request>>(v),
|
||||
false => from_value::<Request>(v).map(|v| vec![v]),
|
||||
}) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
error!("failed to decode JSON-RPC request: {}", err); // Don't use tracing here because it's before the `Node` is initialised which sets that config!
|
||||
callback(Err(query));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let responses = join_all(reqs.into_iter().map(|request| {
|
||||
let node = node.clone();
|
||||
let router = router.clone();
|
||||
async move {
|
||||
let mut channel = EVENT_SENDER.get().unwrap().clone();
|
||||
let mut resp = Sender::ResponseAndChannel(None, &mut channel);
|
||||
|
||||
handle_json_rpc(
|
||||
node.get_request_context(),
|
||||
request,
|
||||
&router,
|
||||
&mut resp,
|
||||
&mut SubscriptionMap::Mutex(&SUBSCRIPTIONS),
|
||||
)
|
||||
.await;
|
||||
|
||||
match resp {
|
||||
Sender::ResponseAndChannel(resp, _) => resp,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}))
|
||||
.await;
|
||||
|
||||
callback(Ok(serde_json::to_string(
|
||||
&responses.into_iter().flatten().collect::<Vec<_>>(),
|
||||
)
|
||||
.unwrap()));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn spawn_core_event_listener(callback: impl Fn(String) + Send + 'static) {
|
||||
let (tx, mut rx) = unbounded_channel();
|
||||
let _ = EVENT_SENDER.set(tx);
|
||||
|
||||
RUNTIME.spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
let data = match to_string(&event) {
|
||||
Ok(json) => json,
|
||||
Err(err) => {
|
||||
println!("Failed to serialize event: {}", err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
callback(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
use std::panic;
|
||||
|
||||
use crate::{EVENT_SENDER, NODE, RUNTIME, SUBSCRIPTIONS};
|
||||
use futures::future::join_all;
|
||||
use jni::objects::{JClass, JObject, JString};
|
||||
use jni::JNIEnv;
|
||||
use rspc::internal::jsonrpc::{handle_json_rpc, Request, Sender, SubscriptionMap};
|
||||
use sd_core::Node;
|
||||
use serde_json::Value;
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
use tracing::{error, info};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_spacedrive_app_SDCore_registerCoreEventListener(
|
||||
env: JNIEnv,
|
||||
class: JClass,
|
||||
) {
|
||||
let result = panic::catch_unwind(|| {
|
||||
let jvm = env.get_java_vm().unwrap();
|
||||
let class = env.new_global_ref(class).unwrap();
|
||||
let (tx, mut rx) = unbounded_channel();
|
||||
let _ = EVENT_SENDER.set(tx);
|
||||
|
||||
RUNTIME.spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
let data = match serde_json::to_string(&event) {
|
||||
Ok(json) => json,
|
||||
Err(err) => {
|
||||
println!("Failed to serialize event: {}", err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let env = jvm.attach_current_thread().unwrap();
|
||||
env.call_method(
|
||||
&class,
|
||||
"sendCoreEvent",
|
||||
"(Ljava/lang/String;)V",
|
||||
&[env
|
||||
.new_string(data)
|
||||
.expect("Couldn't create java string!")
|
||||
.into()],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
// TODO: Send rspc error or something here so we can show this in the UI.
|
||||
// TODO: Maybe reinitialise the core cause it could be in an invalid state?
|
||||
println!(
|
||||
"Error in Java_com_spacedrive_app_SDCore_registerCoreEventListener: {:?}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_spacedrive_app_SDCore_handleCoreMsg(
|
||||
env: JNIEnv,
|
||||
class: JClass,
|
||||
query: JString,
|
||||
callback: JObject,
|
||||
) {
|
||||
let result = panic::catch_unwind(|| {
|
||||
let jvm = env.get_java_vm().unwrap();
|
||||
let query: String = env
|
||||
.get_string(query)
|
||||
.expect("Couldn't get java string!")
|
||||
.into();
|
||||
let class = env.new_global_ref(class).unwrap();
|
||||
let callback = env.new_global_ref(callback).unwrap();
|
||||
|
||||
RUNTIME.spawn(async move {
|
||||
let (node, router) = {
|
||||
let node = &mut *NODE.lock().await;
|
||||
match node {
|
||||
Some(node) => node.clone(),
|
||||
None => {
|
||||
let data_dir: String = {
|
||||
let env = jvm.attach_current_thread().unwrap();
|
||||
let data_dir = env
|
||||
.call_method(
|
||||
&class,
|
||||
"getDataDirectory",
|
||||
"()Ljava/lang/String;",
|
||||
&[],
|
||||
)
|
||||
.unwrap()
|
||||
.l()
|
||||
.unwrap();
|
||||
|
||||
env.get_string(data_dir.into()).unwrap().into()
|
||||
};
|
||||
|
||||
let new_node = Node::new(data_dir).await;
|
||||
let new_node = match new_node {
|
||||
Ok(new_node) => new_node,
|
||||
Err(err) => {
|
||||
info!("677 {:?}", err);
|
||||
|
||||
// TODO: Android return?
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
node.replace(new_node.clone());
|
||||
new_node
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let reqs =
|
||||
match serde_json::from_str::<Value>(&query).and_then(|v| match v.is_array() {
|
||||
true => serde_json::from_value::<Vec<Request>>(v),
|
||||
false => serde_json::from_value::<Request>(v).map(|v| vec![v]),
|
||||
}) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
error!("failed to decode JSON-RPC request: {}", err); // Don't use tracing here because it's before the `Node` is initialised which sets that config!
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let resps = join_all(reqs.into_iter().map(|request| {
|
||||
let node = node.clone();
|
||||
let router = router.clone();
|
||||
async move {
|
||||
let mut channel = EVENT_SENDER.get().unwrap().clone();
|
||||
let mut resp = Sender::ResponseAndChannel(None, &mut channel);
|
||||
|
||||
handle_json_rpc(
|
||||
node.get_request_context(),
|
||||
request,
|
||||
&router,
|
||||
&mut resp,
|
||||
&mut SubscriptionMap::Mutex(&SUBSCRIPTIONS),
|
||||
)
|
||||
.await;
|
||||
|
||||
match resp {
|
||||
Sender::ResponseAndChannel(resp, _) => resp,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}))
|
||||
.await;
|
||||
|
||||
let env = jvm.attach_current_thread().unwrap();
|
||||
env.call_method(
|
||||
&callback,
|
||||
"resolve",
|
||||
"(Ljava/lang/Object;)V",
|
||||
&[env
|
||||
.new_string(
|
||||
serde_json::to_string(&resps.into_iter().flatten().collect::<Vec<_>>())
|
||||
.unwrap(),
|
||||
)
|
||||
.expect("Couldn't create java string!")
|
||||
.into()],
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
// TODO: Send rspc error or something here so we can show this in the UI.
|
||||
// TODO: Maybe reinitialise the core cause it could be in an invalid state?
|
||||
|
||||
// TODO: This log statement doesn't work. I recon the JNI env is being dropped before it's called.
|
||||
error!(
|
||||
"Error in Java_com_spacedrive_app_SDCore_registerCoreEventListener: {:?}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
use crate::{EVENT_SENDER, NODE, RUNTIME, SUBSCRIPTIONS};
|
||||
use futures::future::join_all;
|
||||
use objc::{msg_send, runtime::Object, sel, sel_impl};
|
||||
use objc_foundation::{INSString, NSString};
|
||||
use objc_id::Id;
|
||||
use rspc::internal::jsonrpc::{handle_json_rpc, Request, Sender, SubscriptionMap};
|
||||
use sd_core::Node;
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
os::raw::{c_char, c_void},
|
||||
panic,
|
||||
};
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
|
||||
extern "C" {
|
||||
fn get_data_directory() -> *const c_char;
|
||||
fn call_resolve(resolve: *const c_void, result: *const c_char);
|
||||
}
|
||||
|
||||
// This struct wraps the function pointer which represent a Javascript Promise. We wrap the
|
||||
// function pointers in a struct so we can unsafely assert to Rust that they are `Send`.
|
||||
// We know they are send as we have ensured Objective-C won't deallocate the function pointer
|
||||
// until `call_resolve` is called.
|
||||
struct RNPromise(*const c_void);
|
||||
|
||||
unsafe impl Send for RNPromise {}
|
||||
|
||||
impl RNPromise {
|
||||
// resolve the promise
|
||||
unsafe fn resolve(self, result: CString) {
|
||||
call_resolve(self.0, result.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn register_core_event_listener(id: *mut Object) {
|
||||
let result = panic::catch_unwind(|| {
|
||||
let id = Id::<Object>::from_ptr(id);
|
||||
|
||||
let (tx, mut rx) = unbounded_channel();
|
||||
let _ = EVENT_SENDER.set(tx);
|
||||
|
||||
RUNTIME.spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
let data = match serde_json::to_string(&event) {
|
||||
Ok(json) => json,
|
||||
Err(err) => {
|
||||
println!("Failed to serialize event: {}", err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let data = NSString::from_str(&data);
|
||||
let _: () = msg_send![id, sendCoreEvent: data];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
// TODO: Send rspc error or something here so we can show this in the UI.
|
||||
// TODO: Maybe reinitialise the core cause it could be in an invalid state?
|
||||
println!("Error in register_core_event_listener: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sd_core_msg(query: *const c_char, resolve: *const c_void) {
|
||||
let result = panic::catch_unwind(|| {
|
||||
// This string is cloned to the Rust heap. This is important as Objective-C may remove the query once this function completions but prior to the async block finishing.
|
||||
let query = CStr::from_ptr(query).to_str().unwrap().to_string();
|
||||
|
||||
let resolve = RNPromise(resolve);
|
||||
RUNTIME.spawn(async move {
|
||||
let reqs =
|
||||
match serde_json::from_str::<Value>(&query).and_then(|v| match v.is_array() {
|
||||
true => serde_json::from_value::<Vec<Request>>(v),
|
||||
false => serde_json::from_value::<Request>(v).map(|v| vec![v]),
|
||||
}) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
println!("failed to decode JSON-RPC request: {}", err); // Don't use tracing here because it's before the `Node` is initialised which sets that config!
|
||||
|
||||
resolve.resolve(
|
||||
CString::new(serde_json::to_vec(&(vec![] as Vec<Request>)).unwrap())
|
||||
.unwrap(),
|
||||
); // TODO: Proper error handling
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let resps = join_all(reqs.into_iter().map(|request| async move {
|
||||
let node = &mut *NODE.lock().await;
|
||||
let (node, router) = match node {
|
||||
Some(node) => node.clone(),
|
||||
None => {
|
||||
let data_dir = CStr::from_ptr(get_data_directory())
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let new_node = Node::new(data_dir).await.unwrap();
|
||||
node.replace(new_node.clone());
|
||||
new_node
|
||||
}
|
||||
};
|
||||
|
||||
let mut channel = EVENT_SENDER.get().unwrap().clone();
|
||||
let mut resp = Sender::ResponseAndChannel(None, &mut channel);
|
||||
handle_json_rpc(
|
||||
node.get_request_context(),
|
||||
request,
|
||||
&router,
|
||||
&mut resp,
|
||||
&mut SubscriptionMap::Mutex(&SUBSCRIPTIONS),
|
||||
)
|
||||
.await;
|
||||
|
||||
match resp {
|
||||
Sender::ResponseAndChannel(resp, _) => resp,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}))
|
||||
.await;
|
||||
|
||||
resolve.resolve(
|
||||
CString::new(
|
||||
serde_json::to_vec(&resps.into_iter().filter_map(|v| v).collect::<Vec<_>>())
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
// TODO: Send rspc error or something here so we can show this in the UI.
|
||||
// TODO: Maybe reinitialise the core cause it could be in an invalid state?
|
||||
println!("Error in sd_core_msg: {:?}", err);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use rspc::internal::jsonrpc::{RequestId, Response};
|
||||
use sd_core::{api::Router, Node};
|
||||
use tokio::{
|
||||
runtime::Runtime,
|
||||
sync::{mpsc::UnboundedSender, oneshot, Mutex},
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) static RUNTIME: Lazy<Runtime> = Lazy::new(|| Runtime::new().unwrap());
|
||||
|
||||
type NodeType = Lazy<Mutex<Option<(Arc<Node>, Arc<Router>)>>>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) static NODE: NodeType = Lazy::new(|| Mutex::new(None));
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) static SUBSCRIPTIONS: Lazy<Mutex<HashMap<RequestId, oneshot::Sender<()>>>> =
|
||||
Lazy::new(Default::default);
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) static EVENT_SENDER: OnceCell<UnboundedSender<Response>> = OnceCell::new();
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
mod ios;
|
||||
|
||||
/// This is `not(ios)` instead of `android` because of https://github.com/mozilla/rust-android-gradle/issues/93
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
mod android;
|
||||
Reference in New Issue
Block a user