directory downloads (zipped)

This commit is contained in:
2025-08-29 21:20:44 +02:00
parent 76713db985
commit 3498a2926c
11 changed files with 368 additions and 47 deletions

View File

@@ -1,11 +1,15 @@
use std::{os::unix::fs::MetadataExt, path::PathBuf};
use std::{
io::{Cursor, Write},
os::unix::fs::MetadataExt,
path::{Path, PathBuf},
};
use anyhow::{Context, anyhow, bail};
use futures_util::TryStreamExt;
use rustix::fs::{Statx, statx};
use tokio::{
fs,
io::{self, AsyncWriteExt as _},
fs::{self},
io::{self, AsyncReadExt as _, AsyncWriteExt as _},
};
use tokio_util::io::ReaderStream;
@@ -214,7 +218,48 @@ impl FileSystem {
.open(&path)
.await?;
let file_size = file.metadata().await?.size();
let metadata = file.metadata().await?;
if metadata.is_dir() {
drop(file);
let options = zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Zstd)
.unix_permissions(0o755);
let mut file_buf = Vec::new();
let zip_buf = Vec::new();
let cursor = Cursor::new(zip_buf);
let mut zip = zip::ZipWriter::new(cursor);
for entry_path_buf in walk_dir(&path).await? {
let entry_path = entry_path_buf.as_path();
let entry_str = entry_path
.strip_prefix(&path)?
.to_str()
.context("Failed to get directory entry name")?;
if entry_path.is_dir() {
zip.add_directory(entry_str, options)?;
continue;
}
zip.start_file(entry_str, options)?;
let mut entry_file = tokio::fs::File::open(entry_path).await?;
entry_file.read_to_end(&mut file_buf).await?;
zip.write_all(&file_buf)?;
file_buf.clear();
}
let mut cursor = zip.finish()?;
cursor.set_position(0);
let stream = FileStream::new(ReaderStream::new(cursor));
return Ok(stream);
}
let file_size = metadata.size();
if file_size > self.max_file_fetch_bytes {
bail!("File size exceeds configured limit");
@@ -410,3 +455,27 @@ where
)
}
}
async fn walk_dir<P>(dir: P) -> Result<Vec<PathBuf>, tokio::io::Error>
where
P: AsRef<Path>,
{
let mut dirs = vec![dir.as_ref().to_owned()];
let mut files = vec![];
while !dirs.is_empty() {
let mut dir_iter = tokio::fs::read_dir(dirs.remove(0)).await?;
while let Some(entry) = dir_iter.next_entry().await? {
let entry_path_buf = entry.path();
if entry_path_buf.is_dir() {
dirs.push(entry_path_buf);
} else {
files.push(entry_path_buf);
}
}
}
Ok(files)
}