zip_extensions/deflate/
zip_ignore_entry_handler.rs1use crate::default_entry_handler::DefaultEntryHandler;
2use crate::entry_handler::EntryHandler;
3use ignore::gitignore::{Gitignore, GitignoreBuilder};
4use std::collections::HashMap;
5use std::io;
6use std::io::Write;
7use std::path::{Path, PathBuf};
8use std::sync::Mutex;
9use zip::ZipWriter;
10use zip::result::ZipResult;
11use zip::write::{FileOptionExtension, FileOptions};
12
13pub struct ZipIgnoreEntryHandler<H = DefaultEntryHandler> {
17 per_directory_matcher_cache: Mutex<HashMap<PathBuf, Gitignore>>,
18 ignore_filename: &'static str,
19 inner: H,
20}
21
22pub(crate) const IGNORE_FILENAME: &str = ".zipignore";
23
24impl ZipIgnoreEntryHandler<DefaultEntryHandler> {
25 pub fn new() -> Self {
26 Self {
27 per_directory_matcher_cache: Mutex::new(HashMap::new()),
28 ignore_filename: IGNORE_FILENAME,
29 inner: DefaultEntryHandler,
30 }
31 }
32}
33
34impl<H> ZipIgnoreEntryHandler<H> {
35 pub fn with_inner(inner: H) -> Self {
36 Self {
37 per_directory_matcher_cache: Mutex::new(HashMap::new()),
38 ignore_filename: IGNORE_FILENAME,
39 inner,
40 }
41 }
42
43 fn parent_dir(path: &Path) -> &Path {
44 path.parent().unwrap_or(path)
45 }
46
47 fn build_matcher(&self, root: &Path, dir: &Path) -> io::Result<Gitignore> {
48 let mut ignore_builder = GitignoreBuilder::new(root);
50
51 let mut stack: Vec<PathBuf> = vec![];
53 let mut current_dir = dir;
54 loop {
55 stack.push(current_dir.to_path_buf());
56 if current_dir == root {
57 break;
58 }
59 match current_dir.parent() {
60 Some(p) => current_dir = p,
61 None => break,
62 }
63 }
64 stack.reverse();
65
66 for d in stack {
67 let ignore_file = d.join(self.ignore_filename);
68 if ignore_file.exists() {
69 let _ = ignore_builder.add(ignore_file);
70 }
71 }
72 let built = ignore_builder
73 .build()
74 .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
75 Ok(built)
76 }
77
78 fn matcher_for_dir(&self, root: &Path, dir: &Path) -> io::Result<Gitignore> {
79 let mut cache = self.per_directory_matcher_cache.lock().unwrap();
80 if let Some(existing_matcher) = cache.get(dir) {
81 return Ok(existing_matcher.clone());
82 }
83 let new_matcher = self.build_matcher(root, dir)?;
84 cache.insert(dir.to_path_buf(), new_matcher.clone());
85 Ok(new_matcher)
86 }
87
88 fn is_ignored(&self, root: &Path, path: &Path, is_dir: bool) -> bool {
89 let dir = if path.is_dir() {
90 path
91 } else {
92 Self::parent_dir(path)
93 };
94 match self.matcher_for_dir(root, dir) {
95 Ok(matcher) => matcher
96 .matched_path_or_any_parents(path, is_dir)
97 .is_ignore(),
98 Err(_) => false,
99 }
100 }
101}
102
103impl<T: FileOptionExtension, H> EntryHandler<T> for ZipIgnoreEntryHandler<H>
104where
105 H: EntryHandler<T>,
106{
107 fn handle_entry<W: Write + io::Seek>(
108 &self,
109 writer: &mut ZipWriter<W>,
110 root: &PathBuf,
111 entry_path: &PathBuf,
112 file_options: FileOptions<T>,
113 buffer: &mut Vec<u8>,
114 ) -> ZipResult<()> {
115 let metadata = std::fs::metadata(entry_path)?;
116 let is_dir = metadata.is_dir();
117 if self.is_ignored(root.as_path(), entry_path.as_path(), is_dir) {
118 return Ok(());
119 }
120 self.inner
121 .handle_entry(writer, root, entry_path, file_options, buffer)
122 }
123}