ecad_processor/archive/
temp_manager.rs1use crate::error::{ProcessingError, Result};
2use std::collections::HashMap;
3use std::fs::File;
4use std::io::{BufWriter, Write};
5use std::path::{Path, PathBuf};
6use tempfile::TempDir;
7use zip::ZipArchive;
8
9pub struct TempFileManager {
10 temp_dir: TempDir,
11 extracted_files: HashMap<String, PathBuf>,
12}
13
14impl TempFileManager {
15 pub fn new() -> Result<Self> {
16 let temp_dir = TempDir::new().map_err(|e| {
17 ProcessingError::Io(std::io::Error::new(
18 std::io::ErrorKind::Other,
19 format!("Failed to create temporary directory: {}", e),
20 ))
21 })?;
22
23 Ok(Self {
24 temp_dir,
25 extracted_files: HashMap::new(),
26 })
27 }
28
29 pub fn temp_dir_path(&self) -> &Path {
30 self.temp_dir.path()
31 }
32
33 pub fn extract_file(&mut self, zip_path: &Path, file_name: &str) -> Result<PathBuf> {
34 if let Some(path) = self.extracted_files.get(file_name) {
36 return Ok(path.clone());
37 }
38
39 let file = File::open(zip_path)?;
40 let mut archive = ZipArchive::new(file)?;
41
42 let mut zip_file = archive.by_name(file_name).map_err(|_| {
44 ProcessingError::InvalidFormat(format!(
45 "File '{}' not found in archive '{}'",
46 file_name,
47 zip_path.display()
48 ))
49 })?;
50
51 let dest_path = self.temp_dir.path().join(file_name);
53
54 let mut dest_file = File::create(&dest_path)?;
56 let mut writer = BufWriter::new(&mut dest_file);
57 std::io::copy(&mut zip_file, &mut writer)?;
58 writer.flush()?;
59
60 self.extracted_files
62 .insert(file_name.to_string(), dest_path.clone());
63
64 Ok(dest_path)
65 }
66
67 pub fn extract_files_matching_pattern(
68 &mut self,
69 zip_path: &Path,
70 pattern: &str,
71 ) -> Result<Vec<PathBuf>> {
72 let file = File::open(zip_path)?;
73 let mut archive = ZipArchive::new(file)?;
74 let mut extracted_paths = Vec::new();
75
76 for i in 0..archive.len() {
78 let mut zip_file = archive.by_index(i)?;
79 let file_name = zip_file.name().to_string();
80
81 if file_name.contains(pattern) {
83 if self.extracted_files.contains_key(&file_name) {
85 extracted_paths.push(self.extracted_files[&file_name].clone());
86 continue;
87 }
88
89 let dest_path = self.temp_dir.path().join(&file_name);
91
92 if let Some(parent) = dest_path.parent() {
94 std::fs::create_dir_all(parent)?;
95 }
96
97 let mut dest_file = File::create(&dest_path)?;
99 let mut writer = BufWriter::new(&mut dest_file);
100 std::io::copy(&mut zip_file, &mut writer)?;
101 writer.flush()?;
102
103 self.extracted_files.insert(file_name, dest_path.clone());
105 extracted_paths.push(dest_path);
106 }
107 }
108
109 Ok(extracted_paths)
110 }
111
112 pub fn extract_metadata_files(&mut self, zip_path: &Path) -> Result<HashMap<String, PathBuf>> {
113 let metadata_files = [
114 "stations.txt",
115 "elements.txt",
116 "metadata.txt",
117 "sources.txt",
118 ];
119 let mut extracted = HashMap::new();
120
121 for file_name in &metadata_files {
122 if let Ok(path) = self.extract_file(zip_path, file_name) {
123 extracted.insert(file_name.to_string(), path);
124 }
125 }
126
127 if extracted.is_empty() {
128 return Err(ProcessingError::InvalidFormat(
129 "No metadata files found in archive".to_string(),
130 ));
131 }
132
133 Ok(extracted)
134 }
135
136 pub fn get_extracted_file(&self, file_name: &str) -> Option<&PathBuf> {
137 self.extracted_files.get(file_name)
138 }
139
140 pub fn list_extracted_files(&self) -> Vec<&String> {
141 self.extracted_files.keys().collect()
142 }
143
144 pub fn cleanup(&mut self) -> Result<()> {
145 self.extracted_files.clear();
146
147 Ok(())
150 }
151
152 pub fn estimate_extraction_size(&self, zip_path: &Path) -> Result<u64> {
153 let file = File::open(zip_path)?;
154 let mut archive = ZipArchive::new(file)?;
155 let mut total_size = 0u64;
156
157 for i in 0..archive.len() {
158 let zip_file = archive.by_index(i)?;
159 total_size += zip_file.size();
160 }
161
162 Ok(total_size)
163 }
164}
165
166impl Drop for TempFileManager {
167 fn drop(&mut self) {
168 if let Err(e) = self.cleanup() {
169 eprintln!("Warning: Failed to cleanup temporary files: {}", e);
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use std::io::Write;
178 use tempfile::NamedTempFile;
179 use zip::{CompressionMethod, ZipWriter};
180
181 fn create_test_zip() -> Result<NamedTempFile> {
182 let file = NamedTempFile::new()?;
183 {
184 let mut zip = ZipWriter::new(&file);
185
186 zip.start_file(
188 "stations.txt",
189 zip::write::FileOptions::default().compression_method(CompressionMethod::Stored),
190 )?;
191 zip.write_all(
192 b"STAID,STANAME,CN,LAT,LON,HGHT\n257,TEST STATION,GB,+51:30:00,-000:07:00,100\n",
193 )?;
194
195 zip.start_file(
197 "elements.txt",
198 zip::write::FileOptions::default().compression_method(CompressionMethod::Stored),
199 )?;
200 zip.write_all(b"ELEID,DESC,UNIT\nTX1,Maximum temperature,0.1 C\n")?;
201
202 zip.start_file(
204 "TX_STAID000257.txt",
205 zip::write::FileOptions::default().compression_method(CompressionMethod::Stored),
206 )?;
207 zip.write_all(b"Header\n101,20230101,125,0\n")?;
208
209 zip.finish()?;
210 } Ok(file)
212 }
213
214 #[test]
215 fn test_temp_file_manager_creation() -> Result<()> {
216 let manager = TempFileManager::new()?;
217 assert!(manager.temp_dir_path().exists());
218 Ok(())
219 }
220
221 #[test]
222 fn test_extract_file() -> Result<()> {
223 let test_zip = create_test_zip()?;
224 let mut manager = TempFileManager::new()?;
225
226 let extracted_path = manager.extract_file(test_zip.path(), "stations.txt")?;
227 assert!(extracted_path.exists());
228
229 let content = std::fs::read_to_string(&extracted_path)?;
230 assert!(content.contains("TEST STATION"));
231
232 Ok(())
233 }
234
235 #[test]
236 fn test_extract_metadata_files() -> Result<()> {
237 let test_zip = create_test_zip()?;
238 let mut manager = TempFileManager::new()?;
239
240 let metadata_files = manager.extract_metadata_files(test_zip.path())?;
241 assert!(metadata_files.contains_key("stations.txt"));
242 assert!(metadata_files.contains_key("elements.txt"));
243
244 Ok(())
245 }
246
247 #[test]
248 fn test_extract_files_matching_pattern() -> Result<()> {
249 let test_zip = create_test_zip()?;
250 let mut manager = TempFileManager::new()?;
251
252 let data_files = manager.extract_files_matching_pattern(test_zip.path(), "STAID")?;
253 assert_eq!(data_files.len(), 1);
254 assert!(data_files[0]
255 .file_name()
256 .unwrap()
257 .to_str()
258 .unwrap()
259 .contains("TX_STAID"));
260
261 Ok(())
262 }
263
264 #[test]
265 fn test_already_extracted_file() -> Result<()> {
266 let test_zip = create_test_zip()?;
267 let mut manager = TempFileManager::new()?;
268
269 let path1 = manager.extract_file(test_zip.path(), "stations.txt")?;
271 let path2 = manager.extract_file(test_zip.path(), "stations.txt")?;
273
274 assert_eq!(path1, path2);
275 assert_eq!(manager.list_extracted_files().len(), 1);
276
277 Ok(())
278 }
279}