Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backup): add ability to export uninstalled packages with their description #527

Merged
merged 2 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/core/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl Default for GeneralSettings {
impl Default for DeviceSettings {
fn default() -> Self {
Self {
device_id: String::new(),
device_id: String::default(),
multi_user_mode: get_android_sdk() > 21,
disable_mode: false,
backup: BackupSettings::default(),
Expand Down
12 changes: 6 additions & 6 deletions src/core/save.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ use std::path::{Path, PathBuf};
pub static BACKUP_DIR: PathBuf = CACHE_DIR.join("backups");

#[derive(Default, Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
struct PhoneBackup {
device_id: String,
users: Vec<UserBackup>,
pub struct PhoneBackup {
pub device_id: String,
pub users: Vec<UserBackup>,
}

#[derive(Default, Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
struct UserBackup {
id: u16,
packages: Vec<CorePackage>,
pub struct UserBackup {
pub id: u16,
pub packages: Vec<CorePackage>,
}

// Backup all `Uninstalled` and `Disabled` packages
Rudxain marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
4 changes: 2 additions & 2 deletions src/core/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl Default for Phone {
model: "fetching devices...".to_string(),
android_sdk: 0,
user_list: vec![],
adb_id: String::new(),
adb_id: String::default(),
}
}
}
Expand Down Expand Up @@ -165,7 +165,7 @@ pub fn hashset_system_packages(state: PackageState, user_id: Option<&User>) -> H
let action = match state {
PackageState::Enabled => format!("pm list packages -s -e{user}"),
PackageState::Disabled => format!("pm list package -s -d{user}"),
_ => String::new(), // You probably don't need to use this function for anything else
_ => String::default(), // You probably don't need to use this function for anything else
};

adb_shell_command(true, &action)
Expand Down
43 changes: 43 additions & 0 deletions src/core/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::{fmt, fs};
/// track of the current device serial.
pub const ANDROID_SERIAL: &str = "ANDROID_SERIAL";
pub const EXPORT_FILE_NAME: &str = "selection_export.txt";
pub const UNINSTALLED_PACKAGES_FILE_NAME: &str = "uninstalled_packages";

#[derive(Debug, Clone)]
pub enum Error {
Expand Down Expand Up @@ -115,6 +116,8 @@ pub fn format_diff_time_from_now(date: DateTime<Utc>) -> String {
}
}

/// Export selected packages.
/// File will be saved in same directory where UAD-ng is located.
pub async fn export_selection(packages: Vec<PackageRow>) -> Result<bool, String> {
let selected = packages
.iter()
Expand Down Expand Up @@ -154,6 +157,7 @@ impl fmt::Display for DisplayablePath {
}
}

/// Can be used to choose any folder.
pub async fn open_folder() -> Result<PathBuf, Error> {
let picked_folder = rfd::AsyncFileDialog::new()
.pick_folder()
Expand All @@ -162,3 +166,42 @@ pub async fn open_folder() -> Result<PathBuf, Error> {

Ok(picked_folder.path().to_owned())
}

/// Export uninstalled packages in a file.
/// Exported information will contain package name and description.
pub async fn export_packages(
user: Option<User>,
device_id: String,
phone_packages: Vec<Vec<PackageRow>>,
) -> Result<bool, String> {
let uninstalled_packages: Vec<String> = phone_packages[user.unwrap().index]
.iter()
.filter(|p| p.state.to_string() == "Uninstalled")
.map(|p| {
format!(
"{}Name: {}\nDescription: {}",
"-------------------------------------------------------------------\n",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we end up sticking with txt, consider making this separator string a const somewhere up above, so that we can reuse it elsewhere.

p.name,
p.description.replace('\n', " ")
)
})
.collect();

let backup_content = format!(
"Device ID: {}\nUser ID: {}\n-------------------------------------------------------------------\n{}",
device_id,
user.unwrap().id,
uninstalled_packages.join("\n")
);

let backup_file = format!(
"{}_{}.txt",
UNINSTALLED_PACKAGES_FILE_NAME,
chrono::Local::now().format("%Y%m%d")
);

match fs::write(backup_file, backup_content) {
Ok(_) => Ok(true),
Err(err) => Err(err.to_string()),
}
}
3 changes: 2 additions & 1 deletion src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ impl Application for UadGui {
&self.apps_view.phone_packages,
&mut self.nb_running_async_adb_commands,
msg,
&self.apps_view.selected_user,
)
.map(Message::SettingsAction)
}
Expand Down Expand Up @@ -377,7 +378,7 @@ impl Application for UadGui {
.map(Message::AboutAction),
View::Settings => self
.settings_view
.view(&selected_device)
.view(&selected_device, &self.apps_view)
.map(Message::SettingsAction),
};

Expand Down
2 changes: 1 addition & 1 deletion src/gui/views/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub struct List {
selected_package_state: Option<PackageState>,
selected_removal: Option<Removal>,
selected_list: Option<UadList>,
selected_user: Option<User>,
pub selected_user: Option<User>,
all_selected: bool,
pub input_value: String,
description: String,
Expand Down
109 changes: 99 additions & 10 deletions src/gui/views/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ use crate::core::config::{BackupSettings, Config, DeviceSettings, GeneralSetting
use crate::core::save::{
backup_phone, list_available_backup_user, list_available_backups, restore_backup,
};
use crate::core::sync::{get_android_sdk, perform_adb_commands, CommandType, Phone};
use crate::core::sync::{get_android_sdk, perform_adb_commands, CommandType, Phone, User};
use crate::core::theme::Theme;
use crate::core::utils::{open_folder, open_url, string_to_theme, DisplayablePath};
use crate::core::utils::{
export_packages, open_folder, open_url, string_to_theme, DisplayablePath,
UNINSTALLED_PACKAGES_FILE_NAME,
};
use crate::gui::style;
use crate::gui::views::list::PackageInfo;
use crate::gui::views::list::{List as AppsView, PackageInfo};
use crate::gui::widgets::modal::Modal;
use crate::gui::widgets::navigation_menu::ICONS;
use crate::gui::widgets::package_row::PackageRow;

Expand All @@ -21,11 +25,17 @@ use std::path::PathBuf;

use crate::core::utils::Error;

#[derive(Debug, Clone)]
pub enum PopUpModal {
ExportUninstalled,
}

#[derive(Debug, Clone)]
pub struct Settings {
pub general: GeneralSettings,
pub device: DeviceSettings,
is_loading: bool,
modal: Option<PopUpModal>,
}

impl Default for Settings {
Expand All @@ -34,6 +44,7 @@ impl Default for Settings {
general: Config::load_configuration_file().general,
device: DeviceSettings::default(),
is_loading: false,
modal: None,
}
}
}
Expand All @@ -53,6 +64,9 @@ pub enum Message {
DeviceBackedUp(Result<(), String>),
ChooseBackUpFolder,
FolderChosen(Result<PathBuf, Error>),
ExportPackages,
PackagesExported(Result<bool, String>),
ModalHide,
}

impl Settings {
Expand All @@ -62,8 +76,13 @@ impl Settings {
packages: &[Vec<PackageRow>],
nb_running_async_adb_commands: &mut u32,
msg: Message,
selected_user: &Option<User>,
) -> Command<Message> {
match msg {
Message::ModalHide => {
self.modal = None;
Command::none()
}
Message::ExpertMode(toggled) => {
self.general.expert_mode = toggled;
debug!("Config change: {:?}", self);
Expand Down Expand Up @@ -102,7 +121,7 @@ impl Settings {
selected: backups.first().cloned(),
users: phone.user_list.clone(),
selected_user: phone.user_list.first().copied(),
backup_state: String::new(),
backup_state: String::default(),
};
match Config::load_configuration_file()
.devices
Expand Down Expand Up @@ -200,6 +219,7 @@ impl Settings {
packages,
nb_running_async_adb_commands,
Message::LoadDeviceSettings,
selected_user,
);
}
}
Expand All @@ -213,10 +233,25 @@ impl Settings {
Command::perform(open_folder(), Message::FolderChosen)
}
}
Message::ExportPackages => Command::perform(
export_packages(
*selected_user,
self.device.device_id.clone(),
packages.to_vec(),
),
Message::PackagesExported,
),
Message::PackagesExported(exported) => {
match exported {
Ok(_) => self.modal = Some(PopUpModal::ExportUninstalled),
Err(err) => error!("Failed to export list of uninstalled packages: {:?}", err),
}
Command::none()
}
}
}

pub fn view(&self, phone: &Phone) -> Element<Message, Theme, Renderer> {
pub fn view(&self, phone: &Phone, apps_view: &AppsView) -> Element<Message, Theme, Renderer> {
let radio_btn_theme = Theme::ALL
.iter()
.fold(row![].spacing(10), |column, option| {
Expand Down Expand Up @@ -408,6 +443,8 @@ impl Settings {
))
};

let export_btn = button_primary("Export").on_press(Message::ExportPackages);

let backup_row = row![
backup_btn,
"Backup the current state of the phone",
Expand All @@ -431,11 +468,24 @@ impl Settings {
row![]
};

let backup_restore_ctn = container(column![backup_row, restore_row].spacing(10))
.padding(10)
.width(Length::Fill)
.height(Length::Shrink)
.style(style::Container::Frame);
let export_row = row![
export_btn,
"Export uninstalled packages with their description",
Space::new(Length::Fill, Length::Shrink),
text(format!(
"Selected: user {}",
apps_view.selected_user.unwrap().id
)),
]
.spacing(10)
.align_items(Alignment::Center);

let backup_restore_ctn =
container(column![backup_row, restore_row, export_row].spacing(10))
.padding(10)
.width(Length::Fill)
.height(Length::Shrink)
.style(style::Container::Frame);

let no_device_ctn = || {
container(text("No device detected").style(style::Text::Danger))
Expand Down Expand Up @@ -473,6 +523,45 @@ impl Settings {
.spacing(20)
};

if let Some(PopUpModal::ExportUninstalled) = self.modal {
let title = container(row![text("Success").size(24)].align_items(Alignment::Center))
.width(Length::Fill)
.style(style::Container::Frame)
.padding([10, 0, 10, 0])
.center_y()
.center_x();

let text_box = row![
text("Exported uninstalled packages into file.\nFile is exported in same directory where UAD-ng is located.").width(Length::Fill),
].padding(20);

let file_row = row![text(format!(
"{}_{}.txt",
UNINSTALLED_PACKAGES_FILE_NAME,
chrono::Local::now().format("%Y%m%d")
))
.style(style::Text::Commentary)]
.padding(20);

let modal_btn_row = row![
Space::new(Length::Fill, Length::Shrink),
button(text("Close").width(Length::Shrink))
.width(Length::Shrink)
.on_press(Message::ModalHide),
Space::new(Length::Fill, Length::Shrink),
];

let ctn = container(column![title, text_box, file_row, modal_btn_row])
.height(Length::Shrink)
.width(500)
.padding(10)
.style(style::Container::Frame);

return Modal::new(content.padding(10), ctn)
.on_blur(Message::ModalHide)
.into();
}

container(scrollable(content))
.padding(10)
.width(Length::Fill)
Expand Down
6 changes: 4 additions & 2 deletions src/gui/widgets/modal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,11 @@ where
) -> event::Status {
let content_bounds = layout.children().next().unwrap().bounds();

#[allow(clippy::equatable_if_let)]
if let Some(message) = self.on_blur.as_ref() {
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = &event {
if matches!(
event,
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
) {
if let Some(cursor_position) = cursor.position() {
if !content_bounds.contains(cursor_position) {
shell.publish(message.clone());
Expand Down
Loading