Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,14 @@ jobs:
- run: cargo check

- run: cargo test

fmt:
runs-on: ubuntu-latest
steps:
- uses: taiki-e/checkout-action@v1

- uses: oxc-project/[email protected]
with:
components: rustfmt

- run: cargo fmt --check
2 changes: 2 additions & 0 deletions .rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
style_edition = "2024"
use_small_heuristics = "Max"
196 changes: 115 additions & 81 deletions src/fs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use serde::Deserialize;
use std::{path::{Path, PathBuf}, str::Utf8Error};
use std::{
path::{Path, PathBuf},
str::Utf8Error,
};

use crate::zip::Zip;

Expand Down Expand Up @@ -79,18 +82,14 @@ pub enum Error {
pub fn open_zip_via_mmap<P: AsRef<Path>>(p: P) -> Result<Zip<mmap_rs::Mmap>, std::io::Error> {
let file = fs::File::open(p)?;

let mmap_builder = mmap_rs::MmapOptions::new(file.metadata().unwrap().len().try_into().unwrap())
.unwrap();
let mmap_builder =
mmap_rs::MmapOptions::new(file.metadata().unwrap().len().try_into().unwrap()).unwrap();

let mmap = unsafe {
mmap_builder
.with_file(file, 0)
.map()
.unwrap()
};
let mmap = unsafe { mmap_builder.with_file(file, 0).map().unwrap() };

let zip = Zip::new(mmap)
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "Failed to read the zip file"))?;
let zip = Zip::new(mmap).map_err(|_| {
std::io::Error::new(std::io::ErrorKind::Other, "Failed to read the zip file")
})?;

Ok(zip)
}
Expand All @@ -103,8 +102,9 @@ pub fn open_zip_via_mmap_p(p: &Path) -> Result<Zip<mmap_rs::Mmap>, std::io::Erro
pub fn open_zip_via_read<P: AsRef<Path>>(p: P) -> Result<Zip<Vec<u8>>, std::io::Error> {
let data = std::fs::read(p)?;

let zip = Zip::new(data)
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "Failed to read the zip file"))?;
let zip = Zip::new(data).map_err(|_| {
std::io::Error::new(std::io::ErrorKind::Other, "Failed to read the zip file")
})?;

Ok(zip)
}
Expand All @@ -114,50 +114,85 @@ pub fn open_zip_via_read_p(p: &Path) -> Result<Zip<Vec<u8>>, std::io::Error> {
}

