diff --git a/updater/cli/src/main.rs b/updater/cli/src/main.rs index f2c1a136..967fc349 100644 --- a/updater/cli/src/main.rs +++ b/updater/cli/src/main.rs @@ -22,7 +22,7 @@ fn main() { let config = updater::AppConfig { cache_dir: "updater_cache".to_owned(), release_version: "0.1.0".to_owned(), - original_libapp_path: "libapp.so".to_owned(), + original_libapp_paths: vec!["libapp.so".to_owned()], vm_path: "libflutter.so".to_owned(), }; let yaml_str = " diff --git a/updater/dart_bindings/lib/src/bindings.dart b/updater/dart_bindings/lib/src/bindings.dart index b0e6a5a4..4622135b 100644 --- a/updater/dart_bindings/lib/src/bindings.dart +++ b/updater/dart_bindings/lib/src/bindings.dart @@ -16,7 +16,10 @@ class AppParameters extends ffi.Struct { // ignore: non_constant_identifier_names external ffi.Pointer update_url; // ignore: non_constant_identifier_names - external ffi.Pointer original_libapp_path; + external ffi.Pointer> original_libapp_paths; + @ffi.Int8() + // ignore: non_constant_identifier_names + external int original_libapp_paths_size; // ignore: non_constant_identifier_names external ffi.Pointer vm_path; // ignore: non_constant_identifier_names @@ -27,7 +30,7 @@ class AppParameters extends ffi.Struct { required String version, required String channel, required String? updateUrl, - required String libappPath, + required List libappPaths, required String libflutterPath, required String cacheDir, }) { @@ -38,7 +41,13 @@ class AppParameters extends ffi.Struct { if (updateUrl != null) { config.ref.update_url = updateUrl.toNativeUtf8(); } - config.ref.original_libapp_path = libappPath.toNativeUtf8(); + config.ref.original_libapp_paths = calloc>( + libappPaths.length, + ); + + for (var i = 0; i < libappPaths.length; i++) { + config.ref.original_libapp_paths[i] = libappPaths[i].toNativeUtf8(); + } config.ref.vm_path = libflutterPath.toNativeUtf8(); config.ref.cache_dir = cacheDir.toNativeUtf8(); return config; @@ -49,7 +58,11 @@ class AppParameters extends ffi.Struct { calloc.free(config.ref.base_version); calloc.free(config.ref.channel); calloc.free(config.ref.update_url); - calloc.free(config.ref.original_libapp_path); + // Free all paths in original_libapp_path. + for (var i = 0; i < config.ref.original_libapp_paths_size; i++) { + calloc.free(config.ref.original_libapp_paths[i]); + } + calloc.free(config.ref.original_libapp_paths); calloc.free(config.ref.vm_path); calloc.free(config.ref.cache_dir); calloc.free(config); diff --git a/updater/dart_bindings/lib/updater.dart b/updater/dart_bindings/lib/updater.dart index ad3682d0..bbcf9a73 100644 --- a/updater/dart_bindings/lib/updater.dart +++ b/updater/dart_bindings/lib/updater.dart @@ -54,7 +54,7 @@ class Updater { required String version, required String channel, required String? updateUrl, - required String baseLibraryPath, + required List baseLibraryPaths, required String vmPath, required String cacheDir, }) { @@ -63,7 +63,7 @@ class Updater { version: version, channel: channel, updateUrl: updateUrl, - libappPath: baseLibraryPath, + libappPaths: baseLibraryPaths, libflutterPath: vmPath, cacheDir: cacheDir, ); diff --git a/updater/dart_cli/bin/dart_cli.dart b/updater/dart_cli/bin/dart_cli.dart index e0c53461..32575423 100644 --- a/updater/dart_cli/bin/dart_cli.dart +++ b/updater/dart_cli/bin/dart_cli.dart @@ -14,7 +14,7 @@ void main(List args) async { version: '1.0.0', channel: 'stable', updateUrl: null, - baseLibraryPath: 'libapp.so', + baseLibraryPaths: ['libapp.so'], vmPath: Platform.executable, cacheDir: 'updater_cache', ); diff --git a/updater/library/include/updater.h b/updater/library/include/updater.h index 9002c1bb..d258c7fc 100644 --- a/updater/library/include/updater.h +++ b/updater/library/include/updater.h @@ -26,11 +26,14 @@ typedef struct AppParameters { */ const char *release_version; /** - * Path to the original aot library, required. For Flutter apps this - * is the path to the bundled libapp.so. May be used for compression - * downloaded artifacts. + * Array of paths to the original aot library, required. For Flutter apps + * these are the paths to the bundled libapp.so. May be used for compression downloaded artifacts. */ - const char *original_libapp_path; + const char *const *original_libapp_paths; + /** + * Length of the original_libapp_paths array. + */ + int original_libapp_paths_size; /** * Path to the app's libflutter.so, required. May be used for ensuring * downloaded artifacts are compatible with the Flutter/Dart versions diff --git a/updater/library/src/c_api.rs b/updater/library/src/c_api.rs index 22734384..e9084071 100644 --- a/updater/library/src/c_api.rs +++ b/updater/library/src/c_api.rs @@ -18,10 +18,12 @@ pub struct AppParameters { /// are based. Can be either a version number or a hash. pub release_version: *const libc::c_char, - /// Path to the original aot library, required. For Flutter apps this - /// is the path to the bundled libapp.so. May be used for compression - /// downloaded artifacts. - pub original_libapp_path: *const libc::c_char, + /// Array of paths to the original aot library, required. For Flutter apps + /// these are the paths to the bundled libapp.so. May be used for compression downloaded artifacts. + pub original_libapp_paths: *const *const libc::c_char, + + /// Length of the original_libapp_paths array. + pub original_libapp_paths_size: libc::c_int, /// Path to the app's libflutter.so, required. May be used for ensuring /// downloaded artifacts are compatible with the Flutter/Dart versions @@ -38,13 +40,25 @@ fn to_rust(c_string: *const libc::c_char) -> String { unsafe { CStr::from_ptr(c_string).to_str().unwrap() }.to_string() } +fn to_rust_vector(c_array: *const *const libc::c_char, size: libc::c_int) -> Vec { + let mut result = Vec::new(); + for i in 0..size { + let c_string = unsafe { *c_array.offset(i as isize) }; + result.push(to_rust(c_string)); + } + result +} + fn app_config_from_c(c_params: *const AppParameters) -> updater::AppConfig { let c_params_ref = unsafe { &*c_params }; updater::AppConfig { cache_dir: to_rust(c_params_ref.cache_dir), release_version: to_rust(c_params_ref.release_version), - original_libapp_path: to_rust(c_params_ref.original_libapp_path), + original_libapp_paths: to_rust_vector( + c_params_ref.original_libapp_paths, + c_params_ref.original_libapp_paths_size, + ), vm_path: to_rust(c_params_ref.vm_path), } } diff --git a/updater/library/src/config.rs b/updater/library/src/config.rs index fb52b412..6e99d425 100644 --- a/updater/library/src/config.rs +++ b/updater/library/src/config.rs @@ -40,7 +40,7 @@ pub struct ResolvedConfig { pub channel: String, pub app_id: String, pub release_version: String, - pub original_libapp_path: String, + pub original_libapp_paths: Vec, pub vm_path: String, pub base_url: String, } @@ -54,7 +54,7 @@ impl ResolvedConfig { channel: String::new(), app_id: String::new(), release_version: String::new(), - original_libapp_path: String::new(), + original_libapp_paths: Vec::new(), vm_path: String::new(), base_url: String::new(), } @@ -83,7 +83,7 @@ pub fn set_config(config: AppConfig, yaml: YamlConfig) { lock.download_dir = cache_path.to_str().unwrap().to_string(); lock.app_id = yaml.app_id.to_string(); lock.release_version = config.release_version.to_string(); - lock.original_libapp_path = config.original_libapp_path.to_string(); + lock.original_libapp_paths = config.original_libapp_paths; lock.vm_path = config.vm_path.to_string(); lock.is_initialized = true; info!("Updater configured with: {:?}", lock); diff --git a/updater/library/src/updater.rs b/updater/library/src/updater.rs index 4bf482f5..715c6984 100644 --- a/updater/library/src/updater.rs +++ b/updater/library/src/updater.rs @@ -58,7 +58,7 @@ impl Display for UpdateError { pub struct AppConfig { pub cache_dir: String, pub release_version: String, - pub original_libapp_path: String, + pub original_libapp_paths: Vec, pub vm_path: String, } @@ -145,8 +145,7 @@ fn update_internal(config: &ResolvedConfig) -> anyhow::Result { let download_path = download_dir.join(patch.number.to_string()); download_to_path(&patch.download_url, &download_path)?; - // Inflate the patch from a diff. - let base_path = PathBuf::from(&config.original_libapp_path); + let base_path = get_base_path(&config.original_libapp_paths)?; let output_path = download_dir.join(format!("{}.full", patch.number.to_string())); inflate(&download_path, &base_path, &output_path)?; @@ -169,6 +168,17 @@ fn update_internal(config: &ResolvedConfig) -> anyhow::Result { return Ok(UpdateStatus::UpdateInstalled); } +fn get_base_path(original_lib_app_paths: &Vec) -> anyhow::Result { + // Iterate through the paths and find the first one that exists. + for path in original_lib_app_paths { + let path = PathBuf::from(path); + if path.exists() { + return Ok(path); + } + } + return Err(UpdateError::InvalidState("No base file found".to_string()).into()); +} + fn inflate(patch_path: &Path, base_path: &Path, output_path: &Path) -> anyhow::Result<()> { info!("Patch is compressed, inflating..."); use anyhow::Context; @@ -275,7 +285,7 @@ mod tests { crate::AppConfig { cache_dir: cache_dir.clone(), release_version: "1.0.0".to_string(), - original_libapp_path: "original_libapp_path".to_string(), + original_libapp_paths: vec!["original_libapp_path".to_string()], vm_path: "vm_path".to_string(), }, "app_id: 1234", @@ -292,7 +302,7 @@ mod tests { crate::AppConfig { cache_dir: cache_dir.clone(), release_version: "1.0.0".to_string(), - original_libapp_path: "original_libapp_path".to_string(), + original_libapp_paths: vec!["original_libapp_path".to_string()], vm_path: "vm_path".to_string(), }, "", @@ -398,4 +408,47 @@ mod tests { error = result.unwrap_err(); assert!(format!("{}", error).starts_with("Failed to open patch file:")); } + + #[test] + fn get_base_path_uses_correct_path() { + let tmp_dir = TempDir::new("example").unwrap(); + let missing_file = tmp_dir.path().join("missing_file"); + let existing_file = tmp_dir.path().join("existing_file"); + std::fs::write(&existing_file, "hello world").unwrap(); + + // Should use first file since it exists. + let mut base_paths = vec![ + existing_file.as_path().to_str().unwrap().to_string(), + missing_file.as_path().to_str().unwrap().to_string(), + ]; + + let mut result = super::get_base_path(&base_paths); + + assert_eq!(result.unwrap(), existing_file.as_path()); + + // Should skip first file since it is missing. + base_paths = vec![ + missing_file.as_path().to_str().unwrap().to_string(), + existing_file.as_path().to_str().unwrap().to_string(), + ]; + + result = super::get_base_path(&base_paths); + + assert_eq!(result.unwrap(), existing_file.as_path()); + + // Should error since all files are missing. + base_paths = vec![ + missing_file.as_path().to_str().unwrap().to_string(), + missing_file.as_path().to_str().unwrap().to_string(), + ]; + + result = super::get_base_path(&base_paths); + + let error = result.unwrap_err(); + + assert_eq!( + format!("{}", error), + "Invalid State: No base file found".to_string() + ); + } }