feat: support searching
							parent
							
								
									2eba975066
								
							
						
					
					
						commit
						586b209c89
					
				| 
						 | 
				
			
			@ -258,7 +258,7 @@ dependencies = [
 | 
			
		|||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "duf"
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
version = "0.3.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "async-walkdir",
 | 
			
		||||
 "async_zip",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "duf"
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
version = "0.3.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
authors = ["sigoden <sigoden@gmail.com>"]
 | 
			
		||||
description = "Duf is a simple file server."
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,12 +5,13 @@
 | 
			
		|||
 | 
			
		||||
Duf is a simple file server.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
- Serve static files
 | 
			
		||||
- Download folder as zip file
 | 
			
		||||
- Search files
 | 
			
		||||
- Upload files
 | 
			
		||||
- Delete files
 | 
			
		||||
- Basic authentication
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,14 +4,15 @@ html {
 | 
			
		|||
  color: #24292e;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.head {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: baseline;
 | 
			
		||||
  padding: 1em 1em 0;
 | 
			
		||||
body {
 | 
			
		||||
  width: 700px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.head input {
 | 
			
		||||
  display: none;
 | 
			
		||||
.head {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  padding: 1em 1em 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb {
 | 
			
		||||
| 
						 | 
				
			
			@ -44,11 +45,50 @@ html {
 | 
			
		|||
  padding-left: 0.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.toolbox {
 | 
			
		||||
  display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.searchbar {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-wrap: nowrap;
 | 
			
		||||
  width: 246px;
 | 
			
		||||
  height: 22px;
 | 
			
		||||
  background-color: #fafafa;
 | 
			
		||||
  transition: all .15s;
 | 
			
		||||
  border: 1px #ddd solid;
 | 
			
		||||
  border-radius: 15px;
 | 
			
		||||
  margin: 0 0 2px 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.searchbar #search {
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  line-height: 16px;
 | 
			
		||||
  padding: 1px;
 | 
			
		||||
  font-family: helvetica neue,luxi sans,Tahoma,hiragino sans gb,STHeiti,sans-serif;
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
  border: none;
 | 
			
		||||
  outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.searchbar .icon {
 | 
			
		||||
  color: #9a9a9a;
 | 
			
		||||
  padding: 3px 3px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-control {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  padding-left: 0.25em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-control input {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.main {
 | 
			
		||||
  padding: 0 1em;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,11 +10,20 @@
 | 
			
		|||
<body>
 | 
			
		||||
  <div class="head">
 | 
			
		||||
    <div class="breadcrumb"></div>
 | 
			
		||||
    <div>
 | 
			
		||||
      <a href="?zip" title="Download folder as a .zip file">
 | 
			
		||||
        <svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>
 | 
			
		||||
      </a>
 | 
			
		||||
    <div class="toolbox">
 | 
			
		||||
      <div>
 | 
			
		||||
        <a href="?zip" title="Download folder as a .zip file">
 | 
			
		||||
          <svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>
 | 
			
		||||
        </a>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <form class="searchbar">
 | 
			
		||||
      <div class="icon">
 | 
			
		||||
        <svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/></svg>
 | 
			
		||||
      </div>
 | 
			
		||||
      <input id="search" name="q" type="text" maxlength="128" autocomplete="off" tabindex="1">
 | 
			
		||||
      <input type="submit" hidden />
 | 
			
		||||
    </form>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="main">
 | 
			
		||||
    <div class="uploaders">
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +43,7 @@
 | 
			
		|||
  </div>
 | 
			
		||||
  <script>
 | 
			
		||||
 | 
			
		||||
    const $head = document.querySelector(".head");
 | 
			
		||||
    const $toolbox = document.querySelector(".toolbox");
 | 
			
		||||
    const $tbody = document.querySelector(".main tbody");
 | 
			
		||||
    const $breadcrumb = document.querySelector(".breadcrumb");
 | 
			
		||||
    const $uploaders = document.querySelector(".uploaders");
 | 
			
		||||
| 
						 | 
				
			
			@ -53,9 +62,7 @@
 | 
			
		|||
 | 
			
		||||
      upload() {
 | 
			
		||||
        const { file, idx } = this;
 | 
			
		||||
        let url = location.href.split('?')[0];
 | 
			
		||||
        if (!url.endsWith("/")) url += "/";
 | 
			
		||||
        url += encodeURI(file.name);
 | 
			
		||||
        const url = getUrl(file.name);
 | 
			
		||||
        $uploaders.insertAdjacentHTML("beforeend", `
 | 
			
		||||
      <div class="uploader path">
 | 
			
		||||
        <div><svg height="16" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M6 5H2V4h4v1zM2 8h7V7H2v1zm0 2h7V9H2v1zm0 2h7v-1H2v1zm10-7.5V14c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1h7.5L12 4.5zM11 5L8 2H1v12h10V5z"></path></svg></div>
 | 
			
		||||
| 
						 | 
				
			
			@ -107,7 +114,7 @@
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    function addPath(file, index) {
 | 
			
		||||
      const url = encodeURI(file.path);
 | 
			
		||||
      const url = getUrl(file.name)
 | 
			
		||||
      let actionDelete = "";
 | 
			
		||||
      let actionDownload = "";
 | 
			
		||||
      if (file.path_type.endsWith("Dir")) {
 | 
			
		||||
| 
						 | 
				
			
			@ -154,7 +161,7 @@
 | 
			
		|||
      if (!file) return;
 | 
			
		||||
 | 
			
		||||
      const ajax = new XMLHttpRequest();
 | 
			
		||||
      ajax.open("DELETE", encodeURI(file.path));
 | 
			
		||||
      ajax.open("DELETE", getUrl(file.name));
 | 
			
		||||
      ajax.addEventListener("readystatechange", function() {
 | 
			
		||||
        if(ajax.readyState === 4 && ajax.status === 200) {
 | 
			
		||||
            document.getElementById(`addPath${index}`).remove();
 | 
			
		||||
| 
						 | 
				
			
			@ -164,7 +171,7 @@
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    function addUploadControl() {
 | 
			
		||||
      $head.insertAdjacentHTML("beforeend", `
 | 
			
		||||
      $toolbox.insertAdjacentHTML("beforeend", `
 | 
			
		||||
    <div class="upload-control" title="Upload file">
 | 
			
		||||
      <label for="file">
 | 
			
		||||
        <svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z"/></svg>
 | 
			
		||||
| 
						 | 
				
			
			@ -174,6 +181,13 @@
 | 
			
		|||
`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getUrl(name) {
 | 
			
		||||
        let url = location.href.split('?')[0];
 | 
			
		||||
        if (!url.endsWith("/")) url += "/";
 | 
			
		||||
        url += encodeURI(name);
 | 
			
		||||
        return url;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getSvg(path_type) {
 | 
			
		||||
      switch (path_type) {
 | 
			
		||||
        case "Dir":
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										146
									
								
								src/server.rs
								
								
								
								
							
							
						
						
									
										146
									
								
								src/server.rs
								
								
								
								
							| 
						 | 
				
			
			@ -108,18 +108,21 @@ impl InnerService {
 | 
			
		|||
        match fs::metadata(&path).await {
 | 
			
		||||
            Ok(meta) => {
 | 
			
		||||
                if meta.is_dir() {
 | 
			
		||||
                    if req.uri().query().map(|v| v == "zip").unwrap_or_default() {
 | 
			
		||||
                        self.handle_send_dir_zip(path.as_path()).await
 | 
			
		||||
                    } else {
 | 
			
		||||
                        self.handle_send_dir(path.as_path(), true).await
 | 
			
		||||
                    let req_query = req.uri().query().unwrap_or_default();
 | 
			
		||||
                    if req_query == "zip" {
 | 
			
		||||
                        return self.handle_send_dir_zip(path.as_path()).await;
 | 
			
		||||
                    }
 | 
			
		||||
                    if let Some(q) = req_query.strip_prefix("q=") {
 | 
			
		||||
                        return self.handle_query_dir(path.as_path(), q).await;
 | 
			
		||||
                    }
 | 
			
		||||
                    self.handle_ls_dir(path.as_path(), true).await
 | 
			
		||||
                } else {
 | 
			
		||||
                    self.handle_send_file(path.as_path()).await
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
                if req_path.ends_with('/') {
 | 
			
		||||
                    self.handle_send_dir(path.as_path(), false).await
 | 
			
		||||
                    self.handle_ls_dir(path.as_path(), false).await
 | 
			
		||||
                } else {
 | 
			
		||||
                    Ok(status_code!(StatusCode::NOT_FOUND))
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -176,31 +179,42 @@ impl InnerService {
 | 
			
		|||
        Ok(status_code!(StatusCode::OK))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn handle_send_dir(&self, path: &Path, exist: bool) -> BoxResult<Response> {
 | 
			
		||||
    async fn handle_ls_dir(&self, path: &Path, exist: bool) -> BoxResult<Response> {
 | 
			
		||||
        let mut paths: Vec<PathItem> = vec![];
 | 
			
		||||
        if exist {
 | 
			
		||||
            let mut rd = fs::read_dir(path).await?;
 | 
			
		||||
            while let Some(entry) = rd.next_entry().await? {
 | 
			
		||||
                let entry_path = entry.path();
 | 
			
		||||
                if let Ok(item) = self.get_path_item(entry_path).await {
 | 
			
		||||
                if let Ok(item) = get_path_item(entry_path, path.to_path_buf()).await {
 | 
			
		||||
                    paths.push(item);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            paths.sort_unstable();
 | 
			
		||||
        }
 | 
			
		||||
        let breadcrumb = self.get_breadcrumb(path);
 | 
			
		||||
        let data = SendDirData {
 | 
			
		||||
            breadcrumb,
 | 
			
		||||
            paths,
 | 
			
		||||
            readonly: self.args.readonly,
 | 
			
		||||
        };
 | 
			
		||||
        let data = serde_json::to_string(&data).unwrap();
 | 
			
		||||
        self.send_index(path, paths)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        let mut output =
 | 
			
		||||
            INDEX_HTML.replace("__STYLE__", &format!("<style>\n{}</style>", INDEX_CSS));
 | 
			
		||||
        output = output.replace("__DATA__", &data);
 | 
			
		||||
 | 
			
		||||
        Ok(hyper::Response::builder().body(output.into()).unwrap())
 | 
			
		||||
    async fn handle_query_dir(&self, path: &Path, q: &str) -> BoxResult<Response> {
 | 
			
		||||
        let mut paths: Vec<PathItem> = vec![];
 | 
			
		||||
        let mut walkdir = WalkDir::new(path);
 | 
			
		||||
        while let Some(entry) = walkdir.next().await {
 | 
			
		||||
            if let Ok(entry) = entry {
 | 
			
		||||
                if !entry
 | 
			
		||||
                    .file_name()
 | 
			
		||||
                    .to_string_lossy()
 | 
			
		||||
                    .to_lowercase()
 | 
			
		||||
                    .contains(&q.to_lowercase())
 | 
			
		||||
                {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                if fs::symlink_metadata(entry.path()).await.is_err() {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                if let Ok(item) = get_path_item(entry.path(), path.to_path_buf()).await {
 | 
			
		||||
                    paths.push(item);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        self.send_index(path, paths)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn handle_send_dir_zip(&self, path: &Path) -> BoxResult<Response> {
 | 
			
		||||
| 
						 | 
				
			
			@ -223,6 +237,22 @@ impl InnerService {
 | 
			
		|||
        Ok(Response::new(body))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn send_index(&self, path: &Path, mut paths: Vec<PathItem>) -> BoxResult<Response> {
 | 
			
		||||
        paths.sort_unstable();
 | 
			
		||||
        let breadcrumb = self.get_breadcrumb(path);
 | 
			
		||||
        let data = IndexData {
 | 
			
		||||
            breadcrumb,
 | 
			
		||||
            paths,
 | 
			
		||||
            readonly: self.args.readonly,
 | 
			
		||||
        };
 | 
			
		||||
        let data = serde_json::to_string(&data).unwrap();
 | 
			
		||||
        let mut output =
 | 
			
		||||
            INDEX_HTML.replace("__STYLE__", &format!("<style>\n{}</style>", INDEX_CSS));
 | 
			
		||||
        output = output.replace("__DATA__", &data);
 | 
			
		||||
 | 
			
		||||
        Ok(hyper::Response::builder().body(output.into()).unwrap())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn auth_guard(&self, req: &Request) -> BoxResult<bool> {
 | 
			
		||||
        if let Some(auth) = &self.args.auth {
 | 
			
		||||
            if let Some(value) = req.headers().get("Authorization") {
 | 
			
		||||
| 
						 | 
				
			
			@ -242,43 +272,6 @@ impl InnerService {
 | 
			
		|||
        Ok(true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn get_path_item<P: AsRef<Path>>(&self, path: P) -> BoxResult<PathItem> {
 | 
			
		||||
        let path = path.as_ref();
 | 
			
		||||
        let base_path = &self.args.path;
 | 
			
		||||
        let rel_path = path.strip_prefix(base_path).unwrap();
 | 
			
		||||
        let meta = fs::metadata(&path).await?;
 | 
			
		||||
        let s_meta = fs::symlink_metadata(&path).await?;
 | 
			
		||||
        let is_dir = meta.is_dir();
 | 
			
		||||
        let is_symlink = s_meta.file_type().is_symlink();
 | 
			
		||||
        let path_type = match (is_symlink, is_dir) {
 | 
			
		||||
            (true, true) => PathType::SymlinkDir,
 | 
			
		||||
            (false, true) => PathType::Dir,
 | 
			
		||||
            (true, false) => PathType::SymlinkFile,
 | 
			
		||||
            (false, false) => PathType::File,
 | 
			
		||||
        };
 | 
			
		||||
        let mtime = meta
 | 
			
		||||
            .modified()?
 | 
			
		||||
            .duration_since(SystemTime::UNIX_EPOCH)
 | 
			
		||||
            .ok()
 | 
			
		||||
            .map(|v| v.as_millis() as u64);
 | 
			
		||||
        let size = match path_type {
 | 
			
		||||
            PathType::Dir | PathType::SymlinkDir => None,
 | 
			
		||||
            PathType::File | PathType::SymlinkFile => Some(meta.len()),
 | 
			
		||||
        };
 | 
			
		||||
        let name = rel_path
 | 
			
		||||
            .file_name()
 | 
			
		||||
            .and_then(|v| v.to_str())
 | 
			
		||||
            .unwrap_or_default()
 | 
			
		||||
            .to_owned();
 | 
			
		||||
        Ok(PathItem {
 | 
			
		||||
            path_type,
 | 
			
		||||
            name,
 | 
			
		||||
            path: format!("/{}", normalize_path(rel_path)),
 | 
			
		||||
            mtime,
 | 
			
		||||
            size,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_breadcrumb(&self, path: &Path) -> String {
 | 
			
		||||
        let path = match self.args.path.parent() {
 | 
			
		||||
            Some(p) => path.strip_prefix(p).unwrap(),
 | 
			
		||||
| 
						 | 
				
			
			@ -304,7 +297,7 @@ impl InnerService {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)]
 | 
			
		||||
struct SendDirData {
 | 
			
		||||
struct IndexData {
 | 
			
		||||
    breadcrumb: String,
 | 
			
		||||
    paths: Vec<PathItem>,
 | 
			
		||||
    readonly: bool,
 | 
			
		||||
| 
						 | 
				
			
			@ -314,7 +307,6 @@ struct SendDirData {
 | 
			
		|||
struct PathItem {
 | 
			
		||||
    path_type: PathType,
 | 
			
		||||
    name: String,
 | 
			
		||||
    path: String,
 | 
			
		||||
    mtime: Option<u64>,
 | 
			
		||||
    size: Option<u64>,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -327,6 +319,37 @@ enum PathType {
 | 
			
		|||
    SymlinkFile,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn get_path_item<P: AsRef<Path>>(path: P, base_path: P) -> BoxResult<PathItem> {
 | 
			
		||||
    let path = path.as_ref();
 | 
			
		||||
    let rel_path = path.strip_prefix(base_path).unwrap();
 | 
			
		||||
    let meta = fs::metadata(&path).await?;
 | 
			
		||||
    let s_meta = fs::symlink_metadata(&path).await?;
 | 
			
		||||
    let is_dir = meta.is_dir();
 | 
			
		||||
    let is_symlink = s_meta.file_type().is_symlink();
 | 
			
		||||
    let path_type = match (is_symlink, is_dir) {
 | 
			
		||||
        (true, true) => PathType::SymlinkDir,
 | 
			
		||||
        (false, true) => PathType::Dir,
 | 
			
		||||
        (true, false) => PathType::SymlinkFile,
 | 
			
		||||
        (false, false) => PathType::File,
 | 
			
		||||
    };
 | 
			
		||||
    let mtime = meta
 | 
			
		||||
        .modified()?
 | 
			
		||||
        .duration_since(SystemTime::UNIX_EPOCH)
 | 
			
		||||
        .ok()
 | 
			
		||||
        .map(|v| v.as_millis() as u64);
 | 
			
		||||
    let size = match path_type {
 | 
			
		||||
        PathType::Dir | PathType::SymlinkDir => None,
 | 
			
		||||
        PathType::File | PathType::SymlinkFile => Some(meta.len()),
 | 
			
		||||
    };
 | 
			
		||||
    let name = normalize_path(rel_path);
 | 
			
		||||
    Ok(PathItem {
 | 
			
		||||
        path_type,
 | 
			
		||||
        name,
 | 
			
		||||
        mtime,
 | 
			
		||||
        size,
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn normalize_path<P: AsRef<Path>>(path: P) -> String {
 | 
			
		||||
    let path = path.as_ref().to_str().unwrap_or_default();
 | 
			
		||||
    if cfg!(windows) {
 | 
			
		||||
| 
						 | 
				
			
			@ -341,7 +364,10 @@ async fn dir_zip<W: AsyncWrite + Unpin>(writer: &mut W, dir: &Path) -> BoxResult
 | 
			
		|||
    let mut walkdir = WalkDir::new(dir);
 | 
			
		||||
    while let Some(entry) = walkdir.next().await {
 | 
			
		||||
        if let Ok(entry) = entry {
 | 
			
		||||
            let meta = fs::symlink_metadata(entry.path()).await?;
 | 
			
		||||
            let meta = match fs::symlink_metadata(entry.path()).await {
 | 
			
		||||
                Ok(meta) => meta,
 | 
			
		||||
                Err(_) => continue,
 | 
			
		||||
            };
 | 
			
		||||
            if meta.is_file() {
 | 
			
		||||
                let filepath = entry.path();
 | 
			
		||||
                let filename = match filepath.strip_prefix(dir).ok().and_then(|v| v.to_str()) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue