1use super::*;
2
3use ::vfs04::*;
4
5use ::zip::result::ZipError;
6use ::zip::write::FileOptions;
7
8use std::io::{self, Cursor};
9use std::mem::replace;
10use std::ops::Drop;
11use std::sync::{Arc, Weak};
12
13
14
15impl<IO: Write + Seek + Send + 'static> ZipWriteOnly<IO> {
16 fn normalize_file<'s>(&self, orig: &'s str) -> VfsResult<&'s str> {
17 if orig.contains('\\') || orig.ends_with('/') {
18 return Err(VfsError::InvalidPath { path: orig.into() }); }
20 let path = if orig.starts_with('/') { &orig[1..] } else { orig };
21 if path.split('/').any(|c| c == "" || c == "." || c == "..") {
22 return Err(VfsError::InvalidPath { path: orig.into() });
23 }
24 Ok(path)
25 }
26
27 fn normalize_path_dir<'s>(&self, orig: &'s str) -> VfsResult<(&'s str, bool)> {
28 if orig == "" || orig == "/" {
29 Ok(("", true)) } else if orig.ends_with('/') {
31 Ok((self.normalize_file(&orig[..orig.len()-1])?, true))
32 } else {
33 Ok((self.normalize_file(orig)?, false))
34 }
35 }
36}
37
38impl<IO: Write + Seek + Send + 'static> FileSystem for ZipWriteOnly<IO> {
39 fn create_dir(&self, path: &str) -> VfsResult<()> {
40 let path = self.normalize_path_dir(path)?.0;
41 let mut imp = self.imp.lock().unwrap();
42 if path == "" {
43 imp.dirs.insert(path.into());
44 return Ok(());
45 }
46 #[allow(unreachable_patterns)] match imp.writer.add_directory(path, FileOptions::default()) {
48 Err(ZipError::FileNotFound) => return Err(VfsError::FileNotFound { path: path.into() }),
49 Err(ZipError::Io(e)) => return Err(VfsError::IoError(e)),
50 Err(ZipError::InvalidArchive(e)) => return Err(VfsError::IoError(io::Error::new(io::ErrorKind::InvalidData, e))),
51 Err(ZipError::UnsupportedArchive(e)) => return Err(VfsError::IoError(io::Error::new(io::ErrorKind::InvalidData, e))),
52 Err(other) => return Err(VfsError::Other { message: other.to_string() }),
53 Ok(()) => {},
54 }
55 imp.dirs.insert(path.into());
56 Ok(())
57 }
58
59 fn create_file(&self, path: &str) -> VfsResult<Box<dyn Write>> {
60 let path = self.normalize_file(path)?;
61 Ok(Box::new(ZipFileWriter {
62 path: path.into(),
63 buffer: Cursor::new(Vec::new()),
64 imp: match self.weak {
65 true => ZipFileWriterRef::Weak(Arc::downgrade(&self.imp)),
66 false => ZipFileWriterRef::Strong(Arc::clone(&self.imp)),
67 }
68 }))
69 }
70
71 fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
72 let path = self.normalize_path_dir(path)?.0;
73 if path == "" {
74 Ok(VfsMetadata { file_type: VfsFileType::Directory, len: 0 })
75 } else if self.imp.lock().unwrap().dirs.contains(path) {
76 Ok(VfsMetadata { file_type: VfsFileType::Directory, len: 0 })
77 } else {
78 Err(VfsError::FileNotFound { path: path.into() })
79 }
80 }
81
82 fn exists(&self, path: &str) -> bool {
83 let path = match self.normalize_path_dir(path) {
84 Err(_) => return false,
85 Ok((path, _dir)) => path,
86 };
87 path == "" || self.imp.lock().unwrap().dirs.contains(path)
88 }
89
90 fn read_dir (&self, _path: &str) -> VfsResult<Box<dyn Iterator<Item = String>>> { Err(VfsError::NotSupported) }
92 fn open_file (&self, _path: &str) -> VfsResult<Box<dyn SeekAndRead>> { Err(VfsError::NotSupported) }
93 fn append_file (&self, _path: &str) -> VfsResult<Box<dyn Write>> { Err(VfsError::NotSupported) }
94 fn remove_file (&self, _path: &str) -> VfsResult<()> { Err(VfsError::NotSupported) }
95 fn remove_dir (&self, _path: &str) -> VfsResult<()> { Err(VfsError::NotSupported) }
96 fn copy_file (&self, _src: &str, _dst: &str) -> VfsResult<()> { Err(VfsError::NotSupported) }
97 fn move_file (&self, _src: &str, _dst: &str) -> VfsResult<()> { Err(VfsError::NotSupported) }
98 fn move_dir (&self, _src: &str, _dst: &str) -> VfsResult<()> { Err(VfsError::NotSupported) }
99}
100
101enum ZipFileWriterRef<IO: Write + Seek + Send + 'static> {
102 Weak(Weak<Mutex<Imp<IO>>>),
103 Strong(Arc<Mutex<Imp<IO>>>),
104}
105
106struct ZipFileWriter<IO: Write + Seek + Send + 'static> {
107 path: String,
108 buffer: Cursor<Vec<u8>>,
109 imp: ZipFileWriterRef<IO>,
110}
111
112impl<IO: Write + Seek + Send> Write for ZipFileWriter<IO> {
113 fn write (&mut self, buf: &[u8]) -> io::Result<usize> { self.buffer.write(buf) }
115 fn flush (&mut self) -> io::Result<()> { self.buffer.flush() }
116 fn write_all (&mut self, buf: &[u8]) -> io::Result<()> { self.buffer.write_all(buf) }
117 fn write_fmt (&mut self, fmt: fmt::Arguments<'_>)-> io::Result<()> { self.buffer.write_fmt(fmt) }
118
119 }
124
125impl<IO: Write + Seek + Send> Drop for ZipFileWriter<IO> {
126 fn drop(&mut self) {
127 let path = replace(&mut self.path, String::new());
128 let buffer = replace(&mut self.buffer, Cursor::new(Vec::new())).into_inner();
129 let imp = match replace(&mut self.imp, ZipFileWriterRef::Weak(Weak::default())) {
130 ZipFileWriterRef::Strong(s) => s,
131 ZipFileWriterRef::Weak(w) => match w.upgrade() {
132 Some(s) => s,
133 None => return,
134 }
135 };
136 let mut imp = imp.lock().unwrap();
137 if imp.writer.start_file(path, zip::write::FileOptions::default()).is_err() { return; }
138 let _ = imp.writer.write_all(&buffer[..]);
139 }
140}
141
142#[cfg(test)] mod tests {
143 use crate::*;
144 use super::{VfsError, VfsFileType, VfsPath, VfsResult};
145 use std::fs::{create_dir_all, File};
146
147 #[test] fn copy_early_vfs_zip() {
148 let _ = create_dir_all("target/tmp");
149 let src = VfsPath::new(ZipReadOnly::new_strict(File::open("test/data/early-vfs-zip.zip").unwrap()).unwrap());
150 let dst = VfsPath::new(ZipWriteOnly::new_weak(File::create("target/tmp/early-vfs-zip-copy.zip").unwrap()).unwrap());
151 let copied = copy_dir_merge(&dst, &src).unwrap();
152 assert_eq!(copied, 16);
153 }
154
155 fn copy_dir_merge(dst: &VfsPath, src: &VfsPath) -> VfsResult<usize> {
157 let mut n = 0;
158
159 if !src.exists() { return Err(VfsError::FileNotFound { path: src.as_str().into() }); }
160 if !dst.exists() { dst.create_dir()?; n += 1; }
161
162 for src in src.read_dir()? {
163 let dst = dst.join(src.filename().as_str())?;
164 match src.metadata()?.file_type {
165 VfsFileType::Directory => n += copy_dir_merge(&dst, &src)?,
166 VfsFileType::File => { src.copy_file(&dst)?; n += 1 },
167 }
168 }
169 Ok(n)
170 }
171}