stream zip archive creation
This commit is contained in:
1
backend/Cargo.lock
generated
1
backend/Cargo.lock
generated
@@ -2806,6 +2806,7 @@ dependencies = [
|
|||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ sqlx = { version = "0.8.6", features = [
|
|||||||
] }
|
] }
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
tokio = { version = "1.46.1", features = ["full"] }
|
tokio = { version = "1.46.1", features = ["full"] }
|
||||||
tokio-util = "0.7.15"
|
tokio-stream = "0.1.17"
|
||||||
|
tokio-util = { version = "0.7.15", features = ["io-util"] }
|
||||||
tower = "0.5.2"
|
tower = "0.5.2"
|
||||||
tower-http = { version = "0.6.6", features = ["cors", "fs", "trace"] }
|
tower-http = { version = "0.6.6", features = ["cors", "fs", "trace"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use warren::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
|
use anyhow::{Context as _, anyhow, bail};
|
||||||
|
use futures_util::TryStreamExt;
|
||||||
|
use rustix::fs::{Statx, statx};
|
||||||
use std::{
|
use std::{
|
||||||
io::{Cursor, Write},
|
io::Write,
|
||||||
os::unix::fs::MetadataExt,
|
os::unix::fs::MetadataExt,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context, anyhow, bail};
|
|
||||||
use futures_util::TryStreamExt;
|
|
||||||
use rustix::fs::{Statx, statx};
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{self},
|
fs::{self},
|
||||||
io::{self, AsyncReadExt as _, AsyncWriteExt as _},
|
io::{self, AsyncReadExt as _, AsyncWriteExt as _},
|
||||||
};
|
};
|
||||||
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
use tokio_util::io::ReaderStream;
|
use tokio_util::io::ReaderStream;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -223,38 +223,19 @@ impl FileSystem {
|
|||||||
if metadata.is_dir() {
|
if metadata.is_dir() {
|
||||||
drop(file);
|
drop(file);
|
||||||
|
|
||||||
let options = zip::write::SimpleFileOptions::default()
|
let (sync_tx, sync_rx) =
|
||||||
.compression_method(zip::CompressionMethod::Zstd)
|
std::sync::mpsc::channel::<Result<bytes::Bytes, std::io::Error>>();
|
||||||
.unix_permissions(0o755);
|
let (tx, rx) =
|
||||||
|
tokio::sync::mpsc::channel::<Result<bytes::Bytes, std::io::Error>>(65536);
|
||||||
|
|
||||||
let mut file_buf = Vec::new();
|
tokio::task::spawn(async move {
|
||||||
let zip_buf = Vec::new();
|
while let Ok(v) = sync_rx.recv() {
|
||||||
let cursor = Cursor::new(zip_buf);
|
let _ = tx.send(v).await;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
tokio::task::spawn(create_zip(path, sync_tx));
|
||||||
|
|
||||||
zip.start_file(entry_str, options)?;
|
let stream = FileStream::new(ReceiverStream::new(rx));
|
||||||
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);
|
return Ok(stream);
|
||||||
}
|
}
|
||||||
@@ -479,3 +460,59 @@ where
|
|||||||
|
|
||||||
Ok(files)
|
Ok(files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn create_zip<P>(
|
||||||
|
path: P,
|
||||||
|
tx: std::sync::mpsc::Sender<Result<bytes::Bytes, std::io::Error>>,
|
||||||
|
) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let options = zip::write::SimpleFileOptions::default()
|
||||||
|
.compression_method(zip::CompressionMethod::Deflated)
|
||||||
|
.unix_permissions(0o644);
|
||||||
|
|
||||||
|
let mut file_buf = Vec::new();
|
||||||
|
let mut zip = zip::write::ZipWriter::new_stream(ChannelWriter(tx));
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(zip.finish()?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ChannelWriter(std::sync::mpsc::Sender<Result<bytes::Bytes, std::io::Error>>);
|
||||||
|
|
||||||
|
impl Write for ChannelWriter {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
let len = buf.len();
|
||||||
|
let data = bytes::Bytes::copy_from_slice(&buf[..(len)]);
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.send(Ok(data))
|
||||||
|
.map(|_| len)
|
||||||
|
.map_err(|_| std::io::ErrorKind::Other.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user