cyfs_util/pkg/
zip_package.rs

1extern crate walkdir;
2use cyfs_sha2::{Digest, Sha256};
3use std::error::Error;
4
5use std::fs::File;
6use std::path::{Path, PathBuf};
7use walkdir::{DirEntry, WalkDir};
8use zip;
9use std::fmt::Write;
10use std::io::{Read};
11
12
13pub struct ZipPackage {
14    src_dir: PathBuf,
15    all: Vec<PathBuf>,
16    hash: Option<String>,
17    zip_file: Option<zip::ZipWriter<std::fs::File>>,
18}
19
20impl ZipPackage {
21    pub fn new() -> ZipPackage {
22        ZipPackage {
23            src_dir: PathBuf::from(""),
24            all: Vec::new(),
25            hash: None,
26            zip_file: None,
27        }
28    }
29
30    pub fn load(&mut self, dir: &Path) {
31        assert!(self.all.is_empty());
32
33        self.src_dir = dir.to_owned();
34
35        let is_ignore = |entry: &DirEntry| -> bool {
36            entry
37                .file_name()
38                .to_str()
39                .map(|s| s.starts_with("."))
40                .unwrap_or(false)
41        };
42        let walker = WalkDir::new(dir).into_iter();
43        for entry in walker.filter_entry(|e| !is_ignore(e)) {
44            let entry = entry.unwrap();
45            if entry.file_type().is_dir() {
46                continue;
47            }
48
49            let path = entry.path();
50            //debug!("{}", entry.path().display());
51            self.all.push(path.to_path_buf());
52        }
53        self.all.sort();
54    }
55
56    pub fn calc_hash(&mut self) -> Result<String, Box<dyn Error>> {
57        let mut hasher = Sha256::new();
58        for path in &self.all {
59            let ret = File::open(&path);
60            if let Err(e) = ret {
61                let msg = format!("open file error! file={}, err={}", path.display(), e);
62                error!("{}", msg);
63                return Err(Box::<dyn Error>::from(msg));
64            }
65            let mut file = ret.unwrap();
66            let ret = std::io::copy(&mut file, &mut hasher);
67            if let Err(e) = ret {
68                let msg = format!("read file error! file={}, err={}", path.display(), e);
69                error!("{}", msg);
70                return Err(Box::<dyn Error>::from(msg));
71            }
72        }
73        let hex = hasher.result();
74        let mut s = String::new();
75        for &byte in hex.as_slice() {
76            write!(&mut s, "{:X}", byte).expect("Unable to format hex string");
77        }
78
79        self.hash = Some(s.clone());
80
81        Ok(s)
82    }
83
84    pub fn begin_zip(&mut self, dest_file: &str) -> Result<(), Box<dyn Error>> {
85        assert!(self.zip_file.is_none());
86
87        use std::io::Write;
88
89        let target_file = File::create(dest_file).unwrap();
90
91        let mut zip = zip::ZipWriter::new(target_file);
92
93        let options =
94            zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Bzip2);
95        let mut buffer = Vec::new();
96        for path in &self.all {
97            let name = path.strip_prefix(&self.src_dir).unwrap();
98
99            info!(
100                "adding file to zip {} => {} ...",
101                path.display(),
102                name.display()
103            );
104
105            /*
106            TODO 更新最后修改时间
107            let metadata = std::fs::metadata(path)?;
108
109            let opt;
110            if let Ok(time) = metadata.modified() {
111                opt = options.last_modified_time(time);
112            } else {
113                println!("Not supported on this platform");
114                opt = options;
115            }
116            */
117
118            let opt;
119            #[cfg(windows)] 
120            {
121                opt = options;
122            }
123
124            #[cfg(not(windows))]
125            {
126                use std::os::unix::fs::PermissionsExt;
127                let metadata = std::fs::metadata(path)?;
128                //info!("mode {}", metadata.permissions().mode());
129                opt = options.unix_permissions(metadata.permissions().mode());
130            }
131
132            zip.start_file(name.to_string_lossy(), opt)?;
133
134            let ret = File::open(path);
135            if let Err(e) = ret {
136                return Err(Box::new(e));
137            }
138
139            let mut f = ret.unwrap();
140            f.read_to_end(&mut buffer)?;
141            zip.write_all(&*buffer)?;
142
143            buffer.clear();
144        }
145        self.zip_file = Some(zip);
146
147        Ok(())
148    }
149
150    pub fn append_pkg_hash(&mut self) -> Result<(), Box<dyn Error>> {
151        assert!(self.zip_file.is_some());
152
153        if self.hash.is_none() {
154            if let Err(e) = self.calc_hash() {
155                error!("calc hash error! err={}", e);
156                return Err(e);
157            }
158        }
159
160        // 添加.hash文件
161        {
162            let options = zip::write::FileOptions::default()
163                .compression_method(zip::CompressionMethod::Bzip2);
164            let name = Path::new(".hash");
165
166            info!(
167                "adding .hash file to zip {} = {} ...",
168                name.display(),
169                self.hash.as_ref().unwrap()
170            );
171
172            let zip = self.zip_file.as_mut().unwrap();
173            zip.start_file(name.to_string_lossy(), options)?;
174
175            use std::io::Write;
176            zip.write_all(self.hash.as_ref().unwrap().as_bytes())?;
177        }
178
179        Ok(())
180    }
181
182    pub fn append_file(&mut self, path: &Path, bytes: &[u8]) -> Result<(), Box<dyn Error>> {
183        assert!(self.zip_file.is_some());
184
185        let options =
186            zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Bzip2);
187
188        info!("adding file to zip {}...", path.display(),);
189
190        let zip = self.zip_file.as_mut().unwrap();
191        zip.start_file(path.to_string_lossy(), options)?;
192
193        use std::io::Write;
194        zip.write_all(bytes)?;
195
196        Ok(())
197    }
198
199    pub fn finish_zip(&mut self)  -> Result<(), Box<dyn Error>> {
200        assert!(self.zip_file.is_some());
201
202        let mut zip = self.zip_file.take().unwrap();
203
204        // Optionally finish the zip. (this is also done on drop)
205        zip.finish()?;
206
207        Ok(())
208    }
209}