Skip to content

Commit 23bd90c

Browse files
🔀 Merge feature/check_before_uninstall into develop
2 parents fdedb16 + 4739996 commit 23bd90c

3 files changed

Lines changed: 107 additions & 85 deletions

File tree

src/device.rs

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::config::Platform;
22
use crate::error::Error;
3+
use crate::installation::CommandOutcome;
4+
use crate::package::Package;
35
use regex::Regex;
46
use std::fmt::Display;
57
use std::process::Command;
@@ -11,16 +13,66 @@ pub struct Device {
1113
}
1214

1315
impl Device {
14-
pub fn name(&self) -> &str {
15-
&self.name
16+
pub fn supports(&self, package: &Package) -> bool {
17+
package
18+
.platforms()
19+
.iter()
20+
.any(|p| match package.match_file_name() {
21+
false => &self.platform == p,
22+
true => package.file_name().to_lowercase().contains(&self.platform),
23+
})
24+
}
25+
26+
pub async fn install(&self, package: &Package) -> CommandOutcome {
27+
let path = package.path();
28+
let command = tokio::process::Command::new("adb")
29+
.args(["-s", &self.id, "install", path])
30+
.output();
31+
let description = format!("Installation of {} on {}", package.id(), self.name);
32+
match command.await {
33+
Ok(output) if output.status.success() => CommandOutcome::from_success(&description),
34+
Ok(output) => {
35+
let error = Error::from_installation_error(&output.stderr);
36+
CommandOutcome::from_error(&description, error)
37+
}
38+
Err(e) => CommandOutcome::from_error(&description, Error::Installation(e.to_string())),
39+
}
1640
}
1741

18-
pub fn id(&self) -> &str {
19-
&self.id
42+
pub async fn uninstall(&self, package: &Package) -> CommandOutcome {
43+
let description = format!("Uninstallation of {} on {}", package.id(), self.name);
44+
let command = tokio::process::Command::new("adb")
45+
.args(["-s", &self.id, "uninstall", package.id()])
46+
.output();
47+
match command.await {
48+
Ok(output) if output.status.success() => CommandOutcome::from_success(&description),
49+
Ok(output) => {
50+
let message = String::from_utf8_lossy(&output.stdout);
51+
let error = Error::Uninstall(message.to_string());
52+
CommandOutcome::from_error(&description, error)
53+
}
54+
Err(e) => {
55+
let error = Error::Uninstall(e.to_string());
56+
CommandOutcome::from_error(&description, error)
57+
}
58+
}
2059
}
2160

22-
pub fn platform(&self) -> &String {
23-
&self.platform
61+
pub async fn has_app_installed(&self, app_name: &str) -> Result<bool, Error> {
62+
let output = tokio::process::Command::new("adb")
63+
.args([
64+
"-s",
65+
&self.id,
66+
"shell",
67+
"pm list packages",
68+
"| grep",
69+
app_name,
70+
])
71+
.output();
72+
match output.await {
73+
Ok(output) => Ok(output.status.success()),
74+
Err(e) => Err(Error::Uninstall(e.to_string())),
75+
}
2476
}
2577

2678
fn from_str_with_platforms(line: &str, platforms: &[Platform]) -> Option<Device> {

src/error.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ pub enum Error {
2424
Uninstall(String),
2525
}
2626

27+
impl Error {
28+
pub fn from_installation_error(error: &[u8]) -> Error {
29+
let error = String::from_utf8_lossy(error);
30+
if error.contains("INSTALL_FAILED_UPDATE_INCOMPATIBLE") {
31+
Error::PackageSignatureMismatch
32+
} else if error.contains("INSTALL_FAILED_VERSION_DOWNGRADE") {
33+
Error::PackageDowngrade
34+
} else {
35+
Error::Installation(String::from(error))
36+
}
37+
}
38+
}
39+
2740
impl Display for Error {
2841
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2942
match self {

src/installation.rs

Lines changed: 36 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::device::Device;
22
use crate::error::Error;
3-
use crate::error::Error::Uninstall;
43
use crate::package::Package;
54
use std::sync::Arc;
65
use tokio::sync::mpsc;
@@ -19,6 +18,20 @@ impl CommandOutcome {
1918
pub fn error(&self) -> Option<&Error> {
2019
self.error.as_ref()
2120
}
21+
22+
pub fn from_success(description: &str) -> CommandOutcome {
23+
CommandOutcome {
24+
description: String::from(description),
25+
error: None,
26+
}
27+
}
28+
29+
pub fn from_error(description: &str, error: Error) -> CommandOutcome {
30+
CommandOutcome {
31+
description: String::from(description),
32+
error: Some(error),
33+
}
34+
}
2235
}
2336

2437
pub struct DeviceInstallations {
@@ -33,10 +46,7 @@ impl DeviceInstallations {
3346
) -> Vec<DeviceInstallations> {
3447
let mut requests: Vec<DeviceInstallations> = Vec::new();
3548
for device in devices {
36-
let matches: Vec<_> = packages
37-
.iter()
38-
.filter(|p| is_package_match(device, p))
39-
.collect();
49+
let matches: Vec<_> = packages.iter().filter(|p| device.supports(p)).collect();
4050
if matches.is_empty() {
4151
continue;
4252
}
@@ -66,86 +76,33 @@ impl DeviceInstallations {
6676
let tx = tx.clone();
6777
tokio::task::spawn(async move {
6878
if self.uninstall_first {
69-
let uninstall_outcome = perform_uninstall(&device, &package).await;
70-
tx.send(uninstall_outcome)
71-
.await
72-
.expect("Error sending operation outcome.");
79+
match device.has_app_installed(package.id()).await {
80+
Ok(true) => {
81+
// App is installed, uninstall it first.
82+
let uninstall_outcome = device.uninstall(&package).await;
83+
tx.send(uninstall_outcome)
84+
.await
85+
.expect("Error sending uninstallation outcome.");
86+
}
87+
Ok(false) => (), // Do nothing because the app is not installed.
88+
Err(e) => {
89+
let error = CommandOutcome::from_error("Uninstall check failed", e);
90+
tx.send(error)
91+
.await
92+
.expect("Error sending uninstallation check error.");
93+
}
94+
}
7395
}
74-
let install_outcome = perform_install(&device, &package).await;
96+
let install_outcome = device.install(&package).await;
7597
tx.send(install_outcome)
7698
.await
77-
.expect("Error sending operation outcome.");
99+
.expect("Error sending installation outcome.");
78100
});
79101
}
80102
ReceiverStream::new(rx)
81103
}
82104
}
83105

84-
fn is_package_match(device: &Device, package: &Package) -> bool {
85-
package
86-
.platforms()
87-
.iter()
88-
.any(|p| match package.match_file_name() {
89-
false => device.platform() == p,
90-
true => package
91-
.file_name()
92-
.to_lowercase()
93-
.contains(device.platform()),
94-
})
95-
}
96-
97-
fn parse_installation_error(error: &[u8]) -> Error {
98-
let error = String::from_utf8_lossy(error);
99-
if error.contains("INSTALL_FAILED_UPDATE_INCOMPATIBLE") {
100-
Error::PackageSignatureMismatch
101-
} else if error.contains("INSTALL_FAILED_VERSION_DOWNGRADE") {
102-
Error::PackageDowngrade
103-
} else {
104-
Error::Installation(String::from(error))
105-
}
106-
}
107-
108-
async fn perform_uninstall(device: &Device, package: &Package) -> CommandOutcome {
109-
let description = format!("Uninstallation of {} on {}", package.id(), device.name());
110-
let output = tokio::process::Command::new("adb")
111-
.args(["-s", device.id(), "uninstall", package.id()])
112-
.output();
113-
match output.await {
114-
Ok(output) if output.status.success() => CommandOutcome {
115-
description,
116-
error: None,
117-
},
118-
Ok(output) => {
119-
let message = String::from_utf8_lossy(&output.stderr);
120-
CommandOutcome {
121-
description,
122-
error: Some(Uninstall(message.to_string())),
123-
}
124-
}
125-
Err(e) => CommandOutcome {
126-
description,
127-
error: Some(Uninstall(e.to_string())),
128-
},
129-
}
130-
}
131-
132-
async fn perform_install(device: &Device, package: &Package) -> CommandOutcome {
133-
let path = package.path();
134-
let output = tokio::process::Command::new("adb")
135-
.args(["-s", device.id(), "install", path])
136-
.output();
137-
let error = match output.await {
138-
Ok(output) if output.stderr.is_empty() => None, // Success
139-
Ok(output) => {
140-
let error = parse_installation_error(&output.stderr);
141-
Some(error)
142-
}
143-
Err(e) => Some(Error::Installation(e.to_string())),
144-
};
145-
let description = format!("Installation of {} on {}", package.id(), device.name());
146-
CommandOutcome { description, error }
147-
}
148-
149106
#[cfg(test)]
150107
mod tests {
151108
use super::*;
@@ -156,7 +113,7 @@ mod tests {
156113
[INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package com.lazysquirrellabs.airhockey signatures \
157114
do not match previously installed version; ignoring!]";
158115
assert_eq!(
159-
parse_installation_error(error.as_bytes()),
116+
Error::from_installation_error(error.as_bytes()),
160117
Error::PackageSignatureMismatch
161118
);
162119
}
@@ -166,7 +123,7 @@ mod tests {
166123
let error = "adb: failed to install 5.1/CorpusVR-5.1-5010001-Dashboard.apk: \
167124
Failure [INSTALL_FAILED_VERSION_DOWNGRADE]";
168125
assert_eq!(
169-
parse_installation_error(error.as_bytes()),
126+
Error::from_installation_error(error.as_bytes()),
170127
Error::PackageDowngrade
171128
);
172129
}
@@ -176,7 +133,7 @@ mod tests {
176133
// Sometimes ADB throws errors without no content, just a header, like the one below.
177134
let error = "adb: failed to install 5.1/CorpusVR-5.1-5010001-Dashboard.apk:\n";
178135
assert_eq!(
179-
parse_installation_error(error.as_bytes()),
136+
Error::from_installation_error(error.as_bytes()),
180137
Error::Installation(String::from(error))
181138
);
182139
}

0 commit comments

Comments
 (0)