use std::{
env::current_dir,
path::{Path, PathBuf},
};
use async_zip::base::read::seek::ZipFileReader;
use tokio::fs::{create_dir_all, File, OpenOptions};
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
#[tokio::main]
async fn main() {
let archive = File::open("example.zip").await.expect("Failed to open zip file");
let out_dir = current_dir().expect("Failed to get current working directory");
unzip_file(archive, &out_dir).await;
}
fn sanitize_file_path(path: &str) -> PathBuf {
path.replace('\\', "/")
.split('/')
.map(sanitize_filename::sanitize)
.collect()
}
async fn unzip_file(archive: File, out_dir: &Path) {
let archive = archive.compat();
let mut reader = ZipFileReader::new(archive).await.expect("Failed to read zip file");
for index in 0..reader.file().entries().len() {
let entry = reader.file().entries().get(index).unwrap();
let path = out_dir.join(sanitize_file_path(entry.filename().as_str().unwrap()));
let entry_is_dir = entry.dir().unwrap();
let mut entry_reader = reader.reader_without_entry(index).await.expect("Failed to read ZipEntry");
if entry_is_dir {
if !path.exists() {
create_dir_all(&path).await.expect("Failed to create extracted directory");
}
} else {
let parent = path.parent().expect("A file entry should have parent directories");
if !parent.is_dir() {
create_dir_all(parent).await.expect("Failed to create parent directories");
}
let writer = OpenOptions::new()
.write(true)
.create_new(true)
.open(&path)
.await
.expect("Failed to create extracted file");
futures_util::io::copy(&mut entry_reader, &mut writer.compat_write())
.await
.expect("Failed to copy to extracted file");
}
}
}