Skip to main content

exiftool_rs_wrapper/
process.rs

1//! ExifTool `-stay_open` 进程管理
2
3use crate::error::{Error, Result};
4use std::io::{BufRead, BufReader, BufWriter, Write};
5use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio};
6use std::time::Duration;
7use tracing::{debug, info, warn};
8
9/// ExifTool 进程内部状态
10pub struct ExifToolInner {
11    process: Child,
12    stdin: BufWriter<ChildStdin>,
13    stdout: BufReader<ChildStdout>,
14}
15
16impl std::fmt::Debug for ExifToolInner {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        f.debug_struct("ExifToolInner")
19            .field("process", &self.process.id())
20            .finish()
21    }
22}
23
24impl ExifToolInner {
25    /// 启动新的 ExifTool 进程(-stay_open 模式)
26    pub fn new() -> Result<Self> {
27        info!("Starting ExifTool process with -stay_open mode");
28
29        let mut process = Command::new("exiftool")
30            .arg("-stay_open")
31            .arg("True")
32            .arg("-@")
33            .arg("-")
34            .stdin(Stdio::piped())
35            .stdout(Stdio::piped())
36            .stderr(Stdio::piped())
37            .spawn()
38            .map_err(|e| {
39                if e.kind() == std::io::ErrorKind::NotFound {
40                    Error::ExifToolNotFound
41                } else {
42                    e.into()
43                }
44            })?;
45
46        let stdin = process
47            .stdin
48            .take()
49            .ok_or_else(|| Error::process("Failed to capture stdin"))?;
50
51        let stdout = process
52            .stdout
53            .take()
54            .ok_or_else(|| Error::process("Failed to capture stdout"))?;
55
56        let mut inner = Self {
57            process,
58            stdin: BufWriter::new(stdin),
59            stdout: BufReader::new(stdout),
60        };
61
62        // 验证进程是否正常工作
63        inner.verify_process()?;
64
65        info!("ExifTool process started successfully");
66        Ok(inner)
67    }
68
69    /// 验证进程是否正常工作
70    fn verify_process(&mut self) -> Result<()> {
71        debug!("Verifying ExifTool process");
72
73        // 发送版本查询命令
74        self.send_line("-ver")?;
75        self.send_line("-execute")?;
76        self.stdin.flush()?;
77
78        // 读取响应
79        let response = self.read_response()?;
80        debug!("ExifTool version: {}", response.text().trim());
81
82        Ok(())
83    }
84
85    /// 发送单行命令
86    pub fn send_line(&mut self, line: &str) -> Result<()> {
87        debug!("Sending command: {}", line);
88        writeln!(self.stdin, "{}", line)?;
89        Ok(())
90    }
91
92    /// 执行命令并获取响应
93    pub fn execute(&mut self, args: &[String]) -> Result<Response> {
94        debug!("Executing command with {} args", args.len());
95
96        // 发送所有参数
97        for arg in args {
98            self.send_line(arg)?;
99        }
100
101        // 发送执行命令
102        self.send_line("-execute")?;
103        self.stdin.flush()?;
104
105        // 读取响应
106        self.read_response()
107    }
108
109    /// 读取响应(直到遇到 {ready})
110    pub fn read_response(&mut self) -> Result<Response> {
111        let mut lines = Vec::new();
112        let mut buffer = String::new();
113
114        loop {
115            buffer.clear();
116            let bytes_read = self.stdout.read_line(&mut buffer)?;
117
118            if bytes_read == 0 {
119                // EOF reached unexpectedly
120                return Err(Error::process("Unexpected EOF from ExifTool process"));
121            }
122
123            let trimmed = buffer.trim();
124            debug!("Received line: {}", trimmed);
125
126            if trimmed == "{ready}" {
127                break;
128            }
129
130            lines.push(buffer.clone());
131        }
132
133        Ok(Response::new(lines))
134    }
135
136    /// 批量执行命令
137    pub fn execute_batch(&mut self, commands: &[Vec<String>]) -> Result<Vec<Response>> {
138        debug!("Executing batch of {} commands", commands.len());
139
140        let mut responses = Vec::with_capacity(commands.len());
141
142        for args in commands {
143            let response = self.execute(args)?;
144            responses.push(response);
145        }
146
147        Ok(responses)
148    }
149
150    /// 刷新 stdin
151    pub fn flush(&mut self) -> Result<()> {
152        self.stdin.flush().map_err(|e| e.into())
153    }
154
155    /// 关闭进程
156    pub fn close(&mut self) -> Result<()> {
157        info!("Closing ExifTool process");
158
159        // 发送关闭命令
160        let _ = self.send_line("-stay_open");
161        let _ = self.send_line("False");
162        let _ = self.send_line("-execute");
163        let _ = self.stdin.flush();
164
165        // 等待进程退出
166        match self.wait_with_timeout(Duration::from_secs(5)) {
167            Ok(Some(status)) => {
168                if let Some(code) = status.code() {
169                    if code != 0 {
170                        warn!("ExifTool exited with code: {}", code);
171                    } else {
172                        info!("ExifTool process exited cleanly");
173                    }
174                }
175            }
176            Ok(None) => {
177                warn!("ExifTool did not exit gracefully, forcing kill");
178                let _ = self.process.kill();
179            }
180            Err(e) => {
181                warn!("Error waiting for ExifTool: {}", e);
182                let _ = self.process.kill();
183            }
184        }
185
186        Ok(())
187    }
188
189    /// 带超时的等待
190    fn wait_with_timeout(&mut self, timeout: Duration) -> Result<Option<std::process::ExitStatus>> {
191        use std::thread;
192
193        let start = std::time::Instant::now();
194
195        loop {
196            match self.process.try_wait()? {
197                Some(status) => return Ok(Some(status)),
198                None => {
199                    if start.elapsed() >= timeout {
200                        return Ok(None);
201                    }
202                    thread::sleep(Duration::from_millis(10));
203                }
204            }
205        }
206    }
207}
208
209impl Drop for ExifToolInner {
210    fn drop(&mut self) {
211        if let Err(e) = self.close() {
212            warn!("Error closing ExifTool process: {}", e);
213        }
214    }
215}
216
217/// 命令响应
218#[derive(Debug, Clone)]
219pub struct Response {
220    lines: Vec<String>,
221}
222
223impl Response {
224    /// 创建新响应
225    pub fn new(lines: Vec<String>) -> Self {
226        Self { lines }
227    }
228
229    /// 获取所有行
230    pub fn lines(&self) -> &[String] {
231        &self.lines
232    }
233
234    /// 获取合并的文本内容
235    pub fn text(&self) -> String {
236        self.lines.join("")
237    }
238
239    /// 获取 JSON 解析结果
240    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
241        let text = self.text();
242        serde_json::from_str(&text).map_err(|e| e.into())
243    }
244
245    /// 检查是否有错误
246    pub fn is_error(&self) -> bool {
247        self.lines
248            .iter()
249            .any(|line| line.contains("Error:") || line.contains("Warning:"))
250    }
251
252    /// 获取错误信息
253    pub fn error_message(&self) -> Option<String> {
254        self.lines
255            .iter()
256            .find(|line| line.contains("Error:"))
257            .cloned()
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264
265    #[test]
266    fn test_response() {
267        let lines = vec!["Line 1".to_string(), "Line 2".to_string()];
268        let response = Response::new(lines);
269
270        assert_eq!(response.lines().len(), 2);
271        assert_eq!(response.text(), "Line 1Line 2");
272        assert!(!response.is_error());
273    }
274
275    #[test]
276    fn test_response_error() {
277        let lines = vec!["Error: Something went wrong".to_string()];
278        let response = Response::new(lines);
279
280        assert!(response.is_error());
281        assert!(response.error_message().is_some());
282    }
283
284    #[test]
285    fn test_response_json() {
286        let lines = vec![r#"{"key": "value"}"#.to_string()];
287        let response = Response::new(lines);
288
289        #[derive(Debug, serde::Deserialize, PartialEq)]
290        struct TestData {
291            key: String,
292        }
293
294        let data: TestData = response.json().unwrap();
295        assert_eq!(data.key, "value");
296    }
297}