From d6069a4ba507c62da868f9e88a22de2839738f46 Mon Sep 17 00:00:00 2001 From: Alexander Weiss <alex@weissfam.de> Date: Sat, 23 Nov 2024 13:43:49 +0100 Subject: [PATCH] chore: move webdavfs from rustic_core to rustic --- Cargo.lock | 259 ++++++++++++++++++----- Cargo.toml | 2 + src/commands/webdav.rs | 6 +- src/commands/webdav/webdavfs.rs | 353 ++++++++++++++++++++++++++++++++ 4 files changed, 569 insertions(+), 51 deletions(-) create mode 100644 src/commands/webdav/webdavfs.rs diff --git a/Cargo.lock b/Cargo.lock index 7ffd2d837..f7800dcc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -359,7 +359,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -390,6 +390,33 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5b3469636cdf8543cceab175efca534471f36eee12fb8374aba00eb5e7e7f8a" +[[package]] +name = "aws-lc-rs" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7c2840b66236045acd2607d5866e274380afd87ef99d6226e961e2cb47df45" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad3a619a9de81e1d7de1f1186dcba4506ed661a0e483d84410fdef0ee87b2f96" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "backoff" version = "0.4.0" @@ -463,6 +490,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.89", + "which", +] + [[package]] name = "binrw" version = "0.14.0" @@ -561,9 +611,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "bytesize" @@ -595,7 +645,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -654,6 +704,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -697,6 +756,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.20" @@ -738,7 +808,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -747,6 +817,15 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "cmake" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +dependencies = [ + "cc", +] + [[package]] name = "color-eyre" version = "0.6.3" @@ -831,7 +910,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -1068,7 +1147,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -1092,7 +1171,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -1103,7 +1182,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -1208,7 +1287,7 @@ checksum = "64b697ac90ff296f0fc031ee5a61c7ac31fb9fff50e3fb32873b09223613fc0c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -1228,7 +1307,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", "unicode-xid", ] @@ -1241,7 +1320,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -1331,7 +1410,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -1418,7 +1497,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -1440,7 +1519,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -1687,7 +1766,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -2279,7 +2358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" dependencies = [ "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -2331,6 +2410,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -2439,7 +2527,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -2451,12 +2539,28 @@ dependencies = [ "spin", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if 1.0.0", + "windows-targets 0.52.6", +] + [[package]] name = "libm" version = "0.2.8" @@ -2574,6 +2678,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -2605,6 +2715,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + [[package]] name = "moka" version = "0.12.8" @@ -2665,6 +2781,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonzero_ext" version = "0.3.0" @@ -2820,9 +2946,9 @@ dependencies = [ [[package]] name = "opendal" -version = "0.50.0" +version = "0.50.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36e44fc43be9ffe18dad3e3ef9d61c1ae01991ee6f1c8c026978c35777a711bf" +checksum = "cb28bb6c64e116ceaf8dd4e87099d3cfea4a58e85e62b104fef74c91afba0f44" dependencies = [ "anyhow", "async-tls", @@ -3116,7 +3242,7 @@ checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -3273,6 +3399,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.89", +] + [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -3301,14 +3437,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] name = "proc-macro2" -version = "1.0.87" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -3401,7 +3537,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.0.0", "rustls 0.23.14", "socket2", "thiserror", @@ -3418,7 +3554,7 @@ dependencies = [ "bytes", "rand", "ring", - "rustc-hash", + "rustc-hash 2.0.0", "rustls 0.23.14", "slab", "thiserror", @@ -3490,7 +3626,7 @@ dependencies = [ "crossterm 0.28.1", "indoc", "instability", - "itertools", + "itertools 0.13.0", "lru", "paste", "strum", @@ -3600,9 +3736,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqsign" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03dd4ba7c3901dd43e6b8c7446a760d45bc1ea4301002e1a6fa48f97c3a796fa" +checksum = "eb0075a66c8bfbf4cc8b70dca166e722e1f55a3ea9250ecbb85f4d92a5f64149" dependencies = [ "anyhow", "async-trait", @@ -3742,7 +3878,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -3818,7 +3954,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.79", + "syn 2.0.89", "unicode-ident", ] @@ -3848,6 +3984,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.0.0" @@ -3871,6 +4013,7 @@ dependencies = [ "aho-corasick", "anyhow", "assert_cmd", + "bytes", "bytesize", "cached", "chrono", @@ -3888,13 +4031,14 @@ dependencies = [ "directories", "displaydoc", "fuse_mt", + "futures", "gethostname", "globset", "human-panic", "humantime", "indicatif", "insta", - "itertools", + "itertools 0.13.0", "jemallocator-global", "libc", "log", @@ -3943,7 +4087,7 @@ dependencies = [ "displaydoc", "hex", "humantime", - "itertools", + "itertools 0.13.0", "log", "opendal", "rand", @@ -3995,7 +4139,7 @@ dependencies = [ "humantime", "ignore", "integer-sqrt", - "itertools", + "itertools 0.13.0", "log", "nix", "pariter", @@ -4064,6 +4208,8 @@ version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ + "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -4125,6 +4271,7 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -4306,7 +4453,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -4369,7 +4516,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -4622,7 +4769,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -4633,9 +4780,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "suppaftp" -version = "6.0.1" +version = "6.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5c3d37ce3092d7148494a30a0f5036f8722792a626b25662a87434d3683c33" +checksum = "75de72048c936880d6a4dd40048e0e656652e56ecdd28ca13096783592c0782f" dependencies = [ "async-std", "async-tls", @@ -4645,7 +4792,7 @@ dependencies = [ "lazy-regex", "log", "pin-project", - "rustls 0.21.12", + "rustls 0.23.14", "thiserror", ] @@ -4662,9 +4809,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -4800,7 +4947,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -4915,7 +5062,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -5024,7 +5171,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -5173,7 +5320,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "itertools", + "itertools 0.13.0", "unicode-segmentation", "unicode-width 0.1.14", ] @@ -5364,7 +5511,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -5398,7 +5545,7 @@ checksum = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5476,6 +5623,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -5773,7 +5932,7 @@ checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] @@ -5784,7 +5943,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.89", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c25a13884..3e4977556 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,6 +108,8 @@ open = "5.3.0" self_update = { version = "0.39.0", default-features = false, optional = true, features = ["rustls", "archive-tar", "compression-flate2"] } # FIXME: Downgraded to 0.39.0 due to https://github.com/jaemk/self_update/issues/136 tar = "0.4.42" toml = "0.8" +bytes = "1.8.0" +futures = "0.3.31" [dev-dependencies] abscissa_core = { version = "0.8.1", default-features = false, features = ["testing"] } diff --git a/src/commands/webdav.rs b/src/commands/webdav.rs index 5dfdc4ccd..90b79072c 100644 --- a/src/commands/webdav.rs +++ b/src/commands/webdav.rs @@ -13,6 +13,9 @@ use dav_server::{warp::dav_handler, DavHandler}; use serde::{Deserialize, Serialize}; use rustic_core::vfs::{FilePolicy, IdenticalSnapshot, Latest, Vfs}; +use webdavfs::WebDavFS; + +mod webdavfs; #[derive(Clone, Command, Default, Debug, clap::Parser, Serialize, Deserialize, Merge)] #[serde(default, rename_all = "kebab-case", deny_unknown_fields)] @@ -127,8 +130,9 @@ impl WebDavCmd { |s| s.parse(), )?; + let webdavfs = WebDavFS::new(repo, vfs, file_access); let dav_server = DavHandler::builder() - .filesystem(vfs.into_webdav_fs(repo, file_access)) + .filesystem(Box::new(webdavfs)) .build_handler(); tokio::runtime::Builder::new_current_thread() diff --git a/src/commands/webdav/webdavfs.rs b/src/commands/webdav/webdavfs.rs new file mode 100644 index 000000000..052089dbf --- /dev/null +++ b/src/commands/webdav/webdavfs.rs @@ -0,0 +1,353 @@ +use std::fmt::{Debug, Formatter}; +use std::io::SeekFrom; +#[cfg(not(windows))] +use std::os::unix::ffi::OsStrExt; +use std::sync::{Arc, OnceLock}; +use std::time::SystemTime; + +use bytes::{Buf, Bytes}; +use futures::FutureExt; +use rustic_core::repofile::Node; +use rustic_core::vfs::{FilePolicy, OpenFile, Vfs}; +use rustic_core::{IndexedFull, Repository}; + +use dav_server::{ + davpath::DavPath, + fs::{ + DavDirEntry, DavFile, DavFileSystem, DavMetaData, FsError, FsFuture, FsResult, FsStream, + OpenOptions, ReadDirMeta, + }, +}; + +fn now() -> SystemTime { + static NOW: OnceLock<SystemTime> = OnceLock::new(); + *NOW.get_or_init(SystemTime::now) +} + +/// The inner state of a [`WebDavFS`] instance. +struct DavFsInner<P, S> { + /// The [`Repository`] to use + repo: Repository<P, S>, + + /// The [`Vfs`] to use + vfs: Vfs, + + /// The [`FilePolicy`] to use + file_policy: FilePolicy, +} + +impl<P, S> Debug for DavFsInner<P, S> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "DavFS") + } +} + +/// DAV Filesystem implementation. +/// +/// This is the main entry point for the DAV filesystem. +/// It implements [`DavFileSystem`] and can be used to serve a [`Repository`] via DAV. +#[derive(Debug)] +pub struct WebDavFS<P, S> { + inner: Arc<DavFsInner<P, S>>, +} + +impl<P, S: IndexedFull> WebDavFS<P, S> { + /// Create a new [`WebDavFS`] instance. + /// + /// # Arguments + /// + /// * `repo` - The [`Repository`] to use + /// * `vfs` - The [`Vfs`] to use + /// * `file_policy` - The [`FilePolicy`] to use + /// + /// # Returns + /// + /// A new [`WebDavFS`] instance + pub(crate) fn new(repo: Repository<P, S>, vfs: Vfs, file_policy: FilePolicy) -> Self { + let inner = DavFsInner { + repo, + vfs, + file_policy, + }; + + Self { + inner: Arc::new(inner), + } + } + + /// Get a [`Node`] from the specified [`DavPath`]. + /// + /// # Arguments + /// + /// * `path` - The path to get the [`Tree`] at + /// + /// # Errors + /// + /// * If the [`Tree`] could not be found + /// + /// # Returns + /// + /// The [`Node`] at the specified path + /// + /// [`Tree`]: crate::repofile::Tree + fn node_from_path(&self, path: &DavPath) -> Result<Node, FsError> { + self.inner + .vfs + .node_from_path(&self.inner.repo, &path.as_pathbuf()) + .map_err(|_| FsError::GeneralFailure) + } + + /// Get a list of [`Node`]s from the specified directory path. + /// + /// # Arguments + /// + /// * `path` - The path to get the [`Tree`] at + /// + /// # Errors + /// + /// * If the [`Tree`] could not be found + /// + /// # Returns + /// + /// The list of [`Node`]s at the specified path + /// + /// [`Tree`]: crate::repofile::Tree + fn dir_entries_from_path(&self, path: &DavPath) -> Result<Vec<Node>, FsError> { + self.inner + .vfs + .dir_entries_from_path(&self.inner.repo, &path.as_pathbuf()) + .map_err(|_| FsError::GeneralFailure) + } +} + +impl<P, S: IndexedFull> Clone for WebDavFS<P, S> { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl<P: Debug + Send + Sync + 'static, S: IndexedFull + Debug + Send + Sync + 'static> DavFileSystem + for WebDavFS<P, S> +{ + fn metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'_, Box<dyn DavMetaData>> { + self.symlink_metadata(davpath) + } + + fn symlink_metadata<'a>(&'a self, davpath: &'a DavPath) -> FsFuture<'_, Box<dyn DavMetaData>> { + async move { + let node = self.node_from_path(davpath)?; + let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(node)); + Ok(meta) + } + .boxed() + } + + fn read_dir<'a>( + &'a self, + davpath: &'a DavPath, + _meta: ReadDirMeta, + ) -> FsFuture<'_, FsStream<Box<dyn DavDirEntry>>> { + async move { + let entries = self.dir_entries_from_path(davpath)?; + let entry_iter = entries.into_iter().map(|e| { + let entry: Box<dyn DavDirEntry> = Box::new(DavFsDirEntry(e)); + Ok(entry) + }); + let strm: FsStream<Box<dyn DavDirEntry>> = Box::pin(futures::stream::iter(entry_iter)); + Ok(strm) + } + .boxed() + } + + fn open<'a>( + &'a self, + path: &'a DavPath, + options: OpenOptions, + ) -> FsFuture<'_, Box<dyn DavFile>> { + async move { + if options.write + || options.append + || options.truncate + || options.create + || options.create_new + { + return Err(FsError::Forbidden); + } + + let node = self.node_from_path(path)?; + if matches!(self.inner.file_policy, FilePolicy::Forbidden) { + return Err(FsError::Forbidden); + } + + let open = self + .inner + .repo + .open_file(&node) + .map_err(|_err| FsError::GeneralFailure)?; + let file: Box<dyn DavFile> = Box::new(DavFsFile { + node, + open, + fs: self.inner.clone(), + seek: 0, + }); + Ok(file) + } + .boxed() + } +} + +/// A [`DavDirEntry`] implementation for [`Node`]s. +#[derive(Clone, Debug)] +struct DavFsDirEntry(Node); + +impl DavDirEntry for DavFsDirEntry { + fn metadata(&self) -> FsFuture<'_, Box<dyn DavMetaData>> { + async move { + let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(self.0.clone())); + Ok(meta) + } + .boxed() + } + + #[cfg(not(windows))] + fn name(&self) -> Vec<u8> { + self.0.name().as_bytes().to_vec() + } + + #[cfg(windows)] + fn name(&self) -> Vec<u8> { + self.0 + .name() + .as_os_str() + .to_string_lossy() + .to_string() + .into_bytes() + } +} + +/// A [`DavFile`] implementation for [`Node`]s. +/// +/// This is a read-only file. +struct DavFsFile<P, S> { + /// The [`Node`] this file is for + node: Node, + + /// The [`OpenFile`] for this file + open: OpenFile, + + /// The [`DavFsInner`] this file belongs to + fs: Arc<DavFsInner<P, S>>, + + /// The current seek position + seek: usize, +} + +impl<P, S> Debug for DavFsFile<P, S> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "DavFile") + } +} + +impl<P: Debug + Send + Sync, S: IndexedFull + Debug + Send + Sync> DavFile for DavFsFile<P, S> { + fn metadata(&mut self) -> FsFuture<'_, Box<dyn DavMetaData>> { + async move { + let meta: Box<dyn DavMetaData> = Box::new(DavFsMetaData(self.node.clone())); + Ok(meta) + } + .boxed() + } + + fn write_bytes(&mut self, _buf: Bytes) -> FsFuture<'_, ()> { + async move { Err(FsError::Forbidden) }.boxed() + } + + fn write_buf(&mut self, _buf: Box<dyn Buf + Send>) -> FsFuture<'_, ()> { + async move { Err(FsError::Forbidden) }.boxed() + } + + fn read_bytes(&mut self, count: usize) -> FsFuture<'_, Bytes> { + async move { + let data = self + .fs + .repo + .read_file_at(&self.open, self.seek, count) + .map_err(|_err| FsError::GeneralFailure)?; + self.seek += data.len(); + Ok(data) + } + .boxed() + } + + fn seek(&mut self, pos: SeekFrom) -> FsFuture<'_, u64> { + async move { + match pos { + SeekFrom::Start(start) => { + self.seek = usize::try_from(start).expect("usize overflow should not happen"); + } + SeekFrom::Current(delta) => { + self.seek = usize::try_from( + i64::try_from(self.seek).expect("i64 wrapped around") + delta, + ) + .expect("usize overflow should not happen"); + } + SeekFrom::End(end) => { + self.seek = usize::try_from( + i64::try_from(self.node.meta.size).expect("i64 wrapped around") + end, + ) + .expect("usize overflow should not happen"); + } + } + + Ok(self.seek as u64) + } + .boxed() + } + + fn flush(&mut self) -> FsFuture<'_, ()> { + async move { Ok(()) }.boxed() + } +} + +/// A [`DavMetaData`] implementation for [`Node`]s. +#[derive(Clone, Debug)] +struct DavFsMetaData(Node); + +impl DavMetaData for DavFsMetaData { + fn len(&self) -> u64 { + self.0.meta.size + } + fn created(&self) -> FsResult<SystemTime> { + Ok(now()) + } + fn modified(&self) -> FsResult<SystemTime> { + Ok(self.0.meta.mtime.map_or_else(now, SystemTime::from)) + } + fn accessed(&self) -> FsResult<SystemTime> { + Ok(self.0.meta.atime.map_or_else(now, SystemTime::from)) + } + + fn status_changed(&self) -> FsResult<SystemTime> { + Ok(self.0.meta.ctime.map_or_else(now, SystemTime::from)) + } + + fn is_dir(&self) -> bool { + self.0.is_dir() + } + fn is_file(&self) -> bool { + self.0.is_file() + } + fn is_symlink(&self) -> bool { + self.0.is_symlink() + } + fn executable(&self) -> FsResult<bool> { + if self.0.is_file() { + let Some(mode) = self.0.meta.mode else { + return Ok(false); + }; + return Ok((mode & 0o100) > 0); + } + Err(FsError::NotImplemented) + } +}