Skip to content

Commit

Permalink
Added mv and cp and also original modification time for write
Browse files Browse the repository at this point in the history
  • Loading branch information
ehsan6sha committed Dec 20, 2022
1 parent d100517 commit 4a82cbe
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 20 deletions.
4 changes: 3 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.0.0 Release here )
- [ ] remove dependancy to custom version of wnfs
21 changes: 18 additions & 3 deletions appmock/src/androidTest/java/land/fx/app/WNFSTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
111 changes: 96 additions & 15 deletions dep/wnfsutils/src/private_forest.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -266,16 +267,19 @@ impl<'a> PrivateDirectoryHelper<'a> {
}
}

fn get_file_as_byte_vec(&mut self, filename: &String) -> Result<Vec<u8>, String> {
fn get_file_as_byte_vec(&mut self, filename: &String) -> Result<(Vec<u8>, 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 {
Expand All @@ -287,10 +291,11 @@ impl<'a> PrivateDirectoryHelper<'a> {

pub async fn write_file_from_path(&mut self, forest: Rc<PrivateForest>, root_dir: Rc<PrivateDirectory>, path_segments: &[String], filename: &String) -> Result<(Cid, PrivateRef), String> {
let content: Vec<u8>;
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{
Expand Down Expand Up @@ -326,12 +331,17 @@ impl<'a> PrivateDirectoryHelper<'a> {

}

pub async fn write_file(&mut self, forest: Rc<PrivateForest>, root_dir: Rc<PrivateDirectory>, path_segments: &[String], content: Vec<u8>) -> Result<(Cid, PrivateRef), String> {
pub async fn write_file(&mut self, forest: Rc<PrivateForest>, root_dir: Rc<PrivateDirectory>, path_segments: &[String], content: Vec<u8>, modification_time_seconds: i64) -> Result<(Cid, PrivateRef), String> {
let mut modification_time_utc: DateTime<Utc>= 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,
Expand Down Expand Up @@ -477,6 +487,65 @@ impl<'a> PrivateDirectoryHelper<'a> {

}

pub async fn mv(&mut self, forest: Rc<PrivateForest>, root_dir: Rc<PrivateDirectory>, 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<PrivateForest>, root_dir: Rc<PrivateDirectory>, 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<PrivateForest>, root_dir: Rc<PrivateDirectory>, path_segments: &[String]) -> Result<Vec<(String, Metadata)>, String> {

let res = root_dir
Expand Down Expand Up @@ -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<PrivateForest>, root_dir: Rc<PrivateDirectory>, path_segments: &[String], content: Vec<u8>) -> Result<(Cid, PrivateRef), String>
pub fn synced_write_file(&mut self, forest: Rc<PrivateForest>, root_dir: Rc<PrivateDirectory>, path_segments: &[String], content: Vec<u8>, 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<PrivateForest>, root_dir: Rc<PrivateDirectory>, path_segments: &[String], filename: &String) -> Result<String, String>
Expand Down Expand Up @@ -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<PrivateForest>, root_dir: Rc<PrivateDirectory>, 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<PrivateForest>, root_dir: Rc<PrivateDirectory>, 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<PrivateForest>, root_dir: Rc<PrivateDirectory>, path_segments: &[String]) -> Result<(Cid, PrivateRef), String>
{
let runtime =
Expand Down Expand Up @@ -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);
Expand Down
15 changes: 15 additions & 0 deletions lib/src/main/java/land/fx/wnfslib/Fs.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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{
Expand Down
74 changes: 73 additions & 1 deletion wnfslib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 4a82cbe

Please sign in to comment.