diff --git a/Cargo.lock b/Cargo.lock index 51509aa..d093b06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,9 +394,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener", "event-listener-strategy", @@ -744,9 +744,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -801,9 +801,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] @@ -1907,9 +1907,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "jiff" @@ -2917,9 +2917,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" [[package]] name = "portable-atomic-util" @@ -3777,18 +3777,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.9" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", @@ -3798,18 +3798,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -3830,9 +3830,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] diff --git a/src/main.rs b/src/main.rs index bd127ed..6c9979d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,9 @@ use std::{ collections::{BTreeMap, BTreeSet}, - io::{BufReader, Write}, - sync::mpsc::{Receiver, channel}, - thread::sleep, + io::BufReader, + sync::mpsc::{Receiver, Sender, channel}, + thread::{sleep, spawn}, time::Duration, }; @@ -17,6 +17,60 @@ use egui::{ use egui_alignments::{center_horizontal, column}; const WORKER_THREAD_POLL: Duration = Duration::from_secs(5); +const LABEL_EXTRACTOR: &[u8; 2124] = include_bytes!("./extractor.dex"); + +type FrontendPayload = PackageDiff; + +struct App { + search_query: String, + uninstallable: bool, + reinstallable: bool, + entries: BTreeMap, + package_diff_rx: Receiver, + device_lost_rx: Receiver<()>, + action_tx: Sender, + disable_mode: bool, + + have_device: bool, +} + +type PackageIdentifier = String; +type PackagePath = String; + +#[derive(Clone)] +pub struct Package { + id: PackageIdentifier, + path: PackagePath, + label: String, +} + +struct Entry { + package: Package, + expand_triggered: bool, + enabled: bool, + selected: bool, +} + +struct PackageDiff { + added: Vec, + removed: Vec, +} + +#[derive(Debug)] +pub enum ShellRunError { + Timeout, + ParseError, + Unrecoverable, + UnsuccessfulOperation(PackageIdentifier), + BackupNotPossible(PackageIdentifier), + RevertFailed(PackageIdentifier), +} + +pub enum Action { + Uninstall(Package), + Revert(PackageIdentifier), + Disable(PackageIdentifier), +} fn main() -> eframe::Result { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). @@ -25,58 +79,18 @@ fn main() -> eframe::Result { ..Default::default() }; eframe::run_native( - "My egui App", + "Zilch", options, Box::new(|cc| { let (package_diff_tx, package_diff_rx) = channel(); let (device_lost_tx, device_lost_rx) = channel(); + let (action_tx, action_rx) = channel(); let ctx = cc.egui_ctx.clone(); - std::thread::spawn(move || { - let mut device: Option = None; - let mut pkg_set: BTreeSet = Default::default(); - - loop { - while device.is_none() { - device = ADBUSBDevice::autodetect().ok(); - - sleep(WORKER_THREAD_POLL); - } - - if let Some(device_mut) = device.as_mut() { - let mut label_extractor_dex_stream = BufReader::new(&LABEL_EXTRACTOR[..]); - let remote_path = "/data/local/tmp/extractor.dex"; - device_mut - .push(&mut label_extractor_dex_stream, &remote_path) - .expect("failed to upload extractor to the device"); - } - - while let Some(device_mut) = device.as_mut() { - match fetch_packages(device_mut, &pkg_set) { - Ok((diff, new_pkg_set)) => { - if diff.added.is_empty() && diff.removed.is_empty() { - sleep(WORKER_THREAD_POLL); - continue; - } - pkg_set = new_pkg_set; - package_diff_tx.send(diff).expect("failed to send to ui"); - } - Err(FetchPackageError::Timeout) => {} - Err(_log_this_later) => { - device = None; - pkg_set = BTreeSet::default(); - device_lost_tx.send(()).expect("failed to send to ui"); - } - } - ctx.request_repaint(); - - sleep(WORKER_THREAD_POLL); - } - } - }); + spawn(move || worker_thread(package_diff_tx, device_lost_tx, action_rx, ctx)); Ok(Box::new(App { - n_selected: 0, + // n_selected: 0, disable_mode: false, have_device: false, uninstallable: false, @@ -85,39 +99,120 @@ fn main() -> eframe::Result { device_lost_rx, package_diff_rx, entries: Default::default(), + action_tx, })) }), ) } -type FrontendPayload = PackageDiff; +impl Action { + fn apply_on_device(self, device: &mut ADBUSBDevice) -> Result<(), ShellRunError> { + match self { + Action::Uninstall(pkg) => { + if pkg.path.is_empty() { + return Err(ShellRunError::BackupNotPossible(pkg.id)); + } -struct App { - search_query: String, - uninstallable: bool, - reinstallable: bool, - n_selected: usize, - entries: BTreeMap, - package_diff_rx: Receiver, - device_lost_rx: Receiver<()>, - disable_mode: bool, + let _copy_command_no_output = device.shell_command_text(&format!( + "cp {} /data/local/tmp/{}.apk", + pkg.path, pkg.id + ))?; - have_device: bool, + let output = + device.shell_command_text(&format!("pm uninstall --user 0 -k {}", pkg.id))?; + + if !output.contains("Success") { + return Err(ShellRunError::UnsuccessfulOperation(pkg.id)); + } + } + Action::Revert(id) => { + let revert_command = format!("package install-existing {id}"); + let output = device.shell_command_text(&revert_command)?; + // eprintln!("revert output {output:?}"); + + if !output.contains("inaccessible or not found") { + return Ok(()); + } + + let revert_command = format!("pm install -r --user 0 /data/local/tmp/{id}.apk"); + let output = device.shell_command_text(&revert_command)?; + if !output.contains("Success") { + return Err(ShellRunError::RevertFailed(id)); + } + } + Action::Disable(id) => { + let disable_command = format!("pm disable --user 0 {id}"); + let output = device.shell_command_text(&disable_command)?; + eprintln!("disable output {output:?}"); + + // for id in items { + // let output = + // device.shell_command_text(&format!("pm uninstall --user 0 -k {id}"))?; + + // if !output.contains("Success") { + // return Err(ShellRunError::UnsuccessfulOperation(id)); + // } + // } + } + } + Ok(()) + } } -#[derive(Clone)] -struct Package { - id: String, - path: String, - label: String, -} +fn worker_thread( + package_diff_tx: Sender, + device_lost_tx: Sender<()>, + action_rx: Receiver, + ctx: egui::Context, +) { + let mut maybe_device: Option = None; + let mut pkg_set: BTreeSet = Default::default(); -struct Entry { - id: String, - label: String, - expand_triggered: bool, - enabled: bool, - selected: bool, + loop { + while maybe_device.is_none() { + maybe_device = ADBUSBDevice::autodetect().ok(); + if maybe_device.is_none() { + sleep(WORKER_THREAD_POLL); + } else { + eprintln!("watermelon"); + } + } + + if let Some(device) = maybe_device.as_mut() { + eprintln!("cocomelon"); + let mut label_extractor_dex_stream = BufReader::new(&LABEL_EXTRACTOR[..]); + let remote_path = "/data/local/tmp/extractor.dex"; + device + .push(&mut label_extractor_dex_stream, &remote_path) + .expect("failed to upload extractor to the device"); + eprintln!("pushed"); + } + + while let Some(device) = maybe_device.as_mut() { + if let Ok(action) = action_rx.try_recv() { + action.apply_on_device(device); + } + match fetch_packages(device, &pkg_set) { + Ok((diff, new_pkg_set)) => { + if diff.added.is_empty() && diff.removed.is_empty() { + sleep(WORKER_THREAD_POLL); + continue; + } + pkg_set = new_pkg_set; + package_diff_tx.send(diff).expect("failed to send to ui"); + } + Err(ShellRunError::Timeout) => {} + Err(_log_this_later) => { + maybe_device = None; + pkg_set = BTreeSet::default(); + device_lost_tx.send(()).expect("failed to send to ui"); + } + } + ctx.request_repaint(); + + sleep(WORKER_THREAD_POLL); + } + } } impl eframe::App for App { @@ -130,8 +225,7 @@ impl eframe::App for App { self.entries.insert( package.id.clone(), Entry { - id: package.id, - label: package.label, + package, expand_triggered: false, enabled: true, selected: false, @@ -177,19 +271,13 @@ impl eframe::App for App { egui::ScrollArea::vertical().show(ui, |ui| { self.uninstallable = false; self.reinstallable = false; - self.n_selected = 0; - for (_id, entry) in self.entries.iter_mut() { - render_entry(ui, entry); - if !entry.selected { - continue; - } - - self.n_selected += 1; - if entry.enabled { - self.uninstallable = true; - } else { - self.reinstallable = true; + for (id, entry) in self.entries.iter_mut() { + let query_lower = self.search_query.to_lowercase(); + if id.to_lowercase().contains(&query_lower) + || entry.package.label.to_lowercase().contains(&query_lower) + { + render_entry(ui, entry); } } }); @@ -209,20 +297,62 @@ impl eframe::App for App { Button::new("uninstall") }; + let mut selected: Vec<&Entry> = vec![]; + for entry in self.entries.values().filter(|entry| entry.selected) { + selected.push(entry); + if entry.enabled { + self.uninstallable = true; + } else { + self.reinstallable = true; + } + } + ui.horizontal(|ui| { + let button_size = [80.0, 30.0]; + if self.uninstallable == self.reinstallable { - ui.add_enabled(false, button); + ui.add_enabled_ui(false, |ui| { + ui.add_sized(button_size, button); + }); } else if self.uninstallable { - ui.add_enabled(true, button); + ui.add_enabled_ui(true, |ui| { + if !ui.add_sized(button_size, button).clicked() { + return; + } + + if self.disable_mode { + for entry in selected.iter() { + self.action_tx + .send(Action::Disable(entry.package.id.clone())) + .expect("failed to send message to backend"); + } + + return; + } + + for entry in selected.iter() { + self.action_tx + .send(Action::Uninstall(entry.package.clone())) + .expect("failed to send message to backend"); + } + }); } else if self.reinstallable { - ui.add_enabled(true, Button::new("revert")); + ui.add_enabled_ui(true, |ui| { + if ui.add_sized(button_size, Button::new("revert")).clicked() { + for entry in selected.iter() { + self.action_tx + .send(Action::Revert(entry.package.id.clone())) + .expect("failed to send message to backend"); + } + } + }); } ui.checkbox(&mut self.disable_mode, "disable mode") .on_hover_text("prefer disabling apps to uninstalling"); ui.separator(); - ui.label(format!("{} selected", self.n_selected)); + ui.label(format!("{} selected", selected.len())); ui.separator(); }); ui.add_space(2.0); @@ -230,47 +360,28 @@ impl eframe::App for App { } } -const LABEL_EXTRACTOR: &[u8; 2124] = include_bytes!("./extractor.dex"); - -struct PackageDiff { - added: Vec, - removed: Vec, -} - -pub enum FetchPackageError { - Timeout, - ParseError, - Unrecoverable, -} - pub trait ShellCommandExt { - fn shell_command_ext( - &mut self, - command: &str, - buf: &mut dyn Write, - ) -> Result<(), FetchPackageError>; + fn shell_command_text(&mut self, command: &str) -> Result; } impl ShellCommandExt for ADBUSBDevice { - fn shell_command_ext( - &mut self, - command: &str, - buf: &mut dyn Write, - ) -> Result<(), FetchPackageError> { - self.shell_command(&[command], buf).map_err(|e| match e { - adb_client::RustADBError::UsbError(rusb::Error::Timeout) => FetchPackageError::Timeout, - _ => FetchPackageError::Unrecoverable, - }) + fn shell_command_text(&mut self, command: &str) -> Result { + let mut buf = Vec::with_capacity(4096); + self.shell_command(&[command], &mut buf) + .map_err(|e| match e { + adb_client::RustADBError::UsbError(rusb::Error::Timeout) => ShellRunError::Timeout, + _ => ShellRunError::Unrecoverable, + })?; + String::from_utf8(buf).map_err(|_| ShellRunError::ParseError) } } fn fetch_packages( device: &mut ADBUSBDevice, pkg_set: &BTreeSet, -) -> Result<(PackageDiff, BTreeSet), FetchPackageError> { - let mut buffer = Vec::with_capacity(1024); - device.shell_command_ext("pm list packages -f", &mut buffer)?; - let raw_pkg_text = std::str::from_utf8(&buffer).map_err(|_| FetchPackageError::ParseError)?; +) -> Result<(PackageDiff, BTreeSet), ShellRunError> { + let raw_pkg_text = device.shell_command_text("pm list packages -f")?; + eprintln!("fucky fucky?"); let mut current_set = BTreeSet::new(); @@ -295,13 +406,9 @@ fn fetch_packages( let need_to_fetch_labels = !new_packages.is_empty(); if need_to_fetch_labels { - let mut buffer = Vec::with_capacity(1024); - device.shell_command_ext( - "CLASSPATH=/data/local/tmp/extractor.dex app_process / Main", - &mut buffer, - )?; - let raw_pkg_text = - std::str::from_utf8(&buffer).map_err(|_| FetchPackageError::ParseError)?; + let raw_pkg_text = device + .shell_command_text("CLASSPATH=/data/local/tmp/extractor.dex app_process / Main")?; + eprintln!("sucky sucky?"); for line in raw_pkg_text.lines() { let mut splitn = line.splitn(3, ' '); @@ -313,7 +420,6 @@ fn fetch_packages( if let Some(package_mut) = new_packages.get_mut(id) { package_mut.label = label.to_string(); } - // eprintln!("{line}"); } } @@ -328,8 +434,8 @@ fn fetch_packages( fn create_button(entry: &'_ Entry) -> Button<'_> { let mut job = LayoutJob::default(); - let mut label = RichText::new(format!("{}\n", entry.label)).size(12.0); - let mut package_id = RichText::new(&entry.id).monospace().size(10.0); + let mut label = RichText::new(format!("{}\n", entry.package.label)).size(12.0); + let mut package_id = RichText::new(&entry.package.id).monospace().size(10.0); let disabled_text_color = Color32::from_rgb(100, 100, 100); if !entry.enabled { @@ -358,7 +464,7 @@ fn create_button(entry: &'_ Entry) -> Button<'_> { } fn render_entry(ui: &mut egui::Ui, entry: &mut Entry) { - let id = ui.make_persistent_id(format!("{}_state", entry.id)); + let id = ui.make_persistent_id(format!("{}_state", entry.package.id)); let mut state = egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, false); @@ -371,7 +477,7 @@ fn render_entry(ui: &mut egui::Ui, entry: &mut Entry) { ui.style_mut().spacing.button_padding = egui::vec2(20.0, 10.0); ui.with_layout(Layout::top_down_justified(egui::Align::LEFT), |ui| { let response = ui.add(create_button(entry)); - let id = ui.make_persistent_id(format!("{}_interact", entry.id)); + let id = ui.make_persistent_id(format!("{}_interact", entry.package.id)); if ui .interact(response.rect, id, Sense::click()) .double_clicked()