Skip to content

Commit 124e966

Browse files
committed
add option to purge app data for flatpaks during uninstall
1 parent 37b143a commit 124e966

File tree

5 files changed

+146
-22
lines changed

5 files changed

+146
-22
lines changed

i18n/en/cosmic_store.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ 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 = This will remove the app from your system.
60+
app-settings-data = App settings & data
61+
keep-app-data = Keep
62+
keep-app-data-description = App data and settings will remain on your system.
63+
delete-app-data = Delete
64+
delete-app-data-description = All app data, settings, and cache will be permanently removed.
5965
6066
# Nav Pages
6167
explore = Explore

src/backend/flatpak.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,8 +433,9 @@ impl Backend for Flatpak {
433433
}
434434
}
435435
}
436-
OperationKind::Uninstall => {
436+
OperationKind::Uninstall { purge_data } => {
437437
//TODO: deduplicate code
438+
let mut app_ids_to_purge = Vec::new();
438439
for info in op.infos.iter() {
439440
for r_str in info.flatpak_refs.iter() {
440441
let r = match Ref::parse(r_str) {
@@ -458,10 +459,45 @@ impl Backend for Flatpak {
458459
}
459460
};
460461

461-
log::info!("uninstalling flatpak {}", r_str);
462+
log::info!("uninstalling flatpak {} (purge_data: {})", r_str, purge_data);
462463
tx.add_uninstall(r_str)?;
464+
465+
// If purge_data is requested, collect app IDs for later deletion
466+
if *purge_data {
467+
if let Some(app_id) = r.name() {
468+
app_ids_to_purge.push(app_id.to_string());
469+
}
470+
}
463471
}
464472
}
473+
474+
tx.run(Cancellable::NONE)?;
475+
476+
// After successful uninstall, delete user data if requested
477+
if *purge_data {
478+
for app_id in app_ids_to_purge {
479+
// User data is always stored in ~/.var/app/<app-id> regardless of installation type
480+
if let Ok(home) = std::env::var("HOME") {
481+
let data_path = std::path::PathBuf::from(home)
482+
.join(".var")
483+
.join("app")
484+
.join(&app_id);
485+
486+
if data_path.exists() {
487+
log::info!("Purging user data for {}: {:?}", app_id, data_path);
488+
if let Err(err) = std::fs::remove_dir_all(&data_path) {
489+
log::warn!("Failed to remove user data for {}: {}", app_id, err);
490+
} else {
491+
log::info!("Successfully removed user data for {}", app_id);
492+
}
493+
} else {
494+
log::info!("No user data found for {} at {:?}", app_id, data_path);
495+
}
496+
}
497+
}
498+
}
499+
500+
return Ok(());
465501
}
466502
OperationKind::Update => {
467503
//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: 87 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;
@@ -4038,16 +4058,70 @@ impl Application for App {
40384058
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
40394059
)
40404060
}
4041-
DialogPage::Uninstall(_backend_name, _id, info) => widget::dialog()
4042-
.title(fl!("uninstall-app", name = info.name.as_str()))
4043-
.body(fl!("uninstall-app-warning", name = info.name.as_str()))
4044-
.icon(widget::icon::from_name(Self::APP_ID).size(64))
4045-
.primary_action(
4046-
widget::button::destructive(fl!("uninstall")).on_press(Message::DialogConfirm),
4047-
)
4048-
.secondary_action(
4049-
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
4050-
),
4061+
DialogPage::Uninstall(backend_name, _id, info) => {
4062+
let is_flatpak = backend_name.starts_with("flatpak");
4063+
let mut dialog = widget::dialog()
4064+
.title(fl!("uninstall-app", name = info.name.as_str()))
4065+
.body(if is_flatpak {
4066+
fl!("uninstall-app-flatpak-warning")
4067+
} else {
4068+
fl!("uninstall-app-warning", name = info.name.as_str())
4069+
})
4070+
.icon(widget::icon::from_name(Self::APP_ID).size(64));
4071+
4072+
// Only show data options for Flatpak apps
4073+
if is_flatpak {
4074+
dialog = dialog.control(
4075+
widget::column::with_capacity(4)
4076+
.spacing(12)
4077+
.push(widget::text::heading(fl!("app-settings-data")))
4078+
.push(
4079+
widget::column::with_capacity(2)
4080+
.spacing(4)
4081+
.push(
4082+
widget::radio(
4083+
widget::text::body(fl!("keep-app-data")),
4084+
false,
4085+
Some(self.uninstall_purge_data),
4086+
Message::ToggleUninstallPurgeData,
4087+
)
4088+
)
4089+
.push(
4090+
widget::container(
4091+
widget::text::caption(fl!("keep-app-data-description"))
4092+
)
4093+
.padding([0, 0, 0, 24])
4094+
)
4095+
)
4096+
.push(
4097+
widget::column::with_capacity(2)
4098+
.spacing(4)
4099+
.push(
4100+
widget::radio(
4101+
widget::text::body(fl!("delete-app-data")),
4102+
true,
4103+
Some(self.uninstall_purge_data),
4104+
Message::ToggleUninstallPurgeData,
4105+
)
4106+
)
4107+
.push(
4108+
widget::container(
4109+
widget::text::caption(fl!("delete-app-data-description"))
4110+
)
4111+
.padding([0, 0, 0, 24])
4112+
)
4113+
)
4114+
);
4115+
}
4116+
4117+
dialog
4118+
.primary_action(
4119+
widget::button::destructive(fl!("uninstall")).on_press(Message::DialogConfirm),
4120+
)
4121+
.secondary_action(
4122+
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
4123+
)
4124+
}
40514125
DialogPage::Place(id) => widget::dialog()
40524126
.title(fl!("place-applet"))
40534127
.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)