1 //
2 // Copyright 2023 Google, Inc.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at:
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15
16 //! # Zip Artifact Class
17
18 use std::{
19 fs::{read_dir, remove_file, File},
20 io::{Read, Result, Write},
21 path::PathBuf,
22 };
23
24 use log::warn;
25 use zip::{result::ZipResult, write::FileOptions, ZipWriter};
26
27 use crate::system::netsimd_temp_dir;
28
29 use super::time_display::file_current_time;
30
31 /// Recurse all files in root and put it in Vec<PathBuf>
recurse_files(root: &PathBuf) -> Result<Vec<(PathBuf, String)>>32 fn recurse_files(root: &PathBuf) -> Result<Vec<(PathBuf, String)>> {
33 let mut result = Vec::new();
34 // Read all entries in the given root directory
35 let entries = read_dir(root)?;
36 for entry in entries {
37 let entry = entry?;
38 let meta = entry.metadata()?;
39 // Perform recursion if it's a directory
40 if meta.is_dir() {
41 let mut subdir = recurse_files(&entry.path())?;
42 result.append(&mut subdir);
43 }
44 if meta.is_file() {
45 if let Some(filename) = entry
46 .path()
47 .file_name()
48 .and_then(|os_name| os_name.to_str())
49 .map(|str_name| str_name.to_string())
50 {
51 result.push((entry.path(), filename));
52 } else {
53 warn!("Unable to fetch filename for file: {:?}", entry.path());
54 }
55 }
56 }
57 Ok(result)
58 }
59
60 /// Fetch all zip files in root and put it in sorted Vec<PathBuf>
fetch_zip_files(root: &PathBuf) -> Result<Vec<PathBuf>>61 fn fetch_zip_files(root: &PathBuf) -> Result<Vec<PathBuf>> {
62 // Read all entries in the given root directory
63 // Push path to result if name matches "netsim_artifacts_*.zip"
64 let mut result: Vec<PathBuf> = read_dir(root)?
65 .filter_map(|e| e.ok())
66 .map(|e| e.path())
67 .filter(|path| {
68 path.is_file()
69 && path.file_name().and_then(|os_name| os_name.to_str()).map_or(false, |filename| {
70 filename.starts_with("netsim_artifacts_") && filename.ends_with(".zip")
71 })
72 })
73 .collect();
74 // Sort the zip files by timestamp from oldest to newest
75 result.sort();
76 Ok(result)
77 }
78
79 /// Remove set number of zip files
remove_zip_files() -> Result<()>80 pub fn remove_zip_files() -> Result<()> {
81 // TODO(b/305012017): Add parameter for keeping some number of zip files
82 // Fetch all zip files in netsimd_temp_dir
83 let zip_files = fetch_zip_files(&netsimd_temp_dir())?;
84 for file in zip_files {
85 if let Err(err) = std::fs::remove_file(&file) {
86 warn!("Removing {file:?} error: {err:?}");
87 }
88 }
89 Ok(())
90 }
91
92 /// Zip the whole netsimd temp directory and store it in temp directory.
zip_artifacts() -> ZipResult<()>93 pub fn zip_artifacts() -> ZipResult<()> {
94 // Fetch all files in netsimd_temp_dir
95 let root = netsimd_temp_dir();
96 let files = recurse_files(&root)?;
97
98 // Define PathBuf for zip file
99 let zip_file = root.join(format!("netsim_artifacts_{}.zip", file_current_time()));
100
101 // Create a new ZipWriter
102 let mut zip_writer = ZipWriter::new(File::create(zip_file)?);
103 let mut buffer = Vec::new();
104
105 // Put each artifact files into zip file
106 for (file, filename) in files {
107 // Avoid zip files
108 if filename.starts_with("netsim_artifacts") {
109 continue;
110 }
111
112 // Write to zip file
113 zip_writer.start_file(&filename, FileOptions::default())?;
114 let mut f = File::open(&file)?;
115 f.read_to_end(&mut buffer)?;
116 zip_writer.write_all(&buffer)?;
117 buffer.clear();
118
119 // Remove the file once written except for netsim log and json files
120 if filename.starts_with("netsim_")
121 && (filename.ends_with(".log") || filename.ends_with(".json"))
122 {
123 continue;
124 }
125 remove_file(file)?;
126 }
127
128 // Finish writing zip file
129 zip_writer.finish()?;
130 Ok(())
131 }
132
133 #[cfg(test)]
134 mod tests {
135 use super::*;
136
test_temp_dir() -> PathBuf137 fn test_temp_dir() -> PathBuf {
138 std::env::temp_dir().join("netsim-test").join(format!("{:?}", std::thread::current()))
139 }
140
141 #[test]
test_recurse_files()142 fn test_recurse_files() {
143 let path = test_temp_dir();
144
145 // Create the temporary directory
146 if std::fs::create_dir_all(&path).is_err() {
147 return; // return if test environment disallows file creation
148 }
149
150 // Create a file
151 let file = path.join("hello.txt");
152 if File::create(&file).is_err() {
153 return; // return if test environment disallows file creation
154 }
155
156 // Create a folder and a file inside
157 let folder = path.join("folder");
158 std::fs::create_dir_all(&folder).unwrap();
159 let nested_file = folder.join("world.txt");
160 File::create(&nested_file).unwrap();
161
162 // Recurse Files and check the contents
163 let files_result = recurse_files(&path);
164 assert!(files_result.is_ok());
165 let files = files_result.unwrap();
166 assert_eq!(files.len(), 2);
167 assert!(files.contains(&(file, "hello.txt".to_string())));
168 assert!(files.contains(&(nested_file, "world.txt".to_string())));
169 }
170
171 #[test]
test_fetch_zip_files()172 fn test_fetch_zip_files() {
173 let path = test_temp_dir();
174
175 // Create the temporary directory
176 if std::fs::create_dir_all(&path).is_err() {
177 return; // return if test environment disallows file creation
178 }
179
180 // Create multiple zip files in random order of timestamps
181 let zip_file_1 = path.join("netsim_artifacts_2024-01-01.zip");
182 let zip_file_2 = path.join("netsim_artifacts_2022-12-31.zip");
183 let zip_file_3 = path.join("netsim_artifacts_2023-06-01.zip");
184 let zip_file_faulty = path.join("netsim_arts_2000-01-01.zip");
185 if File::create(&zip_file_1).is_err() {
186 return; // return if test environment disallows file creation
187 }
188 File::create(&zip_file_2).unwrap();
189 File::create(&zip_file_3).unwrap();
190 File::create(zip_file_faulty).unwrap();
191
192 // Fetch all zip files and check the contents if it is in order
193 let files_result = fetch_zip_files(&path);
194 assert!(files_result.is_ok());
195 let files = files_result.unwrap();
196 assert_eq!(files.len(), 3);
197 assert_eq!(files.first().unwrap(), &zip_file_2);
198 assert_eq!(files.get(1).unwrap(), &zip_file_3);
199 assert_eq!(files.get(2).unwrap(), &zip_file_1);
200 }
201 }
202