]> OzVa Git service - ozva-cloud/commitdiff
feat: API to search and list directories (#177)
authorsigoden <sigoden@gmail.com>
Mon, 20 Feb 2023 03:05:53 +0000 (11:05 +0800)
committerGitHub <noreply@github.com>
Mon, 20 Feb 2023 03:05:53 +0000 (11:05 +0800)
use `?simple` to output path name only.
use `?json` to output paths in json format.
By default, output html page.

close #166

README.md
src/server.rs
tests/assets.rs
tests/fixtures.rs
tests/http.rs
tests/log_http.rs
tests/single_file.rs
tests/sort.rs
tests/symlink.rs
tests/utils.rs

index 276565e43fa731780c5a799ee5f55d1e54d5441d..1006499a3292bcd193622797b3f9ca709cb127b9 100644 (file)
--- a/README.md
+++ b/README.md
@@ -171,6 +171,16 @@ Delete a file/folder
 curl -X DELETE http://127.0.0.1:5000/path-to-file-or-folder
 ```
 
+List/search directory contents
+
+```
+curl http://127.0.0.1:5000?simple                 # output pathname only, just like `ls -1`
+
+curl http://127.0.0.1:5000?json                   # output name/mtime/type/size and other information in json format
+
+curl http://127.0.0.1:5000?q=Dockerfile&simple    # search for files, just like `find -name Dockerfile`
+```
+
 <details>
 <summary><h2>Advanced topics</h2></summary>
 
index 5045d790f9d9d9981afea99017a12557e6ffeae8..07b570fb2a806fc515e7112805659c2fc58aec5a 100644 (file)
@@ -831,24 +831,50 @@ impl Server {
         } else {
             paths.sort_unstable();
         }
+        if query_params.contains_key("simple") {
+            let output = paths
+                .into_iter()
+                .map(|v| {
+                    if v.is_dir() {
+                        format!("{}/\n", v.name)
+                    } else {
+                        format!("{}\n", v.name)
+                    }
+                })
+                .collect::<Vec<String>>()
+                .join("");
+            res.headers_mut()
+                .typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
+            res.headers_mut()
+                .typed_insert(ContentLength(output.as_bytes().len() as u64));
+            *res.body_mut() = output.into();
+            if head_only {
+                return Ok(());
+            }
+            return Ok(());
+        }
         let href = format!("/{}", normalize_path(path.strip_prefix(&self.args.path)?));
         let data = IndexData {
             href,
             uri_prefix: self.args.uri_prefix.clone(),
-            paths,
             allow_upload: self.args.allow_upload,
             allow_delete: self.args.allow_delete,
             allow_search: self.args.allow_search,
             allow_archive: self.args.allow_archive,
             dir_exists: exist,
+            paths,
+        };
+        let output = if query_params.contains_key("json") {
+            res.headers_mut()
+                .typed_insert(ContentType::from(mime_guess::mime::APPLICATION_JSON));
+            serde_json::to_string_pretty(&data).unwrap()
+        } else {
+            res.headers_mut()
+                .typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
+            self.html
+                .replace("__ASSERTS_PREFIX__", &self.assets_prefix)
+                .replace("__INDEX_DATA__", &serde_json::to_string(&data).unwrap())
         };
-        let data = serde_json::to_string(&data).unwrap();
-        let output = self
-            .html
-            .replace("__ASSERTS_PREFIX__", &self.assets_prefix)
-            .replace("__INDEX_DATA__", &data);
-        res.headers_mut()
-            .typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
         res.headers_mut()
             .typed_insert(ContentLength(output.as_bytes().len() as u64));
         if head_only {
@@ -996,12 +1022,12 @@ impl Server {
 struct IndexData {
     href: String,
     uri_prefix: String,
-    paths: Vec<PathItem>,
     allow_upload: bool,
     allow_delete: bool,
     allow_search: bool,
     allow_archive: bool,
     dir_exists: bool,
+    paths: Vec<PathItem>,
 }
 
 #[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)]
index aa55f211bc6b89f86884a7e816b96aa48e67e74f..27f1361291aa6d0c747cd90eb3bfa5e72ecbd106 100644 (file)
@@ -11,13 +11,13 @@ use std::process::{Command, Stdio};
 fn assets(server: TestServer) -> Result<(), Error> {
     let ver = env!("CARGO_PKG_VERSION");
     let resp = reqwest::blocking::get(server.url())?;
-    let index_js = format!("/__dufs_v{}_index.js", ver);
-    let index_css = format!("/__dufs_v{}_index.css", ver);
-    let favicon_ico = format!("/__dufs_v{}_favicon.ico", ver);
+    let index_js = format!("/__dufs_v{ver}_index.js");
+    let index_css = format!("/__dufs_v{ver}_index.css");
+    let favicon_ico = format!("/__dufs_v{ver}_favicon.ico");
     let text = resp.text()?;
-    assert!(text.contains(&format!(r#"href="{}""#, index_css)));
-    assert!(text.contains(&format!(r#"href="{}""#, favicon_ico)));
-    assert!(text.contains(&format!(r#"src="{}""#, index_js)));
+    assert!(text.contains(&format!(r#"href="{index_css}""#)));
+    assert!(text.contains(&format!(r#"href="{favicon_ico}""#)));
+    assert!(text.contains(&format!(r#"src="{index_js}""#)));
     Ok(())
 }
 
@@ -67,13 +67,13 @@ fn asset_ico(server: TestServer) -> Result<(), Error> {
 fn assets_with_prefix(#[with(&["--path-prefix", "xyz"])] server: TestServer) -> Result<(), Error> {
     let ver = env!("CARGO_PKG_VERSION");
     let resp = reqwest::blocking::get(format!("{}xyz/", server.url()))?;
-    let index_js = format!("/xyz/__dufs_v{}_index.js", ver);
-    let index_css = format!("/xyz/__dufs_v{}_index.css", ver);
-    let favicon_ico = format!("/xyz/__dufs_v{}_favicon.ico", ver);
+    let index_js = format!("/xyz/__dufs_v{ver}_index.js");
+    let index_css = format!("/xyz/__dufs_v{ver}_index.css");
+    let favicon_ico = format!("/xyz/__dufs_v{ver}_favicon.ico");
     let text = resp.text()?;
-    assert!(text.contains(&format!(r#"href="{}""#, index_css)));
-    assert!(text.contains(&format!(r#"href="{}""#, favicon_ico)));
-    assert!(text.contains(&format!(r#"src="{}""#, index_js)));
+    assert!(text.contains(&format!(r#"href="{index_css}""#)));
+    assert!(text.contains(&format!(r#"href="{favicon_ico}""#)));
+    assert!(text.contains(&format!(r#"src="{index_js}""#)));
     Ok(())
 }
 
@@ -108,7 +108,7 @@ fn assets_override(tmpdir: TempDir, port: u16) -> Result<(), Error> {
 
     wait_for_port(port);
 
-    let url = format!("http://localhost:{}", port);
+    let url = format!("http://localhost:{port}");
     let resp = reqwest::blocking::get(&url)?;
     assert!(resp.text()?.starts_with(&format!(
         "/__dufs_v{}_index.js;DATA",
index 02f218218bbab66809f926e7b586ad9407036202..78c3c968156f170c08894d428c107421b24fd208 100644 (file)
@@ -44,7 +44,7 @@ pub fn tmpdir() -> TempDir {
     for file in FILES {
         tmpdir
             .child(file)
-            .write_str(&format!("This is {}", file))
+            .write_str(&format!("This is {file}"))
             .expect("Couldn't write to file");
     }
     for directory in DIRECTORIES {
@@ -59,8 +59,8 @@ pub fn tmpdir() -> TempDir {
                     continue;
                 }
                 tmpdir
-                    .child(format!("{}{}", directory, file))
-                    .write_str(&format!("This is {}{}", directory, file))
+                    .child(format!("{directory}{file}"))
+                    .write_str(&format!("This is {directory}{file}"))
                     .expect("Couldn't write to file");
             }
         }
@@ -109,11 +109,11 @@ where
 pub fn wait_for_port(port: u16) {
     let start_wait = Instant::now();
 
-    while !port_check::is_port_reachable(format!("localhost:{}", port)) {
+    while !port_check::is_port_reachable(format!("localhost:{port}")) {
         sleep(Duration::from_millis(100));
 
         if start_wait.elapsed().as_secs() > 1 {
-            panic!("timeout waiting for port {}", port);
+            panic!("timeout waiting for port {port}");
         }
     }
 }
index 6ade44a35430c4da5d831366418f8564198f5dd9..a7c297974f1696bf48fbb8925b0fc4b37977d494 100644 (file)
@@ -3,6 +3,7 @@ mod utils;
 
 use fixtures::{server, Error, TestServer};
 use rstest::rstest;
+use serde_json::Value;
 
 #[rstest]
 fn get_dir(server: TestServer) -> Result<(), Error> {
@@ -49,6 +50,32 @@ fn get_dir_zip(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
     Ok(())
 }
 
+#[rstest]
+fn get_dir_json(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
+    let resp = reqwest::blocking::get(format!("{}?json", server.url()))?;
+    assert_eq!(resp.status(), 200);
+    assert_eq!(
+        resp.headers().get("content-type").unwrap(),
+        "application/json"
+    );
+    let json: Value = serde_json::from_str(&resp.text().unwrap()).unwrap();
+    assert!(json["paths"].as_array().is_some());
+    Ok(())
+}
+
+#[rstest]
+fn get_dir_simple(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
+    let resp = reqwest::blocking::get(format!("{}?simple", server.url()))?;
+    assert_eq!(resp.status(), 200);
+    assert_eq!(
+        resp.headers().get("content-type").unwrap(),
+        "text/html; charset=utf-8"
+    );
+    let text = resp.text().unwrap();
+    assert!(text.split('\n').any(|v| v == "index.html"));
+    Ok(())
+}
+
 #[rstest]
 fn head_dir_zip(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
     let resp = fetch!(b"HEAD", format!("{}?zip", server.url())).send()?;
@@ -86,6 +113,15 @@ fn get_dir_search2(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
     Ok(())
 }
 
+#[rstest]
+fn get_dir_search3(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
+    let resp = reqwest::blocking::get(format!("{}?q={}&simple", server.url(), "test.html"))?;
+    assert_eq!(resp.status(), 200);
+    let text = resp.text().unwrap();
+    assert!(text.split('\n').any(|v| v == "test.html"));
+    Ok(())
+}
+
 #[rstest]
 fn head_dir_search(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
     let resp = fetch!(b"HEAD", format!("{}?q={}", server.url(), "test.html")).send()?;
index 598913878f71de69e9d8eeca45224c702ae5beeb..143608e92a0f72e0f9afad6115fc02483fb92540 100644 (file)
@@ -31,7 +31,7 @@ fn log_remote_user(
 
     let stdout = child.stdout.as_mut().expect("Failed to get stdout");
 
-    let req = fetch!(b"GET", &format!("http://localhost:{}", port));
+    let req = fetch!(b"GET", &format!("http://localhost:{port}"));
 
     let resp = if is_basic {
         req.basic_auth("user", Some("pass")).send()?
@@ -66,7 +66,7 @@ fn no_log(tmpdir: TempDir, port: u16, #[case] args: &[&str]) -> Result<(), Error
 
     let stdout = child.stdout.as_mut().expect("Failed to get stdout");
 
-    let resp = fetch!(b"GET", &format!("http://localhost:{}", port)).send()?;
+    let resp = fetch!(b"GET", &format!("http://localhost:{port}")).send()?;
     assert_eq!(resp.status(), 200);
 
     let mut buf = [0; 1000];
index 1d97f3ff8f1a939c57b96331bbd4bde4b2a977ff..f2b3f8d17fe265b0bf8cd5714c8728cd926d2faa 100644 (file)
@@ -21,11 +21,11 @@ fn single_file(tmpdir: TempDir, port: u16, #[case] file: &str) -> Result<(), Err
 
     wait_for_port(port);
 
-    let resp = reqwest::blocking::get(format!("http://localhost:{}", port))?;
+    let resp = reqwest::blocking::get(format!("http://localhost:{port}"))?;
     assert_eq!(resp.text()?, "This is index.html");
-    let resp = reqwest::blocking::get(format!("http://localhost:{}/", port))?;
+    let resp = reqwest::blocking::get(format!("http://localhost:{port}/"))?;
     assert_eq!(resp.text()?, "This is index.html");
-    let resp = reqwest::blocking::get(format!("http://localhost:{}/index.html", port))?;
+    let resp = reqwest::blocking::get(format!("http://localhost:{port}/index.html"))?;
     assert_eq!(resp.text()?, "This is index.html");
 
     child.kill()?;
@@ -46,13 +46,13 @@ fn path_prefix_single_file(tmpdir: TempDir, port: u16, #[case] file: &str) -> Re
 
     wait_for_port(port);
 
-    let resp = reqwest::blocking::get(format!("http://localhost:{}/xyz", port))?;
+    let resp = reqwest::blocking::get(format!("http://localhost:{port}/xyz"))?;
     assert_eq!(resp.text()?, "This is index.html");
-    let resp = reqwest::blocking::get(format!("http://localhost:{}/xyz/", port))?;
+    let resp = reqwest::blocking::get(format!("http://localhost:{port}/xyz/"))?;
     assert_eq!(resp.text()?, "This is index.html");
-    let resp = reqwest::blocking::get(format!("http://localhost:{}/xyz/index.html", port))?;
+    let resp = reqwest::blocking::get(format!("http://localhost:{port}/xyz/index.html"))?;
     assert_eq!(resp.text()?, "This is index.html");
-    let resp = reqwest::blocking::get(format!("http://localhost:{}", port))?;
+    let resp = reqwest::blocking::get(format!("http://localhost:{port}"))?;
     assert_eq!(resp.status(), 404);
 
     child.kill()?;
index 4563a5132489042cc03f760be05b390ae2bce2d4..3254ed384b51ba995690efd3bb2b0942c76a9882 100644 (file)
@@ -7,9 +7,9 @@ use rstest::rstest;
 #[rstest]
 fn ls_dir_sort_by_name(server: TestServer) -> Result<(), Error> {
     let url = server.url();
-    let resp = reqwest::blocking::get(format!("{}?sort=name&order=asc", url))?;
+    let resp = reqwest::blocking::get(format!("{url}?sort=name&order=asc"))?;
     let paths1 = self::utils::retrieve_index_paths(&resp.text()?);
-    let resp = reqwest::blocking::get(format!("{}?sort=name&order=desc", url))?;
+    let resp = reqwest::blocking::get(format!("{url}?sort=name&order=desc"))?;
     let mut paths2 = self::utils::retrieve_index_paths(&resp.text()?);
     paths2.reverse();
     assert_eq!(paths1, paths2);
@@ -19,9 +19,9 @@ fn ls_dir_sort_by_name(server: TestServer) -> Result<(), Error> {
 #[rstest]
 fn search_dir_sort_by_name(server: TestServer) -> Result<(), Error> {
     let url = server.url();
-    let resp = reqwest::blocking::get(format!("{}?q={}&sort=name&order=asc", url, "test.html"))?;
+    let resp = reqwest::blocking::get(format!("{url}?q=test.html&sort=name&order=asc"))?;
     let paths1 = self::utils::retrieve_index_paths(&resp.text()?);
-    let resp = reqwest::blocking::get(format!("{}?q={}&sort=name&order=desc", url, "test.html"))?;
+    let resp = reqwest::blocking::get(format!("{url}?q=test.html&sort=name&order=desc"))?;
     let mut paths2 = self::utils::retrieve_index_paths(&resp.text()?);
     paths2.reverse();
     assert_eq!(paths1, paths2);
index 7eeeadd1b0dae900f60f7d4e011ac33c8e744409..2ecce066f2b932b563f6e218d9e03e2c924c24b6 100644 (file)
@@ -22,7 +22,7 @@ fn default_not_allow_symlink(server: TestServer, tmpdir: TempDir) -> Result<(),
     let resp = reqwest::blocking::get(server.url())?;
     let paths = utils::retrieve_index_paths(&resp.text()?);
     assert!(!paths.is_empty());
-    assert!(!paths.contains(&format!("{}/", dir)));
+    assert!(!paths.contains(&format!("{dir}/")));
     Ok(())
 }
 
@@ -41,6 +41,6 @@ fn allow_symlink(
     let resp = reqwest::blocking::get(server.url())?;
     let paths = utils::retrieve_index_paths(&resp.text()?);
     assert!(!paths.is_empty());
-    assert!(paths.contains(&format!("{}/", dir)));
+    assert!(paths.contains(&format!("{dir}/")));
     Ok(())
 }
index 0789d334cfd41cdc12f511e000dc298efee2239c..f8d4b5a629cb45a5e4c6614f13ae83714228f8f7 100644 (file)
@@ -48,7 +48,7 @@ fn retrieve_index_paths_impl(index: &str) -> Option<IndexSet<String>> {
             let name = v.get("name")?.as_str()?;
             let path_type = v.get("path_type")?.as_str()?;
             if path_type.ends_with("Dir") {
-                Some(format!("{}/", name))
+                Some(format!("{name}/"))
             } else {
                 Some(name.to_owned())
             }