pub trait ZipCache<Storage>
where Storage: AsRef<[u8]> + Send + Sync {
fn act<T, P: AsRef<Path>, F : FnOnce(&Zip<Storage>) -> T>(&self, p: P, cb: F) -> Result<T, std::io::Error>;

fn file_type<P: AsRef<Path>, S: AsRef<str>>(&self, zip_path: P, sub: S) -> Result<FileType, std::io::Error>;
fn read<P: AsRef<Path>, S: AsRef<str>>(&self, zip_path: P, sub: S) -> Result<Vec<u8>, std::io::Error>;
fn read_to_string<P: AsRef<Path>, S: AsRef<str>>(&self, zip_path: P, sub: S) -> Result<String, std::io::Error>;
where
Storage: AsRef<[u8]> + Send + Sync,
{
fn act<T, P: AsRef<Path>, F: FnOnce(&Zip<Storage>) -> T>(
&self,
p: P,
cb: F,
) -> Result<T, std::io::Error>;

fn file_type<P: AsRef<Path>, S: AsRef<str>>(
&self,
zip_path: P,
sub: S,
) -> Result<FileType, std::io::Error>;
fn read<P: AsRef<Path>, S: AsRef<str>>(
&self,
zip_path: P,
sub: S,
) -> Result<Vec<u8>, std::io::Error>;
fn read_to_string<P: AsRef<Path>, S: AsRef<str>>(
&self,
zip_path: P,
sub: S,
) -> Result<String, std::io::Error>;
}

#[derive(Debug)]
pub struct LruZipCache<Storage>
where Storage: AsRef<[u8]> + Send + Sync {
where
Storage: AsRef<[u8]> + Send + Sync,
{
lru: concurrent_lru::sharded::LruCache<PathBuf, Zip<Storage>>,
open: fn(&Path) -> std::io::Result<Zip<Storage>>,
}

impl<Storage> LruZipCache<Storage>
where Storage: AsRef<[u8]> + Send + Sync {
where
Storage: AsRef<[u8]> + Send + Sync,
{
pub fn new(n: u64, open: fn(&Path) -> std::io::Result<Zip<Storage>>) -> LruZipCache<Storage> {
LruZipCache {
lru: concurrent_lru::sharded::LruCache::new(n),
open,
}
LruZipCache { lru: concurrent_lru::sharded::LruCache::new(n), open }
}
}

impl<Storage> ZipCache<Storage> for LruZipCache<Storage>
where Storage: AsRef<[u8]> + Send + Sync {
fn act<T, P: AsRef<Path>, F: FnOnce(&Zip<Storage>) -> T>(&self, p: P, cb: F) -> Result<T, std::io::Error> {
let zip = self.lru.get_or_try_init(p.as_ref().to_path_buf(), 1, |p| {
(self.open)(&p)
})?;
where
Storage: AsRef<[u8]> + Send + Sync,
{
fn act<T, P: AsRef<Path>, F: FnOnce(&Zip<Storage>) -> T>(
&self,
p: P,
cb: F,
) -> Result<T, std::io::Error> {
let zip = self.lru.get_or_try_init(p.as_ref().to_path_buf(), 1, |p| (self.open)(&p))?;

Ok(cb(zip.value()))
}

fn file_type<P: AsRef<Path>, S: AsRef<str>>(&self, zip_path: P, p: S) -> Result<FileType, std::io::Error> {
fn file_type<P: AsRef<Path>, S: AsRef<str>>(
&self,
zip_path: P,
p: S,
) -> Result<FileType, std::io::Error> {
self.act(zip_path, |zip| zip.file_type(p.as_ref()))?
}

fn read<P: AsRef<Path>, S: AsRef<str>>(&self, zip_path: P, p: S) -> Result<Vec<u8>, std::io::Error> {
fn read<P: AsRef<Path>, S: AsRef<str>>(
&self,
zip_path: P,
p: S,
) -> Result<Vec<u8>, std::io::Error> {
self.act(zip_path, |zip| zip.read(p.as_ref()))?
}

fn read_to_string<P: AsRef<Path>, S: AsRef<str>>(&self, zip_path: P, p: S) -> Result<String, std::io::Error> {
fn read_to_string<P: AsRef<Path>, S: AsRef<str>>(
&self,
zip_path: P,
p: S,
) -> Result<String, std::io::Error> {
self.act(zip_path, |zip| zip.read_to_string(p.as_ref()))?
}
}
Expand All @@ -167,31 +202,23 @@ fn vpath(p: &Path) -> std::io::Result<VPath> {
return Ok(VPath::Native(p.to_path_buf()));
};

let normalized_path
= crate::util::normalize_path(p_str);
let normalized_path = crate::util::normalize_path(p_str);

// We remove potential leading slashes to avoid __virtual__ accidentally removing them
let normalized_relative_path
= normalized_path.strip_prefix('/')
.unwrap_or(&normalized_path);
let normalized_relative_path = normalized_path.strip_prefix('/').unwrap_or(&normalized_path);

let mut segment_it
= normalized_relative_path.split('/');
let mut segment_it = normalized_relative_path.split('/');

// `split` returns [""] if the path is empty; we need to remove it
if normalized_relative_path.is_empty() {
segment_it.next();
}

let mut base_items: Vec<&str>
= Vec::new();
let mut base_items: Vec<&str> = Vec::new();

let mut virtual_items: Option<Vec<&str>>
= None;
let mut internal_items: Option<Vec<&str>>
= None;
let mut zip_items: Option<Vec<&str>>
= None;
let mut virtual_items: Option<Vec<&str>> = None;
let mut internal_items: Option<Vec<&str>> = None;
let mut zip_items: Option<Vec<&str>> = None;

while let Some(segment) = segment_it.next() {
if let Some(zip_segments) = &mut zip_items {
Expand All @@ -200,8 +227,7 @@ fn vpath(p: &Path) -> std::io::Result<VPath> {
}

if segment == "__virtual__" && virtual_items.is_none() {
let mut acc_segments
= Vec::with_capacity(3);
let mut acc_segments = Vec::with_capacity(3);

acc_segments.push(segment);

Expand All @@ -212,15 +238,14 @@ fn vpath(p: &Path) -> std::io::Result<VPath> {

// We retrieve the depth
if let Some(depth_segment) = segment_it.next() {
let depth = depth_segment
.parse::<usize>();
let depth = depth_segment.parse::<usize>();

acc_segments.push(depth_segment);

// We extract the backward segments from the base ones
if let Ok(depth) = depth {
let parent_segments = base_items
.split_off(base_items.len().saturating_sub(depth));
let parent_segments =
base_items.split_off(base_items.len().saturating_sub(depth));

acc_segments.splice(0..0, parent_segments);
}
Expand Down Expand Up @@ -259,9 +284,7 @@ fn vpath(p: &Path) -> std::io::Result<VPath> {
Some((virtual_segments.join("/"), internal_segments.join("/")))
}

_ => {
None
},
_ => None,
};

if let Some(zip_segments) = zip_items {
Expand All @@ -273,12 +296,9 @@ fn vpath(p: &Path) -> std::io::Result<VPath> {
}));
}
}

if let Some(virtual_info) = virtual_info {
return Ok(VPath::Virtual(VirtualInfo {
base_path,
virtual_segments: virtual_info,
}));
return Ok(VPath::Virtual(VirtualInfo { base_path, virtual_segments: virtual_info }));
}

Ok(VPath::Native(PathBuf::from(base_path)))
Expand Down Expand Up @@ -328,39 +348,53 @@ mod tests {

#[test]
fn test_zip_list() {
let zip = open_zip_via_read(&PathBuf::from("data/@babel-plugin-syntax-dynamic-import-npm-7.8.3-fb9ff5634a-8.zip"))
.unwrap();
let zip = open_zip_via_read(&PathBuf::from(
"data/@babel-plugin-syntax-dynamic-import-npm-7.8.3-fb9ff5634a-8.zip",
))
.unwrap();

let mut dirs: Vec<&String> = zip.dirs.iter().collect();
let mut files: Vec<&String> = zip.files.keys().collect();

dirs.sort();
files.sort();

assert_eq!(dirs, vec![
"node_modules/",
"node_modules/@babel/",
"node_modules/@babel/plugin-syntax-dynamic-import/",
"node_modules/@babel/plugin-syntax-dynamic-import/lib/",
]);

assert_eq!(files, vec![
"node_modules/@babel/plugin-syntax-dynamic-import/LICENSE",
"node_modules/@babel/plugin-syntax-dynamic-import/README.md",
"node_modules/@babel/plugin-syntax-dynamic-import/lib/index.js",
"node_modules/@babel/plugin-syntax-dynamic-import/package.json",
]);
assert_eq!(
dirs,
vec![
"node_modules/",
"node_modules/@babel/",
"node_modules/@babel/plugin-syntax-dynamic-import/",
"node_modules/@babel/plugin-syntax-dynamic-import/lib/",
]
);

assert_eq!(
files,
vec![
"node_modules/@babel/plugin-syntax-dynamic-import/LICENSE",
"node_modules/@babel/plugin-syntax-dynamic-import/README.md",
"node_modules/@babel/plugin-syntax-dynamic-import/lib/index.js",
"node_modules/@babel/plugin-syntax-dynamic-import/package.json",
]
);
}

#[test]
fn test_zip_read() {
let zip = open_zip_via_read(&PathBuf::from("data/@babel-plugin-syntax-dynamic-import-npm-7.8.3-fb9ff5634a-8.zip"))
.unwrap();
let zip = open_zip_via_read(&PathBuf::from(
"data/@babel-plugin-syntax-dynamic-import-npm-7.8.3-fb9ff5634a-8.zip",
))
.unwrap();

let res = zip.read_to_string("node_modules/@babel/plugin-syntax-dynamic-import/package.json")
let res = zip
.read_to_string("node_modules/@babel/plugin-syntax-dynamic-import/package.json")
.unwrap();

assert_eq!(res, "{\n \"name\": \"@babel/plugin-syntax-dynamic-import\",\n \"version\": \"7.8.3\",\n \"description\": \"Allow parsing of import()\",\n \"repository\": \"https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-dynamic-import\",\n \"license\": \"MIT\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"main\": \"lib/index.js\",\n \"keywords\": [\n \"babel-plugin\"\n ],\n \"dependencies\": {\n \"@babel/helper-plugin-utils\": \"^7.8.0\"\n },\n \"peerDependencies\": {\n \"@babel/core\": \"^7.0.0-0\"\n },\n \"devDependencies\": {\n \"@babel/core\": \"^7.8.0\"\n }\n}\n");
assert_eq!(
res,
"{\n \"name\": \"@babel/plugin-syntax-dynamic-import\",\n \"version\": \"7.8.3\",\n \"description\": \"Allow parsing of import()\",\n \"repository\": \"https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-dynamic-import\",\n \"license\": \"MIT\",\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"main\": \"lib/index.js\",\n \"keywords\": [\n \"babel-plugin\"\n ],\n \"dependencies\": {\n \"@babel/helper-plugin-utils\": \"^7.8.0\"\n },\n \"peerDependencies\": {\n \"@babel/core\": \"^7.0.0-0\"\n },\n \"devDependencies\": {\n \"@babel/core\": \"^7.8.0\"\n }\n}\n"
);
}

#[rstest]
Expand Down
Loading