Skip to content

Commit b3ccb64

Browse files
authored
Merge pull request #401 from FreddyFunk/feature/purge-app-data
add option to purge app data for flatpaks during uninstall
2 parents 0cdc1fe + 8d17e9c commit b3ccb64

File tree

5 files changed

+108
-22
lines changed

5 files changed

+108
-22
lines changed

i18n/en/cosmic_store.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ removing = Removing...
5656
# Uninstall Dialog
5757
uninstall-app = Uninstall {$name}?
5858
uninstall-app-warning = Uninstalling {$name} will delete its data.
59+
uninstall-app-flatpak-warning = Uninstalling {$name} will keep its documents and data.
60+
delete-app-data = Permanently delete app data
5961
6062
# Nav Pages
6163
explore = Explore

src/backend/flatpak.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,9 @@ impl Backend for Flatpak {
426426
}
427427
}
428428
}
429-
OperationKind::Uninstall => {
429+
OperationKind::Uninstall { purge_data } => {
430430
//TODO: deduplicate code
431+
let mut app_ids_to_purge = Vec::new();
431432
for info in op.infos.iter() {
432433
for r_str in info.flatpak_refs.iter() {
433434
let r = match Ref::parse(r_str) {
@@ -451,10 +452,45 @@ impl Backend for Flatpak {
451452
}
452453
};
453454

454-
log::info!("uninstalling flatpak {}", r_str);
455+
log::info!("uninstalling flatpak {} (purge_data: {})", r_str, purge_data);
455456
tx.add_uninstall(r_str)?;
457+
458+
// If purge_data is requested, collect app IDs for later deletion
459+
if *purge_data {
460+
if let Some(app_id) = r.name() {
461+
app_ids_to_purge.push(app_id.to_string());
462+
}
463+
}
456464
}
457465
}
466+
467+
tx.run(Cancellable::NONE)?;
468+
469+
// After successful uninstall, delete user data if requested
470+
if *purge_data {
471+
for app_id in app_ids_to_purge {
472+
// User data is always stored in ~/.var/app/<app-id> regardless of installation type
473+
if let Ok(home) = std::env::var("HOME") {
474+
let data_path = std::path::PathBuf::from(home)
475+
.join(".var")
476+
.join("app")
477+
.join(&app_id);
478+
479+
if data_path.exists() {
480+
log::info!("Purging user data for {}: {:?}", app_id, data_path);
481+
if let Err(err) = std::fs::remove_dir_all(&data_path) {
482+
log::warn!("Failed to remove user data for {}: {}", app_id, err);
483+
} else {
484+
log::info!("Successfully removed user data for {}", app_id);
485+
}
486+
} else {
487+
log::info!("No user data found for {} at {:?}", app_id, data_path);
488+
}
489+
}
490+
}
491+
}
492+
493+
return Ok(());
458494
}
459495
OperationKind::Update => {
460496
//TODO: deduplicate code

src/backend/packagekit.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ impl Backend for Packagekit {
391391
| FilterKind::Newest as u64
392392
| FilterKind::Arch as u64
393393
}
394-
OperationKind::Uninstall => FilterKind::Installed as u64,
394+
OperationKind::Uninstall { .. } => FilterKind::Installed as u64,
395395
// Other operations not supported
396396
_ => 0,
397397
};
@@ -416,9 +416,17 @@ impl Backend for Packagekit {
416416
tx.install_packages(TransactionFlag::OnlyTrusted as u64, &package_ids)?;
417417
}
418418
}
419-
OperationKind::Uninstall => {
420-
log::info!("uninstalling packages {:?}", package_ids);
419+
OperationKind::Uninstall { purge_data } => {
420+
log::info!("uninstalling packages {:?} (purge_data: {})", package_ids, purge_data);
421+
if *purge_data {
422+
log::warn!(
423+
"PackageKit backend does not fully support purging configuration files. \
424+
Only the package will be removed. Configuration files may remain in user directories."
425+
);
426+
}
421427
//TODO: transaction flags?
428+
//TODO: investigate if we can detect package managers like dnf, apt, etc
429+
// and use purge-specific functionality
422430
tx.remove_packages(0, &package_ids, true, true)?;
423431
}
424432
OperationKind::Update => {

src/main.rs

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use cosmic::{
1212
core::SmolStr,
1313
event::{self, Event},
1414
futures::{self, SinkExt},
15-
keyboard::{Event as KeyEvent, Key, Modifiers},
15+
keyboard::{Event as KeyEvent, Key, Modifiers, key},
1616
stream,
1717
widget::scrollable,
1818
window::{self, Event as WindowEvent},
@@ -312,6 +312,7 @@ pub enum Message {
312312
SelectedAddonsViewMore(bool),
313313
SelectedScreenshot(usize, String, Vec<u8>),
314314
SelectedScreenshotShown(usize),
315+
ToggleUninstallPurgeData(bool),
315316
SelectedSource(usize),
316317
SystemThemeModeChange(cosmic_theme::ThemeMode),
317318
ToggleContextPage(ContextPage),
@@ -824,6 +825,7 @@ pub struct App {
824825
search_results: Option<(String, Vec<SearchResult>)>,
825826
selected_opt: Option<Selected>,
826827
applet_placement_buttons: cosmic::widget::segmented_button::SingleSelectModel,
828+
uninstall_purge_data: bool,
827829
}
828830

829831
impl App {
@@ -3041,6 +3043,7 @@ impl Application for App {
30413043
search_results: None,
30423044
selected_opt: None,
30433045
applet_placement_buttons,
3046+
uninstall_purge_data: false,
30443047
};
30453048

30463049
if let Some(subcommand) = flags.subcommand_opt {
@@ -3205,6 +3208,7 @@ impl Application for App {
32053208
}
32063209
Message::DialogCancel => {
32073210
self.dialog_pages.pop_front();
3211+
self.uninstall_purge_data = false;
32083212
}
32093213
Message::DialogConfirm => match self.dialog_pages.pop_front() {
32103214
Some(DialogPage::RepositoryRemove(backend_name, repo_rm)) => {
@@ -3216,8 +3220,10 @@ impl Application for App {
32163220
});
32173221
}
32183222
Some(DialogPage::Uninstall(backend_name, id, info)) => {
3223+
let purge_data = self.uninstall_purge_data;
3224+
self.uninstall_purge_data = false;
32193225
return self.update(Message::Operation(
3220-
OperationKind::Uninstall,
3226+
OperationKind::Uninstall { purge_data },
32213227
backend_name,
32223228
id,
32233229
info,
@@ -3260,7 +3266,7 @@ impl Application for App {
32603266
);
32613267
if installed != selected.contains(&i) {
32623268
let kind = if installed {
3263-
OperationKind::Uninstall
3269+
OperationKind::Uninstall { purge_data: false }
32643270
} else {
32653271
OperationKind::Install
32663272
};
@@ -3325,6 +3331,17 @@ impl Application for App {
33253331
self.installed_results = Some(installed_results);
33263332
}
33273333
Message::Key(modifiers, key, text) => {
3334+
// Handle ESC key to close dialogs
3335+
if !self.dialog_pages.is_empty()
3336+
&& matches!(key, Key::Named(key::Named::Escape))
3337+
&& !modifiers.logo()
3338+
&& !modifiers.control()
3339+
&& !modifiers.alt()
3340+
&& !modifiers.shift()
3341+
{
3342+
return self.update(Message::DialogCancel);
3343+
}
3344+
33283345
for (key_bind, action) in self.key_binds.iter() {
33293346
if key_bind.matches(modifiers, &key) {
33303347
return self.update(action.message());
@@ -3733,6 +3750,9 @@ impl Application for App {
37333750
selected.screenshot_shown = i;
37343751
}
37353752
}
3753+
Message::ToggleUninstallPurgeData(value) => {
3754+
self.uninstall_purge_data = value;
3755+
}
37363756
Message::SelectedSource(i) => {
37373757
//TODO: show warnings if anything is not found?
37383758
let mut next_ids = None;
@@ -4041,16 +4061,36 @@ impl Application for App {
40414061
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
40424062
)
40434063
}
4044-
DialogPage::Uninstall(_backend_name, _id, info) => widget::dialog()
4045-
.title(fl!("uninstall-app", name = info.name.as_str()))
4046-
.body(fl!("uninstall-app-warning", name = info.name.as_str()))
4047-
.icon(widget::icon::from_name(Self::APP_ID).size(64))
4048-
.primary_action(
4049-
widget::button::destructive(fl!("uninstall")).on_press(Message::DialogConfirm),
4050-
)
4051-
.secondary_action(
4052-
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
4053-
),
4064+
DialogPage::Uninstall(backend_name, _id, info) => {
4065+
let is_flatpak = backend_name.starts_with("flatpak");
4066+
let mut dialog = widget::dialog()
4067+
.title(fl!("uninstall-app", name = info.name.as_str()))
4068+
.body(if is_flatpak {
4069+
fl!("uninstall-app-flatpak-warning", name = info.name.as_str())
4070+
} else {
4071+
fl!("uninstall-app-warning", name = info.name.as_str())
4072+
})
4073+
.icon(widget::icon::from_name(Self::APP_ID).size(64));
4074+
4075+
// Only show data deletion option for Flatpak apps
4076+
if is_flatpak {
4077+
dialog = dialog.control(
4078+
widget::checkbox(
4079+
fl!("delete-app-data"),
4080+
self.uninstall_purge_data,
4081+
)
4082+
.on_toggle(Message::ToggleUninstallPurgeData)
4083+
);
4084+
}
4085+
4086+
dialog
4087+
.primary_action(
4088+
widget::button::destructive(fl!("uninstall")).on_press(Message::DialogConfirm),
4089+
)
4090+
.secondary_action(
4091+
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
4092+
)
4093+
}
40544094
DialogPage::Place(id) => widget::dialog()
40554095
.title(fl!("place-applet"))
40564096
.body(fl!("place-applet-desc"))

src/operation.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{AppId, AppInfo};
55
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
66
pub enum OperationKind {
77
Install,
8-
Uninstall,
8+
Uninstall { purge_data: bool },
99
Update,
1010
RepositoryAdd(Vec<RepositoryAdd>),
1111
RepositoryRemove(Vec<RepositoryRemove>, bool),
@@ -72,7 +72,7 @@ impl Operation {
7272
//TODO: translate
7373
let verb = match &self.kind {
7474
OperationKind::Install => "Installing",
75-
OperationKind::Uninstall => "Uninstalling",
75+
OperationKind::Uninstall { .. } => "Uninstalling",
7676
OperationKind::Update => "Updating",
7777
OperationKind::RepositoryAdd(adds) => {
7878
return format!(
@@ -99,7 +99,7 @@ impl Operation {
9999
//TODO: translate
100100
let verb = match &self.kind {
101101
OperationKind::Install => "Installed",
102-
OperationKind::Uninstall => "Uninstalled",
102+
OperationKind::Uninstall { .. } => "Uninstalled",
103103
OperationKind::Update => "Updated",
104104
OperationKind::RepositoryAdd(adds) => {
105105
return format!("Added repositories {:?}", RepositoryAdd::ids(adds));
@@ -118,7 +118,7 @@ impl Operation {
118118
//TODO: translate
119119
let verb = match &self.kind {
120120
OperationKind::Install => "install",
121-
OperationKind::Uninstall => "uninstall",
121+
OperationKind::Uninstall { .. } => "uninstall",
122122
OperationKind::Update => "update",
123123
OperationKind::RepositoryAdd(adds) => {
124124
return (

0 commit comments

Comments
 (0)