oma_pm/
commit.rs

1use std::{borrow::Cow, fs::create_dir_all, path::Path};
2
3use apt_auth_config::AuthConfig;
4use chrono::Local;
5use oma_apt::{
6    error::AptErrors,
7    progress::{AcquireProgress, InstallProgress},
8    util::{apt_lock, apt_lock_inner, apt_unlock, apt_unlock_inner},
9};
10use oma_fetch::{Event, Summary, reqwest::Client};
11use oma_pm_operation_type::{InstallEntry, OmaOperation};
12use std::io::Write;
13use tracing::debug;
14
15use crate::{
16    apt::{DownloadConfig, InstallProgressOpt, OmaApt, OmaAptError, OmaAptResult},
17    dbus::change_status,
18    download::download_pkgs,
19    progress::{InstallProgressArgs, OmaAptInstallProgress},
20};
21
22const TIME_FORMAT: &str = "%H:%M:%S on %Y-%m-%d";
23
24pub struct CommitConfig<'a> {
25    pub network_thread: Option<usize>,
26    pub auth_config: Option<&'a AuthConfig>,
27    pub download_only: bool,
28}
29
30pub struct DoInstall<'a> {
31    apt: OmaApt,
32    client: &'a Client,
33    sysroot: &'a str,
34    config: CommitConfig<'a>,
35}
36
37pub type CustomDownloadMessage = Box<dyn Fn(&InstallEntry) -> Cow<'static, str>>;
38
39impl<'a> DoInstall<'a> {
40    pub fn new(
41        apt: OmaApt,
42        client: &'a Client,
43        sysroot: &'a str,
44        config: CommitConfig<'a>,
45    ) -> Result<Self, OmaAptError> {
46        Ok(Self {
47            apt,
48            sysroot,
49            client,
50            config,
51        })
52    }
53
54    pub fn commit(
55        self,
56        op: &OmaOperation,
57        install_progress_manager: InstallProgressOpt,
58        custom_download_message: CustomDownloadMessage,
59        callback: impl AsyncFn(Event),
60    ) -> OmaAptResult<()> {
61        let summary = self.download_pkgs(&op.install, custom_download_message, callback)?;
62
63        if !summary.failed.is_empty() {
64            return Err(OmaAptError::FailedToDownload(summary.failed.len()));
65        }
66
67        if !self.config.download_only {
68            self.do_install(install_progress_manager, op)?;
69        }
70
71        Ok(())
72    }
73
74    fn download_pkgs(
75        &self,
76        download_pkg_list: &[InstallEntry],
77        custom_download_message: CustomDownloadMessage,
78        callback: impl AsyncFn(Event),
79    ) -> OmaAptResult<Summary> {
80        let path = self.apt.get_archive_dir();
81
82        create_dir_all(path)
83            .map_err(|e| OmaAptError::FailedOperateDirOrFile(path.display().to_string(), e))?;
84
85        self.apt.get_or_init_async_runtime()?.block_on(async {
86            if let Some(conn) = self.apt.conn.get() {
87                change_status(conn, "Downloading").await.ok();
88            }
89
90            let config = DownloadConfig {
91                network_thread: self.config.network_thread,
92                download_dir: Some(path),
93                auth: self.config.auth_config,
94            };
95
96            download_pkgs(
97                self.client,
98                download_pkg_list,
99                config,
100                false,
101                custom_download_message,
102                callback,
103            )
104            .await
105        })
106    }
107
108    fn do_install(
109        self,
110        install_progress_manager: InstallProgressOpt,
111        op: &OmaOperation,
112    ) -> OmaAptResult<()> {
113        apt_lock().map_err(OmaAptError::LockApt)?;
114
115        debug!("Try to get apt archives");
116
117        self.apt
118            .cache
119            .get_archives(&mut AcquireProgress::quiet())
120            .inspect_err(|e| {
121                debug!("Get exception: {e}. Try to unlock apt lock");
122                apt_unlock();
123            })
124            .map_err(AptErrors::from)
125            .map_err(OmaAptError::InstallPackages)?;
126
127        let args = InstallProgressArgs {
128            config: self.apt.config,
129            tokio: self.apt.tokio,
130            connection: self.apt.conn,
131        };
132
133        let mut progress = match install_progress_manager {
134            InstallProgressOpt::TermLike(install_progress_manager) => {
135                InstallProgress::new(OmaAptInstallProgress::new(args, install_progress_manager))
136            }
137            InstallProgressOpt::Fd(fd) => InstallProgress::Fd(fd),
138        };
139
140        debug!("Try to unlock apt lock inner");
141
142        apt_unlock_inner();
143
144        debug!("Do install");
145
146        self.apt
147            .cache
148            .do_install(&mut progress)
149            .inspect_err(|e| {
150                debug!("do_install got except: {e}");
151                apt_lock_inner().ok();
152                apt_unlock();
153            })
154            .map_err(OmaAptError::InstallPackages)?;
155
156        debug!("Try to unlock apt lock");
157
158        apt_unlock();
159
160        Self::log(self.sysroot, op)?;
161
162        Ok(())
163    }
164
165    fn log(sysroot: &'a str, op: &OmaOperation) -> OmaAptResult<()> {
166        let end_time = Local::now().format(TIME_FORMAT).to_string();
167
168        let sysroot = Path::new(sysroot);
169        let history = sysroot.join("var/log/oma/history");
170        let parent = history
171            .parent()
172            .ok_or_else(|| OmaAptError::FailedGetParentPath(history.clone()))?;
173
174        std::fs::create_dir_all(parent)
175            .map_err(|e| OmaAptError::FailedOperateDirOrFile(parent.display().to_string(), e))?;
176
177        let mut log = std::fs::OpenOptions::new()
178            .append(true)
179            .create(true)
180            .open(&history)
181            .map_err(|e| OmaAptError::FailedOperateDirOrFile(history.display().to_string(), e))?;
182
183        let start_time = Local::now();
184        writeln!(log, "Start-Date: {start_time}").ok();
185
186        let args = std::env::args().collect::<Vec<_>>().join(" ");
187
188        if !args.is_empty() {
189            writeln!(log, "Commandline: {args}").ok();
190        }
191
192        if let Some((user, uid)) = std::env::var("SUDO_USER")
193            .ok()
194            .zip(std::env::var("SUDO_UID").ok())
195        {
196            writeln!(log, "Requested-By: {user} ({uid})").ok();
197        }
198
199        write!(log, "{op}").ok();
200        writeln!(log, "End-Date: {end_time}\n").ok();
201
202        Ok(())
203    }
204}