Skip to content

Commit 77a52be

Browse files
committed
refactor: more pre-parsing
1 parent 240dde1 commit 77a52be

File tree

2 files changed

+63
-36
lines changed

2 files changed

+63
-36
lines changed

src/core/adb.rs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
//!
2020
//! Despite being "low-level", we can still "have cake and eat it too";
2121
//! After all, what's the point of an abstraction if it doesn't come with goodies?:
22-
//! We can take some freedoms, such as:
23-
//! - pre-parsing or validanting output, to provide types with invariants.
22+
//! We can reserve some artistic license, such as:
23+
//! - shorter names, complemented by context
24+
//! - pre-parsing or validanting output, to provide types with invariants
2425
//! - strongly-typed rather than "stringly-typed" APIs
2526
//! - nicer IDE support
2627
//! - compile-time prevention of malformed cmds
@@ -77,14 +78,31 @@ impl Cmd {
7778
self.0.arg("shell");
7879
ShCmd(self)
7980
}
80-
/// List all detected devices:
81+
/// List attached devices (as serials) and their status:
8182
/// - USB
8283
/// - TCP/IP: WIFI, Ethernet, etc...
8384
/// - Local emulators
84-
/// Some may not be authorized by the user (yet)
85-
pub fn devices(mut self) -> Result<String, String> {
85+
/// Status can be (but not limited to):
86+
/// - "unauthorized"
87+
/// - "device"
88+
pub fn devices(mut self) -> Result<Vec<(String, String)>, String> {
8689
self.0.arg("devices");
87-
self.run()
90+
Ok(self
91+
.run()?
92+
.lines()
93+
.skip(1) // header
94+
.map(|dev_stat| {
95+
let tab_idx = dev_stat
96+
.find('\t')
97+
.expect("There must be 1 tab after serial");
98+
(
99+
// serial
100+
dev_stat[..tab_idx].to_string(),
101+
// status
102+
dev_stat[(tab_idx + 1)..].to_string(),
103+
)
104+
})
105+
.collect())
88106
}
89107
/// Reboots default device
90108
pub fn reboot(mut self) -> Result<String, String> {
@@ -182,9 +200,9 @@ impl ToString for PmLsPackFlag {
182200
}
183201
}
184202

185-
pub const PACK_URI_SCHEME: &str = "package:";
203+
pub const PACK_PREFIX: &str = "package:";
186204
#[expect(clippy::cast_possible_truncation, reason = "")]
187-
pub const PACK_URI_LEN: u8 = PACK_URI_SCHEME.len() as _;
205+
pub const PACK_URI_LEN: u8 = PACK_PREFIX.len() as _;
188206

189207
pub const PM_LIST_PACKS: &str = "pm list packages";
190208
pub const PM_CLEAR_PACK: &str = "pm clear";
@@ -195,7 +213,8 @@ pub const PM_CLEAR_PACK: &str = "pm clear";
195213
#[derive(Debug)]
196214
pub struct PmCmd(ShCmd);
197215
impl PmCmd {
198-
/// `list packages` sub-command
216+
/// `list packages` sub-command,
217+
/// stripped of "package:" prefix
199218
pub fn ls_packs(
200219
mut self,
201220
f: Option<PmLsPackFlag>,
@@ -214,13 +233,14 @@ impl PmCmd {
214233
pack_ls
215234
.lines()
216235
.map(|p_ln| {
217-
debug_assert!(p_ln.starts_with(PACK_URI_SCHEME));
236+
debug_assert!(p_ln.starts_with(PACK_PREFIX));
218237
String::from(&p_ln[PACK_URI_LEN as usize..])
219238
})
220239
.collect()
221240
})
222241
}
223-
/// `list packages` sub-command, but pre-validated
242+
/// `list packages` sub-command, pre-validated.
243+
/// This is strongly-typed, at the cost of regex overhead.
224244
pub fn ls_packs_valid(
225245
self,
226246
f: Option<PmLsPackFlag>,
@@ -230,9 +250,12 @@ impl PmCmd {
230250
.into_iter()
231251
.map(|p| PackId::new(p).expect("One of these is wrong: `PackId` regex, ADB implementation. Or the spec now allows a wider char-set")).collect())
232252
}
233-
/// `list users` sub-command
234-
pub fn ls_users(mut self) -> Result<String, String> {
253+
#[allow(clippy::doc_markdown, reason = "Multi URL")]
254+
/// `list users` sub-command.
255+
/// - https://source.android.com/docs/devices/admin/multi-user-testing
256+
/// - https://stackoverflow.com/questions/37495126/android-get-list-of-users-and-profile-name
257+
pub fn ls_users(mut self) -> Result<Vec<String>, String> {
235258
self.0 .0 .0.args(["list", "users"]);
236-
self.0 .0.run()
259+
Ok(self.0 .0.run()?.lines().skip(1).map(String::from).collect())
237260
}
238261
}

src/core/sync.rs

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ use regex::Regex;
77
use retry::{delay::Fixed, retry, OperationResult};
88
use serde::{Deserialize, Serialize};
99
use static_init::dynamic;
10-
use std::collections::HashSet;
11-
use std::process::Command;
10+
use std::{collections::HashSet, process::Command};
1211

1312
#[cfg(target_os = "windows")]
1413
use std::os::windows::process::CommandExt;
@@ -327,45 +326,50 @@ pub fn is_protected_user(user_id: &str, device_serial: &str) -> bool {
327326
}
328327

329328
/// `pm list users` parsed into a vec with extra info.
330-
pub fn list_users_parsed(device_serial: &str) -> Vec<User> {
329+
pub fn ls_users_parsed(device_serial: &str) -> Vec<User> {
331330
#[dynamic]
332331
static RE: Regex = Regex::new(r"\{([0-9]+)").unwrap_or_else(|_| unreachable!());
332+
333333
AdbCmd::new()
334334
.sh(device_serial)
335335
.pm()
336336
.ls_users()
337-
.map(|users| {
338-
RE.find_iter(&users)
339-
.enumerate()
340-
.map(|(i, u)| User {
341-
id: u.as_str()[1..].parse().unwrap(),
342-
index: i,
343-
protected: is_protected_user(&u.as_str()[1..], device_serial),
344-
})
345-
.collect()
346-
})
337+
// if default, then empty iter, which becomes empty vec (again)
347338
.unwrap_or_default()
339+
.into_iter()
340+
.enumerate()
341+
.map(|(i, user)| {
342+
// It seems each line is a user,
343+
// optionally associated with a work-profile.
344+
// This will ignore the work-profiles!
345+
let u = &RE.captures(&user).expect("Each user should have an ID")[1];
346+
User {
347+
id: u
348+
.parse()
349+
.unwrap_or_else(|_| unreachable!("User ID must be valid `u16`")),
350+
index: i,
351+
protected: is_protected_user(u, device_serial),
352+
}
353+
})
354+
.collect()
348355
}
349356

350-
// getprop ro.serialno
357+
/// This matches serials (`getprop ro.serialno`)
358+
/// that are authorized by the user.
351359
pub async fn get_devices_list() -> Vec<Device> {
352-
/// This matches serials that are authorized by the user.
353-
#[dynamic]
354-
static RE: Regex = Regex::new(r"\n(\S+)\s+device").unwrap_or_else(|_| unreachable!());
355-
356360
retry(Fixed::from_millis(500).take(120), || {
357361
match AdbCmd::new().devices() {
358362
Ok(devices) => {
359363
let mut device_list: Vec<Device> = vec![];
360-
if !RE.is_match(&devices) {
364+
if devices.iter().all(|(_, stat)| stat != "device") {
361365
return OperationResult::Retry(vec![]);
362366
}
363-
for device in RE.captures_iter(&devices) {
364-
let serial = &device[1];
367+
for device in devices {
368+
let serial = &device.0;
365369
device_list.push(Device {
366370
model: get_device_brand(serial),
367371
android_sdk: get_android_sdk(serial),
368-
user_list: list_users_parsed(serial),
372+
user_list: ls_users_parsed(serial),
369373
adb_id: serial.to_string(),
370374
});
371375
}

0 commit comments

Comments
 (0)