1use crate::error::{ADBError, ADBResult};
2use std::collections::HashMap;
3use std::path::{Path, PathBuf};
4use std::time::{Duration, Instant};
5use log::warn;
6use regex::Regex;
7use std::fs;
8use rand::Rng;
9
10pub fn retry_with_backoff<F, T>(max_retries: u32, initial_delay_ms: u64, f: F) -> ADBResult<T>
12where
13 F: Fn() -> ADBResult<T>,
14{
15 let mut retries = 0;
16 let mut delay = initial_delay_ms;
17
18 loop {
19 match f() {
20 Ok(result) => return Ok(result),
21 Err(e) => {
22 retries += 1;
23 if retries > max_retries {
24 return Err(e);
25 }
26
27 warn!(
28 "操作失败 (重试 {}/{}), 延迟 {}ms: {}",
29 retries, max_retries, delay, e
30 );
31
32 std::thread::sleep(Duration::from_millis(delay));
33 delay = (delay * 2).min(10000);
35 }
36 }
37 }
38}
39
40pub fn with_timeout<F, T>(timeout_ms: u64, f: F) -> ADBResult<T>
42where
43 F: FnOnce() -> ADBResult<T> + Send + 'static,
44 T: Send + 'static,
45{
46 let timeout = Duration::from_millis(timeout_ms);
47
48 let (sender, receiver) = std::sync::mpsc::channel();
50
51 std::thread::spawn(move || {
53 let result = f();
54 let _ = sender.send(result);
55 });
56
57 match receiver.recv_timeout(timeout) {
59 Ok(result) => result,
60 Err(_) => Err(ADBError::TimeoutError {
61 message: "操作超时".to_string(),
62 duration: timeout,
63 }),
64 }
65}
66
67pub fn wait_with_polling<F, C>(
69 timeout_ms: u64,
70 poll_interval_ms: u64,
71 condition_fn: F,
72 callback: Option<C>,
73) -> ADBResult<bool>
74where
75 F: Fn() -> ADBResult<bool>,
76 C: Fn(u64),
77{
78 let start = Instant::now();
79 let timeout = Duration::from_millis(timeout_ms);
80 let interval = Duration::from_millis(poll_interval_ms);
81
82 loop {
83 let elapsed = start.elapsed();
85 if elapsed > timeout {
86 return Ok(false);
87 }
88
89 if let Some(cb) = &callback {
91 cb(elapsed.as_millis() as u64);
92 }
93
94 match condition_fn() {
96 Ok(true) => return Ok(true),
97 Ok(false) => {
98 std::thread::sleep(interval);
100 }
101 Err(e) => {
102 warn!("检查条件时出错: {}", e);
104 std::thread::sleep(interval);
105 }
106 }
107 }
108}
109
110pub fn parse_properties(output: &str) -> HashMap<String, String> {
112 let mut properties = HashMap::new();
113 let re = Regex::new(r"^\[([^\]]+)\]:\s*\[([^\]]*)\]$").unwrap_or_else(|_| {
114 Regex::new(r"^$").unwrap()
116 });
117
118 for line in output.lines() {
119 if let Some(caps) = re.captures(line.trim()) {
120 if let (Some(key), Some(value)) = (caps.get(1), caps.get(2)) {
121 properties.insert(key.as_str().to_string(), value.as_str().to_string());
122 }
123 }
124 }
125
126 properties
127}
128
129pub fn cleanup_partial_files(path_pattern: &str) -> ADBResult<()> {
131 let pattern = format!("{}*", path_pattern);
132 let glob_paths = match glob::glob(&pattern) {
133 Ok(paths) => paths,
134 Err(e) => {
135 return Err(ADBError::FileError(format!(
136 "无法匹配部分文件: {}",
137 e
138 )))
139 }
140 };
141
142 for entry in glob_paths {
143 match entry {
144 Ok(path) => {
145 if path.is_file() {
146 if let Err(e) = fs::remove_file(&path) {
147 warn!("无法删除临时文件 {:?}: {}", path, e);
148 }
149 }
150 }
151 Err(e) => warn!("无法访问文件路径: {}", e),
152 }
153 }
154
155 Ok(())
156}
157
158pub fn create_temp_dir_path(prefix: &str) -> ADBResult<PathBuf> {
160 let temp_dir = std::env::temp_dir();
161
162 let random_string: String = rand::rng().sample_iter(&rand::distr::Alphanumeric)
164 .take(10)
165 .map(char::from)
166 .collect();
167
168 let dir_name = format!("{}_{}", prefix, random_string);
169 let full_path = temp_dir.join(dir_name);
170
171 fs::create_dir_all(&full_path).map_err(|e| {
173 ADBError::FileError(format!("无法创建临时目录: {}", e))
174 })?;
175
176 Ok(full_path)
177}
178
179pub fn is_valid_apk(path: &Path) -> bool {
181 if !path.exists() || !path.is_file() {
182 return false;
183 }
184
185 if let Some(ext) = path.extension() {
187 return ext == "apk";
188 }
189
190 false
191}
192
193pub fn extract_number(s: &str) -> Option<i32> {
195 let re = Regex::new(r"\d+").ok()?;
196 re.find(s)
197 .map(|m| m.as_str())
198 .and_then(|num_str| num_str.parse::<i32>().ok())
199}
200
201pub fn format_size(size_bytes: u64) -> String {
203 const KB: u64 = 1024;
204 const MB: u64 = KB * 1024;
205 const GB: u64 = MB * 1024;
206
207 if size_bytes >= GB {
208 format!("{:.2} GB", size_bytes as f64 / GB as f64)
209 } else if size_bytes >= MB {
210 format!("{:.2} MB", size_bytes as f64 / MB as f64)
211 } else if size_bytes >= KB {
212 format!("{:.2} KB", size_bytes as f64 / KB as f64)
213 } else {
214 format!("{} B", size_bytes)
215 }
216}
217
218pub fn parse_args(args: &[String]) -> HashMap<String, String> {
220 let mut result = HashMap::new();
221 let mut i = 0;
222
223 while i < args.len() {
224 if args[i].starts_with("--") {
225 let key = args[i][2..].to_string();
226
227 if i + 1 < args.len() && !args[i + 1].starts_with("--") {
228 result.insert(key, args[i + 1].clone());
229 i += 2;
230 } else {
231 result.insert(key, "true".to_string());
232 i += 1;
233 }
234 } else {
235 i += 1;
236 }
237 }
238
239 result
240}
241
242pub fn get_line_at<'a>(lines: &mut std::str::Lines<'a>, index: usize) -> Option<&'a str> {
244 lines.nth(index)
245}
246
247pub fn parse_number<T: std::str::FromStr>(s: &str) -> Option<T> {
249 s.trim().parse::<T>().ok()
250}
251
252pub fn contains_any(s: &str, keywords: &[&str]) -> bool {
254 for keyword in keywords {
255 if s.contains(keyword) {
256 return true;
257 }
258 }
259 false
260}
261
262pub fn format_duration(seconds: u64) -> String {
264 let hours = seconds / 3600;
265 let minutes = (seconds % 3600) / 60;
266 let secs = seconds % 60;
267
268 format!("{:02}:{:02}:{:02}", hours, minutes, secs)
269}