Skip to content

Commit 19974e2

Browse files
feat: allow uninstalling apps
1 parent 1a4d57c commit 19974e2

4 files changed

Lines changed: 52 additions & 21 deletions

File tree

winapps-cli/src/main.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
use std::collections::HashSet;
2+
13
use clap::{Command, arg};
4+
use inquire::MultiSelect;
25
use miette::{IntoDiagnostic, Result};
36
use tracing::{Level, info};
47
use tracing_subscriber::EnvFilter;
5-
use winapps::{Config, Freerdp, RemoteClient};
8+
use winapps::{Config, Freerdp, RemoteClient, config::App};
69

710
fn cli() -> Command {
811
Command::new("winapps-cli")
@@ -45,16 +48,34 @@ fn main() -> Result<()> {
4548
Some(("setup", _)) => {
4649
info!("Running setup");
4750

48-
// TODO: Allow deleting apps, maybe pass installed apps
49-
// so they can be deselected?
50-
match inquire::MultiSelect::new("Select apps to link", config.get_available_apps()?)
51+
let apps = config.get_available_apps()?;
52+
let installed: Vec<usize> = apps
53+
.iter()
54+
.enumerate()
55+
.filter_map(|(i, app)| config.linked_apps.contains_key(&app.id).then_some(i))
56+
.collect();
57+
58+
match MultiSelect::new("Select apps to link", apps)
59+
.with_default(installed.as_slice())
5160
.prompt_skippable()
5261
.map_err(|e| winapps::Error::Command {
5362
message: "Failed to display selection dialog".into(),
5463
source: e.into(),
5564
})? {
56-
Some(apps) => apps.into_iter().try_for_each(|app| app.link(&mut config))?,
57-
None => info!("No apps selected, skipping setup..."),
65+
Some(apps) => {
66+
let selected: HashSet<App> = apps.into_iter().collect();
67+
let installed: HashSet<App> = config.linked_apps.values().cloned().collect();
68+
69+
for app in selected.symmetric_difference(&installed).cloned() {
70+
match (selected.contains(&app), installed.contains(&app)) {
71+
(true, false) => app.link(&mut config)?,
72+
(false, true) => app.unlink(&mut config)?,
73+
(false, false) => (),
74+
(true, true) => unreachable!(),
75+
}
76+
}
77+
}
78+
None => info!("No apps (de-)selected, skipping setup..."),
5879
};
5980

6081
Ok(())

winapps/src/config/apps.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use crate::{
55
ensure,
66
};
77
use base64::{Engine, prelude::BASE64_STANDARD};
8-
use std::{fmt::Display, fs::write};
8+
use std::{
9+
fmt::Display,
10+
fs::{self, write},
11+
};
912
use tracing::debug;
1013

1114
impl PartialEq for App {
@@ -14,6 +17,8 @@ impl PartialEq for App {
1417
}
1518
}
1619

20+
impl Eq for App {}
21+
1722
impl Display for App {
1823
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1924
f.write_fmt(format_args!("{} ({})", self.name, self.win_exec))
@@ -74,19 +79,23 @@ Comment={} (WinApps)",
7479

7580
write(&path, self.try_as_desktop_file()?)?;
7681

77-
if !config.linked_apps.contains(&self) {
82+
if !config.linked_apps.contains_key(&self.id) {
7883
debug!("Writing app {} to config", self.id);
7984

80-
config.linked_apps.push(self);
85+
config.linked_apps.insert(self.id.clone(), self);
8186
config.save()?;
8287
}
8388

8489
Ok(())
8590
}
86-
}
8791

88-
impl Config {
89-
pub fn find_linked_app(&self, id: String) -> Option<&App> {
90-
self.linked_apps.iter().find(|app| app.id == id)
92+
pub fn unlink(self, config: &mut Config) -> Result<()> {
93+
let path = desktop_dir()?.join(format!("{}.desktop", self.id));
94+
fs::remove_file(path)?;
95+
96+
debug!("Removing app {} to config", self.id);
97+
98+
config.linked_apps.remove_entry(&self.id);
99+
config.save()
91100
}
92101
}

winapps/src/config/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use derive_new::new;
22
use serde::{Deserialize, Serialize};
3-
use std::net::IpAddr;
3+
use std::{collections::HashMap, net::IpAddr};
44

55
use crate::Backends;
66

@@ -19,8 +19,8 @@ pub struct Config {
1919
pub manual: ManualConfig,
2020
#[new(value = "FreerdpConfig::new()")]
2121
pub freerdp: FreerdpConfig,
22-
#[new(value = "Vec::new()")]
23-
pub linked_apps: Vec<App>,
22+
#[new(value = "HashMap::new()")]
23+
pub linked_apps: HashMap<String, App>,
2424
#[new(value = "false")]
2525
pub debug: bool,
2626

@@ -83,16 +83,18 @@ pub struct FreerdpConfig {
8383
pub executable: String,
8484
}
8585

86-
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
86+
#[derive(Debug, Default, Deserialize, Serialize, Clone, Hash)]
8787
pub enum AppKind {
8888
FromBase64(String),
8989
#[default]
9090
Existing,
9191
}
9292

93-
#[derive(Debug, Deserialize, Serialize, Clone)]
93+
#[derive(Debug, Deserialize, Serialize, Clone, Hash)]
9494
pub struct App {
95+
#[serde(skip)]
9596
pub id: String,
97+
9698
pub name: String,
9799
pub win_exec: String,
98100
#[serde(skip)]

winapps/src/remote_client/freerdp.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,8 @@ impl RemoteClient for Freerdp {
4848
fn run_app(self, config: &Config, app_name: String, args: Vec<String>) -> Result<()> {
4949
let path = config
5050
.linked_apps
51-
.iter()
52-
.filter_map(|app| app.id.eq(&app_name).then_some(app.win_exec.clone()))
53-
.next()
51+
.get(&app_name)
52+
.map(|app| app.win_exec.clone())
5453
.unwrap_or(app_name);
5554

5655
let Some(home_regex) = dirs::home_dir().map(|home| {

0 commit comments

Comments
 (0)