soar_core/package/
remove.rs

1use std::{
2    ffi::OsString,
3    fs,
4    path::{Path, PathBuf},
5    sync::{Arc, Mutex},
6};
7
8use rusqlite::{params, Connection};
9
10use crate::{
11    config::get_config,
12    database::{models::InstalledPackage, packages::ProvideStrategy},
13    error::ErrorContext,
14    utils::{desktop_dir, icons_dir, process_dir},
15    SoarResult,
16};
17
18pub struct PackageRemover {
19    package: InstalledPackage,
20    db: Arc<Mutex<Connection>>,
21}
22
23impl PackageRemover {
24    pub async fn new(package: InstalledPackage, db: Arc<Mutex<Connection>>) -> Self {
25        Self { package, db }
26    }
27
28    pub async fn remove(&self) -> SoarResult<()> {
29        let mut conn = self.db.lock()?;
30        let tx = conn.transaction()?;
31
32        // to prevent accidentally removing required files by other package,
33        // remove only if the installation was successful
34        if self.package.is_installed {
35            let bin_path = get_config().get_bin_path()?;
36            let def_bin = bin_path.join(&self.package.pkg_name);
37            if def_bin.is_symlink() && def_bin.is_file() {
38                fs::remove_file(&def_bin)
39                    .with_context(|| format!("removing binary {}", def_bin.display()))?;
40            }
41
42            if let Some(provides) = &self.package.provides {
43                for provide in provides {
44                    if let Some(ref target) = provide.target {
45                        let is_symlink = matches!(
46                            provide.strategy,
47                            Some(ProvideStrategy::KeepTargetOnly) | Some(ProvideStrategy::KeepBoth)
48                        );
49                        if is_symlink {
50                            let target_name = bin_path.join(target);
51                            if target_name.exists() {
52                                std::fs::remove_file(&target_name).with_context(|| {
53                                    format!("removing provide {}", target_name.display())
54                                })?;
55                            }
56                        }
57                    }
58                }
59            }
60
61            let installed_path = PathBuf::from(&self.package.installed_path);
62
63            let mut remove_action = |path: &Path| -> SoarResult<()> {
64                if path.extension() == Some(&OsString::from("desktop")) {
65                    if let Ok(real_path) = fs::read_link(path) {
66                        if real_path.parent() == Some(&installed_path) {
67                            let _ = fs::remove_file(path);
68                        }
69                    }
70                }
71                Ok(())
72            };
73            process_dir(desktop_dir(), &mut remove_action)?;
74
75            let mut remove_action = |path: &Path| -> SoarResult<()> {
76                if let Ok(real_path) = fs::read_link(path) {
77                    if real_path.parent() == Some(&installed_path) {
78                        let _ = fs::remove_file(path);
79                    }
80                }
81                Ok(())
82            };
83            process_dir(icons_dir(), &mut remove_action)?;
84        }
85
86        if let Err(err) = fs::remove_dir_all(&self.package.installed_path) {
87            // if not found, the package is already removed.
88            if err.kind() != std::io::ErrorKind::NotFound {
89                return Err(err).with_context(|| {
90                    format!("removing package directory {}", self.package.installed_path)
91                })?;
92            }
93        };
94
95        {
96            let mut stmt = tx.prepare(
97                r#"
98                DELETE FROM packages WHERE id = ?
99            "#,
100            )?;
101            stmt.execute(params![self.package.id])?;
102
103            let mut stmt = tx.prepare(
104                r#"
105                DELETE FROM portable_package WHERE package_id = ?
106            "#,
107            )?;
108            stmt.execute(params![self.package.id])?;
109        }
110
111        tx.commit()?;
112
113        Ok(())
114    }
115}