diff --git a/Readme.md b/Readme.md index 603c69d..5b46642 100644 --- a/Readme.md +++ b/Readme.md @@ -7,6 +7,8 @@ Webnative Filesystem(WNFS) wrapper for Android. ## Usage +Exposed endpoint: mkdir, writeFile, writeFileFromPath, readFile, readFileToPath, readFilestreamToPath, rm, cp, mv + - Library is already packaged and published on Jitpack and ready to be used in Android applications (Java, Kotlin). Please checkout the AppMock for all usage examples: https://github.com/functionland/wnfs-android/blob/main/appmock/src/androidTest/java/land/fx/app/WNFSTest.kt - .aar files are available here that can be imported in ny framework: https://github.com/functionland/wnfs-build-aar @@ -73,5 +75,5 @@ Please note the following might not be done in order: - [x] Add WNFS tree encryption key generation from an input (deterministically) - [x] add error catching - [x] add metadata to ls and make it array -- [ ] Improve ls, read, and write functions to use a stream. ( :100: v1.0.0 Release here ) +- [x] Improve read function to use a stream. ( :100: v1 Release here ) - [ ] remove dependancy to custom version of wnfs diff --git a/appmock/src/androidTest/java/land/fx/app/WNFSTest.kt b/appmock/src/androidTest/java/land/fx/app/WNFSTest.kt index 0e44e3d..ad6a03f 100644 --- a/appmock/src/androidTest/java/land/fx/app/WNFSTest.kt +++ b/appmock/src/androidTest/java/land/fx/app/WNFSTest.kt @@ -112,7 +112,7 @@ class WNFSTest { } */ - config = writeFileFromPath(client, config.cid, config.private_ref, "root/testfrompath.txt", pathString+"/test.txt") + config = writeFileFromPath(client, config.cid, config.private_ref, "root/testfrompath.txt", pathString+"/test.txt") //target folder does not need to exist Log.d("AppMock", "config writeFile. cid="+config.cid+" & private_ref="+config.private_ref) assertNotNull("config should not be null", config) assertNotNull("cid should not be null", config.cid) @@ -138,11 +138,26 @@ class WNFSTest { assert(readcontentstream contentEquals "Hello, World!".toByteArray()) Log.d("AppMock", "readFileFromPathOfReadstreamTo. content="+String(readcontentstream)) - config = rm(client, config.cid, config.private_ref, "root/testfrompath.txt") - val content2 = readFile(client, config.cid, config.private_ref, "root/testfrompath.txt") + config = cp(client, config.cid, config.private_ref, "root/testfrompath.txt", "root/testfrompathcp.txt") //target folder must exists + val content_cp = readFile(client, config.cid, config.private_ref, "root/testfrompathcp.txt") + Log.d("AppMock", "cp. content_cp="+String(content_cp)) + assert(content_cp contentEquals "Hello, World!".toByteArray()) + + config = mv(client, config.cid, config.private_ref, "root/testfrompath.txt", "root/testfrompathmv.txt") //target folder must exists + val content_mv = readFile(client, config.cid, config.private_ref, "root/testfrompathmv.txt") + Log.d("AppMock", "mv. content_mv="+String(content_mv)) + assert(content_mv contentEquals "Hello, World!".toByteArray()) + + config = rm(client, config.cid, config.private_ref, "root/testfrompathmv.txt") + val content2 = readFile(client, config.cid, config.private_ref, "root/testfrompathmv.txt") Log.d("AppMock", "rm. content="+String(content2)) assert(content2 contentEquals "".toByteArray()) + config = rm(client, config.cid, config.private_ref, "root/testfrompathcp.txt") + val content3 = readFile(client, config.cid, config.private_ref, "root/testfrompathcp.txt") + Log.d("AppMock", "rm. content="+String(content3)) + assert(content3 contentEquals "".toByteArray()) + config = writeFile(client, config.cid, config.private_ref, "root/test.txt", "Hello, World!".toByteArray()) assertNotNull("cid should not be null", config.cid) diff --git a/dep/wnfsutils/src/private_forest.rs b/dep/wnfsutils/src/private_forest.rs index 0ada34f..e7fafe4 100644 --- a/dep/wnfsutils/src/private_forest.rs +++ b/dep/wnfsutils/src/private_forest.rs @@ -1,13 +1,14 @@ //! This example shows how to add a directory to a private forest (also HAMT) which encrypts it. //! It also shows how to retrieve encrypted nodes from the forest using `PrivateRef`s. -use chrono::Utc; +use chrono::{Utc, prelude::*}; use libipld::Cid; use rand::{thread_rng, rngs::ThreadRng}; use std::{ rc::Rc, fs::{File, OpenOptions}, - io::{Read, Write} + io::{Read, Write}, + os::unix::fs::MetadataExt }; use wnfs::{ dagcbor, Hasher, utils, @@ -266,16 +267,19 @@ impl<'a> PrivateDirectoryHelper<'a> { } } - fn get_file_as_byte_vec(&mut self, filename: &String) -> Result, String> { + fn get_file_as_byte_vec(&mut self, filename: &String) -> Result<(Vec, i64), String> { let f = File::open(&filename); if f.is_ok() { - let metadata = std::fs::metadata(&filename); - if metadata.is_ok() { - let mut buffer = vec![0; metadata.ok().unwrap().len() as usize]; + let metadata_res = std::fs::metadata(&filename); + if metadata_res.is_ok() { + let metadata = metadata_res.ok().unwrap(); + let modification_time_seconds = metadata.mtime(); + + let mut buffer = vec![0; metadata.len() as usize]; f.ok().unwrap().read(&mut buffer).expect("buffer overflow"); - Ok(buffer) + Ok((buffer, modification_time_seconds)) } else { - trace!("wnfsError in get_file_as_byte_vec, unable to read metadata: {:?}", metadata.err().unwrap()); + trace!("wnfsError in get_file_as_byte_vec, unable to read metadata: {:?}", metadata_res.err().unwrap()); Err("wnfsError unable to read metadata".to_string()) } } else { @@ -287,10 +291,11 @@ impl<'a> PrivateDirectoryHelper<'a> { pub async fn write_file_from_path(&mut self, forest: Rc, root_dir: Rc, path_segments: &[String], filename: &String) -> Result<(Cid, PrivateRef), String> { let content: Vec; + let modification_time_seconds: i64; let try_content = self.get_file_as_byte_vec(filename); if try_content.is_ok() { - content = try_content.ok().unwrap(); - let writefile_res = self.write_file(forest, root_dir, path_segments, content).await; + (content, modification_time_seconds) = try_content.ok().unwrap(); + let writefile_res = self.write_file(forest, root_dir, path_segments, content, modification_time_seconds).await; if writefile_res.is_ok() { Ok(writefile_res.ok().unwrap()) }else{ @@ -326,12 +331,17 @@ impl<'a> PrivateDirectoryHelper<'a> { } - pub async fn write_file(&mut self, forest: Rc, root_dir: Rc, path_segments: &[String], content: Vec) -> Result<(Cid, PrivateRef), String> { + pub async fn write_file(&mut self, forest: Rc, root_dir: Rc, path_segments: &[String], content: Vec, modification_time_seconds: i64) -> Result<(Cid, PrivateRef), String> { + let mut modification_time_utc: DateTime= Utc::now(); + if modification_time_seconds > 0 { + let naive_datetime = NaiveDateTime::from_timestamp_opt(modification_time_seconds, 0).unwrap(); + modification_time_utc = DateTime::from_utc(naive_datetime, Utc); + } let write_res = root_dir .write( path_segments, true, - Utc::now(), + modification_time_utc, content, forest, &mut self.store, @@ -477,6 +487,65 @@ impl<'a> PrivateDirectoryHelper<'a> { } + pub async fn mv(&mut self, forest: Rc, root_dir: Rc, source_path_segments: &[String], target_path_segments: &[String]) -> Result<(Cid, PrivateRef), String> { + let mv_result = root_dir + .basic_mv( + source_path_segments, + target_path_segments, + true, + Utc::now(), + forest, + &mut self.store, + &mut self.rng, + ) + .await; + if mv_result.is_ok() { + let PrivateOpResult { + root_dir, forest, .. + } = mv_result.ok().unwrap(); + + let update_res = self.update_forest(forest).await; + if update_res.is_ok() { + Ok((update_res.ok().unwrap(), root_dir.header.get_private_ref())) + } else { + trace!("wnfsError occured in mv update_res: {:?}", update_res.as_ref().err().unwrap()); + Err(update_res.err().unwrap().to_string()) + } + } else { + trace!("wnfsError occured in mv mv_result: {:?}", mv_result.as_ref().err().unwrap()); + Err(mv_result.err().unwrap().to_string()) + } + } + + pub async fn cp(&mut self, forest: Rc, root_dir: Rc, source_path_segments: &[String], target_path_segments: &[String]) -> Result<(Cid, PrivateRef), String> { + let cp_result = root_dir + .cp( + source_path_segments, + target_path_segments, + true, + Utc::now(), + forest, + &mut self.store, + &mut self.rng + ) + .await; + if cp_result.is_ok() { + let PrivateOpResult { forest, root_dir, .. } + = cp_result.ok().unwrap(); + + let update_res = self.update_forest(forest).await; + if update_res.is_ok() { + Ok((update_res.ok().unwrap(), root_dir.header.get_private_ref())) + } else { + trace!("wnfsError occured in cp update_res: {:?}", update_res.as_ref().err().unwrap()); + Err(update_res.err().unwrap().to_string()) + } + } else { + trace!("wnfsError occured in cp cp_result: {:?}", cp_result.as_ref().err().unwrap()); + Err(cp_result.err().unwrap().to_string()) + } + } + pub async fn ls_files(&mut self, forest: Rc, root_dir: Rc, path_segments: &[String]) -> Result, String> { let res = root_dir @@ -538,11 +607,11 @@ impl<'a> PrivateDirectoryHelper<'a> { return runtime.block_on(self.write_file_from_path(forest, root_dir, path_segments, filename)); } - pub fn synced_write_file(&mut self, forest: Rc, root_dir: Rc, path_segments: &[String], content: Vec) -> Result<(Cid, PrivateRef), String> + pub fn synced_write_file(&mut self, forest: Rc, root_dir: Rc, path_segments: &[String], content: Vec, modification_time_seconds: i64) -> Result<(Cid, PrivateRef), String> { let runtime = tokio::runtime::Runtime::new().expect("Unable to create a runtime"); - return runtime.block_on(self.write_file(forest, root_dir, path_segments, content)); + return runtime.block_on(self.write_file(forest, root_dir, path_segments, content, modification_time_seconds)); } pub fn synced_read_file_to_path(&mut self, forest: Rc, root_dir: Rc, path_segments: &[String], filename: &String) -> Result @@ -572,6 +641,18 @@ impl<'a> PrivateDirectoryHelper<'a> { return runtime.block_on(self.mkdir(forest, root_dir, path_segments)); } + pub fn synced_mv(&mut self, forest: Rc, root_dir: Rc, source_path_segments: &[String], target_path_segments: &[String]) -> Result<(Cid, PrivateRef), String> { + let runtime = + tokio::runtime::Runtime::new().expect("Unable to create a runtime"); + return runtime.block_on(self.mv(forest, root_dir, source_path_segments, target_path_segments)); + } + + pub fn synced_cp(&mut self, forest: Rc, root_dir: Rc, source_path_segments: &[String], target_path_segments: &[String]) -> Result<(Cid, PrivateRef), String> { + let runtime = + tokio::runtime::Runtime::new().expect("Unable to create a runtime"); + return runtime.block_on(self.cp(forest, root_dir, source_path_segments, target_path_segments)); + } + pub fn synced_rm(&mut self, forest: Rc, root_dir: Rc, path_segments: &[String]) -> Result<(Cid, PrivateRef), String> { let runtime = @@ -621,7 +702,7 @@ mod private_tests { let (forest_cid, private_ref) = helper.init(forest, empty_key).await; let forest = helper.load_forest(forest_cid).await.unwrap(); let root_dir = helper.get_root_dir(forest.to_owned(), private_ref.to_owned()).await.unwrap(); - let (new_cid, _) = helper.write_file(forest.to_owned(), root_dir.to_owned(), &["root".into(), "hello".into(), "world.txt".into()], b"hello, world!".to_vec()).await; + let (new_cid, _) = helper.write_file(forest.to_owned(), root_dir.to_owned(), &["root".into(), "hello".into(), "world.txt".into()], b"hello, world!".to_vec(), 0).await; let forest = helper.load_forest(new_cid).await.unwrap(); let ls_result = helper.ls_files(forest.to_owned(), root_dir.to_owned(), &["root".into()]).await; println!("ls: {:?}", ls_result); diff --git a/jitpack.yml b/jitpack.yml index beb0372..d498ca4 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -2,4 +2,4 @@ before_install: - git lfs pull install: - FILE="-Dfile=lib/build/outputs/aar/lib-release.aar" - - mvn install:install-file $FILE -DgroupId=com.group.module -DartifactId=wnfs-android -Dversion=1.4.0 -Dpackaging=aar -DgeneratePom=true + - mvn install:install-file $FILE -DgroupId=com.group.module -DartifactId=wnfs-android -Dversion=1.4.1 -Dpackaging=aar -DgeneratePom=true diff --git a/lib/src/main/java/land/fx/wnfslib/Fs.java b/lib/src/main/java/land/fx/wnfslib/Fs.java index dfe8353..e6b43d0 100644 --- a/lib/src/main/java/land/fx/wnfslib/Fs.java +++ b/lib/src/main/java/land/fx/wnfslib/Fs.java @@ -29,6 +29,10 @@ public final class Fs { private static native Config rmNative(Datastore datastore, String cid, String privateRef, String path); + private static native Config mvNative(Datastore datastore, String cid, String privateRef, String sourcePath, String targetPath); + + private static native Config cpNative(Datastore datastore, String cid, String privateRef, String sourcePath, String targetPath); + private static native String readFileToPathNative(Datastore datastore, String cid, String privateRef, String path, String filename); private static native String readFilestreamToPathNative(Datastore datastore, String cid, String privateRef, String path, String filename); @@ -168,10 +172,21 @@ public static Config mkdir(Datastore datastore, String cid, String privateRef, S } } + @NonNull public static Config rm(Datastore datastore, String cid, String privateRef, String path) { return rmNative(datastore, cid, privateRef, path); } + @NonNull + public static Config mv(Datastore datastore, String cid, String privateRef, String sourcePath, String targetPath) { + return mvNative(datastore, cid, privateRef, sourcePath, targetPath); + } + + @NonNull + public static Config cp(Datastore datastore, String cid, String privateRef, String sourcePath, String targetPath) { + return cpNative(datastore, cid, privateRef, sourcePath, targetPath); + } + @NonNull public static String readFileToPath(Datastore datastore, String cid, String privateRef, String path, String filename) throws Exception { try{ diff --git a/pom.xml b/pom.xml index 31353a7..84833d3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,5 +5,5 @@ 4.0.0 com.group.module wnfs-android - 1.4.0 + 1.4.1 diff --git a/wnfslib/src/lib.rs b/wnfslib/src/lib.rs index 44a65a3..ef88665 100644 --- a/wnfslib/src/lib.rs +++ b/wnfslib/src/lib.rs @@ -373,7 +373,7 @@ pub mod android { let content = jbyte_array_to_vec(env, jni_content); //let (cid, private_ref) = let write_file_res = - helper.synced_write_file(forest.to_owned(), root_dir, &path_segments, content); + helper.synced_write_file(forest.to_owned(), root_dir, &path_segments, content, 0); trace!("**********************writeFileNative finished**************"); if write_file_res.is_ok() { let (cid, private_ref) = write_file_res.ok().unwrap(); @@ -480,6 +480,78 @@ pub mod android { } } + #[no_mangle] + pub extern "C" fn Java_land_fx_wnfslib_Fs_mvNative( + env: JNIEnv, + _: JClass, + jni_fula_client: JObject, + jni_cid: JString, + jni_private_ref: JString, + jni_source_path_segments: JString, + jni_target_path_segments: JString, + ) -> jobject { + trace!("**********************mvNative started**************"); + let store = JNIStore::new(env, jni_fula_client); + let block_store = FFIFriendlyBlockStore::new(Box::new(store)); + let helper = &mut PrivateDirectoryHelper::new(block_store); + + let cid = deserialize_cid(env, jni_cid); + let private_ref = deserialize_private_ref(env, jni_private_ref); + + let forest = helper.synced_load_forest(cid).unwrap(); + let root_dir = helper + .synced_get_root_dir(forest.to_owned(), private_ref) + .unwrap(); + let source_path_segments = prepare_path_segments(env, jni_source_path_segments); + let target_path_segments = prepare_path_segments(env, jni_target_path_segments); + let result = helper.synced_mv(forest.to_owned(), root_dir, &source_path_segments, &target_path_segments); + trace!("**********************mvNative finished**************"); + if result.is_ok() { + let (cid, private_ref) = result.ok().unwrap(); + return serialize_config(env, cid, private_ref); + }else { + trace!("wnfsError occured in Java_land_fx_wnfslib_Fs_mvNative: {:?}", result.err().unwrap()); + return JObject::null().into_inner(); + } + + } + + #[no_mangle] + pub extern "C" fn Java_land_fx_wnfslib_Fs_cpNative( + env: JNIEnv, + _: JClass, + jni_fula_client: JObject, + jni_cid: JString, + jni_private_ref: JString, + jni_source_path_segments: JString, + jni_target_path_segments: JString, + ) -> jobject { + trace!("**********************cpNative started**************"); + let store = JNIStore::new(env, jni_fula_client); + let block_store = FFIFriendlyBlockStore::new(Box::new(store)); + let helper = &mut PrivateDirectoryHelper::new(block_store); + + let cid = deserialize_cid(env, jni_cid); + let private_ref = deserialize_private_ref(env, jni_private_ref); + + let forest = helper.synced_load_forest(cid).unwrap(); + let root_dir = helper + .synced_get_root_dir(forest.to_owned(), private_ref) + .unwrap(); + let source_path_segments = prepare_path_segments(env, jni_source_path_segments); + let target_path_segments = prepare_path_segments(env, jni_target_path_segments); + let result = helper.synced_cp(forest.to_owned(), root_dir, &source_path_segments, &target_path_segments); + trace!("**********************mvNative finished**************"); + if result.is_ok() { + let (cid, private_ref) = result.ok().unwrap(); + return serialize_config(env, cid, private_ref); + }else { + trace!("wnfsError occured in Java_land_fx_wnfslib_Fs_cpNative: {:?}", result.err().unwrap()); + return JObject::null().into_inner(); + } + + } + #[no_mangle] pub extern "C" fn Java_land_fx_wnfslib_Fs_rmNative( env: JNIEnv,