From ca70e8660d417daa665a502138b8213d368f957c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 10 Dec 2024 16:46:30 -0800 Subject: [PATCH] feat: android asset hotreloading (#3332) --- packages/cli/src/serve/handle.rs | 21 ++++++++++++++++++++- packages/cli/src/serve/mod.rs | 2 +- packages/cli/src/serve/runner.rs | 4 ++-- packages/desktop/src/protocol.rs | 17 +++++++++++++---- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/serve/handle.rs b/packages/cli/src/serve/handle.rs index f5968f3ae..dc348a3f5 100644 --- a/packages/cli/src/serve/handle.rs +++ b/packages/cli/src/serve/handle.rs @@ -164,7 +164,7 @@ impl AppHandle { /// This will return the bundled name of the asset such that we can send it to the clients letting /// them know what to reload. It's not super important that this is robust since most clients will /// kick all stylsheets without necessarily checking the name. - pub(crate) fn hotreload_bundled_asset(&self, changed_file: &PathBuf) -> Option { + pub(crate) async fn hotreload_bundled_asset(&self, changed_file: &PathBuf) -> Option { let mut bundled_name = None; // Use the build dir if there's no runtime asset dir as the override. For the case of ios apps, @@ -202,6 +202,25 @@ impl AppHandle { } } + // If the emulator is android, we need to copy the asset to the device with `adb push asset /data/local/tmp/dx/assets/filename.ext` + if self.app.build.build.platform() == Platform::Android { + if let Some(bundled_name) = bundled_name.as_ref() { + let target = format!("/data/local/tmp/dx/{}", bundled_name.display()); + tracing::debug!("Pushing asset to device: {target}"); + let res = tokio::process::Command::new("adb") + .arg("push") + .arg(changed_file) + .arg(target) + .output() + .await + .context("Failed to push asset to device"); + + if let Err(e) = res { + tracing::debug!("Failed to push asset to device: {e}"); + } + } + } + // Now we can return the bundled asset name to send to the hotreload engine bundled_name } diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index f8f05e64a..4f76d1956 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -91,7 +91,7 @@ pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> { // if change is hotreloadable, hotreload it // and then send that update to all connected clients - if let Some(hr) = runner.attempt_hot_reload(files) { + if let Some(hr) = runner.attempt_hot_reload(files).await { // Only send a hotreload message for templates and assets - otherwise we'll just get a full rebuild if hr.templates.is_empty() && hr.assets.is_empty() diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 3e7a49c33..33d173f4f 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -157,7 +157,7 @@ impl AppRunner { Ok(()) } - pub(crate) fn attempt_hot_reload( + pub(crate) async fn attempt_hot_reload( &mut self, modified_files: Vec, ) -> Option { @@ -183,7 +183,7 @@ impl AppRunner { // Otherwise, it might be an asset and we should look for it in all the running apps for runner in self.running.values() { - if let Some(bundled_name) = runner.hotreload_bundled_asset(&path) { + if let Some(bundled_name) = runner.hotreload_bundled_asset(&path).await { // todo(jon): don't hardcode this here let asset_relative = PathBuf::from("/assets/").join(bundled_name); assets.push(asset_relative); diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index 1ef240c57..56dd3642e 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -275,6 +275,19 @@ fn get_mime_by_ext(trimmed: &Path) -> &'static str { #[cfg(target_os = "android")] pub(crate) fn to_java_load_asset(filepath: &str) -> Option> { + let normalized = filepath + .trim_start_matches("/assets/") + .trim_start_matches('/'); + + // in debug mode, the asset might be under `/data/local/tmp/dx/` - attempt to read it from there if it exists + #[cfg(debug_assertions)] + { + let path = std::path::PathBuf::from("/data/local/tmp/dx/").join(normalized); + if path.exists() { + return std::fs::read(path).ok(); + } + } + use std::{io::Read, ptr::NonNull}; let ctx = ndk_context::android_context(); @@ -301,10 +314,6 @@ pub(crate) fn to_java_load_asset(filepath: &str) -> Option> { NonNull::new(asset_manager).expect("Invalid asset manager"), ); - let normalized = filepath - .trim_start_matches("/assets/") - .trim_start_matches('/'); - let cstr = std::ffi::CString::new(normalized).unwrap(); let mut asset = asset_manager.open(&cstr)?;