Compare commits
1 Commits
master
...
5a94fdd4a9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a94fdd4a9 |
251
Cargo.lock
generated
251
Cargo.lock
generated
@@ -4,24 +4,18 @@ version = 3
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.18"
|
version = "0.7.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "2.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
@@ -32,6 +26,15 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bsvfs"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"camino",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "camino"
|
name = "camino"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -62,12 +65,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.82"
|
version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
|
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
@@ -77,9 +77,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.1.5"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
|
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@@ -93,21 +93,15 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "equivalent"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.2"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
|
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"errno-dragonfly",
|
"errno-dragonfly",
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -122,59 +116,65 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.0.0"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
|
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
|
||||||
|
dependencies = [
|
||||||
|
"instant",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.4"
|
version = "0.14.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "instant"
|
||||||
version = "0.14.0"
|
version = "0.1.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "io-lifetimes"
|
||||||
version = "2.0.0"
|
version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"libc",
|
||||||
"hashbrown",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libbsv"
|
name = "libbsv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bsvfs",
|
||||||
"camino",
|
"camino",
|
||||||
"cas-core",
|
"cas-core",
|
||||||
"cas-simple",
|
"cas-simple",
|
||||||
"regex",
|
"regex",
|
||||||
"tempfile",
|
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.147"
|
version = "0.2.140"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.5"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
|
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
@@ -208,18 +208,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.3.5"
|
version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.6.0"
|
version = "1.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -228,43 +228,35 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.27"
|
version = "0.6.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.8"
|
version = "0.36.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
|
checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
|
"io-lifetimes",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.183"
|
version = "1.0.155"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
|
checksum = "71f2b4817415c6d4210bfe1c7bfcf4801b2d904cb4d0e1a8fdb651013c9e86b8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_spanned"
|
|
||||||
version = "0.6.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.9.5"
|
version = "0.9.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
|
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -286,15 +278,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.7.1"
|
version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651"
|
checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -319,43 +311,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.7.6"
|
version = "0.5.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
|
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"toml_edit",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_datetime"
|
|
||||||
version = "0.6.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_edit"
|
|
||||||
version = "0.19.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"serde",
|
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"winnow",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.13.0"
|
version = "1.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
|
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
@@ -365,24 +332,61 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.3"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.42.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.45.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.48.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
|
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc",
|
||||||
@@ -395,51 +399,42 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.48.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.48.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.48.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.48.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.48.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.48.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.48.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winnow"
|
|
||||||
version = "0.5.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5504cc7644f4b593cbc05c4a55bf9bd4e94b867c3c0bd440934174d50482427d"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
|
"bsvfs",
|
||||||
"cas-core",
|
"cas-core",
|
||||||
"cas-simple",
|
"cas-simple",
|
||||||
"libbsv",
|
"libbsv",
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cas-core"
|
name = "bsvfs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Simon Boyé <sim.boye@gmail.com>"]
|
authors = ["Simon Boyé <sim.boye@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0.25"
|
|
||||||
camino = { version = "1.0.7" }
|
camino = { version = "1.0.7" }
|
||||||
|
thiserror = "1.0.25"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
tempfile = "3.2.0"
|
||||||
|
|||||||
491
bsvfs/src/fs.rs
Normal file
491
bsvfs/src/fs.rs
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
// This file is part of bsv.
|
||||||
|
//
|
||||||
|
// bsv is free software: you can redistribute it and/or modify it under the
|
||||||
|
// terms of the GNU Affero General Public License as published by the Free
|
||||||
|
// Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// bsv is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
|
// more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the Affero GNU General Public License
|
||||||
|
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
io::{Read, Result, Seek, Write},
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
|
|
||||||
|
|
||||||
|
// FileType
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum FileType {
|
||||||
|
Dir,
|
||||||
|
File,
|
||||||
|
Symlink,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileType {
|
||||||
|
pub fn is_dir(&self) -> bool {
|
||||||
|
*self == FileType::Dir
|
||||||
|
}
|
||||||
|
pub fn is_file(&self) -> bool {
|
||||||
|
*self == FileType::File
|
||||||
|
}
|
||||||
|
pub fn is_symlink(&self) -> bool {
|
||||||
|
*self == FileType::Symlink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Permissions
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Permissions {
|
||||||
|
read: bool,
|
||||||
|
write: bool,
|
||||||
|
execute: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Permissions {
|
||||||
|
pub fn new(read: bool, write: bool, execute: bool) -> Self {
|
||||||
|
Permissions { read, write, execute }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_read(&self) -> bool {
|
||||||
|
self.read
|
||||||
|
}
|
||||||
|
pub fn is_write(&self) -> bool {
|
||||||
|
self.write
|
||||||
|
}
|
||||||
|
pub fn is_execute(&self) -> bool {
|
||||||
|
self.execute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Permissions {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
f.write_char(if self.read { 'r' } else { '-' })?;
|
||||||
|
f.write_char(if self.write { 'w' } else { '-' })?;
|
||||||
|
f.write_char(if self.execute { 'x' } else { '-' })?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for Permissions {
|
||||||
|
type Err = ParsePermissionError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
if s.len() == 3 {
|
||||||
|
let mut chars = s.chars();
|
||||||
|
let read = parse_permission_char(&mut chars, 'r')?;
|
||||||
|
let write = parse_permission_char(&mut chars, 'w')?;
|
||||||
|
let execute = parse_permission_char(&mut chars, 'x')?;
|
||||||
|
assert!(chars.next().is_none());
|
||||||
|
|
||||||
|
Ok(Self { read, write, execute })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Err(ParsePermissionError::InvalidLength(s.len()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_permission_char(chars: &mut std::str::Chars, bit_char: char)
|
||||||
|
-> std::result::Result<bool, ParsePermissionError>
|
||||||
|
{
|
||||||
|
let c = chars.next().unwrap();
|
||||||
|
if c == bit_char || c == '-' {
|
||||||
|
Ok(c == bit_char)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Err(ParsePermissionError::InvalidChar(bit_char, c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum ParsePermissionError {
|
||||||
|
InvalidLength(usize),
|
||||||
|
InvalidChar(char, char),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ParsePermissionError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::InvalidLength(length) => write!(f, "expected 3 characters, got {}", length),
|
||||||
|
Self::InvalidChar(expected, actual) => write!(f, "exected character {}, got {}", expected, actual),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ParsePermissionError {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
|
||||||
|
pub trait Metadata {
|
||||||
|
fn file_type(&self) -> FileType;
|
||||||
|
fn is_dir(&self) -> bool {
|
||||||
|
self.file_type().is_dir()
|
||||||
|
}
|
||||||
|
fn is_file(&self) -> bool {
|
||||||
|
self.file_type().is_file()
|
||||||
|
}
|
||||||
|
fn is_symlink(&self) -> bool {
|
||||||
|
self.file_type().is_symlink()
|
||||||
|
}
|
||||||
|
fn len(&self) -> u64;
|
||||||
|
fn permissions(&self) -> Permissions;
|
||||||
|
fn modified(&self) -> Result<SystemTime>;
|
||||||
|
fn created(&self) -> Result<SystemTime>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// File
|
||||||
|
|
||||||
|
const OPEN_READ: u8 = 0x01;
|
||||||
|
const OPEN_WRITE: u8 = 0x02;
|
||||||
|
const OPEN_APPEND: u8 = 0x04;
|
||||||
|
const OPEN_TRUNCATE: u8 = 0x08;
|
||||||
|
const OPEN_CREATE: u8 = 0x10;
|
||||||
|
const OPEN_CREATE_NEW: u8 = 0x20;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct OpenOptions {
|
||||||
|
options: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenOptions {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
OpenOptions {
|
||||||
|
options: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_read(&self) -> bool {
|
||||||
|
(self.options & OPEN_READ) != 0
|
||||||
|
}
|
||||||
|
pub fn is_write(&self) -> bool {
|
||||||
|
(self.options & OPEN_WRITE) != 0
|
||||||
|
}
|
||||||
|
pub fn is_append(&self) -> bool {
|
||||||
|
(self.options & OPEN_APPEND) != 0
|
||||||
|
}
|
||||||
|
pub fn is_truncate(&self) -> bool {
|
||||||
|
(self.options & OPEN_TRUNCATE) != 0
|
||||||
|
}
|
||||||
|
pub fn is_create(&self) -> bool {
|
||||||
|
(self.options & OPEN_CREATE) != 0
|
||||||
|
}
|
||||||
|
pub fn is_create_new(&self) -> bool {
|
||||||
|
(self.options & OPEN_CREATE_NEW) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self, read: bool) -> &mut Self {
|
||||||
|
if read {
|
||||||
|
self.options |= OPEN_READ;
|
||||||
|
} else {
|
||||||
|
self.options &= !OPEN_READ;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, write: bool) -> &mut Self {
|
||||||
|
if write {
|
||||||
|
self.options |= OPEN_WRITE;
|
||||||
|
} else {
|
||||||
|
self.options &= !OPEN_WRITE;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append(&mut self, append: bool) -> &mut Self {
|
||||||
|
if append {
|
||||||
|
self.options |= OPEN_APPEND;
|
||||||
|
} else {
|
||||||
|
self.options &= !OPEN_APPEND;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn truncate(&mut self, truncate: bool) -> &mut Self {
|
||||||
|
if truncate {
|
||||||
|
self.options |= OPEN_TRUNCATE;
|
||||||
|
} else {
|
||||||
|
self.options &= !OPEN_TRUNCATE;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(&mut self, create: bool) -> &mut Self {
|
||||||
|
if create {
|
||||||
|
self.options |= OPEN_CREATE;
|
||||||
|
} else {
|
||||||
|
self.options &= !OPEN_CREATE;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_new(&mut self, create_new: bool) -> &mut Self {
|
||||||
|
if create_new {
|
||||||
|
self.options |= OPEN_CREATE_NEW;
|
||||||
|
} else {
|
||||||
|
self.options &= !OPEN_CREATE_NEW;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait File: Sized + Read + Write + Seek {
|
||||||
|
type Metadata: Metadata;
|
||||||
|
|
||||||
|
fn metadata(&self) -> Result<Self::Metadata>;
|
||||||
|
|
||||||
|
fn set_len(&self, size: u64) -> Result<()>;
|
||||||
|
|
||||||
|
fn sync_data(&self) -> Result<()>;
|
||||||
|
fn sync_all(&self) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DirEntry
|
||||||
|
|
||||||
|
pub trait DirEntry {
|
||||||
|
type Metadata: Metadata;
|
||||||
|
|
||||||
|
fn path(&self) -> Result<Utf8PathBuf>;
|
||||||
|
fn metadata(&self) -> Result<Self::Metadata>;
|
||||||
|
fn file_type(&self) -> Result<FileType> {
|
||||||
|
Ok(self.metadata()?.file_type())
|
||||||
|
}
|
||||||
|
fn file_name(&self) -> Result<String> {
|
||||||
|
Ok(
|
||||||
|
self.path()?
|
||||||
|
.file_name()
|
||||||
|
.expect("DirEntry::path() returned an invalid path with no file name")
|
||||||
|
.to_string()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FileSystem
|
||||||
|
|
||||||
|
pub trait FileSystem {
|
||||||
|
type File: File;
|
||||||
|
type DirEntry: DirEntry;
|
||||||
|
type ReadDir: Iterator<Item=Result<Self::DirEntry>>;
|
||||||
|
|
||||||
|
fn open_with_options<P: AsRef<Utf8Path>>(&self, path: P, options: &OpenOptions) -> Result<Self::File>;
|
||||||
|
|
||||||
|
fn open<P: AsRef<Utf8Path>>(&self, path: P) -> Result<Self::File> {
|
||||||
|
self.open_with_options(
|
||||||
|
path,
|
||||||
|
OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn create_or_truncate<P: AsRef<Utf8Path>>(&self, path: P) -> Result<Self::File> {
|
||||||
|
self.open_with_options(
|
||||||
|
path,
|
||||||
|
OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn create_new<P: AsRef<Utf8Path>>(&self, path: P) -> Result<Self::File> {
|
||||||
|
self.open_with_options(
|
||||||
|
path,
|
||||||
|
OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_dir<P: AsRef<Utf8Path>>(&self, path: P) -> Result<Self::ReadDir>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_file_type() {
|
||||||
|
let dir = FileType::Dir;
|
||||||
|
assert!(dir.is_dir());
|
||||||
|
assert!(!dir.is_file());
|
||||||
|
assert!(!dir.is_symlink());
|
||||||
|
|
||||||
|
let file = FileType::File;
|
||||||
|
assert!(!file.is_dir());
|
||||||
|
assert!(file.is_file());
|
||||||
|
assert!(!file.is_symlink());
|
||||||
|
|
||||||
|
let symlink = FileType::Symlink;
|
||||||
|
assert!(!symlink.is_dir());
|
||||||
|
assert!(!symlink.is_file());
|
||||||
|
assert!(symlink.is_symlink());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_open_options() {
|
||||||
|
let mut options = OpenOptions::new();
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
|
||||||
|
options.read(true);
|
||||||
|
assert!(options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
|
||||||
|
options.read(false);
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
|
||||||
|
options.write(true);
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
|
||||||
|
options.write(false);
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
|
||||||
|
options.append(true);
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
|
||||||
|
options.append(false);
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
|
||||||
|
options.truncate(true);
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
|
||||||
|
options.truncate(false);
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
|
||||||
|
options.create(true);
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
|
||||||
|
options.create(false);
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
|
||||||
|
options.create_new(true);
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(options.is_create_new());
|
||||||
|
|
||||||
|
options.create_new(false);
|
||||||
|
assert!(!options.is_read());
|
||||||
|
assert!(!options.is_write());
|
||||||
|
assert!(!options.is_append());
|
||||||
|
assert!(!options.is_truncate());
|
||||||
|
assert!(!options.is_create());
|
||||||
|
assert!(!options.is_create_new());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_permission_display() {
|
||||||
|
let expected = [
|
||||||
|
"---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx",
|
||||||
|
];
|
||||||
|
|
||||||
|
for i in 0..8 {
|
||||||
|
let read = (i & 0x04) != 0;
|
||||||
|
let write = (i & 0x02) != 0;
|
||||||
|
let execute = (i & 0x01) != 0;
|
||||||
|
|
||||||
|
let perm = Permissions { read, write, execute };
|
||||||
|
let perm_str = perm.to_string();
|
||||||
|
|
||||||
|
assert_eq!(perm_str, expected[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_permission_from_str() {
|
||||||
|
let perms = [
|
||||||
|
"---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx",
|
||||||
|
];
|
||||||
|
|
||||||
|
for i in 0..8 {
|
||||||
|
let read = (i & 0x04) != 0;
|
||||||
|
let write = (i & 0x02) != 0;
|
||||||
|
let execute = (i & 0x01) != 0;
|
||||||
|
|
||||||
|
let perm = Permissions::from_str(perms[i]).unwrap();
|
||||||
|
let expected = Permissions { read, write, execute };
|
||||||
|
|
||||||
|
assert_eq!(perm, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(Permissions::from_str("rw"), Err(ParsePermissionError::InvalidLength(2)));
|
||||||
|
assert_eq!(Permissions::from_str("rw--"), Err(ParsePermissionError::InvalidLength(4)));
|
||||||
|
assert_eq!(Permissions::from_str("-x-"), Err(ParsePermissionError::InvalidChar('w', 'x')));
|
||||||
|
assert_eq!(Permissions::from_str("123"), Err(ParsePermissionError::InvalidChar('r', '1')));
|
||||||
|
}
|
||||||
|
}
|
||||||
386
bsvfs/src/fs_impl.rs
Normal file
386
bsvfs/src/fs_impl.rs
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
// This file is part of bsv.
|
||||||
|
//
|
||||||
|
// bsv is free software: you can redistribute it and/or modify it under the
|
||||||
|
// terms of the GNU Affero General Public License as published by the Free
|
||||||
|
// Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// bsv is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
|
// more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the Affero GNU General Public License
|
||||||
|
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
ffi::OsString,
|
||||||
|
fmt::{Arguments, Display},
|
||||||
|
fs::{
|
||||||
|
self,
|
||||||
|
DirEntry as StdDirEntry,
|
||||||
|
File as StdFile,
|
||||||
|
Metadata as StdMetadata,
|
||||||
|
ReadDir as StdReadDir,
|
||||||
|
},
|
||||||
|
io::{self, IoSlice, IoSliceMut, Read, Result, Seek, SeekFrom, Write},
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
fs::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct NonUtf8Filename {
|
||||||
|
filename: OsString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NonUtf8Filename {
|
||||||
|
fn new(filename: OsString) -> Self {
|
||||||
|
Self {
|
||||||
|
filename: filename,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for NonUtf8Filename {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Non UTF-8 filename: '{:?}'", self.filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for NonUtf8Filename {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn as_io_error<E>(error: E) -> io::Error
|
||||||
|
where E: Into<Box<dyn Error + Send + Sync>>,
|
||||||
|
{
|
||||||
|
io::Error::new(io::ErrorKind::Other, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_string(os_string: OsString) -> Result<String> {
|
||||||
|
os_string.into_string()
|
||||||
|
.map_err(|filename| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
NonUtf8Filename::new(filename),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn as_filetype(std_filetype: fs::FileType) -> FileType {
|
||||||
|
if std_filetype.is_dir() {
|
||||||
|
FileType::Dir
|
||||||
|
} else if std_filetype.is_file() {
|
||||||
|
FileType::File
|
||||||
|
} else if std_filetype.is_symlink() {
|
||||||
|
FileType::Symlink
|
||||||
|
} else {
|
||||||
|
FileType::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SysMetadata {
|
||||||
|
metadata: StdMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Metadata for SysMetadata {
|
||||||
|
fn file_type(&self) -> FileType {
|
||||||
|
as_filetype(self.metadata.file_type())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> u64 {
|
||||||
|
self.metadata.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn permissions(&self) -> Permissions {
|
||||||
|
Permissions(true, self.metadata.permissions().readonly(), false)
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn permissions(&self) -> Permissions {
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
|
let mode = self.metadata.mode();
|
||||||
|
Permissions::new(
|
||||||
|
mode & 0o100 != 0,
|
||||||
|
mode & 0o200 != 0,
|
||||||
|
mode & 0o400 != 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn modified(&self) -> Result<SystemTime> {
|
||||||
|
Ok(self.metadata.modified()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn created(&self) -> Result<SystemTime> {
|
||||||
|
Ok(self.metadata.created()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StdMetadata> for SysMetadata {
|
||||||
|
fn from(value: StdMetadata) -> Self {
|
||||||
|
SysMetadata {
|
||||||
|
metadata: value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct SysFile {
|
||||||
|
file: StdFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl File for SysFile {
|
||||||
|
type Metadata = SysMetadata;
|
||||||
|
|
||||||
|
fn metadata(&self) -> Result<Self::Metadata> {
|
||||||
|
Ok(self.file.metadata()?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_len(&self, size: u64) -> Result<()> {
|
||||||
|
Ok(self.file.set_len(size)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_data(&self) -> Result<()> {
|
||||||
|
Ok(self.file.sync_data()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_all(&self) -> Result<()> {
|
||||||
|
Ok(self.file.sync_all()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for SysFile {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||||
|
self.file.read(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
|
||||||
|
self.file.read_vectored(bufs)
|
||||||
|
}
|
||||||
|
// fn is_read_vectored(&self) -> bool {
|
||||||
|
// self.file.is_read_vectored()
|
||||||
|
// }
|
||||||
|
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
|
||||||
|
self.file.read_to_end(buf)
|
||||||
|
}
|
||||||
|
fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
|
||||||
|
self.file.read_to_string(buf)
|
||||||
|
}
|
||||||
|
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
|
||||||
|
self.file.read_exact(buf)
|
||||||
|
}
|
||||||
|
// fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> {
|
||||||
|
// self.file.read_buf(buf)
|
||||||
|
// }
|
||||||
|
// fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> {
|
||||||
|
// self.file.read_buf_exact(cursor)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for SysFile {
|
||||||
|
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
|
||||||
|
self.file.seek(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rewind(&mut self) -> Result<()> {
|
||||||
|
self.file.rewind()
|
||||||
|
}
|
||||||
|
// fn stream_len(&mut self) -> Result<u64> {
|
||||||
|
// self.file.stream_len()
|
||||||
|
// }
|
||||||
|
fn stream_position(&mut self) -> Result<u64> {
|
||||||
|
self.file.stream_position()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for SysFile {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||||
|
self.file.write(buf)
|
||||||
|
}
|
||||||
|
fn flush(&mut self) -> Result<()> {
|
||||||
|
self.file.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
|
||||||
|
self.file.write_vectored(bufs)
|
||||||
|
}
|
||||||
|
// fn is_write_vectored(&self) -> bool {
|
||||||
|
// self.file.is_write_vectored()
|
||||||
|
// }
|
||||||
|
fn write_all(&mut self, buf: &[u8]) -> Result<()> {
|
||||||
|
self.file.write_all(buf)
|
||||||
|
}
|
||||||
|
// fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> {
|
||||||
|
// self.file.write_all_vectored(bufs)
|
||||||
|
// }
|
||||||
|
fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> {
|
||||||
|
self.file.write_fmt(fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct SysDirEntry {
|
||||||
|
dir_entry: StdDirEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DirEntry for SysDirEntry {
|
||||||
|
type Metadata = SysMetadata;
|
||||||
|
|
||||||
|
fn path(&self) -> Result<Utf8PathBuf> {
|
||||||
|
Utf8PathBuf::try_from(
|
||||||
|
self.dir_entry.path()
|
||||||
|
).map_err(as_io_error)
|
||||||
|
}
|
||||||
|
fn metadata(&self) -> Result<Self::Metadata> {
|
||||||
|
Ok(self.dir_entry.metadata()?.into())
|
||||||
|
}
|
||||||
|
fn file_type(&self) -> Result<FileType> {
|
||||||
|
Ok(as_filetype(self.dir_entry.file_type()?))
|
||||||
|
}
|
||||||
|
fn file_name(&self) -> Result<String> {
|
||||||
|
as_string(self.dir_entry.file_name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StdDirEntry> for SysDirEntry {
|
||||||
|
fn from(dir_entry: StdDirEntry) -> Self {
|
||||||
|
SysDirEntry {
|
||||||
|
dir_entry: dir_entry,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SysReadDir {
|
||||||
|
read_dir: StdReadDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for SysReadDir {
|
||||||
|
type Item = Result<SysDirEntry>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self.read_dir.next() {
|
||||||
|
Some(Ok(dir_entry)) => Some(Ok(dir_entry.into())),
|
||||||
|
Some(Err(error)) => Some(Err(error)),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct SysFileSystem;
|
||||||
|
|
||||||
|
impl SysFileSystem {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSystem for SysFileSystem {
|
||||||
|
type File = SysFile;
|
||||||
|
type DirEntry = SysDirEntry;
|
||||||
|
type ReadDir = SysReadDir;
|
||||||
|
|
||||||
|
fn open_with_options<P: AsRef<Utf8Path>>(&self, path: P, options: &OpenOptions) -> Result<Self::File> {
|
||||||
|
Ok(SysFile{
|
||||||
|
file: StdFile::options()
|
||||||
|
.read(options.is_read())
|
||||||
|
.write(options.is_write())
|
||||||
|
.append(options.is_append())
|
||||||
|
.truncate(options.is_truncate())
|
||||||
|
.create(options.is_create())
|
||||||
|
.create_new(options.is_create_new())
|
||||||
|
.open(path.as_ref().as_std_path())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_dir<P: AsRef<Utf8Path>>(&self, path: P) -> Result<SysReadDir> {
|
||||||
|
Ok(SysReadDir {
|
||||||
|
read_dir: fs::read_dir(path.as_ref().as_std_path())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use camino::Utf8PathBuf;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::FileSystem;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_file_read_write() {
|
||||||
|
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
||||||
|
let file_path = Utf8PathBuf::from_path_buf(dir.path().join("test.txt")).unwrap();
|
||||||
|
|
||||||
|
let message = b"Hello world!\nThis is a test\n";
|
||||||
|
|
||||||
|
let fs = SysFileSystem::new();
|
||||||
|
let mut file = fs.create_new(&file_path).unwrap();
|
||||||
|
file.write(message).unwrap();
|
||||||
|
drop(file);
|
||||||
|
|
||||||
|
let mut file = fs.open(&file_path).unwrap();
|
||||||
|
let mut buf = [0u8; 256];
|
||||||
|
let read_size = file.read(&mut buf).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(read_size, message.len());
|
||||||
|
assert_eq!(&buf[..message.len()], message);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_dir() {
|
||||||
|
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
||||||
|
let dir_path = Utf8Path::from_path(dir.path()).unwrap();
|
||||||
|
let file0_path = dir_path.join("file.txt");
|
||||||
|
let file1_path = dir_path.join("zzz.txt");
|
||||||
|
let dir0_path = dir_path.join("some_dir");
|
||||||
|
|
||||||
|
let fs = SysFileSystem::new();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut file = fs.create_new(file0_path).unwrap();
|
||||||
|
file.write(b"file.txt").unwrap();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let mut file = fs.create_new(file1_path).unwrap();
|
||||||
|
file.write(b"zzz.txt").unwrap();
|
||||||
|
}
|
||||||
|
fs::create_dir(dir0_path).unwrap();
|
||||||
|
|
||||||
|
let mut entries = fs.read_dir(dir_path).unwrap()
|
||||||
|
.collect::<Result<Vec<_>>>().unwrap();
|
||||||
|
entries.sort_by_key(|entry| entry.file_name().unwrap());
|
||||||
|
|
||||||
|
let mut it = entries.iter();
|
||||||
|
|
||||||
|
let file0_entry = it.next().unwrap();
|
||||||
|
assert_eq!(file0_entry.file_name().unwrap(), "file.txt");
|
||||||
|
assert_eq!(file0_entry.file_type().unwrap(), FileType::File);
|
||||||
|
let file0_metadata = file0_entry.metadata().unwrap();
|
||||||
|
assert!(file0_metadata.is_file());
|
||||||
|
assert_eq!(file0_metadata.len(), 8);
|
||||||
|
|
||||||
|
let dir0_entry = it.next().unwrap();
|
||||||
|
assert_eq!(dir0_entry.file_name().unwrap(), "some_dir");
|
||||||
|
assert_eq!(dir0_entry.file_type().unwrap(), FileType::Dir);
|
||||||
|
let dir0_metadata = dir0_entry.metadata().unwrap();
|
||||||
|
assert!(dir0_metadata.is_dir());
|
||||||
|
|
||||||
|
let file1_entry = it.next().unwrap();
|
||||||
|
assert_eq!(file1_entry.file_name().unwrap(), "zzz.txt");
|
||||||
|
assert_eq!(file1_entry.file_type().unwrap(), FileType::File);
|
||||||
|
let file1_metadata = file1_entry.metadata().unwrap();
|
||||||
|
assert!(file1_metadata.is_file());
|
||||||
|
assert_eq!(file1_metadata.len(), 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// This file is part of bsv.
|
||||||
|
//
|
||||||
|
// bsv is free software: you can redistribute it and/or modify it under the
|
||||||
|
// terms of the GNU Affero General Public License as published by the Free
|
||||||
|
// Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// bsv is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
|
// more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the Affero GNU General Public License
|
||||||
|
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
extern crate camino;
|
||||||
|
extern crate thiserror;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate tempfile;
|
||||||
|
|
||||||
|
|
||||||
|
// mod error;
|
||||||
|
mod fs;
|
||||||
|
mod fs_impl;
|
||||||
|
mod mem_fs;
|
||||||
|
|
||||||
|
|
||||||
|
pub use crate::{
|
||||||
|
fs::*,
|
||||||
|
fs_impl::*,
|
||||||
|
};
|
||||||
|
|||||||
468
bsvfs/src/mem_fs.rs
Normal file
468
bsvfs/src/mem_fs.rs
Normal file
@@ -0,0 +1,468 @@
|
|||||||
|
// This file is part of bsv.
|
||||||
|
//
|
||||||
|
// bsv is free software: you can redistribute it and/or modify it under the
|
||||||
|
// terms of the GNU Affero General Public License as published by the Free
|
||||||
|
// Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// bsv is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
|
// more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the Affero GNU General Public License
|
||||||
|
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fmt::{Arguments},
|
||||||
|
io::{Cursor, Error, IoSlice, IoSliceMut, Read, Result, Seek, SeekFrom, Write, ErrorKind},
|
||||||
|
time::SystemTime, rc::Rc, cell::RefCell,
|
||||||
|
};
|
||||||
|
|
||||||
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
fs::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct MemMetadata {
|
||||||
|
file_type: FileType,
|
||||||
|
len: u64,
|
||||||
|
permissions: Permissions,
|
||||||
|
modified: SystemTime,
|
||||||
|
created: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Metadata for MemMetadata {
|
||||||
|
fn file_type(&self) -> FileType {
|
||||||
|
self.file_type
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> u64 {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permissions(&self) -> Permissions {
|
||||||
|
self.permissions.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn modified(&self) -> Result<SystemTime> {
|
||||||
|
Ok(self.modified)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn created(&self) -> Result<SystemTime> {
|
||||||
|
Ok(self.created)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct MemFile {
|
||||||
|
metadata: MemMetadata,
|
||||||
|
data: RefCell<Cursor<Vec<u8>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl File for MemFile {
|
||||||
|
type Metadata = MemMetadata;
|
||||||
|
|
||||||
|
// fn open_with_options<P: AsRef<Utf8Path>>(path: P, options: &OpenOptions) -> Result<Self> {
|
||||||
|
// Ok(SysFile{
|
||||||
|
// file: StdFile::options()
|
||||||
|
// .read(options.is_read())
|
||||||
|
// .write(options.is_write())
|
||||||
|
// .append(options.is_append())
|
||||||
|
// .truncate(options.is_truncate())
|
||||||
|
// .create(options.is_create())
|
||||||
|
// .create_new(options.is_create_new())
|
||||||
|
// .open(path.as_ref().as_std_path())?,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn metadata(&self) -> Result<Self::Metadata> {
|
||||||
|
Ok(self.metadata.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_len(&self, size: u64) -> Result<()> {
|
||||||
|
let mut data = self.data.borrow_mut();
|
||||||
|
let pos = data.position();
|
||||||
|
data.get_mut().resize(size as usize, 0);
|
||||||
|
if size < pos {
|
||||||
|
data.seek(SeekFrom::Start(pos));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_data(&self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_all(&self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for MemFile {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.read(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.read_vectored(bufs)
|
||||||
|
}
|
||||||
|
// fn is_read_vectored(&self) -> bool {
|
||||||
|
// self.data.is_read_vectored()
|
||||||
|
// }
|
||||||
|
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.read_to_end(buf)
|
||||||
|
}
|
||||||
|
fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.read_to_string(buf)
|
||||||
|
}
|
||||||
|
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.read_exact(buf)
|
||||||
|
}
|
||||||
|
// fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> {
|
||||||
|
// self.data.read_buf(buf)
|
||||||
|
// }
|
||||||
|
// fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> {
|
||||||
|
// self.data.read_buf_exact(cursor)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for MemFile {
|
||||||
|
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.seek(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rewind(&mut self) -> Result<()> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.rewind()
|
||||||
|
}
|
||||||
|
// fn stream_len(&mut self) -> Result<u64> {
|
||||||
|
// self.data.stream_len()
|
||||||
|
// }
|
||||||
|
fn stream_position(&mut self) -> Result<u64> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.stream_position()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for MemFile {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.write(buf)
|
||||||
|
}
|
||||||
|
fn flush(&mut self) -> Result<()> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.write_vectored(bufs)
|
||||||
|
}
|
||||||
|
// fn is_write_vectored(&self) -> bool {
|
||||||
|
// self.data.is_write_vectored()
|
||||||
|
// }
|
||||||
|
fn write_all(&mut self, buf: &[u8]) -> Result<()> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.write_all(buf)
|
||||||
|
}
|
||||||
|
// fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> {
|
||||||
|
// self.data.write_all_vectored(bufs)
|
||||||
|
// }
|
||||||
|
fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> {
|
||||||
|
let data = self.data.get_mut();
|
||||||
|
data.write_fmt(fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct MemDir {
|
||||||
|
metadata: MemMetadata,
|
||||||
|
items: Vec<Rc<RefCell<DirItem>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemDir {
|
||||||
|
fn new() -> Self {
|
||||||
|
let time = SystemTime::now();
|
||||||
|
Self::new_with_time(&time)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_with_time(time: &SystemTime) -> Self {
|
||||||
|
MemDir {
|
||||||
|
metadata: MemMetadata {
|
||||||
|
file_type: FileType::Dir,
|
||||||
|
len: 0,
|
||||||
|
permissions: Permissions::new(true, true, false),
|
||||||
|
modified: time.clone(),
|
||||||
|
created: time.clone(),
|
||||||
|
},
|
||||||
|
items: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, name: &str) -> Option<&DirItem> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn get_mut(&self, name: &str) -> Option<&mut DirItem> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(&self, name: &str) -> std::result::Result<usize, usize> {
|
||||||
|
self.items.binary_search_by(
|
||||||
|
|item| item.borrow().name().cmp(name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum DirItem {
|
||||||
|
Dir(String, MemDir),
|
||||||
|
File(String, MemFile),
|
||||||
|
Err(String, ErrorKind, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DirItem {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
DirItem::Dir(ref name, _) => name,
|
||||||
|
DirItem::File(ref name, _) => name,
|
||||||
|
DirItem::Err(ref name, _, _) => name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct MemDirEntry {
|
||||||
|
path: Utf8PathBuf,
|
||||||
|
metadata: MemMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DirEntry for MemDirEntry {
|
||||||
|
type Metadata = MemMetadata;
|
||||||
|
|
||||||
|
fn path(&self) -> Result<Utf8PathBuf> {
|
||||||
|
Ok(self.path.clone())
|
||||||
|
}
|
||||||
|
fn metadata(&self) -> Result<Self::Metadata> {
|
||||||
|
Ok(self.metadata.clone())
|
||||||
|
}
|
||||||
|
fn file_type(&self) -> Result<FileType> {
|
||||||
|
Ok(self.metadata.file_type())
|
||||||
|
}
|
||||||
|
fn file_name(&self) -> Result<String> {
|
||||||
|
Ok(self.path.file_name().unwrap().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MemReadDir {
|
||||||
|
base_path: Utf8PathBuf,
|
||||||
|
items: Vec<DirItem>,
|
||||||
|
index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for MemReadDir {
|
||||||
|
type Item = Result<MemDirEntry>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self.items.get(self.index) {
|
||||||
|
Some(DirItem::Dir(ref name, ref dir)) => {
|
||||||
|
let mut path = self.base_path.clone();
|
||||||
|
path.push(name);
|
||||||
|
|
||||||
|
self.index += 1;
|
||||||
|
Some(Ok(MemDirEntry{path, metadata: dir.metadata.clone()}))
|
||||||
|
},
|
||||||
|
Some(DirItem::File(ref name, ref file)) => {
|
||||||
|
let mut path = self.base_path.clone();
|
||||||
|
path.push(name);
|
||||||
|
|
||||||
|
self.index += 1;
|
||||||
|
Some(Ok(MemDirEntry{path, metadata: file.metadata.clone()}))
|
||||||
|
},
|
||||||
|
Some(DirItem::Err(_, kind, ref msg)) => {
|
||||||
|
self.index += 1;
|
||||||
|
Some(Err(Error::new(*kind, msg.clone())))
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct MemFileSystem {
|
||||||
|
root: MemDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemFileSystem {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
root: MemDir::new_with_time(&SystemTime::UNIX_EPOCH),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mkdir<P: AsRef<Utf8Path>>(&mut self, path: P, parents: bool, exist_ok: bool) -> Result<()> {
|
||||||
|
use camino::Utf8Component::*;
|
||||||
|
let mut parents = Vec::new();
|
||||||
|
let mut iter = path.as_ref().components();
|
||||||
|
|
||||||
|
match iter.next() {
|
||||||
|
Some(Prefix(_)) => {
|
||||||
|
return make_err("Windows-like path are not supported by MemFileSystem");
|
||||||
|
},
|
||||||
|
Some(RootDir) => {
|
||||||
|
parents.push(&mut self.root);
|
||||||
|
},
|
||||||
|
Some(_) => {
|
||||||
|
return make_err("path must be absolute");
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
return make_err("path is empty")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for comp in iter {
|
||||||
|
match comp {
|
||||||
|
Prefix(_) |
|
||||||
|
RootDir => {
|
||||||
|
panic!();
|
||||||
|
},
|
||||||
|
CurDir => {},
|
||||||
|
ParentDir => {
|
||||||
|
if parents.len() <= 1 {
|
||||||
|
return make_err("invalid path")
|
||||||
|
}
|
||||||
|
parents.pop();
|
||||||
|
},
|
||||||
|
Normal(name) => {
|
||||||
|
let maybe_item = parents.last().unwrap().get_mut(name);
|
||||||
|
match maybe_item {
|
||||||
|
Some(DirItem::Dir(_, ref mut dir)) => {
|
||||||
|
parents.push(dir);
|
||||||
|
},
|
||||||
|
Some(DirItem::File(_, _)) => {
|
||||||
|
return make_err("item is a file")
|
||||||
|
}
|
||||||
|
Some(DirItem::Err(_, kind, msg)) => {
|
||||||
|
return Err(std::io::Error::new(*kind, &**msg));
|
||||||
|
}
|
||||||
|
None => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
make_err("TODO")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSystem for MemFileSystem {
|
||||||
|
type File = MemFile;
|
||||||
|
type DirEntry = MemDirEntry;
|
||||||
|
type ReadDir = MemReadDir;
|
||||||
|
|
||||||
|
fn open_with_options<P: AsRef<Utf8Path>>(&self, path: P, options: &OpenOptions) -> Result<Self::File> {
|
||||||
|
make_err("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_dir<P: AsRef<Utf8Path>>(&self, path: P) -> Result<MemReadDir> {
|
||||||
|
Ok(MemReadDir {
|
||||||
|
base_path: path.as_ref().into(),
|
||||||
|
items: vec![], // TODO
|
||||||
|
index: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn make_error(message: &str) -> std::io::Error {
|
||||||
|
std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn make_err<T>(message: &str) -> Result<T> {
|
||||||
|
Err(make_error(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
// use camino::Utf8PathBuf;
|
||||||
|
|
||||||
|
// use super::*;
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn test_file_read_write() {
|
||||||
|
// let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
||||||
|
// let file_path = Utf8PathBuf::from_path_buf(dir.path().join("test.txt")).unwrap();
|
||||||
|
|
||||||
|
// let message = b"Hello world!\nThis is a test\n";
|
||||||
|
|
||||||
|
// let mut file = SysFile::create_new(&file_path).unwrap();
|
||||||
|
// file.write(message).unwrap();
|
||||||
|
// drop(file);
|
||||||
|
|
||||||
|
// let mut file = SysFile::open(&file_path).unwrap();
|
||||||
|
// let mut buf = [0u8; 256];
|
||||||
|
// let read_size = file.read(&mut buf).unwrap();
|
||||||
|
|
||||||
|
// assert_eq!(read_size, message.len());
|
||||||
|
// assert_eq!(&buf[..message.len()], message);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn test_read_dir() {
|
||||||
|
// let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
||||||
|
// let dir_path = Utf8Path::from_path(dir.path()).unwrap();
|
||||||
|
// let file0_path = dir_path.join("file.txt");
|
||||||
|
// let file1_path = dir_path.join("zzz.txt");
|
||||||
|
// let dir0_path = dir_path.join("some_dir");
|
||||||
|
|
||||||
|
// {
|
||||||
|
// let mut file = SysFile::create_new(file0_path).unwrap();
|
||||||
|
// file.write(b"file.txt").unwrap();
|
||||||
|
// }
|
||||||
|
// {
|
||||||
|
// let mut file = SysFile::create_new(file1_path).unwrap();
|
||||||
|
// file.write(b"zzz.txt").unwrap();
|
||||||
|
// }
|
||||||
|
// fs::create_dir(dir0_path).unwrap();
|
||||||
|
|
||||||
|
// let mut entries = SysFileSystem::read_dir(dir_path).unwrap()
|
||||||
|
// .collect::<Result<Vec<_>>>().unwrap();
|
||||||
|
// entries.sort_by_key(|entry| entry.file_name().unwrap());
|
||||||
|
|
||||||
|
// let mut it = entries.iter();
|
||||||
|
|
||||||
|
// let file0_entry = it.next().unwrap();
|
||||||
|
// assert_eq!(file0_entry.file_name().unwrap(), "file.txt");
|
||||||
|
// assert_eq!(file0_entry.file_type().unwrap(), FileType::File);
|
||||||
|
// let file0_metadata = file0_entry.metadata().unwrap();
|
||||||
|
// assert!(file0_metadata.is_file());
|
||||||
|
// assert_eq!(file0_metadata.len(), 8);
|
||||||
|
|
||||||
|
// let dir0_entry = it.next().unwrap();
|
||||||
|
// assert_eq!(dir0_entry.file_name().unwrap(), "some_dir");
|
||||||
|
// assert_eq!(dir0_entry.file_type().unwrap(), FileType::Dir);
|
||||||
|
// let dir0_metadata = dir0_entry.metadata().unwrap();
|
||||||
|
// assert!(dir0_metadata.is_dir());
|
||||||
|
|
||||||
|
// let file1_entry = it.next().unwrap();
|
||||||
|
// assert_eq!(file1_entry.file_name().unwrap(), "zzz.txt");
|
||||||
|
// assert_eq!(file1_entry.file_type().unwrap(), FileType::File);
|
||||||
|
// let file1_metadata = file1_entry.metadata().unwrap();
|
||||||
|
// assert!(file1_metadata.is_file());
|
||||||
|
// assert_eq!(file1_metadata.len(), 7);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -63,10 +63,7 @@ pub trait Cas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait RefStore {
|
pub trait RefStore {
|
||||||
fn get_ref(&self, key: &Utf8Path) -> Result<ObjectId>;
|
fn get_ref<P: AsRef<Utf8Path>>(&self, key: P) -> Result<ObjectId>;
|
||||||
fn set_ref(&mut self, key: &Utf8Path, value: &ObjectId) -> Result<()>;
|
fn set_ref<P: AsRef<Utf8Path>>(&mut self, key: P, value: &ObjectId) -> Result<()>;
|
||||||
fn remove_ref(&mut self, key: &Utf8Path) -> Result<()>;
|
fn remove_ref<P: AsRef<Utf8Path>>(&mut self, key: P) -> Result<()>;
|
||||||
}
|
|
||||||
|
|
||||||
pub trait CasWithRef: Cas + RefStore {
|
|
||||||
}
|
}
|
||||||
@@ -16,10 +16,6 @@
|
|||||||
|
|
||||||
// use std::path::Utf8PathBuf;
|
// use std::path::Utf8PathBuf;
|
||||||
|
|
||||||
use std::path::StripPrefixError;
|
|
||||||
|
|
||||||
use crate::ObjectId;
|
|
||||||
|
|
||||||
|
|
||||||
/// Result type used through cas-core.
|
/// Result type used through cas-core.
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@@ -91,9 +87,6 @@ pub enum Error {
|
|||||||
#[error("non-unicode file name: '{0}'")]
|
#[error("non-unicode file name: '{0}'")]
|
||||||
NonUnicodeFileName(String),
|
NonUnicodeFileName(String),
|
||||||
|
|
||||||
#[error("object {0} does not exists")]
|
|
||||||
ObjectDoesNotExists(ObjectId),
|
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
UnknownError(String),
|
UnknownError(String),
|
||||||
}
|
}
|
||||||
@@ -136,11 +129,6 @@ impl Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<StripPrefixError> for Error {
|
|
||||||
fn from(value: StripPrefixError) -> Self {
|
|
||||||
Error::unknown(format!("Invalid operation: {}", value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn format_optional_path(maybe_path: &Option<Utf8PathBuf>) -> String {
|
// fn format_optional_path(maybe_path: &Option<Utf8PathBuf>) -> String {
|
||||||
// match maybe_path {
|
// match maybe_path {
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ mod cas;
|
|||||||
pub use crate::{
|
pub use crate::{
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
object_id::{ObjectId, hex, write_hex},
|
object_id::{ObjectId, hex, write_hex},
|
||||||
object_type::ObjectType,
|
object_type::{ObjectType},
|
||||||
object_metadata::ObjectMetadata,
|
object_metadata::{ObjectMetadata},
|
||||||
pipeline::{Pipeline, DefaultPipeline, Reader, Writer, ReadWrapper, WriteWrapper},
|
pipeline::{Pipeline, DefaultPipeline, Reader, Writer, ReadWrapper, WriteWrapper},
|
||||||
cas::{Cas, CasWithRef, RefStore},
|
cas::{Cas, RefStore},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
use super::object_type::ObjectType;
|
use super::object_type::{ObjectType};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ license = "AGPL-3.0-or-later"
|
|||||||
digest = { version = "0.9.0", features = ["alloc"] }
|
digest = { version = "0.9.0", features = ["alloc"] }
|
||||||
sha2 = "0.9.5"
|
sha2 = "0.9.5"
|
||||||
camino = { version = "1.0.7" }
|
camino = { version = "1.0.7" }
|
||||||
toml = "0.7.6"
|
toml = "0.5.8"
|
||||||
cas-core = { path = "../cas-core" }
|
cas-core = { path = "../cas-core" }
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -17,75 +17,60 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use digest::DynDigest;
|
use digest::DynDigest;
|
||||||
use toml::{Value, Table};
|
use toml::Value;
|
||||||
use camino::{Utf8Path, Utf8PathBuf};
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
use cas_core::{
|
use cas_core::{
|
||||||
Cas, DefaultPipeline, err, Error, ObjectId, ObjectMetadata, ObjectType,
|
Cas, DefaultPipeline, err, Error, ObjectId, ObjectMetadata, ObjectType,
|
||||||
Pipeline, Reader, ReadWrapper, RefStore, Result, Writer, CasWithRef,
|
Pipeline, Reader, ReadWrapper, RefStore, Result, Writer,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
obj_dir, obj_path, ref_dir, tmp_dir,
|
obj_dir, obj_path, ref_dir, tmp_dir,
|
||||||
|
read_config, write_config,
|
||||||
read_metadata, write_metadata,
|
read_metadata, write_metadata,
|
||||||
new_digest,
|
new_digest,
|
||||||
};
|
};
|
||||||
use crate::wfile::WFile;
|
use crate::wfile::WFile;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct SimpleCasConfig {
|
|
||||||
pub digest_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SimpleCasConfig {
|
|
||||||
pub fn new(digest_id: String) -> Self {
|
|
||||||
Self {
|
|
||||||
digest_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_toml_value(value: &Value) -> Result<Self> {
|
|
||||||
if let Value::Table(ref table) = value {
|
|
||||||
let engine =
|
|
||||||
table.get("engine")
|
|
||||||
.ok_or_else(|| Error::unknown("missing engine field in cas config"))?
|
|
||||||
.as_str()
|
|
||||||
.ok_or_else(|| Error::unknown("cas engine must be a string"))?;
|
|
||||||
if engine != "simple" {
|
|
||||||
return err!("expected simple cas engine");
|
|
||||||
}
|
|
||||||
let digest_id =
|
|
||||||
table.get("digest")
|
|
||||||
.ok_or_else(|| Error::unknown("missing digest field in cas config"))?
|
|
||||||
.as_str()
|
|
||||||
.ok_or_else(|| Error::unknown("cas digest must be a string"))?;
|
|
||||||
Ok(Self {
|
|
||||||
digest_id: digest_id.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
err!("cas config must be a table")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_toml_value(&self) -> Result<Value> {
|
|
||||||
let mut table = Table::new();
|
|
||||||
table["engine"] = "simple".into();
|
|
||||||
table["digest"] = "sha256".into();
|
|
||||||
return Ok(Value::Table(table))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub struct SimpleCas {
|
pub struct SimpleCas {
|
||||||
config: SimpleCasConfig,
|
|
||||||
db_path: Utf8PathBuf,
|
db_path: Utf8PathBuf,
|
||||||
digest: Box<dyn DynDigest>,
|
digest: Box<dyn DynDigest>,
|
||||||
pipeline: DefaultPipeline,
|
pipeline: DefaultPipeline,
|
||||||
|
// config: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl SimpleCas {
|
impl SimpleCas {
|
||||||
pub fn create(db_path: Utf8PathBuf, config: SimpleCasConfig) -> Result<Self> {
|
pub fn create(db_path: Utf8PathBuf, mut config: Value) -> Result<Self> {
|
||||||
|
if !config.is_table() {
|
||||||
|
return Error::err("invalid config object: must be table");
|
||||||
|
}
|
||||||
|
|
||||||
|
let maybe_engine = config.as_table_mut().unwrap()
|
||||||
|
.entry("cas")
|
||||||
|
.or_insert_with(|| toml::value::Table::new().into())
|
||||||
|
.as_table_mut().unwrap()
|
||||||
|
.entry("engine")
|
||||||
|
.or_insert("simple".into())
|
||||||
|
.as_str();
|
||||||
|
match maybe_engine {
|
||||||
|
Some(engine) if engine != "simple" => {
|
||||||
|
return err!("invalid cas.engine in config: got {}, expected simple", engine);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
return Error::err("invalid casengine in config: expected String");
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let digest_id = config["cas"]["digest"].as_str()
|
||||||
|
.ok_or_else(|| Error::unknown(
|
||||||
|
"mandatory cas.digest value is invalid or missing from config"
|
||||||
|
))?;
|
||||||
|
let digest = new_digest(digest_id)?;
|
||||||
|
let pipeline = DefaultPipeline::new(digest.box_clone());
|
||||||
|
|
||||||
if db_path.exists() {
|
if db_path.exists() {
|
||||||
return err!(
|
return err!(
|
||||||
"failed to create SimpleCas: target directory already exists ({})",
|
"failed to create SimpleCas: target directory already exists ({})",
|
||||||
@@ -107,24 +92,37 @@ impl SimpleCas {
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::open(db_path, config)
|
write_config(&config, &db_path)?;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open(db_path: Utf8PathBuf, config: SimpleCasConfig) -> Result<Self> {
|
|
||||||
let digest = new_digest(&config.digest_id)?;
|
|
||||||
let pipeline = DefaultPipeline::new(digest.box_clone());
|
|
||||||
|
|
||||||
Ok(SimpleCas {
|
Ok(SimpleCas {
|
||||||
config,
|
|
||||||
db_path,
|
db_path,
|
||||||
digest,
|
digest,
|
||||||
pipeline,
|
pipeline,
|
||||||
|
// config,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_config(&self) -> &SimpleCasConfig {
|
pub fn open(db_path: Utf8PathBuf) -> Result<Self> {
|
||||||
&self.config
|
let config = read_config(&db_path)?;
|
||||||
|
|
||||||
|
let digest_id = config["cas"]["digest"].as_str()
|
||||||
|
.ok_or_else(|| Error::unknown(
|
||||||
|
"mandatory cas.digest value is invalid or missing from config"
|
||||||
|
))?;
|
||||||
|
let digest = new_digest(digest_id)?;
|
||||||
|
let pipeline = DefaultPipeline::new(digest.box_clone());
|
||||||
|
|
||||||
|
Ok(SimpleCas {
|
||||||
|
db_path,
|
||||||
|
digest,
|
||||||
|
pipeline,
|
||||||
|
// config,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pub fn save_config(&self) -> Result<()> {
|
||||||
|
// write_config(&self.config, &self.db_path)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -154,7 +152,7 @@ impl Cas for SimpleCas {
|
|||||||
fn open_object(&self, oid: &ObjectId) -> Result<(ObjectMetadata, Box<dyn Reader>)> {
|
fn open_object(&self, oid: &ObjectId) -> Result<(ObjectMetadata, Box<dyn Reader>)> {
|
||||||
let opath = obj_path(&self.db_path, oid);
|
let opath = obj_path(&self.db_path, oid);
|
||||||
if !opath.is_file() {
|
if !opath.is_file() {
|
||||||
return Err(Error::ObjectDoesNotExists(oid.clone()));
|
return err!("object not found: {}", oid);
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = std::fs::File::open(opath).or_else(|err|
|
let file = std::fs::File::open(opath).or_else(|err|
|
||||||
@@ -186,59 +184,56 @@ impl Cas for SimpleCas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RefStore for SimpleCas {
|
impl RefStore for SimpleCas {
|
||||||
fn get_ref(&self, key: &Utf8Path) -> Result<ObjectId> {
|
fn get_ref<P: AsRef<Utf8Path>>(&self, key: P) -> Result<ObjectId> {
|
||||||
let path = ref_dir(&self.db_path).join(key);
|
let path = ref_dir(&self.db_path).join(key.as_ref());
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
err!("reference {} does not exists", key)
|
err!("reference {} does not exists", key.as_ref())
|
||||||
}
|
}
|
||||||
else if !path.is_file() {
|
else if !path.is_file() {
|
||||||
err!("reference {} is not a file", key)
|
err!("reference {} is not a file", key.as_ref())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let file = std::fs::read(path).or_else(|err|
|
let file = std::fs::read(path).or_else(|err|
|
||||||
err!("failed to read reference file for {}: {}", key, err)
|
err!("failed to read reference file for {}: {}", key.as_ref(), err)
|
||||||
)?;
|
)?;
|
||||||
Ok(
|
Ok(
|
||||||
ObjectId::from_str(
|
ObjectId::from_str(
|
||||||
std::str::from_utf8(&file).or_else(|err|
|
std::str::from_utf8(&file).or_else(|err|
|
||||||
err!("invalid reference file at {}: {}", key, err)
|
err!("invalid reference file at {}: {}", key.as_ref(), err)
|
||||||
)?
|
)?
|
||||||
)?
|
)?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_ref(&mut self, key: &Utf8Path, value: &ObjectId) -> Result<()> {
|
fn set_ref<P: AsRef<Utf8Path>>(&mut self, key: P, value: &ObjectId) -> Result<()> {
|
||||||
let path = ref_dir(&self.db_path).join(key);
|
let path = ref_dir(&self.db_path).join(key.as_ref());
|
||||||
std::fs::create_dir_all(path.parent().ok_or_else(||
|
std::fs::create_dir_all(path.parent().ok_or_else(||
|
||||||
Error::unknown(format!("reference file {} has no parent dir?", key))
|
Error::unknown(format!("reference file {} has no parent dir?", key.as_ref()))
|
||||||
)?).or_else(|err|
|
)?).or_else(|err|
|
||||||
err!("failed to create reference dir for {}: {}", key, err)
|
err!("failed to create reference dir for {}: {}", key.as_ref(), err)
|
||||||
)?;
|
)?;
|
||||||
std::fs::write(path, value.to_string()).or_else(|err|
|
std::fs::write(path, value.to_string()).or_else(|err|
|
||||||
err!("failed to write reference {}: {}", key, err)
|
err!("failed to write reference {}: {}", key.as_ref(), err)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_ref(&mut self, key: &Utf8Path) -> Result<()> {
|
fn remove_ref<P: AsRef<Utf8Path>>(&mut self, key: P) -> Result<()> {
|
||||||
let path = ref_dir(&self.db_path).join(key);
|
let path = ref_dir(&self.db_path).join(key.as_ref());
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
err!("reference {} does not exists", key)
|
err!("reference {} does not exists", key.as_ref())
|
||||||
}
|
}
|
||||||
else if !path.is_file() {
|
else if !path.is_file() {
|
||||||
err!("reference {} is not a file", key)
|
err!("reference {} is not a file", key.as_ref())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
std::fs::remove_file(path).or_else(|err|
|
std::fs::remove_file(path).or_else(|err|
|
||||||
err!("failed to remove reference file {}: {}", key, err)
|
err!("failed to remove reference file {}: {}", key.as_ref(), err)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CasWithRef for SimpleCas {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub struct ObjectIdIterator {
|
pub struct ObjectIdIterator {
|
||||||
root_dirs: Vec<Utf8PathBuf>,
|
root_dirs: Vec<Utf8PathBuf>,
|
||||||
@@ -392,6 +387,13 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn get_config() -> Value {
|
||||||
|
toml::toml!(
|
||||||
|
[cas]
|
||||||
|
digest = "sha256"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_cas_path(dir: &Utf8Path) -> Utf8PathBuf {
|
fn get_cas_path(dir: &Utf8Path) -> Utf8PathBuf {
|
||||||
let mut cas_path = dir.to_path_buf();
|
let mut cas_path = dir.to_path_buf();
|
||||||
cas_path.push(".bsv");
|
cas_path.push(".bsv");
|
||||||
@@ -402,7 +404,7 @@ mod tests {
|
|||||||
fn test_create_simple_cas() {
|
fn test_create_simple_cas() {
|
||||||
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
||||||
let cas_path = get_cas_path(Utf8Path::from_path(dir.path()).unwrap());
|
let cas_path = get_cas_path(Utf8Path::from_path(dir.path()).unwrap());
|
||||||
let config = SimpleCasConfig::new("sha256".into());
|
let config = get_config();
|
||||||
|
|
||||||
let cas = SimpleCas::create(cas_path, config)
|
let cas = SimpleCas::create(cas_path, config)
|
||||||
.expect("failed to create SimpleCas object");
|
.expect("failed to create SimpleCas object");
|
||||||
@@ -416,7 +418,7 @@ mod tests {
|
|||||||
fn test_write_object() {
|
fn test_write_object() {
|
||||||
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
||||||
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
||||||
let config = SimpleCasConfig::new("sha256".into());
|
let config = get_config();
|
||||||
|
|
||||||
let otype = ObjectType::new(b"blob").expect("failed to create object type");
|
let otype = ObjectType::new(b"blob").expect("failed to create object type");
|
||||||
let payload = b"Hello World!";
|
let payload = b"Hello World!";
|
||||||
@@ -446,7 +448,7 @@ mod tests {
|
|||||||
|
|
||||||
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
||||||
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
||||||
let config = SimpleCasConfig::new("sha256".into());
|
let config = get_config();
|
||||||
|
|
||||||
let otype = ObjectType::new(b"blob").expect("failed to create object type");
|
let otype = ObjectType::new(b"blob").expect("failed to create object type");
|
||||||
let payload = b"Hello World!";
|
let payload = b"Hello World!";
|
||||||
@@ -485,7 +487,7 @@ mod tests {
|
|||||||
fn test_read_write_object() {
|
fn test_read_write_object() {
|
||||||
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
||||||
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
||||||
let config = SimpleCasConfig::new("sha256".into());
|
let config = get_config();
|
||||||
|
|
||||||
let otype = ObjectType::new(b"blob").expect("failed to create object type");
|
let otype = ObjectType::new(b"blob").expect("failed to create object type");
|
||||||
let payload = b"This is a test.";
|
let payload = b"This is a test.";
|
||||||
@@ -505,7 +507,7 @@ mod tests {
|
|||||||
fn test_remove_object() {
|
fn test_remove_object() {
|
||||||
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
||||||
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
||||||
let config = SimpleCasConfig::new("sha256".into());
|
let config = get_config();
|
||||||
|
|
||||||
let otype = ObjectType::new(b"blob").expect("failed to create object type");
|
let otype = ObjectType::new(b"blob").expect("failed to create object type");
|
||||||
let payload = b"This is a test.";
|
let payload = b"This is a test.";
|
||||||
@@ -526,7 +528,7 @@ mod tests {
|
|||||||
fn test_object_id_iterator() {
|
fn test_object_id_iterator() {
|
||||||
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
||||||
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
||||||
let config = SimpleCasConfig::new("sha256".into());
|
let config = get_config();
|
||||||
|
|
||||||
let mut cas = SimpleCas::create(cas_path.clone(), config)
|
let mut cas = SimpleCas::create(cas_path.clone(), config)
|
||||||
.expect("failed to create SimpleCas object");
|
.expect("failed to create SimpleCas object");
|
||||||
@@ -640,21 +642,21 @@ mod tests {
|
|||||||
fn test_reference() {
|
fn test_reference() {
|
||||||
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
let dir = tempfile::tempdir().expect("failed to create temp test dir");
|
||||||
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
let cas_path = get_cas_path(dir.path().try_into().unwrap());
|
||||||
let config = SimpleCasConfig::new("sha256".into());
|
let config = get_config();
|
||||||
|
|
||||||
let mut cas = SimpleCas::create(cas_path.clone(), config)
|
let mut cas = SimpleCas::create(cas_path.clone(), config)
|
||||||
.expect("failed to create SimpleCas object");
|
.expect("failed to create SimpleCas object");
|
||||||
|
|
||||||
let oid_0 = ObjectId::from_str("f731f6bc6a6a73bad170e56452473ef6930b7a0ab33cc54be44221a89b49d786").unwrap();
|
let oid_0 = ObjectId::from_str("f731f6bc6a6a73bad170e56452473ef6930b7a0ab33cc54be44221a89b49d786").unwrap();
|
||||||
|
|
||||||
assert!(cas.get_ref("foo/bar".into()).is_err());
|
assert!(cas.get_ref("foo/bar").is_err());
|
||||||
assert!(cas.remove_ref("foo/bar".into()).is_err());
|
assert!(cas.remove_ref("foo/bar").is_err());
|
||||||
|
|
||||||
cas.set_ref("foo/bar".into(), &oid_0).unwrap();
|
cas.set_ref("foo/bar", &oid_0).unwrap();
|
||||||
assert_eq!(cas.get_ref("foo/bar".into()).unwrap(), oid_0);
|
assert_eq!(cas.get_ref("foo/bar").unwrap(), oid_0);
|
||||||
|
|
||||||
cas.remove_ref("foo/bar".into()).unwrap();
|
cas.remove_ref("foo/bar").unwrap();
|
||||||
|
|
||||||
assert!(cas.get_ref("foo/bar".into()).is_err());
|
assert!(cas.get_ref("foo/bar").is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,4 @@ mod wfile;
|
|||||||
mod cas;
|
mod cas;
|
||||||
|
|
||||||
|
|
||||||
pub use cas::{SimpleCas, SimpleCasConfig};
|
pub use cas::SimpleCas;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
|
|
||||||
use digest::DynDigest;
|
use digest::DynDigest;
|
||||||
// use toml::Value;
|
use toml::Value;
|
||||||
use camino::{Utf8Path, Utf8PathBuf};
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
|
|
||||||
use cas_core::{
|
use cas_core::{
|
||||||
@@ -49,39 +49,39 @@ pub fn config_path(cas_path: &Utf8Path) -> Utf8PathBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// pub fn read_config(db_path: &Utf8Path) -> Result<Value> {
|
pub fn read_config(db_path: &Utf8Path) -> Result<Value> {
|
||||||
// use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
// let mut file = std::fs::File::open(config_path(db_path)).or_else(|err|
|
let mut file = std::fs::File::open(config_path(db_path)).or_else(|err|
|
||||||
// err!("invalid repository: no config file: {}", err)
|
err!("invalid repository: no config file: {}", err)
|
||||||
// )?;
|
)?;
|
||||||
// let mut config_str = String::new();
|
let mut config_str = String::new();
|
||||||
// file.read_to_string(&mut config_str).or_else(|err|
|
file.read_to_string(&mut config_str).or_else(|err|
|
||||||
// err!("cannot read config file: {}", err)
|
err!("cannot read config file: {}", err)
|
||||||
// )?;
|
)?;
|
||||||
// let config = toml::from_str(&config_str).or_else(|err|
|
let config = toml::from_str(&config_str).or_else(|err|
|
||||||
// err!("error while reading config file: {}", err)
|
err!("error while reading config file: {}", err)
|
||||||
// )?;
|
)?;
|
||||||
// Ok(config)
|
Ok(config)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn write_config(config: &Value, db_path: &Utf8Path) -> Result<()> {
|
pub fn write_config(config: &Value, db_path: &Utf8Path) -> Result<()> {
|
||||||
// use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
// let config_str = toml::to_string_pretty(config).or_else(|err|
|
let config_str = toml::to_string_pretty(config).or_else(|err|
|
||||||
// err!("failed to serialize config: {}", err)
|
err!("failed to serialize config: {}", err)
|
||||||
// )?;
|
)?;
|
||||||
// let mut file = tempfile::NamedTempFile::new_in(tmp_dir(db_path)).or_else(|err|
|
let mut file = tempfile::NamedTempFile::new_in(tmp_dir(db_path)).or_else(|err|
|
||||||
// err!("cannot create temp config file: {}", err)
|
err!("cannot create temp config file: {}", err)
|
||||||
// )?;
|
)?;
|
||||||
// file.write_all(config_str.as_bytes()).or_else(|err|
|
file.write_all(config_str.as_bytes()).or_else(|err|
|
||||||
// err!("failed to write to temp config: {}", err)
|
err!("failed to write to temp config: {}", err)
|
||||||
// )?;
|
)?;
|
||||||
// file.persist(config_path(db_path)).or_else(|err|
|
file.persist(config_path(db_path)).or_else(|err|
|
||||||
// err!("failed to (over)write config: {}", err)
|
err!("failed to (over)write config: {}", err)
|
||||||
// )?;
|
)?;
|
||||||
// Ok(())
|
Ok(())
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn read_metadata(read: &mut dyn std::io::Read) -> Result<ObjectMetadata> {
|
pub fn read_metadata(read: &mut dyn std::io::Read) -> Result<ObjectMetadata> {
|
||||||
@@ -151,28 +151,28 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn test_read_write_config() {
|
fn test_read_write_config() {
|
||||||
// let config = toml::toml!{
|
let config = toml::toml!{
|
||||||
// [cas]
|
[cas]
|
||||||
// path = "/foo/bar"
|
path = "/foo/bar"
|
||||||
// digest = "sha1"
|
digest = "sha1"
|
||||||
|
|
||||||
// [extra]
|
[extra]
|
||||||
// test = 42
|
test = 42
|
||||||
// };
|
};
|
||||||
|
|
||||||
// let dir = tempfile::TempDir::new()
|
let dir = tempfile::TempDir::new()
|
||||||
// .expect("failed to create tmp dir");
|
.expect("failed to create tmp dir");
|
||||||
// std::fs::create_dir(tmp_dir(Utf8Path::from_path(dir.path()).unwrap())).expect("failed to create db/tmp dir");
|
std::fs::create_dir(tmp_dir(Utf8Path::from_path(dir.path()).unwrap())).expect("failed to create db/tmp dir");
|
||||||
|
|
||||||
// write_config(&config, Utf8Path::from_path(dir.path()).unwrap()).expect("failed to write config");
|
write_config(&config, Utf8Path::from_path(dir.path()).unwrap()).expect("failed to write config");
|
||||||
// let config2 = read_config(Utf8Path::from_path(dir.path()).unwrap()).expect("failed to read config");
|
let config2 = read_config(Utf8Path::from_path(dir.path()).unwrap()).expect("failed to read config");
|
||||||
|
|
||||||
// assert_eq!(config2, config);
|
assert_eq!(config2, config);
|
||||||
|
|
||||||
// dir.close().expect("failed to close tmp dir")
|
dir.close().expect("failed to close tmp dir")
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_read_write_metadata() {
|
fn test_read_write_metadata() {
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ edition = "2021"
|
|||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
toml = { version = "0.7.6", features = ["parse"] }
|
toml = "0.5.8"
|
||||||
camino = "1.0.7"
|
camino = "1.0.7"
|
||||||
regex = "1.6.0"
|
regex = "1.6.0"
|
||||||
|
bsvfs = { path = "../bsvfs" }
|
||||||
cas-core = { path = "../cas-core" }
|
cas-core = { path = "../cas-core" }
|
||||||
cas-simple = { path = "../cas-simple" }
|
cas-simple = { path = "../cas-simple" }
|
||||||
tempfile = "3.7.1"
|
|
||||||
|
|||||||
31
libbsv/src/config.rs
Normal file
31
libbsv/src/config.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// This file is part of bsv.
|
||||||
|
//
|
||||||
|
// bsv is free software: you can redistribute it and/or modify it under the
|
||||||
|
// terms of the GNU Affero General Public License as published by the Free
|
||||||
|
// Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// bsv is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
|
// more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the Affero GNU General Public License
|
||||||
|
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
use std::collections::{HashMap};
|
||||||
|
use camino::Utf8PathBuf;
|
||||||
|
|
||||||
|
use cas_core::{Cas, Result};
|
||||||
|
|
||||||
|
|
||||||
|
pub trait CasConfig {
|
||||||
|
fn build_cas(&self) -> Result<Box<dyn Cas>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct BsvConfig {
|
||||||
|
cas: Box<dyn CasConfig>,
|
||||||
|
dir_map: HashMap<Utf8PathBuf, Utf8PathBuf>,
|
||||||
|
}
|
||||||
@@ -14,23 +14,23 @@
|
|||||||
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
use camino::Utf8Path;
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
use regex::RegexSet;
|
use regex::RegexSet;
|
||||||
|
|
||||||
use cas_core::{err, Error, Result};
|
use cas_core::{err, Error, Result};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum IgnoreAction {
|
pub enum Action {
|
||||||
Ignore,
|
Ignore,
|
||||||
Accept,
|
Accept,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct IgnoreRules {
|
pub struct IgnoreRules {
|
||||||
patterns: RegexSet,
|
patterns: RegexSet,
|
||||||
actions: Vec<IgnoreAction>,
|
actions: Vec<Action>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ impl IgnoreRules {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut patterns = Vec::<String>::default();
|
let mut patterns = Vec::<String>::default();
|
||||||
let mut actions = Vec::<IgnoreAction>::default();
|
let mut actions = Vec::<Action>::default();
|
||||||
|
|
||||||
for line in source.lines() {
|
for line in source.lines() {
|
||||||
let rule = line.trim();
|
let rule = line.trim();
|
||||||
@@ -65,10 +65,10 @@ impl IgnoreRules {
|
|||||||
|
|
||||||
if rule_it.peek() == Some(&'!') {
|
if rule_it.peek() == Some(&'!') {
|
||||||
rule_it.next();
|
rule_it.next();
|
||||||
actions.push(IgnoreAction::Accept);
|
actions.push(Action::Accept);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
actions.push(IgnoreAction::Ignore);
|
actions.push(Action::Ignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pat = String::new();
|
let mut pat = String::new();
|
||||||
@@ -125,13 +125,13 @@ impl IgnoreRules {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn action_for<P: AsRef<Utf8Path>>(&self, path: P) -> IgnoreAction {
|
pub fn action_for<P: AsRef<Utf8Path>>(&self, path: P) -> Action {
|
||||||
assert!(path.as_ref().is_absolute());
|
assert!(path.as_ref().is_absolute());
|
||||||
let index = self.patterns.matches(path.as_ref().as_str())
|
let index = self.patterns.matches(path.as_ref().as_str())
|
||||||
.iter()
|
.iter()
|
||||||
.next()
|
.next()
|
||||||
.unwrap_or(self.actions.len());
|
.unwrap_or(self.actions.len());
|
||||||
*self.actions.get(index).unwrap_or(&IgnoreAction::Accept)
|
*self.actions.get(index).unwrap_or(&Action::Accept)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,8 +139,6 @@ impl IgnoreRules {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use camino::Utf8PathBuf;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -149,17 +147,17 @@ mod tests {
|
|||||||
let patterns = "!hello/world\nhello\n/world/\n\\!\\*\n*.bak";
|
let patterns = "!hello/world\nhello\n/world/\n\\!\\*\n*.bak";
|
||||||
let ignore = IgnoreRules::from_source(patterns, root).unwrap();
|
let ignore = IgnoreRules::from_source(patterns, root).unwrap();
|
||||||
|
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test")), IgnoreAction::Accept);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test")), Action::Accept);
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello/aoeu")), IgnoreAction::Ignore);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello/aoeu")), Action::Ignore);
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello_world/aoeu")), IgnoreAction::Accept);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello_world/aoeu")), Action::Accept);
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/world/aoeu")), IgnoreAction::Accept);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/world/aoeu")), Action::Accept);
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello/world/aoeu")), IgnoreAction::Accept);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/hello/world/aoeu")), Action::Accept);
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/world/aoeu")), IgnoreAction::Ignore);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/world/aoeu")), Action::Ignore);
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/world")), IgnoreAction::Ignore);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/world")), Action::Ignore);
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/fooXdir/bar/world")), IgnoreAction::Accept);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/fooXdir/bar/world")), Action::Accept);
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/!*/aoeu")), IgnoreAction::Ignore);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/!*/aoeu")), Action::Ignore);
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/file.bak")), IgnoreAction::Ignore);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/file.bak")), Action::Ignore);
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/fileXbak")), IgnoreAction::Accept);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test/fileXbak")), Action::Accept);
|
||||||
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test.bak/file")), IgnoreAction::Ignore);
|
assert_eq!(ignore.action_for(Utf8PathBuf::from("/foo.dir/bar/test.bak/file")), Action::Ignore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,23 +17,21 @@
|
|||||||
extern crate toml;
|
extern crate toml;
|
||||||
extern crate camino;
|
extern crate camino;
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
extern crate tempfile;
|
|
||||||
|
|
||||||
|
extern crate bsvfs;
|
||||||
extern crate cas_core;
|
extern crate cas_core;
|
||||||
|
|
||||||
|
|
||||||
// mod config;
|
// mod permissions;
|
||||||
mod ignore;
|
|
||||||
mod path_map;
|
|
||||||
mod permissions;
|
|
||||||
mod repository;
|
|
||||||
mod tree_item;
|
mod tree_item;
|
||||||
mod tree_walker;
|
mod tree_walker;
|
||||||
|
// mod config;
|
||||||
|
mod path_map;
|
||||||
|
mod ignore;
|
||||||
|
mod repository;
|
||||||
|
|
||||||
|
|
||||||
pub use crate::ignore::{IgnoreAction, IgnoreRules};
|
// pub use crate::permissions::Permissions;
|
||||||
pub use crate::path_map::{PathMap, PathPair};
|
|
||||||
pub use crate::permissions::Permissions;
|
|
||||||
pub use crate::repository::Repository;
|
|
||||||
pub use crate::tree_item::{Serialize, TreeItem};
|
pub use crate::tree_item::{Serialize, TreeItem};
|
||||||
pub use crate::tree_walker::{Action, TreeWalker};
|
pub use crate::path_map::{PathMap, PathPair};
|
||||||
|
pub use crate::repository::{Repository};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
|
|
||||||
use camino::{Utf8Path, Utf8PathBuf};
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
use toml::{Value, Table};
|
use toml::Value;
|
||||||
|
|
||||||
use cas_core::{err, Error, Result};
|
use cas_core::{err, Error, Result};
|
||||||
|
|
||||||
@@ -30,28 +30,10 @@ impl PathPair {
|
|||||||
pub fn new(logic: Utf8PathBuf, physic: Utf8PathBuf) -> Self {
|
pub fn new(logic: Utf8PathBuf, physic: Utf8PathBuf) -> Self {
|
||||||
Self { logic, physic }
|
Self { logic, physic }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn physic_from_logic<P: AsRef<Utf8Path>>(&self, logic_path: P) -> Result<Utf8PathBuf> {
|
|
||||||
let mut physic = self.physic.clone();
|
|
||||||
physic.push(
|
|
||||||
logic_path.as_ref().strip_prefix(&self.logic)?
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(physic)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn logic_from_physic<P: AsRef<Utf8Path>>(&self, physic_path: P) -> Result<Utf8PathBuf> {
|
|
||||||
let mut logic = self.logic.clone();
|
|
||||||
logic.push(
|
|
||||||
physic_path.as_ref().strip_prefix(&self.physic)?
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(logic)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PathMap {
|
pub struct PathMap {
|
||||||
pairs: Vec<PathPair>,
|
pairs: Vec<PathPair>,
|
||||||
logic_order: Vec<usize>,
|
logic_order: Vec<usize>,
|
||||||
@@ -131,42 +113,38 @@ impl PathMap {
|
|||||||
Self::from_vec(map)
|
Self::from_vec(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_toml_value(&self) -> Result<Value> {
|
pub fn physic_from_logic<P: AsRef<Utf8Path>>(&self, logic_path: P) -> Option<Utf8PathBuf> {
|
||||||
let mut table = Table::new();
|
|
||||||
for pair in self.pairs.iter() {
|
|
||||||
table[pair.logic.as_str()] = pair.physic.as_str().into();
|
|
||||||
}
|
|
||||||
Ok(Value::Table(table))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path_pair_from_logic<P: AsRef<Utf8Path>>(&self, logic_path: P) -> Option<&PathPair> {
|
|
||||||
let path = logic_path.as_ref();
|
let path = logic_path.as_ref();
|
||||||
|
|
||||||
Some(self.logic_order.iter()
|
let pair = self.logic_order.iter()
|
||||||
.map(|&i| &self.pairs[i])
|
.map(|&i| &self.pairs[i])
|
||||||
.filter(|p| path.starts_with(&p.logic))
|
.filter(|p| path.starts_with(&p.logic))
|
||||||
.next()?
|
.next()?;
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path_pair_from_physic<P: AsRef<Utf8Path>>(&self, physic_path: P) -> Option<&PathPair> {
|
let mut physic = pair.physic.clone();
|
||||||
let path = physic_path.as_ref();
|
physic.push(
|
||||||
|
path.strip_prefix(&pair.logic)
|
||||||
|
.ok()?
|
||||||
|
);
|
||||||
|
|
||||||
Some(self.physic_order.iter()
|
return Some(physic)
|
||||||
.map(|&i| &self.pairs[i])
|
|
||||||
.filter(|p| path.starts_with(&p.physic))
|
|
||||||
.next()?
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn physic_from_logic<P: AsRef<Utf8Path>>(&self, logic_path: P) -> Option<Utf8PathBuf> {
|
|
||||||
self.path_pair_from_logic(&logic_path)?
|
|
||||||
.physic_from_logic(&logic_path).ok()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn logic_from_physic<P: AsRef<Utf8Path>>(&self, physic_path: P) -> Option<Utf8PathBuf> {
|
pub fn logic_from_physic<P: AsRef<Utf8Path>>(&self, physic_path: P) -> Option<Utf8PathBuf> {
|
||||||
self.path_pair_from_physic(&physic_path)?
|
let path = physic_path.as_ref();
|
||||||
.logic_from_physic(&physic_path).ok()
|
|
||||||
|
let pair = self.physic_order.iter()
|
||||||
|
.map(|&i| &self.pairs[i])
|
||||||
|
.filter(|p| path.starts_with(&p.physic))
|
||||||
|
.next()?;
|
||||||
|
|
||||||
|
let mut logic = pair.logic.clone();
|
||||||
|
logic.push(
|
||||||
|
path.strip_prefix(&pair.physic)
|
||||||
|
.ok()?
|
||||||
|
);
|
||||||
|
|
||||||
|
return Some(logic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
use bsvfs::{Metadata};
|
||||||
use cas_core::{err, Error, Result};
|
use cas_core::{err, Error, Result};
|
||||||
|
|
||||||
|
|
||||||
@@ -25,24 +26,12 @@ pub struct Permissions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Permissions {
|
impl Permissions {
|
||||||
#[cfg(not(unix))]
|
pub fn from_metadata<M: Metadata>(metadata: &M) -> Result<Self> {
|
||||||
pub fn from_metadata(metadata: &std::fs::Metadata) -> Result<Self> {
|
let permissions = metadata.permissions();
|
||||||
if metadata.permissions().readonly() {
|
|
||||||
Ok(Permissions::READ_ONLY)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Ok(Permission::READ_WRITE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
pub fn from_metadata(metadata: &std::fs::Metadata) -> Result<Self> {
|
|
||||||
use std::os::unix::fs::MetadataExt;
|
|
||||||
let mode = metadata.mode();
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
read: mode & 0o400 != 0,
|
read: permissions.is_read(),
|
||||||
write: mode & 0o200 != 0,
|
write: permissions.is_write(),
|
||||||
execute: mode & 0o100 != 0,
|
execute: permissions.is_execute(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,118 +14,99 @@
|
|||||||
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
// along with bsv. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
use std::{fs::create_dir_all, io::BufReader};
|
|
||||||
|
|
||||||
use camino::{Utf8Path, Utf8PathBuf};
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
use cas_simple::utils::config_path;
|
use toml::Value;
|
||||||
use toml::{Value, Table};
|
|
||||||
|
|
||||||
use cas_core::{err, Error, ObjectId, Result, ObjectType, CasWithRef};
|
use cas_core::{Cas, err, Error, ObjectId, Result};
|
||||||
use cas_simple::{SimpleCas, SimpleCasConfig};
|
use cas_simple::{SimpleCas};
|
||||||
|
|
||||||
use crate::PathMap;
|
|
||||||
pub use crate::permissions::Permissions;
|
|
||||||
use crate::tree_item::Deserialize;
|
|
||||||
pub use crate::tree_item::{Serialize, TreeItem};
|
pub use crate::tree_item::{Serialize, TreeItem};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
pub fn create_cas(path: Utf8PathBuf, config: Value) -> Result<Box<dyn Cas>> {
|
||||||
pub struct Config {
|
let engine = config
|
||||||
pub device_name: String,
|
.get("cas")
|
||||||
pub cas: SimpleCasConfig,
|
.ok_or_else(|| Error::unknown("config must have a cas item"))?
|
||||||
pub path_map: PathMap,
|
.get("engine")
|
||||||
}
|
.ok_or_else(|| Error::unknown("config must have a cas.engine item"))?
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn from_toml_value(value: &Value) -> Result<Self> {
|
|
||||||
let bsv_value = value.get("bsv")
|
|
||||||
.ok_or_else(|| Error::unknown("bsv section missing from config"))?;
|
|
||||||
if !bsv_value.is_table() {
|
|
||||||
return err!("bsv section must be a table");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
device_name: bsv_value.get("device_name")
|
|
||||||
.ok_or_else(|| Error::unknown("bsv.device_name missing from config"))?
|
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or_else(|| Error::unknown("bsv.device_name must be a string"))?
|
.ok_or_else(|| Error::unknown("cas.engine must be a string"))?;
|
||||||
.to_string(),
|
|
||||||
cas: SimpleCasConfig::from_toml_value(
|
|
||||||
value.get("cas").ok_or_else(|| Error::unknown("config must have a cas section"))?
|
|
||||||
)?,
|
|
||||||
path_map: PathMap::from_toml_value(
|
|
||||||
value.get("mapping").ok_or_else(|| Error::unknown("config must have a mapping section"))?
|
|
||||||
)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_toml_file(config_path: &Utf8Path) -> Result<Self> {
|
match engine {
|
||||||
use std::io::Read;
|
"simple" => { SimpleCas::create(path, config).map(|cas| Box::new(cas) as Box<dyn Cas>) }
|
||||||
|
_ => { err!("unknown cas engine {}", engine) }
|
||||||
let mut file = std::fs::File::open(config_path).or_else(|err|
|
|
||||||
err!("invalid repository: failed to read config file: {}", err)
|
|
||||||
)?;
|
|
||||||
let mut config_str = String::new();
|
|
||||||
file.read_to_string(&mut config_str).or_else(|err|
|
|
||||||
err!("failed to read config file: {}", err)
|
|
||||||
)?;
|
|
||||||
let value = config_str.parse::<Value>().or_else(|err|
|
|
||||||
err!("parse error while reading config file: {}", err)
|
|
||||||
)?;
|
|
||||||
Self::from_toml_value(&value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_toml_value(&self) -> Result<Value> {
|
|
||||||
let mut bsv = Table::new();
|
|
||||||
bsv["device_name"] = Value::String(self.device_name.clone());
|
|
||||||
|
|
||||||
let mut table = Table::new();
|
|
||||||
table["bsv"] = Value::Table(bsv);
|
|
||||||
table["cas"] = self.cas.to_toml_value()?;
|
|
||||||
table["mapping"] = self.path_map.to_toml_value()?;
|
|
||||||
|
|
||||||
Ok(Value::Table(table))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_toml_file(&self, config_path: &Utf8Path) -> Result<()> {
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
let value = self.to_toml_value()?;
|
|
||||||
let config_str = toml::to_string_pretty(&value).or_else(|err|
|
|
||||||
err!("failed to serialize config: {}", err)
|
|
||||||
)?;
|
|
||||||
let mut file = tempfile::NamedTempFile::new_in(config_path.parent().unwrap()).or_else(|err|
|
|
||||||
err!("cannot create temp config file: {}", err)
|
|
||||||
)?;
|
|
||||||
file.write_all(config_str.as_bytes()).or_else(|err|
|
|
||||||
err!("failed to write to temp config: {}", err)
|
|
||||||
)?;
|
|
||||||
file.persist(config_path).or_else(|err|
|
|
||||||
err!("failed to (over)write config: {}", err)
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn open_cas(path: Utf8PathBuf, config: &Value) -> Result<Box<dyn Cas>> {
|
||||||
|
let engine = config
|
||||||
|
.get("cas")
|
||||||
|
.ok_or_else(|| Error::unknown("config must have a cas item"))?
|
||||||
|
.get("engine")
|
||||||
|
.ok_or_else(|| Error::unknown("config must have a cas.engine item"))?
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| Error::unknown("cas.engine must be a string"))?;
|
||||||
|
|
||||||
|
match engine {
|
||||||
|
"simple" => { SimpleCas::open(path).map(|cas| Box::new(cas) as Box<dyn Cas>) }
|
||||||
|
_ => { err!("unknown cas engine {}", engine) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// pub trait FsVisitor {
|
||||||
|
// fn accept(&self, path: &Utf8Path, metadata: &Metadata) -> bool;
|
||||||
|
// fn handle_error(&mut self, error: Error) -> Option<Error>;
|
||||||
|
|
||||||
|
// fn handle_result<T>(&mut self, result: Result<T>) -> Result<T>
|
||||||
|
// {
|
||||||
|
// result.map_err(|error|
|
||||||
|
// self.handle_error(error)
|
||||||
|
// .unwrap_or(Error::Skipped)
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// fn read_path_map(config: &Value) -> Result<Vec<PathPair>> {
|
||||||
|
// if let Some(mapping) = config.get("mapping") {
|
||||||
|
// if let Some(ref table) = mapping.as_table() {
|
||||||
|
// table.iter()
|
||||||
|
// .map(|(k, v)| Ok(PathPair{
|
||||||
|
// logic: k.into(),
|
||||||
|
// physic: v.as_str()
|
||||||
|
// .ok_or_else(|| Error::unknown("mapping values must be strings"))?
|
||||||
|
// .into()
|
||||||
|
// }))
|
||||||
|
// .collect()
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// err!("mapping must be a table, got {}", mapping)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// Ok(vec![])
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
pub struct Repository {
|
pub struct Repository {
|
||||||
config: Config,
|
cas: Box<dyn Cas>,
|
||||||
cas: Box<dyn CasWithRef>,
|
// path_map: Vec<PathPair>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Repository {
|
impl Repository {
|
||||||
pub fn create(path: Utf8PathBuf, config: Config) -> Result<Self> {
|
pub fn create(path: Utf8PathBuf, config: Value) -> Result<Self> {
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
return err!("cannot create bsv repository, path {} already exists", path);
|
return err!("cannot create bsv repository, path {} already exists", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
create_dir_all(&path)?;
|
let cas = create_cas(path, config)?;
|
||||||
config.write_toml_file(&config_path(&path))?;
|
|
||||||
let cas = SimpleCas::create(path.to_path_buf(), config.cas.clone())?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
config,
|
cas,
|
||||||
cas: Box::new(cas),
|
// path_map: vec![],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,61 +116,39 @@ impl Repository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let config_file = cas_simple::utils::config_path(&path);
|
let config_file = cas_simple::utils::config_path(&path);
|
||||||
let config = Config::from_toml_file(&config_file)?;
|
let config = cas_simple::utils::read_config(&config_file)?;
|
||||||
|
|
||||||
let cas = SimpleCas::open(path, config.cas.clone())?;
|
let cas = open_cas(path, &config)?;
|
||||||
|
// let path_map = read_path_map(&config)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
config,
|
cas,
|
||||||
cas: Box::new(cas),
|
// path_map,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cas(&self) -> &dyn CasWithRef {
|
pub fn cas(&self) -> &dyn Cas {
|
||||||
self.cas.as_ref()
|
self.cas.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn physic_from_logic_path<P: AsRef<Utf8Path>>(&self, logic_path: P) -> Option<Utf8PathBuf> {
|
// pub fn path_pair_from_logic_path<P: AsRef<Utf8Path>>(&self, logic_path: P) -> Option<PathPair> {
|
||||||
self.config.path_map.physic_from_logic(&logic_path)
|
// None
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn path_pair_from_physic_path<P: AsRef<Utf8Path>>(&self, physic_path: P) -> Option<Utf8PathBuf> {
|
// pub fn path_pair_from_physic_path<P: AsRef<Utf8Path>>(&self, physic_path: P) -> Option<PathPair> {
|
||||||
self.config.path_map.logic_from_physic(&physic_path)
|
// None
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn oid_from_logic_path<P: AsRef<Utf8Path>>(&self, _logic_path: P) -> Result<ObjectId> {
|
pub fn oid_from_logic_path<P: AsRef<Utf8Path>>(&self, logic_path: P) -> Result<ObjectId> {
|
||||||
// let snapshot_oid = self.cas.get_ref(Utf8Path::new("latest"))?;
|
|
||||||
// let snapshot = self.read_snapshot(snapshot_oid)?;
|
|
||||||
err!("not implemented")
|
err!("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn oid_from_physic_path<P: AsRef<Utf8Path>>(&self, physic_path: P) -> Result<ObjectId> {
|
pub fn oid_from_physic_path<P: AsRef<Utf8Path>>(&self, physic_path: P) -> Result<ObjectId> {
|
||||||
let logic_path = self.config.path_map.logic_from_physic(physic_path)
|
err!("not implemented")
|
||||||
.ok_or_else(|| Error::unknown("physic path do not map to a logic path"))?;
|
|
||||||
self.oid_from_logic_path(logic_path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_tree(&self, oid: &ObjectId) -> Result<Option<Vec<TreeItem>>> {
|
pub fn read_tree(&self, oid: &ObjectId) -> Result<Option<Vec<TreeItem>>> {
|
||||||
match self.cas.open_object(oid) {
|
err!("not implemented")
|
||||||
Ok((metadata, mut reader)) => {
|
|
||||||
if metadata.otype() != &ObjectType::new(b"tree")? {
|
|
||||||
err!("object is not a tree")
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let mut buf_read = BufReader::new(reader.as_mut());
|
|
||||||
Ok(Some(
|
|
||||||
Vec::<TreeItem>::deserialize(&mut buf_read)?
|
|
||||||
.unwrap_or_else(|| Vec::new())
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(Error::ObjectDoesNotExists(_)) => {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn add<P, V>(&mut self, path: P, visitor: &mut V) -> Result<ObjectId>
|
// pub fn add<P, V>(&mut self, path: P, visitor: &mut V) -> Result<ObjectId>
|
||||||
|
|||||||
@@ -17,20 +17,19 @@
|
|||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
use std::io::{BufRead, Write};
|
use std::io::{BufRead, Write};
|
||||||
|
|
||||||
|
use bsvfs::{Metadata, Permissions};
|
||||||
use cas_core::{err, Error, ObjectId, ObjectType, Result};
|
use cas_core::{err, Error, ObjectId, ObjectType, Result};
|
||||||
|
|
||||||
use crate::Permissions;
|
|
||||||
|
|
||||||
|
|
||||||
pub trait Serialize {
|
pub trait Serialize {
|
||||||
fn serialize<W: Write>(&self, out: &mut W) -> Result<()>;
|
fn serialize<W: Write>(&self, out: &mut W) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Deserialize {
|
pub trait Deserialize {
|
||||||
fn deserialize_with_buf<R: BufRead>(stream: &mut R, buf: &mut Vec<u8>) -> Result<Option<Self>>
|
fn deserialize_with_buf<R: BufRead>(stream: &mut R, buf: &mut Vec<u8>) -> Result<Self>
|
||||||
where Self: Sized;
|
where Self: Sized;
|
||||||
|
|
||||||
fn deserialize<R: BufRead>(stream: &mut R) -> Result<Option<Self>>
|
fn deserialize<R: BufRead>(stream: &mut R) -> Result<Self>
|
||||||
where Self: Sized
|
where Self: Sized
|
||||||
{
|
{
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
@@ -39,15 +38,6 @@ pub trait Deserialize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn is_tree_item_name_valid(name: &str) -> bool {
|
|
||||||
return
|
|
||||||
!name.contains('/') &&
|
|
||||||
!name.contains('\\') &&
|
|
||||||
!name.contains('\n') &&
|
|
||||||
!name.contains('\0');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct TreeItem {
|
pub struct TreeItem {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -60,18 +50,15 @@ pub struct TreeItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TreeItem {
|
impl TreeItem {
|
||||||
pub fn from_metadata(name: String, metadata: &std::fs::Metadata, oid: ObjectId) -> Result<Self> {
|
// pub fn from_metadata(name: String, metadata: &std::fs::Metadata, oid: ObjectId) -> Result<Self> {
|
||||||
if !is_tree_item_name_valid(&name) {
|
pub fn from_metadata<M: Metadata>(name: String, metadata: &M, oid: ObjectId) -> Result<Self> {
|
||||||
return err!("invalid item name {:?}", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let otype = otype_from_metadata(metadata)?;
|
let otype = otype_from_metadata(metadata)?;
|
||||||
let permissions = Permissions::from_metadata(metadata)?;
|
let permissions = metadata.permissions();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: name,
|
name: name,
|
||||||
otype,
|
otype,
|
||||||
size: if metadata.is_file() { metadata.len() } else { 0 },
|
size: metadata.len(),
|
||||||
created: metadata.created().unwrap_or(UNIX_EPOCH),
|
created: metadata.created().unwrap_or(UNIX_EPOCH),
|
||||||
modified: metadata.modified().unwrap_or(UNIX_EPOCH),
|
modified: metadata.modified().unwrap_or(UNIX_EPOCH),
|
||||||
permissions,
|
permissions,
|
||||||
@@ -83,7 +70,7 @@ impl TreeItem {
|
|||||||
impl Serialize for TreeItem {
|
impl Serialize for TreeItem {
|
||||||
fn serialize<W: Write>(&self, out: &mut W) -> Result<()> {
|
fn serialize<W: Write>(&self, out: &mut W) -> Result<()> {
|
||||||
// TODO: Check that name do not contain invalid characters
|
// TODO: Check that name do not contain invalid characters
|
||||||
writeln!(out, "{}\t{}\t{}\t{}\t{}\t{}\t{}",
|
writeln!(out, "{}\t{}\t{}\t{}\t{}\t{}\t{}/",
|
||||||
self.oid,
|
self.oid,
|
||||||
self.otype,
|
self.otype,
|
||||||
self.size,
|
self.size,
|
||||||
@@ -100,39 +87,28 @@ impl Serialize for TreeItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Deserialize for TreeItem {
|
impl Deserialize for TreeItem {
|
||||||
fn deserialize_with_buf<R: BufRead>(stream: &mut R, buf: &mut Vec<u8>) -> Result<Option<Self>> {
|
fn deserialize_with_buf<R: BufRead>(stream: &mut R, buf: &mut Vec<u8>) -> Result<Self> {
|
||||||
let oid: ObjectId = if let Some(oid) = read_field_parse(stream, buf, "object ID", b'\t')? {
|
let oid: ObjectId = read_field_parse(stream, buf, "object ID", b'\t')?;
|
||||||
oid
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
read_field(stream, buf, "object type", b'\t')?;
|
read_field(stream, buf, "object type", b'\t')?;
|
||||||
let otype = ObjectType::new(buf)?;
|
let otype = ObjectType::new(buf)?;
|
||||||
let size: u64 = read_field_parse(stream, buf, "object type", b'\t')?
|
let size: u64 = read_field_parse(stream, buf, "object type", b'\t')?;
|
||||||
.ok_or_else(|| Error::unknown("unexpected end-of-file"))?;
|
let permissions: Permissions = read_field_parse(stream, buf, "permissions", b'\t')?;
|
||||||
let permissions: Permissions = read_field_parse(stream, buf, "permissions", b'\t')?
|
|
||||||
.ok_or_else(|| Error::unknown("unexpected end-of-file"))?;
|
|
||||||
let created = UNIX_EPOCH + Duration::from_millis(
|
let created = UNIX_EPOCH + Duration::from_millis(
|
||||||
read_field_parse(stream, buf, "creation date", b'\t')?
|
read_field_parse(stream, buf, "creation date", b'\t')?
|
||||||
.ok_or_else(|| Error::unknown("unexpected end-of-file"))?
|
|
||||||
);
|
);
|
||||||
let modified = UNIX_EPOCH + Duration::from_millis(
|
let modified = UNIX_EPOCH + Duration::from_millis(
|
||||||
read_field_parse(stream, buf, "modification date", b'\t')?
|
read_field_parse(stream, buf, "modification date", b'\t')?
|
||||||
.ok_or_else(|| Error::unknown("unexpected end-of-file"))?
|
|
||||||
);
|
);
|
||||||
let name = read_field_str(stream, buf, "name", b'\n')?
|
let name = read_field_str(stream, buf, "name", b'/')?
|
||||||
.ok_or_else(|| Error::unknown("unexpected end-of-file"))?
|
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
if name.is_empty() {
|
stream.read_exact(&mut buf[..1])
|
||||||
err!("tree item name is empty")
|
.or_else(|err| err!("failed to read new line character: {}", err))?;
|
||||||
}
|
if buf[0] != b'\n' {
|
||||||
else if !is_tree_item_name_valid(&name) {
|
err!("expected new line character, got {:x}", buf[0])
|
||||||
err!("tree item name has invalid character(s)")
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Ok(Some(TreeItem {
|
Ok(TreeItem {
|
||||||
name,
|
name,
|
||||||
otype,
|
otype,
|
||||||
size,
|
size,
|
||||||
@@ -140,7 +116,7 @@ impl Deserialize for TreeItem {
|
|||||||
modified,
|
modified,
|
||||||
permissions,
|
permissions,
|
||||||
oid,
|
oid,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,24 +133,8 @@ impl Serialize for [TreeItem] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deserialize for Vec<TreeItem> {
|
|
||||||
fn deserialize_with_buf<R: BufRead>(stream: &mut R, buf: &mut Vec<u8>) -> Result<Option<Self>> {
|
|
||||||
let mut items = Vec::new();
|
|
||||||
while let Some(item) = TreeItem::deserialize_with_buf(stream, buf)? {
|
|
||||||
items.push(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(if items.is_empty() {
|
pub fn otype_from_metadata<M: Metadata>(metadata: &M) -> Result<ObjectType> {
|
||||||
None
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Some(items)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn otype_from_metadata(metadata: &std::fs::Metadata) -> Result<ObjectType> {
|
|
||||||
let file_type = metadata.file_type();
|
let file_type = metadata.file_type();
|
||||||
|
|
||||||
if file_type.is_file() {
|
if file_type.is_file() {
|
||||||
@@ -192,45 +152,29 @@ pub fn otype_from_metadata(metadata: &std::fs::Metadata) -> Result<ObjectType> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn read_field<R: BufRead>(stream: &mut R, buf: &mut Vec<u8>, field_name: &str, byte: u8) -> Result<bool> {
|
fn read_field<R: BufRead>(stream: &mut R, buf: &mut Vec<u8>, field_name: &str, byte: u8) -> Result<()> {
|
||||||
buf.clear();
|
buf.clear();
|
||||||
stream.read_until(byte, buf)
|
stream.read_until(byte, buf)
|
||||||
.or_else(|err| err!("failed to read TreeItem {}: {}", field_name, err))?;
|
.or_else(|err| err!("failed to read TreeItem {}: {}", field_name, err))?;
|
||||||
if buf.is_empty() {
|
|
||||||
return Ok(false)
|
|
||||||
}
|
|
||||||
buf.pop();
|
buf.pop();
|
||||||
Ok(true)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_field_str<'a, R: BufRead>(stream: &mut R, buf: &'a mut Vec<u8>, field_name: &str, byte: u8) -> Result<Option<&'a str>> {
|
fn read_field_str<'a, R: BufRead>(stream: &mut R, buf: &'a mut Vec<u8>, field_name: &str, byte: u8) -> Result<&'a str> {
|
||||||
if read_field(stream, buf, field_name, byte)? {
|
read_field(stream, buf, field_name, byte)?;
|
||||||
Ok(Some(
|
|
||||||
std::str::from_utf8(buf)
|
std::str::from_utf8(buf)
|
||||||
.or_else(|err| err!("TreeItem {} is not valid utf-8: {}", field_name, err))?
|
.or_else(|err| err!("TreeItem {} is not valid utf-8: {}", field_name, err))
|
||||||
))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_field_parse<R, I>(stream: &mut R, buf: &mut Vec<u8>, field_name: &str, byte: u8) -> Result<Option<I>>
|
fn read_field_parse<R, I>(stream: &mut R, buf: &mut Vec<u8>, field_name: &str, byte: u8) -> Result<I>
|
||||||
where
|
where
|
||||||
R: BufRead,
|
R: BufRead,
|
||||||
I: std::str::FromStr,
|
I: std::str::FromStr,
|
||||||
<I as std::str::FromStr>::Err: std::fmt::Display
|
<I as std::str::FromStr>::Err: std::fmt::Display
|
||||||
{
|
{
|
||||||
let maybe_str = read_field_str(stream, buf, field_name, byte)?;
|
let int_str = read_field_str(stream, buf, field_name, byte)?;
|
||||||
if let Some(int_str) = maybe_str {
|
|
||||||
Ok(Some(
|
|
||||||
I::from_str(int_str)
|
I::from_str(int_str)
|
||||||
.or_else(|err| err!("failed to parse TreeItem {}: {}", field_name, err))?
|
.or_else(|err| err!("failed to parse TreeItem {}: {}", field_name, err))
|
||||||
))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -249,10 +193,10 @@ mod tests {
|
|||||||
size: 42,
|
size: 42,
|
||||||
created: UNIX_EPOCH + Duration::from_secs(1234),
|
created: UNIX_EPOCH + Duration::from_secs(1234),
|
||||||
modified: UNIX_EPOCH + Duration::from_secs(5678),
|
modified: UNIX_EPOCH + Duration::from_secs(5678),
|
||||||
permissions: Permissions { read: true, write: false, execute: true },
|
permissions: Permissions::new(true, false, true),
|
||||||
oid: ObjectId::from_str("0123456789abcdef").unwrap(),
|
oid: ObjectId::from_str("0123456789abcdef").unwrap(),
|
||||||
};
|
};
|
||||||
let expected = "0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한\n".as_bytes();
|
let expected = "0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한/\n".as_bytes();
|
||||||
|
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
item.serialize(&mut result).unwrap();
|
item.serialize(&mut result).unwrap();
|
||||||
@@ -264,7 +208,7 @@ mod tests {
|
|||||||
fn test_deserialize_tree_item() {
|
fn test_deserialize_tree_item() {
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
let item_bytes = "0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한\n".as_bytes();
|
let item_bytes = "0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한/\n".as_bytes();
|
||||||
let mut item_cursor = Cursor::new(item_bytes);
|
let mut item_cursor = Cursor::new(item_bytes);
|
||||||
let expected = TreeItem {
|
let expected = TreeItem {
|
||||||
name: "Test $¢ह€한".to_string(),
|
name: "Test $¢ह€한".to_string(),
|
||||||
@@ -272,24 +216,23 @@ mod tests {
|
|||||||
size: 42,
|
size: 42,
|
||||||
created: UNIX_EPOCH + Duration::from_secs(1234),
|
created: UNIX_EPOCH + Duration::from_secs(1234),
|
||||||
modified: UNIX_EPOCH + Duration::from_secs(5678),
|
modified: UNIX_EPOCH + Duration::from_secs(5678),
|
||||||
permissions: Permissions { read: true, write: false, execute: true },
|
permissions: Permissions::new(true, false, true),
|
||||||
oid: ObjectId::from_str("0123456789abcdef").unwrap(),
|
oid: ObjectId::from_str("0123456789abcdef").unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let item = TreeItem::deserialize(&mut item_cursor).unwrap().unwrap();
|
let item = TreeItem::deserialize(&mut item_cursor).unwrap();
|
||||||
|
|
||||||
assert_eq!(item, expected);
|
assert_eq!(item, expected);
|
||||||
|
|
||||||
assert!(TreeItem::deserialize(&mut Cursor::new("")).unwrap().is_none());
|
|
||||||
assert!(TreeItem::deserialize(&mut Cursor::new(
|
assert!(TreeItem::deserialize(&mut Cursor::new(
|
||||||
"0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한".as_bytes()
|
"0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한/".as_bytes()
|
||||||
)).is_err());
|
)).is_err());
|
||||||
assert!(TreeItem::deserialize(&mut Cursor::new(
|
assert!(TreeItem::deserialize(&mut Cursor::new(
|
||||||
"0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한/bar\n".as_bytes()
|
"0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한/bar/\n".as_bytes()
|
||||||
|
)).is_err());
|
||||||
|
assert!(TreeItem::deserialize(&mut Cursor::new(
|
||||||
|
"0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한\t\n".as_bytes()
|
||||||
)).is_err());
|
)).is_err());
|
||||||
// assert!(TreeItem::deserialize(&mut Cursor::new(
|
|
||||||
// "0123456789abcdef\ttest\t42\tr-x\t1234000\t5678000\tTest $¢ह€한\t\n".as_bytes()
|
|
||||||
// )).is_err());
|
|
||||||
assert!(TreeItem::deserialize(&mut Cursor::new(
|
assert!(TreeItem::deserialize(&mut Cursor::new(
|
||||||
"0123456789abcdef\ttest\t42\tr-x\t5678000\tTest $¢ह€한\t\n".as_bytes()
|
"0123456789abcdef\ttest\t42\tr-x\t5678000\tTest $¢ह€한\t\n".as_bytes()
|
||||||
)).is_err());
|
)).is_err());
|
||||||
|
|||||||
@@ -15,15 +15,15 @@
|
|||||||
|
|
||||||
|
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::fs::read_dir;
|
// use std::fs::{DirEntry, Metadata, ReadDir, read_dir};
|
||||||
use std::vec::IntoIter;
|
use std::vec::IntoIter;
|
||||||
|
|
||||||
use camino::{Utf8Path, Utf8PathBuf};
|
use camino::{Utf8Path};
|
||||||
|
|
||||||
use cas_core::{Error, ObjectId, Result};
|
use bsvfs::{DirEntry, FileSystem};
|
||||||
|
use cas_core::{ObjectId, Result};
|
||||||
|
|
||||||
use crate::{IgnoreAction, IgnoreRules};
|
use crate::{TreeItem};
|
||||||
use crate::TreeItem;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -32,34 +32,24 @@ pub enum Action {
|
|||||||
Update,
|
Update,
|
||||||
Remove,
|
Remove,
|
||||||
Skip,
|
Skip,
|
||||||
Ignore,
|
// Ignore,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TreeWalker {
|
pub struct TreeWalker {
|
||||||
path: Utf8PathBuf,
|
|
||||||
dir_it: Peekable<IntoIter<Result<TreeItem>>>,
|
dir_it: Peekable<IntoIter<Result<TreeItem>>>,
|
||||||
prev_tree_it: Peekable<IntoIter<TreeItem>>,
|
prev_tree_it: Peekable<IntoIter<TreeItem>>,
|
||||||
ignore_rules: Option<IgnoreRules>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl TreeWalker {
|
impl TreeWalker {
|
||||||
pub fn new<P: AsRef<Utf8Path>>(
|
pub fn new<Fs: FileSystem, P: AsRef<Utf8Path>>(fs: Fs, path: P, prev_tree: Vec<TreeItem>) -> Result<Self> {
|
||||||
path: P,
|
let dir_entries = fs.read_dir(path.as_ref().to_path_buf())?
|
||||||
prev_tree: Vec<TreeItem>,
|
|
||||||
ignore_rules: Option<IgnoreRules>
|
|
||||||
) -> Result<Self> {
|
|
||||||
let dir_entries = read_dir(path.as_ref().to_path_buf())?
|
|
||||||
.map(|res| res.map_err(|err| err.into()))
|
.map(|res| res.map_err(|err| err.into()))
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
let mut dir_items: Vec<_> = dir_entries.into_iter()
|
let mut dir_items: Vec<_> = dir_entries.into_iter()
|
||||||
.map(|dir_entry| {
|
.map(|dir_entry| {
|
||||||
let file_name = dir_entry
|
let file_name = dir_entry.file_name()?;
|
||||||
.file_name()
|
|
||||||
.into_string()
|
|
||||||
// .or_else(|os_string| err!("non-unicode file name '{}'", os_string.to_string_lossy()))?;
|
|
||||||
.or_else(|os_string| Err(Error::NonUnicodeFileName(os_string.to_string_lossy().into())))?;
|
|
||||||
let metadata = dir_entry.metadata()?;
|
let metadata = dir_entry.metadata()?;
|
||||||
Ok(TreeItem::from_metadata(
|
Ok(TreeItem::from_metadata(
|
||||||
file_name,
|
file_name,
|
||||||
@@ -77,26 +67,10 @@ impl TreeWalker {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
path: path.as_ref().to_path_buf(),
|
|
||||||
dir_it: dir_items.into_iter().peekable(),
|
dir_it: dir_items.into_iter().peekable(),
|
||||||
prev_tree_it: prev_tree.into_iter().peekable(),
|
prev_tree_it: prev_tree.into_iter().peekable(),
|
||||||
ignore_rules: ignore_rules,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_ignore(&self, item_name: &str, default_action: Action) -> Action {
|
|
||||||
if let Some(ignore_rules) = self.ignore_rules.as_ref() {
|
|
||||||
let mut path = self.path.clone();
|
|
||||||
path.push(item_name);
|
|
||||||
match ignore_rules.action_for(path) {
|
|
||||||
IgnoreAction::Accept => { default_action },
|
|
||||||
IgnoreAction::Ignore => { Action::Ignore },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
default_action
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for TreeWalker {
|
impl Iterator for TreeWalker {
|
||||||
@@ -105,42 +79,32 @@ impl Iterator for TreeWalker {
|
|||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
match (self.dir_it.peek(), self.prev_tree_it.peek()) {
|
match (self.dir_it.peek(), self.prev_tree_it.peek()) {
|
||||||
(Some(Err(_)), _) => {
|
(Some(Err(_)), _) => {
|
||||||
let error = self.dir_it.next().unwrap().unwrap_err();
|
Some(Err(self.dir_it.next().unwrap().unwrap_err()))
|
||||||
Some(Err(error))
|
|
||||||
}
|
}
|
||||||
(Some(Ok(curr_item)), Some(prev_item)) => {
|
(Some(Ok(curr_item)), Some(prev_item)) => {
|
||||||
if curr_item.name == prev_item.name {
|
if curr_item.name == prev_item.name {
|
||||||
let action =
|
let action =
|
||||||
if curr_item.modified != prev_item.modified
|
if curr_item.modified != prev_item.modified {
|
||||||
|| curr_item.otype != prev_item.otype
|
|
||||||
|| curr_item.size != prev_item.size {
|
|
||||||
Action::Update
|
Action::Update
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Action::Skip
|
Action::Skip
|
||||||
};
|
};
|
||||||
let item = self.dir_it.next().unwrap().unwrap();
|
self.prev_tree_it.next();
|
||||||
self.prev_tree_it.next().unwrap();
|
Some(Ok((action, self.dir_it.next().unwrap().unwrap())))
|
||||||
Some(Ok((action, item)))
|
|
||||||
}
|
}
|
||||||
else if curr_item.name < prev_item.name {
|
else if curr_item.name < prev_item.name {
|
||||||
let item = self.dir_it.next().unwrap().unwrap();
|
Some(Ok((Action::Add, self.dir_it.next().unwrap().unwrap())))
|
||||||
let action = self.test_ignore(&item.name, Action::Add);
|
|
||||||
Some(Ok((action, item)))
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let item = self.prev_tree_it.next().unwrap();
|
Some(Ok((Action::Remove, self.prev_tree_it.next().unwrap())))
|
||||||
Some(Ok((Action::Remove, item)))
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(Some(_), None) => {
|
(Some(_), None) => {
|
||||||
let item = self.dir_it.next().unwrap().unwrap();
|
Some(Ok((Action::Add, self.dir_it.next().unwrap().unwrap())))
|
||||||
let action = self.test_ignore(&item.name, Action::Add);
|
|
||||||
Some(Ok((action, item)))
|
|
||||||
},
|
},
|
||||||
(None, Some(_)) => {
|
(None, Some(_)) => {
|
||||||
let item = self.prev_tree_it.next().unwrap();
|
Some(Ok((Action::Remove, self.prev_tree_it.next().unwrap())))
|
||||||
Some(Ok((Action::Remove, item)))
|
|
||||||
},
|
},
|
||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
}
|
}
|
||||||
@@ -150,124 +114,17 @@ impl Iterator for TreeWalker {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::fs::{create_dir, remove_file as fs_remove_file, write};
|
use bsvfs::SysFileSystem;
|
||||||
|
|
||||||
use tempfile::tempdir;
|
|
||||||
|
|
||||||
use cas_core::ObjectType;
|
|
||||||
use crate::Permissions;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn mkdir(root: &Utf8Path, path: &str) -> Result<()> {
|
|
||||||
let mut full_path = root.to_path_buf();
|
|
||||||
full_path.push(path);
|
|
||||||
create_dir(full_path).map_err(|err| err.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_file(root: &Utf8Path, path: &str, content: &[u8]) -> Result<()> {
|
|
||||||
let mut full_path = root.to_path_buf();
|
|
||||||
full_path.push(path);
|
|
||||||
write(full_path, content).map_err(|err| err.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_file(root: &Utf8Path, path: &str) -> Result<()> {
|
|
||||||
let mut full_path = root.to_path_buf();
|
|
||||||
full_path.push(path);
|
|
||||||
fs_remove_file(full_path).map_err(|err| err.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_tree_items(items: Vec<Result<(Action, TreeItem)>>) -> Vec<TreeItem> {
|
|
||||||
items.into_iter()
|
|
||||||
.filter(|ref item| item.as_ref().unwrap().0 != Action::Remove)
|
|
||||||
.map(|item| item.unwrap().1)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tree_walker() -> Result<()>{
|
fn test_tree_walker() {
|
||||||
let root_dir = tempdir()?;
|
for item in TreeWalker::new(SysFileSystem::new(), "/home/draklaw/tmp", vec![]).unwrap() {
|
||||||
let root = Utf8Path::from_path(root_dir.path()).unwrap();
|
match item {
|
||||||
|
Ok((action, tree_item)) => println!("{:?} {:?}", action, tree_item.name),
|
||||||
let ignore_rules = IgnoreRules::from_source("*.bak", root).unwrap();
|
Err(err) => println!("error while iterating directory: {}", err),
|
||||||
|
}
|
||||||
mkdir(root, "test")?;
|
}
|
||||||
write_file(root, "test/foobar.txt", b"baz")?;
|
|
||||||
write_file(root, "readme", b"hello world!")?;
|
|
||||||
|
|
||||||
let items: Vec<_> = TreeWalker::new(root, vec![], Some(ignore_rules.clone())).unwrap().collect();
|
|
||||||
assert_eq!(items.len(), 2);
|
|
||||||
let (action, item) = items[0].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Add);
|
|
||||||
assert_eq!(item.name, "readme");
|
|
||||||
assert_eq!(item.otype, ObjectType::new(b"blob").unwrap());
|
|
||||||
assert_eq!(item.size, 12);
|
|
||||||
assert_eq!(item.permissions, Permissions{read: true, write: true, execute: false});
|
|
||||||
let (action, item) = items[1].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Add);
|
|
||||||
assert_eq!(item.name, "test");
|
|
||||||
assert_eq!(item.otype, ObjectType::new(b"tree").unwrap());
|
|
||||||
assert_eq!(item.size, 0);
|
|
||||||
assert_eq!(item.permissions, Permissions{read: true, write: true, execute: true});
|
|
||||||
|
|
||||||
write_file(root, "abc", b"xxxx")?;
|
|
||||||
|
|
||||||
let items: Vec<_> = TreeWalker::new(root, to_tree_items(items), Some(ignore_rules.clone())).unwrap().collect();
|
|
||||||
assert_eq!(items.len(), 3);
|
|
||||||
let (action, item) = items[0].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Add);
|
|
||||||
assert_eq!(item.name, "abc");
|
|
||||||
assert_eq!(item.otype, ObjectType::new(b"blob").unwrap());
|
|
||||||
assert_eq!(item.size, 4);
|
|
||||||
assert_eq!(item.permissions, Permissions{read: true, write: true, execute: false});
|
|
||||||
let (action, item) = items[1].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Skip);
|
|
||||||
assert_eq!(item.name, "readme");
|
|
||||||
let (action, item) = items[2].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Skip);
|
|
||||||
assert_eq!(item.name, "test");
|
|
||||||
|
|
||||||
remove_file(root, "readme")?;
|
|
||||||
|
|
||||||
let items: Vec<_> = TreeWalker::new(root, to_tree_items(items), Some(ignore_rules.clone())).unwrap().collect();
|
|
||||||
assert_eq!(items.len(), 3);
|
|
||||||
let (action, item) = items[0].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Skip);
|
|
||||||
assert_eq!(item.name, "abc");
|
|
||||||
let (action, item) = items[1].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Remove);
|
|
||||||
assert_eq!(item.name, "readme");
|
|
||||||
let (action, item) = items[2].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Skip);
|
|
||||||
assert_eq!(item.name, "test");
|
|
||||||
|
|
||||||
write_file(root, "abc", b"ab")?;
|
|
||||||
write_file(root, "test/foobar.txt", b"redacted")?;
|
|
||||||
|
|
||||||
let items: Vec<_> = TreeWalker::new(root, to_tree_items(items), Some(ignore_rules.clone())).unwrap().collect();
|
|
||||||
assert_eq!(items.len(), 2);
|
|
||||||
let (action, item) = items[0].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Update);
|
|
||||||
assert_eq!(item.name, "abc");
|
|
||||||
assert_eq!(item.size, 2);
|
|
||||||
let (action, item) = items[1].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Skip);
|
|
||||||
assert_eq!(item.name, "test");
|
|
||||||
|
|
||||||
write_file(root, "test.bak", b"ignore this")?;
|
|
||||||
|
|
||||||
let items: Vec<_> = TreeWalker::new(root, to_tree_items(items), Some(ignore_rules.clone())).unwrap().collect();
|
|
||||||
assert_eq!(items.len(), 3);
|
|
||||||
let (action, item) = items[0].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Skip);
|
|
||||||
assert_eq!(item.name, "abc");
|
|
||||||
let (action, item) = items[1].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Skip);
|
|
||||||
assert_eq!(item.name, "test");
|
|
||||||
let (action, item) = items[2].as_ref().unwrap();
|
|
||||||
assert_eq!(action, &Action::Ignore);
|
|
||||||
assert_eq!(item.name, "test.bak");
|
|
||||||
assert_eq!(item.size, 11);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user