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}