diff --git a/.gitignore b/.gitignore index 184752b7..ef70b169 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ **/.DS_Store **/node_modules packages/wasm/pkg +packages/wasm/index.* **/yarn-error.log diff --git a/Cargo.lock b/Cargo.lock index b11246d5..9f0637c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -29,6 +40,128 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-mutex", + "blocking", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +dependencies = [ + "concurrent-queue", + "futures-lite", + "libc", + "log", + "once_cell", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-lock" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-std" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" +dependencies = [ + "async-attributes", + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" + [[package]] name = "async-trait" version = "0.1.53" @@ -40,6 +173,12 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + [[package]] name = "autocfg" version = "1.1.0" @@ -102,6 +241,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + [[package]] name = "bumpalo" version = "3.9.1" @@ -120,13 +273,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + [[package]] name = "cached" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af4dfac631a8e77b2f327f7852bb6172771f5279c4512efe79fad6067b37be3d" dependencies = [ - "hashbrown", + "hashbrown 0.11.2", "once_cell", ] @@ -168,6 +327,15 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -192,6 +360,16 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if", + "lazy_static", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -202,6 +380,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.3.2" @@ -244,6 +432,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + [[package]] name = "fastrand" version = "1.7.0" @@ -266,13 +460,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "fs" -version = "0.1.0" +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ - "chrono", - "libipld", - "multihash", - "semver", + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", ] [[package]] @@ -285,12 +505,44 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gloo-timers" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +dependencies = [ + "ahash", +] + [[package]] name = "heck" version = "0.3.3" @@ -300,6 +552,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "indexmap" version = "1.8.1" @@ -307,7 +568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.11.2", ] [[package]] @@ -349,6 +610,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -470,6 +740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if", + "value-bag", ] [[package]] @@ -545,12 +816,28 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.12.0" @@ -584,6 +871,31 @@ dependencies = [ "indexmap", ] +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "polling" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +dependencies = [ + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + [[package]] name = "proc-macro-crate" version = "1.1.3" @@ -794,12 +1106,28 @@ dependencies = [ "keccak", ] +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + [[package]] name = "smallvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "syn" version = "1.0.90" @@ -901,29 +1229,34 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" +[[package]] +name = "value-bag" +version = "1.0.0-alpha.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" +dependencies = [ + "ctor", + "version_check", +] + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" -[[package]] -name = "wasm" -version = "0.1.0" -dependencies = [ - "fs", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasm-bindgen" version = "0.2.79" @@ -990,6 +1323,19 @@ version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +[[package]] +name = "wasm-wnfs" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wnfs", +] + [[package]] name = "web-sys" version = "0.3.56" @@ -1001,8 +1347,13 @@ dependencies = [ ] [[package]] -name = "webnative-tests" -version = "0.1.0" +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] [[package]] name = "which" @@ -1079,3 +1430,28 @@ name = "windows_x86_64_msvc" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + +[[package]] +name = "wnfs" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-std", + "async-trait", + "chrono", + "hashbrown 0.12.0", + "libipld", + "multihash", + "semver", +] + +[[package]] +name = "wnfs-tests" +version = "0.1.0" +dependencies = [ + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wnfs", +] diff --git a/README.md b/README.md index 2042b53f..fc5ad729 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- Fission Logo + Fission Logo

WebNative FileSystem (WNFS)

diff --git a/packages/fs/Cargo.toml b/packages/fs/Cargo.toml index 0a8b5e1b..371105c9 100644 --- a/packages/fs/Cargo.toml +++ b/packages/fs/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "fs" +name = "wnfs" version = "0.1.0" description = "WebNative filesystem core implementation" keywords = ["wnfs", "webnative", "ipfs", "decentralisation"] @@ -8,7 +8,7 @@ categories = [ "cryptography", "web programming", ] -license-file = "LICENSE" +license-file = "../../LICENSE" readme = "README.md" edition = "2021" repository = "https://github.com/fission-suite/rs-wnfs" @@ -20,11 +20,15 @@ libipld = "0.13.1" multihash = "0.16.2" semver = "1.0.7" chrono = "0.4.19" +anyhow = "1.0.56" +hashbrown = "0.12.0" +async-trait = "0.1.53" +async-std = { version = "1.11.0", features = ["attributes"] } [lib] path = "lib.rs" +crate-type = ["cdylib" , "rlib"] [features] default = [] wasm = [] -wasm-bindgen = ["wasm"] diff --git a/packages/fs/common/blockstore.rs b/packages/fs/common/blockstore.rs new file mode 100644 index 00000000..acc5d11c --- /dev/null +++ b/packages/fs/common/blockstore.rs @@ -0,0 +1,91 @@ +//! Block store traits. + +use super::FsError; +use anyhow::Result; +use async_trait::async_trait; +use hashbrown::HashMap; +use libipld::{ + cbor::DagCborCodec, + cid::Version, + codec::{Codec, Decode}, + Cid, IpldCodec, +}; +use multihash::{Code, MultihashDigest}; +use std::borrow::Cow; + +//-------------------------------------------------------------------------------------------------- +// Type Definitions +//-------------------------------------------------------------------------------------------------- + +/// For types that implement getting a block from a CID. +#[async_trait] +pub trait BlockStoreLookup { + async fn get_block<'a>(&'a self, cid: &Cid) -> Result>; +} + +/// For types that implement loading a cbor model from a blockstore using a CID. +#[async_trait] +pub trait BlockStoreCidLoad { + /// Loads a cbor model from the store with provided CID. + async fn load>(&self, cid: &Cid) -> Result; +} + +/// For types that implement block store operations. +#[async_trait] +pub trait BlockStore: BlockStoreLookup + BlockStoreCidLoad { + async fn put_block(&mut self, bytes: Vec, codec: IpldCodec) -> Result; +} + +/// An in-memory block store to simulate IPFS. IPFS is basically an glorified HashMap. +#[derive(Debug, Default)] +pub struct MemoryBlockStore(HashMap>); + +//-------------------------------------------------------------------------------------------------- +// Implementations +//-------------------------------------------------------------------------------------------------- + +impl MemoryBlockStore { + /// Creates a new in-memory block store. + pub fn new() -> Self { + Self::default() + } +} + +#[async_trait] +impl BlockStore for MemoryBlockStore { + /// Stores an array of bytes in the block store. + async fn put_block(&mut self, bytes: Vec, codec: IpldCodec) -> Result { + let hash = Code::Sha2_256.digest(&bytes); + let cid = Cid::new(Version::V1, codec.into(), hash)?; + + self.0.insert((&cid).to_string(), bytes); + + Ok(cid) + } +} + +#[async_trait] +impl BlockStoreLookup for MemoryBlockStore { + /// Retrieves an array of bytes from the block store with given CID. + async fn get_block<'a>(&'a self, cid: &Cid) -> Result> { + let bytes = self + .0 + .get(&cid.to_string()) + .ok_or(FsError::CIDNotFoundInBlockstore)?; + + Ok(Cow::Borrowed(bytes)) + } +} + +#[async_trait] +impl BlockStoreCidLoad for MemoryBlockStore { + /// Loads a cbor-encoded data from the store with provided CID. + async fn load>(&self, cid: &Cid) -> Result { + let bytes = self.get_block(cid).await?; + let decoded = DagCborCodec.decode(bytes.as_ref())?; + Ok(decoded) + } +} + +#[cfg(test)] +mod blockstore_tests {} diff --git a/packages/fs/common/error.rs b/packages/fs/common/error.rs new file mode 100644 index 00000000..8079f961 --- /dev/null +++ b/packages/fs/common/error.rs @@ -0,0 +1,26 @@ +//! File system errors. + +use anyhow::Result; +use std::{ + error::Error, + fmt::{Debug, Display}, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FsError { + CIDNotFoundInBlockstore, + InvalidPath, + NodeNotFound, +} + +impl std::error::Error for FsError {} + +impl Display for FsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +pub fn error(err: impl Error + Send + Sync + 'static) -> Result { + Err(err.into()) +} diff --git a/packages/fs/common/metadata.rs b/packages/fs/common/metadata.rs index a40d8997..a6288563 100644 --- a/packages/fs/common/metadata.rs +++ b/packages/fs/common/metadata.rs @@ -1,7 +1,13 @@ +//! File system metadata. + +use chrono::{DateTime, Utc}; use semver::Version; -pub enum UnixNodeKind { - // See https://docs.ipfs.io/concepts/file-systems/#unix-file-system-unixfs +/// Represents the type of node in the UnixFS file system. +/// +/// See https://docs.ipfs.io/concepts/file-systems/#unix-file-system-unixfs +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum UnixFsNodeKind { Raw, File, Dir, @@ -10,16 +16,67 @@ pub enum UnixNodeKind { HAMTShard, } -pub struct UnixMetadata { - // See https://docs.ipfs.io/concepts/file-systems/#unix-file-system-unixfs - mtime: u64, - ctime: u64, - mode: u32, - kind: UnixNodeKind, +/// Mode represents the Unix permissions for a UnixFS node. +/// +/// See +/// - https://docs.ipfs.io/concepts/file-systems/#unix-file-system-unixfs +/// - https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum UnixFsMode { + NoPermissions = 0, + OwnerReadWriteExecute = 700, + OwnerGroupReadWriteExecute = 770, + AllReadWriteExecute = 777, + AllExecute = 111, + AllWrite = 222, + AllWriteExecute = 333, + AllRead = 444, + AllReadExecute = 555, + AllReadWrite = 666, + OwnerReadWriteExecuteGroupRead = 740, + OwnerReadWriteExecuteGroupOthersReadExecute = 755, + OwnerReadWriteGroupOthersRead = 644, +} + +/// The metadata of a node in the UnixFS file system. +/// +/// See https://docs.ipfs.io/concepts/file-systems/#unix-file-system-unixfs +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UnixFsMetadata { + created: DateTime, + modified: DateTime, + mode: UnixFsMode, + kind: UnixFsNodeKind, } +/// The metadata of a node on the WNFS file system. +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Metadata { - unix_metadata: UnixMetadata, - is_file: bool, // TODO: Already in UnixMetadata? + unixfs_metadata: UnixFsMetadata, version: Version, } + +impl Metadata { + /// Creates a new metadata representing a UnixFS node. + pub fn new(time: DateTime, kind: UnixFsNodeKind) -> Self { + let mode = + if matches!(kind, UnixFsNodeKind::Dir) || matches!(kind, UnixFsNodeKind::HAMTShard) { + UnixFsMode::OwnerReadWriteGroupOthersRead + } else { + UnixFsMode::OwnerReadWriteExecuteGroupOthersReadExecute + }; + + Self { + unixfs_metadata: UnixFsMetadata { + created: time, + modified: time, + mode, + kind, + }, + version: Version::new(1, 0, 0), + } + } +} + +#[cfg(test)] +mod metadata_tests {} diff --git a/packages/fs/common/mod.rs b/packages/fs/common/mod.rs index fe2907e2..fe8ec0a6 100644 --- a/packages/fs/common/mod.rs +++ b/packages/fs/common/mod.rs @@ -1,3 +1,7 @@ +mod blockstore; +mod error; mod metadata; +pub use blockstore::*; +pub(crate) use error::*; pub use metadata::*; diff --git a/packages/fs/lib.rs b/packages/fs/lib.rs index 732bb824..7e89dab2 100644 --- a/packages/fs/lib.rs +++ b/packages/fs/lib.rs @@ -1,6 +1,10 @@ mod common; pub mod public; -// RE-EXPORTS +pub use common::*; -pub use libipld::Cid; +//-------------------------------------------------------------------------------------------------- +// Re-exports +//-------------------------------------------------------------------------------------------------- + +pub use libipld::{Cid, IpldCodec}; diff --git a/packages/fs/public/filesystem.rs b/packages/fs/public/filesystem.rs new file mode 100644 index 00000000..e2074d15 --- /dev/null +++ b/packages/fs/public/filesystem.rs @@ -0,0 +1,39 @@ +//! The public file system API. + +use super::PublicDirectory; +use crate::common::BlockStore; +use anyhow::Result; + +//-------------------------------------------------------------------------------------------------- +// Type Definitions +//-------------------------------------------------------------------------------------------------- + +/// The WNFS public file system. +struct PublicFileSystem<'s, T: BlockStore> { + blockstore: &'s T, + root_dir: PublicDirectory, +} + +//-------------------------------------------------------------------------------------------------- +// Implementations +//-------------------------------------------------------------------------------------------------- + +impl<'s, T: BlockStore> PublicFileSystem<'s, T> { + /// Creates a new WNFS public file system. + pub fn new(blockstore: &'s T, root_dir: PublicDirectory) -> Self { + Self { + blockstore, + root_dir, + } + } + + /// Reads file content at the specified path from the file system. + pub async fn read(path_segments: &[String]) -> Result<()> { + todo!( + r#" + This is entering javascript land. + We can make writing file blocks to linear memory and return the base address. + "# + ) + } +} diff --git a/packages/fs/public/mod.rs b/packages/fs/public/mod.rs index 41b54b13..107486c4 100644 --- a/packages/fs/public/mod.rs +++ b/packages/fs/public/mod.rs @@ -1,3 +1,5 @@ +mod filesystem; mod node; +pub use filesystem::*; pub use node::*; diff --git a/packages/fs/public/node.rs b/packages/fs/public/node.rs index 1d6a0a4a..acfcec6e 100644 --- a/packages/fs/public/node.rs +++ b/packages/fs/public/node.rs @@ -1,41 +1,192 @@ -use crate::common::Metadata; -use libipld::Cid; -use std::collections::HashMap; +//! Public file system in-memory representation. +use crate::common::{error, BlockStore, FsError, Metadata, UnixFsNodeKind}; +use anyhow::Result; +use chrono::{DateTime, Utc}; +use hashbrown::HashMap; +use libipld::{cbor::DagCborCodec, codec::Decode, Cid}; +use std::{ops::Deref, rc::Rc}; + +//-------------------------------------------------------------------------------------------------- +// Type Definitions +//-------------------------------------------------------------------------------------------------- + +/// A node in a WNFS public file system. This can either be a file or a directory. +#[derive(Debug, Clone, PartialEq, Eq)] pub enum PublicNode { File(PublicFile), - Dir(PublicDirKind), -} - -pub enum PublicDirKind { - Link(PublicDir), - Cid(PublicDir), // Persisted. TODO: what does that mean? + Dir(PublicDirectory), } -pub enum LinkKind { +/// A link to another node in the WNFS public file system. It can be held as a simple serialised CID or as a reference to the node itself. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Link { Cid(Cid), - Node(PublicNode), + Node(Rc), } -pub struct PublicDir { - metadata: Metadata, - userland: HashMap, // TODO: Hashbrown? - previous: Option, +/// A directory in a WNFS public file system. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PublicDirectory { + // metadata: Metadata, + userland: HashMap, + // previous: Option, } +/// A file in a WNFS public file system. +#[derive(Debug, Clone, PartialEq, Eq)] pub struct PublicFile { metadata: Metadata, userland: Cid, previous: Option, } -pub enum OperationContext { - AbortContext, - BlockStore, +//-------------------------------------------------------------------------------------------------- +// Implementations +//-------------------------------------------------------------------------------------------------- + +// TODO(appcypher) +impl Decode for PublicNode { + fn decode(c: DagCborCodec, r: &mut R) -> Result { + todo!() + } +} + +impl PublicNode { + /// Stores a WNFS node as block(s) in chosen block store. + pub async fn store(&self, store: &mut B) -> Cid { + match self { + PublicNode::File(file) => file.store(store).await, + PublicNode::Dir(dir) => dir.store(store).await, + } + } + + /// Casts a node to a directory. + /// + /// # Panics + /// + /// Panics if the node is not a directory. + pub fn as_dir(&self) -> &PublicDirectory { + match self { + PublicNode::Dir(dir) => dir, + _ => unreachable!(), + } + } +} + +impl PublicDirectory { + /// Creates a new directory using the given metadata. + pub fn new(time: DateTime) -> Self { + Self { + // metadata: Metadata::new(time, UnixFsNodeKind::Dir), + userland: HashMap::new(), + // previous: None, + } + } + + /// Follows a path and fetches the node at the end of the path. + pub async fn get_node( + &self, + path_segments: &[String], + store: &B, + ) -> Result> { + if path_segments.is_empty() { + return error(FsError::InvalidPath); + } + + let mut working_node: Rc = Rc::new(PublicNode::Dir(self.clone())); + + // Iterate over the path segments until we get the node of the last segment. + for (index, segment) in path_segments.iter().enumerate() { + // Cast working node to directory. + let dir = working_node.deref().as_dir(); + + // Fetch node representing path segment in working directory. + if let Some(node) = dir.lookup_node(segment, store).await? { + match node.as_ref() { + PublicNode::Dir(_) => { + // If the node is a directory, set it as the working node. + working_node = Rc::clone(&node); + } + PublicNode::File(_) => { + // If the node is a file, we return it if it's the last segment. + if index != path_segments.len() - 1 { + return error(FsError::InvalidPath); + } + working_node = Rc::clone(&node); + break; + } + } + + // We continue loop after setting the working node to a directory node. + continue; + } + + // If the node is not found, we return an error. + return error(FsError::NodeNotFound); + } + + Ok(working_node) + } + + /// Looks up a node by its path name in the current directory. + /// + /// TODO(appcypher): What is a valid path segment identifier? + pub async fn lookup_node( + &self, + path_segment: &str, + store: &B, + ) -> Result>> { + Ok(match self.userland.get(path_segment) { + Some(link) => Some(link.resolve(store).await?), + None => None, + }) + } + + /// Stores WNFS directory as block(s) in chosen block store. + pub async fn store(&self, store: &mut B) -> Cid { + todo!() + } +} + +impl PublicFile { + /// Stores WNFS block(s) in chosen block store. + pub async fn store(&self, store: &mut B) -> Cid { + todo!() + } +} + +impl Link { + // Resolves a CID link in the file system to a node. + pub async fn resolve(&self, store: &B) -> Result> { + Ok(match self { + Link::Cid(cid) => { + let node = store.load(cid).await?; + Rc::new(node) + } + Link::Node(node) => Rc::clone(node), + }) + } + + // Adds the link to the file system. + pub async fn seal(&self, store: &mut B) { + todo!() + } } -type PublicDirWithNodes = PublicDir; +#[cfg(test)] +mod public_node_tests { + use super::PublicDirectory; + use crate::MemoryBlockStore; + use chrono::Utc; -pub async fn lookup_node(path: &str, dir: PublicDirWithNodes, ) -> Cid { - todo!("implement lookup_node") + #[async_std::test] + async fn unadded_node_lookup_unsuccessful() { + let root = PublicDirectory::new(Utc::now()); + let store = MemoryBlockStore::default(); + let node = root.lookup_node("Unknown", &store).await; + dbg!(&node); + assert!(node.is_ok()); + assert_eq!(node.unwrap(), None); + } } diff --git a/packages/tests/Cargo.toml b/packages/tests/Cargo.toml index f4a83a82..908a00a9 100644 --- a/packages/tests/Cargo.toml +++ b/packages/tests/Cargo.toml @@ -1,12 +1,31 @@ [package] -name = "webnative-tests" +name = "wnfs-tests" version = "0.1.0" +description = "WebNative filesystem integration tests" +keywords = ["wnfs", "webnative", "ipfs", "decentralisation"] +categories = [ + "filesystem", + "cryptography", + "web programming", +] +license-file = "../../LICENSE" +readme = "README.md" edition = "2021" +repository = "https://github.com/fission-suite/rs-wnfs" +homepage = "https://fission.codes" +authors = ["The Fission Authors"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dev-dependencies] +[dependencies] +wnfs = { path = "../fs", version = "0.1.0" } +wasm-bindgen = { version = "0.2.79", optional = true } +wasm-bindgen-futures = { version = "0.4.29", optional = true } +js-sys = { version = "0.3.56", optional = true } +web-sys = { version = "0.3.56", optional = true } [lib] path = "lib.rs" +[features] +default = ["wasm"] +wasm = ["wasm-bindgen", "wasm-bindgen-futures", "js-sys", "wnfs/wasm"] +web = ["wasm", "web-sys"] diff --git a/packages/wasm/Cargo.toml b/packages/wasm/Cargo.toml index 615d6240..e8b5c7b8 100644 --- a/packages/wasm/Cargo.toml +++ b/packages/wasm/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wasm" +name = "wasm-wnfs" version = "0.1.0" description = "WebNative filesystem WebAssembly API" keywords = ["wnfs", "webnative", "ipfs", "decentralisation"] @@ -8,7 +8,7 @@ categories = [ "cryptography", "web programming", ] -license-file = "LICENSE" +license-file = "../../LICENSE" readme = "README.md" edition = "2021" repository = "https://github.com/fission-suite/rs-wnfs" @@ -16,17 +16,21 @@ homepage = "https://fission.codes" authors = ["The Fission Authors"] [dependencies] -fs = { path = "../fs", version = "0.1.0" } +wnfs = { path = "../fs", version = "0.1.0" } wasm-bindgen = { version = "0.2.79", optional = true } wasm-bindgen-futures = { version = "0.4.29", optional = true } js-sys = { version = "0.3.56", optional = true } web-sys = { version = "0.3.56", optional = true } +chrono = "0.4.19" +anyhow = "1.0.56" [lib] path = "lib.rs" crate-type = ["cdylib" , "rlib"] [features] -default = ["wasm"] -wasm = ["wasm-bindgen", "wasm-bindgen-futures", "fs/wasm", "fs/wasm-bindgen", "js-sys"] +default = ["js"] +wasm = ["wnfs/wasm"] +js = ["wasm", "wasm-bindgen", "wasm-bindgen-futures", "js-sys"] web = ["wasm", "web-sys"] + diff --git a/packages/wasm/fs/mod.rs b/packages/wasm/fs/mod.rs index f40da107..a4f34753 100644 --- a/packages/wasm/fs/mod.rs +++ b/packages/wasm/fs/mod.rs @@ -1,7 +1,28 @@ -use js_sys::Object; +use chrono::Utc; use wasm_bindgen::prelude::*; +use wnfs::{public::PublicDirectory, BlockStore, BlockStoreLookup, IpldCodec, MemoryBlockStore}; #[wasm_bindgen] -pub fn lookup_node(fs: &Object, path: &str, options: &Object) -> Result { - todo!("lookup_node should wrap original implementation") +pub async fn lookup_node() -> String { + let root = PublicDirectory::new(Utc::now()); + + let store = MemoryBlockStore::default(); + + let node = root.lookup_node("Test", &store).await.unwrap(); + + format!("Node lookup done!: {:?}", node) } + +// #[wasm_bindgen] +// pub async fn lookup_node() -> String { +// let mut store = MemoryBlockStore::default(); + +// let cid = store +// .put_block(vec![0, 255, 1], IpldCodec::DagCbor) +// .await +// .unwrap(); + +// let bytes = store.get_block(&cid).await.unwrap(); + +// format!("Node lookup done!: CID = {:?} | bytes = {:?}", cid, bytes) +// }