GPG keystore initialization, reimplemented progress event modules,

additions to global configuration module, and bug fixes.

- Progress verbosity option for progress indicators
- Logging verbsoity option for future implementation of logging
  infrastructure
- Small addendum to cache API, with get_instance providing a Result enum
- Validate the program is running under a non-privileged user upon
  front-end invocation
This commit is contained in:
Xavier Moffett 2024-01-10 17:31:43 -05:00
parent fa9de91330
commit 3b5951cea6
22 changed files with 583 additions and 313 deletions

View file

@ -33,9 +33,9 @@ use pacwrap_core::{err,
TransactionMetadata,
TransactionParameters,
MAGIC_NUMBER},
event::{download::{DownloadCallback, download_event},
progress::{ProgressEvent, callback},
query::questioncb}},
event::{download::{self, DownloadEvent},
progress::{self, ProgressEvent},
query}},
utils::{print_warning, read_le_32}, config::Global};
use crate::error::AgentError;
@ -70,13 +70,17 @@ pub fn transact() -> Result<()> {
let alpm = sync::instantiate_alpm_agent(&config, &alpm_remotes);
let mut handle = TransactionHandle::new(&config, alpm, &mut metadata);
conduct_transaction(&mut handle, params)
conduct_transaction(&config, &mut handle, params)
}
fn conduct_transaction(handle: &mut TransactionHandle, agent: TransactionParameters) -> Result<()> {
fn conduct_transaction(config: &Global, handle: &mut TransactionHandle, agent: TransactionParameters) -> Result<()> {
let flags = handle.retrieve_flags();
let mode = agent.mode();
let action = agent.action();
let config = config.config();
let progress = config.progress();
let bytes = agent.bytes();
let files = agent.files();
if let Err(error) = handle.alpm_mut().trans_init(flags.1.unwrap()) {
err!(SyncError::InitializationFailure(error.to_string().into()))?
@ -96,9 +100,17 @@ fn conduct_transaction(handle: &mut TransactionHandle, agent: TransactionParamet
erroneous_preparation(error)?
}
handle.alpm().set_progress_cb(ProgressEvent::new(&action), callback(&mode));
handle.alpm().set_question_cb((), questioncb);
handle.alpm().set_dl_cb(DownloadCallback::new(agent.bytes(), agent.files()), download_event);
let progress_cb = ProgressEvent::new()
.style(progress.0)
.configure(&action);
let download_cb = DownloadEvent::new()
.style(progress.0)
.total(bytes, files)
.configure(&mode, progress.1);
handle.alpm().set_question_cb((), query::callback);
handle.alpm().set_progress_cb(progress_cb, progress::callback(&mode, progress.0));
handle.alpm().set_dl_cb(download_cb, download::callback(progress.1));
if let Err(error) = handle.alpm_mut().trans_commit() {
erroneous_transaction(error)?

View file

@ -45,7 +45,7 @@ pub mod cache;
pub mod instance;
pub mod init;
pub mod register;
mod global;
pub mod global;
#[derive(Debug, Clone)]
pub enum ConfigError {
@ -64,8 +64,8 @@ impl Display for ConfigError {
match self {
Self::Filesystem(module, err) => write!(fmter, "Failed to register filesystem {}: {} ", module, err),
Self::Permission(module, err) => write!(fmter, "Failed to register permission {}: {} ", module, err),
Self::Load(ins, error) => write!(fmter, "Failed to load '{ins}.yml': {error}"),
Self::Save(ins, error) => write!(fmter, "Failed to save '{ins}.yml': {error}"),
Self::Load(ins, error) => write!(fmter, "Failed to load '{ins}': {error}"),
Self::Save(ins, error) => write!(fmter, "Failed to save '{ins}': {error}"),
Self::AlreadyExists(ins) => write!(fmter, "Container {}{ins}{} already exists.", *BOLD, *RESET),
Self::ConfigNotFound(ins) => write!(fmter, "Configuration '{}{ins}{}.yml' not found.", *BOLD, *RESET)
}

View file

@ -54,7 +54,13 @@ impl <'a>InstanceCache<'a> {
err!(ConfigError::AlreadyExists(ins.into()))?
}
let deps = deps.iter().map(|a| (*a).into()).collect();
for dep in deps.iter() {
if let None = self.instances.get(dep) {
err!(ErrorKind::DependencyNotFound((*dep).into(), ins.into()))?
}
}
let deps = deps.iter().map(|a| (*a).into()).collect();
let handle = match config::provide_new_handle(ins) {
Ok(mut handle) => {
handle.metadata_mut().set(deps, vec!());
@ -79,7 +85,7 @@ impl <'a>InstanceCache<'a> {
fn map(&mut self, ins: &'a str) -> Result<()> {
if let Some(_) = self.instances.get(ins) {
err!(ConfigError::AlreadyExists(ins.into()))?
err!(ConfigError::AlreadyExists(ins.to_owned()))?
}
Ok(self.register(ins, match config::provide_handle(ins) {
@ -125,7 +131,13 @@ impl <'a>InstanceCache<'a> {
}
}
pub fn get_instance(&self, ins: &str) -> Option<&InstanceHandle> {
pub fn get_instance(&self, ins: &str) -> Result<&InstanceHandle> {
match self.instances.get(ins) {
Some(ins) => Ok(ins), None => err!(ErrorKind::InstanceNotFound(ins.into())),
}
}
pub fn get_instance_option(&self, ins: &str) -> Option<&InstanceHandle> {
self.instances.get(ins)
}
}
@ -146,24 +158,19 @@ pub fn populate<'a>() -> Result<InstanceCache<'a>> {
fn roots<'a>() -> Result<Vec<&'a str>> {
match read_dir(format!("{}/root", *DATA_DIR)) {
Ok(dir) => Ok(dir.filter(|f| match f {
Ok(dir) => Ok(dir.filter(|f| match f {
Ok(f) => match f.metadata() {
Ok(meta) => meta.is_dir() | meta.is_symlink(),
Err(_) => false,
Ok(meta) => meta.is_dir() | meta.is_symlink(), Err(_) => false
},
Err(_) => false })
Err(_) => false
})
.map(|s| match s {
Ok(f) => f.file_name()
.to_str()
.unwrap_or("")
.to_string()
.leak(),
Ok(f) => match f.file_name().to_str() {
Some(f) => f.to_owned().leak(), None => "",
},
Err(_) => "",
})
.filter_map(|e| match e.is_empty() {
true => None,
false => Some(e)
})
.filter(|e| ! e.is_empty())
.collect()),
Err(error) => err!(ErrorKind::IOError(format!("{}/root", *DATA_DIR), error.kind())),
}

View file

@ -35,12 +35,27 @@ pub enum Verbosity {
Verbose,
}
#[derive(Serialize, Deserialize, Clone)]
pub enum ProgressKind {
Simple,
Condensed,
CondensedForeign,
CondensedLocal,
Verbose,
}
impl Default for Verbosity {
fn default() -> Self {
Self::Verbose
}
}
impl Default for ProgressKind {
fn default() -> Self {
Self::CondensedForeign
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Global {
#[serde(default = "Configuration::new")]
@ -49,12 +64,22 @@ pub struct Global {
alpm: AlpmConfiguration,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Progress {
#[serde(default = "ProgressKind::default")]
transact: ProgressKind,
#[serde(default = "ProgressKind::default")]
download: ProgressKind,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Configuration {
#[serde(default = "Verbosity::default")]
summary: Verbosity,
#[serde(default = "Verbosity::default")]
progress: Verbosity,
logging: Verbosity,
#[serde(default = "Progress::new")]
progress: Progress,
}
#[derive(Serialize, Deserialize, Clone)]
@ -79,7 +104,29 @@ impl Configuration {
fn new() -> Self {
Self {
summary: Verbosity::Basic,
progress: Verbosity::Verbose,
logging: Verbosity::Basic,
progress: Progress::new(),
}
}
pub fn progress(&self) -> (&ProgressKind, &ProgressKind) {
(&self.progress.transact, &self.progress.download)
}
pub fn logging(&self) -> &Verbosity {
&self.summary
}
pub fn summary(&self) -> &Verbosity {
&self.summary
}
}
impl Progress {
fn new() -> Self {
Self {
transact: ProgressKind::CondensedForeign,
download: ProgressKind::CondensedForeign,
}
}
}

View file

@ -23,7 +23,8 @@ use crate::{err,
Error,
Result,
ErrorKind,
constants::{CACHE_DIR, CONFIG_DIR, DATA_DIR}};
constants::{CACHE_DIR, CONFIG_DIR, DATA_DIR},
config::global::CONFIG};
static REPO_CONF_DEFAULT: &'static str = r###"## See the pacman.conf(5) manpage for information on repository directives.
## All other libalpm-related options therein are ignored.
@ -44,8 +45,11 @@ static PACWRAP_CONF_DEFAULT: &'static str = r###"## See the pacwrap.yml(2) manpa
## Documentation is also available at https://git.sapphirus.org/pacwrap/pacwrap/docs/
config:
summary: Basic
progress: Verbose
logging: Basic
summmary: Basic
#progress:
#transact: CondensedForeign
#download: CondensedForeign
alpm:
#ignore_pkg:
#- somepackage
@ -84,7 +88,7 @@ impl DirectoryLayout {
fn data_layout() -> DirectoryLayout {
DirectoryLayout {
dirs: vec!("/root", "/home", "/state", "/pacman/gnupg", "/pacman/sync"),
dirs: vec!("/root", "/home", "/state", "/pacman/sync"),
root: *DATA_DIR,
}
}
@ -121,6 +125,8 @@ fn write_to_file(location: &str, contents: &str) -> Result<()> {
}
pub fn init() -> Result<()> {
let _ = *CONFIG;
config_layout().instantiate()?;
data_layout().instantiate()?;
cache_layout().instantiate()?;

View file

@ -18,7 +18,6 @@
*/
use std::{borrow::Cow,
time::{SystemTime, UNIX_EPOCH},
fmt::{Display, Debug, Formatter},
vec::Vec};
@ -29,7 +28,8 @@ use crate::{Result,
filesystem::{Filesystem, root::ROOT, home::HOME},
dbus::Dbus,
vars::InsVars,
save}};
save},
constants::UNIX_TIMESTAMP};
#[derive(Serialize, Deserialize, Clone)]
pub struct Instance<'a> {
@ -228,14 +228,14 @@ impl <'a>InstanceMetadata<'a> {
container_type: ctype,
dependencies: deps.iter().map(|a| (*a).into()).collect(),
explicit_packages: pkgs.iter().map(|a| (*a).into()).collect(),
meta_version: time_as_seconds(),
meta_version: *UNIX_TIMESTAMP,
}
}
pub fn set(&mut self, deps: Vec<&'a str>, pkgs: Vec<&'a str>) {
self.dependencies = deps.iter().map(|a| (*a).into()).collect();
self.explicit_packages = pkgs.iter().map(|a| (*a).into()).collect();
self.meta_version = time_as_seconds();
self.meta_version = *UNIX_TIMESTAMP;
}
pub fn container_type(&self) -> &InstanceType {
@ -249,15 +249,16 @@ impl <'a>InstanceMetadata<'a> {
pub fn explicit_packages(&'a self) -> Vec<&'a str> {
self.explicit_packages.iter().map(|a| a.as_ref()).collect()
}
}
fn time_as_seconds() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
pub fn timestamp(&self) -> u64 {
self.meta_version
}
}
fn default_true() -> bool {
true
}
fn time_as_seconds() -> u64 {
*UNIX_TIMESTAMP
}

View file

@ -22,11 +22,12 @@ use std::{env::var, process::id};
use lazy_static::lazy_static;
use nix::unistd::{geteuid, getegid};
use crate::{error, Error, ErrorKind, utils::{is_color_terminal, is_truecolor_terminal}};
use crate::{error, Error, ErrorKind, utils::{is_color_terminal, is_truecolor_terminal, unix_time_as_seconds}};
pub const BWRAP_EXECUTABLE: &str = "bwrap";
pub const DBUS_PROXY_EXECUTABLE: &str = "xdg-dbus-proxy";
pub const DEFAULT_PATH: &str = "/usr/local/bin:/usr/bin/:/bin";
pub const PACMAN_KEY_SCRIPT: &'static str = "pacman-key";
const PACWRAP_CONFIG_DIR: &str = "/.config/pacwrap";
const PACWRAP_DATA_DIR: &str = "/.local/share/pacwrap";
@ -66,6 +67,7 @@ lazy_static! {
pub static ref DBUS_SOCKET: String = format!("/run/user/{}/pacwrap_dbus_{}", *UID, &id());
pub static ref WAYLAND_SOCKET: String = format!("{}{}", *XDG_RUNTIME_DIR, *WAYLAND_DISPLAY);
pub static ref LOG_LOCATION: &'static str = format_str!("{}/pacwrap.log", *DATA_DIR);
pub static ref UNIX_TIMESTAMP: u64 = unix_time_as_seconds();
pub static ref IS_COLOR_TERMINLAL: bool = is_color_terminal();
pub static ref IS_TRUECOLOR_TERMINLAL: bool = is_truecolor_terminal();
pub static ref BOLD: &'static str = bold();

View file

@ -17,12 +17,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{process::{Child, Command}, fmt::{Formatter, Display}, os::fd::AsRawFd};
use std::{process::{Child, Command, Stdio}, fmt::{Formatter, Display}};
use command_fds::{CommandFdExt, FdMapping};
use lazy_static::lazy_static;
use os_pipe::{PipeWriter, PipeReader};
use serde::Serialize;
use crate::{err,
impl_error,
@ -31,10 +29,10 @@ use crate::{err,
ErrorKind,
Error,
Result,
constants::{LOG_LOCATION, BWRAP_EXECUTABLE, BOLD, RESET, GID, UID, TERM, LANG, COLORTERM},
config::{InstanceHandle, CONFIG},
sync::{DEFAULT_ALPM_CONF, SyncError, transaction::{TransactionParameters, TransactionMetadata}},
exec::seccomp::{provide_bpf_program, FilterType::*}};
constants::{LOG_LOCATION, BWRAP_EXECUTABLE, BOLD, RESET, GID, UID, TERM, LANG, COLORTERM, PACMAN_KEY_SCRIPT},
config::InstanceHandle,
sync::transaction::{TransactionParameters, TransactionMetadata},
exec::{utils::{wait_on_process, agent_params}, seccomp::{provide_bpf_program, FilterType::*}}};
pub mod args;
pub mod utils;
@ -173,17 +171,15 @@ pub fn transaction_agent(ins: &InstanceHandle, params: &TransactionParameters, m
}
}
fn agent_params(reader: &PipeReader, writer: &PipeWriter, params: &TransactionParameters, metadata: &TransactionMetadata) -> Result<i32> {
serialize(params, writer)?;
serialize(&*CONFIG, writer)?;
serialize(&*DEFAULT_ALPM_CONF, writer)?;
serialize(metadata, writer)?;
Ok(reader.as_raw_fd())
}
fn serialize<T: for<'de> Serialize>(input: &T, file: &PipeWriter) -> Result<()> {
match bincode::serialize_into::<&PipeWriter, T>(file, input) {
Ok(()) => Ok(()),
Err(error) => err!(SyncError::TransactionFailure(format!("Agent data serialization failed: {}", error))),
pub fn pacman_key(path: &str, cmd: Vec<&str>) -> Result<()> {
match Command::new(PACMAN_KEY_SCRIPT)
.stderr(Stdio::null())
.env("EUID", "0")
.arg("--gpgdir")
.arg(path)
.args(cmd)
.spawn() {
Ok(proc) => wait_on_process(PACMAN_KEY_SCRIPT, proc),
Err(error) => err!(ErrorKind::ProcessInitFailure(PACMAN_KEY_SCRIPT, error.kind()))?,
}
}

View file

@ -17,12 +17,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::{process::Child, io::Read};
use std::{process::Child, io::Read, os::fd::AsRawFd};
use os_pipe::{PipeReader, PipeWriter};
use serde::Serialize;
use serde_yaml::Value;
use crate::{constants::BWRAP_EXECUTABLE, config::InstanceHandle, ErrorKind, error::*, err};
use crate::{err,
error::*,
ErrorKind,
constants::BWRAP_EXECUTABLE,
config::{InstanceHandle, CONFIG},
sync::{SyncError, transaction::{TransactionParameters,
TransactionMetadata},
DEFAULT_ALPM_CONF}};
pub fn execute_fakeroot_container(ins: &InstanceHandle, arguments: Vec<&str>) -> Result<()> {
match super::fakeroot_container(ins, arguments)?.wait() {
@ -46,13 +54,31 @@ pub fn bwrap_json(mut reader: PipeReader, writer: PipeWriter) -> Result<i32> {
}
}
pub fn handle_process(name: &str, result: std::result::Result<Child, std::io::Error>) -> Result<()> {
pub fn agent_params(reader: &PipeReader, writer: &PipeWriter, params: &TransactionParameters, metadata: &TransactionMetadata) -> Result<i32> {
serialize(params, writer)?;
serialize(&*CONFIG, writer)?;
serialize(&*DEFAULT_ALPM_CONF, writer)?;
serialize(metadata, writer)?;
Ok(reader.as_raw_fd())
}
fn serialize<T: for<'de> Serialize>(input: &T, file: &PipeWriter) -> Result<()> {
match bincode::serialize_into::<&PipeWriter, T>(file, input) {
Ok(()) => Ok(()),
Err(error) => err!(SyncError::TransactionFailure(format!("Agent data serialization failed: {}", error))),
}
}
pub fn handle_process(name: &'static str, result: std::result::Result<Child, std::io::Error>) -> Result<()> {
match result {
Ok(child) => Ok(wait_on_process(child)),
Ok(child) => wait_on_process(name, child),
Err(error) => err!(ErrorKind::IOError(name.into(), error.kind())),
}
}
fn wait_on_process(mut child: Child) {
child.wait().ok();
pub fn wait_on_process(name: &'static str, mut child: Child) -> Result<()> {
match child.wait() {
Ok(_) => Ok(()),
Err(error) => err!(ErrorKind::ProcessWaitFailure(name, error.kind()))
}
}

View file

@ -16,7 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::fmt::{Display, Formatter};
use std::{fmt::{Display, Formatter}, path::Path};
use alpm::{Alpm, SigLevel, Usage};
use lazy_static::lazy_static;
@ -26,12 +26,14 @@ use serde::{Serialize, Deserialize};
use crate::{err,
impl_error,
error::*,
ErrorKind,
utils::print_warning,
exec::pacman_key,
constants::{BAR_GREEN, RESET, BOLD, CACHE_DIR, DATA_DIR, CONFIG_DIR, ARROW_RED},
sync::event::download::{DownloadCallback, download_event},
sync::event::download::{self, DownloadEvent},
config::{InsVars,
InstanceHandle,
cache::InstanceCache, CONFIG, Global}};
cache::InstanceCache, CONFIG, Global, global::ProgressKind}};
pub mod event;
pub mod utils;
@ -64,6 +66,7 @@ pub enum SyncError {
InitializationFailure(String),
InternalError(String),
NoCompatibleRemotes,
UnableToLocateKeyrings,
}
impl_error!(SyncError);
@ -85,6 +88,7 @@ impl Display for SyncError {
Self::NoCompatibleRemotes => write!(fmter, "No compatible containers available to synchronize remote database."),
Self::InvalidMagicNumber => write!(fmter, "Deserialization of input parameters failed: Invalid magic number."),
Self::InternalError(msg) => write!(fmter, "Internal failure: {msg}"),
Self::UnableToLocateKeyrings => write!(fmter, "Unable to locate pacman keyrings."),
Self::NothingToDo(_) => write!(fmter, "Nothing to do."),
_ => Ok(()),
}?;
@ -124,7 +128,7 @@ impl AlpmConfigData {
remotes.push(("pacwrap".into(),
(SigLevel::PACKAGE_MARGINAL_OK | SigLevel::DATABASE_MARGINAL_OK).bits(),
vec![format!("file://{}", env!("PACWRAP_DIST_REPO")), format!("file:///mnt/dist-repo/")]));
vec![format!("file://{}", env!("PACWRAP_DIST_REPO")), format!("file:///mnt/share/dist-repo/")]));
Self {
repos: remotes,
@ -149,7 +153,6 @@ pub fn instantiate_alpm_agent(config: &Global, remotes: &AlpmConfigData) -> Alpm
pub fn instantiate_alpm(inshandle: &InstanceHandle) -> Alpm {
alpm_handle(inshandle.vars(), format!("{}/var/lib/pacman/", inshandle.vars().root()), &*DEFAULT_ALPM_CONF)
}
fn alpm_handle(insvars: &InsVars, db_path: String, remotes: &AlpmConfigData) -> Alpm {
@ -166,6 +169,29 @@ fn alpm_handle(insvars: &InsVars, db_path: String, remotes: &AlpmConfigData) ->
handle
}
//TODO: Port pacman-key to Rust
pub fn instantiate_trust() -> Result<()> {
let path = &format!("{}/pacman/gnupg/", *DATA_DIR);
if Path::new(path).exists() {
return Ok(());
}
println!("{} {}Initializing package trust database...{}", *BAR_GREEN, *BOLD, *RESET);
if ! Path::new("/usr/share/pacman/keyrings").exists() {
err!(SyncError::UnableToLocateKeyrings)?
}
if let Err(error) = std::fs::create_dir_all(path) {
err!(ErrorKind::IOError(path.into(), error.kind()))?
}
pacman_key(path, vec!["--init"])?;
pacman_key(path, vec!["--populate"])
}
fn register_remote(mut handle: Alpm, config: &AlpmConfigData) -> Alpm {
for repo in &config.repos {
let core = handle.register_syncdb_mut(repo.0.clone(),
@ -187,8 +213,8 @@ fn synchronize_database(cache: &InstanceCache, force: bool) -> Result<()> {
let db_path = format!("{}/pacman/", *DATA_DIR);
let mut handle = alpm_handle(&ins.vars(), db_path, &*DEFAULT_ALPM_CONF);
println!("{} {}Synchronising package databases...{}", *BAR_GREEN, *BOLD, *RESET);
handle.set_dl_cb(DownloadCallback::new(0, 0), download_event);
println!("{} {}Synchronizing package databases...{}", *BAR_GREEN, *BOLD, *RESET);
handle.set_dl_cb(DownloadEvent::new().style(&ProgressKind::Verbose), download::event);
if let Err(err) = handle.syncdbs_mut().update(force) {
err!(SyncError::InitializationFailure(err.to_string()))?
@ -226,7 +252,7 @@ fn signature(sigs: &Vec<String>, default: SigLevel) -> SigLevel {
let mut sig = SigLevel::empty();
for level in sigs {
sig = sig | if level == "PackageRequired" || level == "PackageTrustedOnly" {
sig = sig | if level == "Required" || level == "PackageRequired" {
SigLevel::PACKAGE
} else if level == "DatabaseRequired" || level == "DatabaseTrustedOnly" {
SigLevel::DATABASE

View file

@ -22,14 +22,25 @@ pub mod query;
pub mod progress;
fn whitespace(total: usize, current: usize) -> String {
let total = log10(total);
let current = log10(current);
let mut whitespace = String::new();
let difference = total-current;
if difference > 0 {
for _ in 0..difference {
whitespace.push_str(" ");
}
}
for _ in 0..difference {
whitespace.push_str(" ");
}
whitespace
}
fn log10(mut value: usize) -> usize {
let mut length = 0;
while value > 0 {
value /= 10;
length += 1;
}
length
}

View file

@ -19,16 +19,18 @@
use std::collections::HashMap;
use alpm::{AnyDownloadEvent, DownloadEvent};
use alpm::{AnyDownloadEvent, DownloadEvent as Event};
use dialoguer::console::Term;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle, ProgressDrawTarget};
use lazy_static::lazy_static;
use simplebyteunit::simplebyteunit::*;
use crate::{constants::{ARROW_CYAN, BOLD, RESET}, config::global::ProgressKind, sync::transaction::TransactionMode};
use super::whitespace;
lazy_static!{
static ref INIT: ProgressStyle = ProgressStyle::with_template(" {spinner:.green} {msg}")
.unwrap();
static ref INIT: ProgressStyle = ProgressStyle::with_template(" {spinner:.green} {msg}").unwrap();
static ref UP_TO_DATE: ProgressStyle = ProgressStyle::with_template(" {spinner:.green} {msg} is up-to-date!")
.unwrap()
.progress_chars("#-")
@ -36,104 +38,185 @@ lazy_static!{
}
#[derive(Clone)]
pub struct DownloadCallback {
pub struct DownloadEvent {
total: usize,
position: usize,
total_bar: Option<ProgressBar>,
condensed: bool,
progress: MultiProgress,
prbar: HashMap<String, ProgressBar>,
style: ProgressStyle,
total_files: usize,
total_files_len: usize,
files_done: usize
bars: HashMap<&'static str, ProgressBar>,
style: Option<ProgressStyle>,
}
impl DownloadCallback {
pub fn new(total_bytes: u64, files: usize) -> Self {
let mut bars = HashMap::new();
let files_str_len = files.to_string().len();
let size = Term::size(&Term::stdout());
let width = ((size.1 / 2) - 14).to_string();
let multiprogress = MultiProgress::new();
let pb_style_tmpl = " {spinner:.green} {msg:<".to_owned()+width.as_str()
+ "} {bytes:>11} {bytes_per_sec:>12} {elapsed_precise:>5} [{wide_bar}] {percent:<3}%";
let pb_style = ProgressStyle::with_template(&(pb_style_tmpl))
.unwrap()
.progress_chars("#-")
.tick_strings(&[" ", ""]);
if total_bytes > 0 {
let pb_total = multiprogress.add(
ProgressBar::new(total_bytes)
.with_style(pb_style.clone())
.with_message(format!("Total ({}0/{})", whitespace(files_str_len, 1), files)));
bars.insert("total".into(), pb_total);
}
impl DownloadEvent {
pub fn new() -> Self {
Self {
style: pb_style,
total_files: files,
total_files_len: files_str_len,
files_done: 0,
progress: multiprogress,
prbar: bars,
total: 0,
position: 0,
total_bar: None,
condensed: false,
progress: MultiProgress::new(),
bars: HashMap::new(),
style: None,
}
}
pub fn style(mut self, kind: &ProgressKind) -> Self {
self.style = match kind {
ProgressKind::Simple => None,
_ => Some({
let size = Term::size(&Term::stdout());
let width = ((size.1 / 2) - 14).to_string();
ProgressStyle::with_template(&(" {spinner:.green} {msg:<".to_owned()+&width
+ "} {bytes:>11} {bytes_per_sec:>12} {elapsed_precise:>5} [{wide_bar}] {percent:<3}%"))
.unwrap()
.progress_chars("#-")
.tick_strings(&[" ", ""])
}),
};
self
}
pub fn total(mut self, bytes: u64, files: usize) -> Self {
self.total = files;
self.total_bar = match (bytes > 0 && files > 1, self.style.as_ref()) {
(true, Some(style)) => Some({
let bar = self.progress.add(ProgressBar::new(bytes)
.with_style(style.clone())
.with_message(format!("Total ({}0/{})", whitespace(files, 1), files)));
bar.set_position(0);
bar
}),
_ => None,
};
self
}
pub fn configure(mut self, mode: &TransactionMode, progress: &ProgressKind) -> Self {
self.condensed = match progress {
ProgressKind::Condensed => true,
ProgressKind::CondensedForeign => match mode {
TransactionMode::Foreign => true,
TransactionMode::Local => false,
},
ProgressKind::CondensedLocal => match mode {
TransactionMode::Foreign => false,
TransactionMode::Local => true,
},
_ => false,
};
self
}
fn increment(&mut self, progress: u64) {
let bar = match self.total_bar.as_mut() {
Some(bar) => bar, None => return,
};
self.position += 1;
let total = self.total;
let pos = self.position;
let whitespace = whitespace(total, pos);
bar.inc(progress);
bar.set_message(format!("Total ({}{}/{})", whitespace, pos, total));
if total == pos {
bar.finish();
}
}
fn insert(&mut self, file: &str) {
let pb = match self.total_bar.as_mut() {
Some(total) => match self.condensed {
true => self.progress.insert_after(&total, ProgressBar::new(0)),
false => self.progress.insert_before(&total, ProgressBar::new(0)),
},
None => self.progress.add(ProgressBar::new(0))
};
pb.set_style(INIT.clone());
pb.set_message(message(file));
/*
* alpm-rs expects our callback signature to provide a struct bound by a 'static lifetime,
* therefore allocate this literal and then leak it for use as a key.
*/
self.bars.insert(file.to_owned().leak(), pb);
}
}
pub fn download_event(file: &str, download: AnyDownloadEvent, this: &mut DownloadCallback) {
if file.ends_with(".sig") {
pub fn simple(file: &str, download: AnyDownloadEvent, this: &mut DownloadEvent) {
if file.ends_with(".sig") {
return;
}
if let Event::Completed(progress) = download.event() {
this.position += 1;
let size = progress.total.abs().to_byteunit(SI);
let total = this.total;
let pos = this.position;
let whitespace = whitespace(total, pos);
let message = message(file);
eprintln!("{} ({}{whitespace}{pos}{}/{}{total}{}) {message} downloaded ({size})", *ARROW_CYAN, *BOLD, *RESET, *BOLD, *RESET);
}
}
pub fn event(file: &str, download: AnyDownloadEvent, this: &mut DownloadEvent) {
if file.ends_with(".sig") {
return;
}
match download.event() {
DownloadEvent::Progress(progress) => {
if let Some(pb) = this.prbar.get_mut(&file.to_string()) {
if pb.length().unwrap() == 0 {
Event::Progress(progress) => {
if let Some(pb) = this.bars.get_mut(file) {
if pb.length().unwrap() == 0 {
pb.set_length(progress.total.unsigned_abs());
pb.set_style(this.style.clone());
pb.set_style(this.style.as_ref().unwrap().clone());
}
pb.set_position(progress.downloaded.unsigned_abs());
if let Some(total) = this.prbar.get("total") {
total.inc(progress.downloaded.unsigned_abs());
}
pb.set_position(progress.downloaded.unsigned_abs());
}
},
DownloadEvent::Completed(_) => {
if let Some(pb) = this.prbar.remove(&file.to_string()) {
Event::Completed(progress) => {
if let Some(pb) = this.bars.remove(file) {
if pb.length().unwrap() == 0 {
pb.set_style(UP_TO_DATE.clone());
}
pb.finish();
pb.finish();
this.increment(progress.total.unsigned_abs());
if let Some(total) = this.prbar.get("total") {
this.files_done += 1;
total.set_message(format!("Total ({}{}/{})",
whitespace(this.total_files_len,
this.files_done.to_string().len()),
this.files_done,
this.total_files));
if this.files_done == this.total_files {
total.finish();
}
if this.condensed {
pb.set_draw_target(ProgressDrawTarget::hidden());
}
}
},
DownloadEvent::Init(_) => {
let pb = if let Some(total) = this.prbar.get("total") {
this.progress.insert_before(&total, ProgressBar::new(0))
} else {
this.progress.add(ProgressBar::new(0))
};
Event::Init(progress) => {
if progress.optional {
return;
}
pb.set_style(INIT.clone());
pb.set_message(message(file));
this.prbar.insert(file.into(), pb);
this.insert(file);
},
_ => (),
Event::Retry(_) => {
if let Some(pb) = this.bars.get_mut(file) {
pb.set_position(0);
pb.set_style(INIT.clone());
}
},
}
}
pub fn callback(progress: &ProgressKind) -> for<'a, 'b, 'c> fn(file: &'a str, AnyDownloadEvent<'b>, this: &'c mut DownloadEvent) {
match progress {
ProgressKind::Simple => simple, _ => event
}
}

View file

@ -17,114 +17,168 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use alpm::Progress;
use alpm::Progress as Event;
use dialoguer::console::Term;
use indicatif::{ProgressBar, ProgressStyle};
use crate::{constants::{BOLD, RESET}, sync::transaction::{TransactionType, TransactionMode}};
use super::whitespace;
use crate::{constants::{BOLD, RESET, ARROW_CYAN},
sync::{event::whitespace, transaction::{TransactionType, TransactionMode}},
config::global::ProgressKind};
#[derive(Clone)]
pub struct ProgressEvent {
progress: ProgressBar,
current: Option<String>,
progress: Option<ProgressBar>,
offset: usize,
style: ProgressStyle,
current: String,
style: Option<ProgressStyle>,
}
impl ProgressEvent {
pub fn new(state: &TransactionType) -> Self {
let size = Term::size(&Term::stdout());
let width = (size.1 / 2).to_string();
pub fn new() -> Self {
Self {
offset: state.pr_offset(),
style: ProgressStyle::with_template(&(" {spinner:.green} {msg:<".to_owned()+width.as_str()+"} [{wide_bar}] {percent:<3}%"))
.unwrap().progress_chars("#-").tick_strings(&[" ", ""]),
progress: ProgressBar::new(0),
current: "".into(),
current: None,
progress: None,
style: None,
offset: 0,
}
}
pub fn configure(mut self, state: &TransactionType) -> Self {
self.offset = state.pr_offset();
self
}
pub fn style(mut self, kind: &ProgressKind) -> Self {
self.style = match kind {
ProgressKind::Simple => None,
_ => Some({
let size = Term::size(&Term::stdout());
let width = (size.1 / 2).to_string();
ProgressStyle::with_template(&(" {spinner:.green} {msg:<".to_owned()
+ &width +"} [{wide_bar}] {percent:<3}%"))
.unwrap().progress_chars("#-").tick_strings(&[" ", ""])
}),
};
self
}
fn bar(&mut self, ident: Option<String>, name: &str, howmany: usize, current: usize, size: usize) {
let pos = current + self.offset;
let total = howmany + self.offset;
let whitespace = whitespace(total, pos);
let progress = ProgressBar::new(size as u64);
progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) {name}", *BOLD, *RESET, *BOLD, *RESET));
progress.set_style(self.style.as_ref().unwrap().clone());
self.progress = Some(progress);
self.current = ident
}
}
pub fn event(progress: Progress, pkgname: &str, percent: i32, howmany: usize, current: usize, this: &mut ProgressEvent) {
let ident = ident(progress,pkgname);
pub fn event(event: Event, pkgname: &str, percent: i32, howmany: usize, current: usize, this: &mut ProgressEvent) {
let ident = ident(event,pkgname);
if ident != this.current {
let pos = current + this.offset;
let total = howmany + this.offset;
let progress_name: String = name(progress,pkgname);
let whitespace = whitespace(total.to_string().len(), pos.to_string().len());
this.progress = ProgressBar::new(100);
this.progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) {progress_name}", *BOLD, *RESET, *BOLD, *RESET));
this.progress.set_style(this.style.clone());
this.current = ident;
}
this.progress.set_position(percent as u64);
match this.current.as_deref() {
Some(current_ident) => if ident != current_ident {
this.bar(Some(ident), &name(event,pkgname), howmany, current, 100)
},
None => this.bar(Some(ident), &name(event,pkgname), howmany, current, 100),
};
let progress = match this.progress.as_mut() {
Some(progress) => progress, None => return,
};
progress.set_position(percent as u64);
if percent == 100 {
this.progress.finish();
progress.finish();
}
}
pub fn condensed(progress: Progress, pkgname: &str, percent: i32, howmany: usize, current: usize, this: &mut ProgressEvent) {
if let Progress::AddStart | Progress::RemoveStart | Progress::UpgradeStart = progress {
pub fn simple(progress: Event, pkgname: &str, percent: i32, howmany: usize, current: usize, this: &mut ProgressEvent) {
if percent == 0 {
let pos = current + this.offset;
let total = howmany + this.offset;
let progress_name: String = name(progress,pkgname);
let whitespace = whitespace(total.to_string().len(), pos.to_string().len());
let whitespace = whitespace(total, pos);
if this.current != "" {
this.progress = ProgressBar::new(howmany as u64);
this.progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) {progress_name}", *BOLD, *RESET, *BOLD, *RESET));
this.progress.set_style(this.style.clone());
this.progress.set_position(current as u64);
this.current = "".into();
} else {
this.progress.set_position(current as u64);
this.progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) {progress_name}", *BOLD, *RESET, *BOLD, *RESET));
}
eprintln!("{} ({}{whitespace}{pos}{}/{}{total}{}) {progress_name}", *ARROW_CYAN, *BOLD, *RESET, *BOLD, *RESET);
if current == howmany {
this.progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) Foreign synchronization complete", *BOLD, *RESET, *BOLD, *RESET));
this.progress.finish();
eprintln!("{} ({}{whitespace}{pos}{}/{}{total}{}) Synchronization complete", *ARROW_CYAN, *BOLD, *RESET, *BOLD, *RESET);
}
}
}
pub fn condensed(kind: Event, pkgname: &str, percent: i32, howmany: usize, current: usize, this: &mut ProgressEvent) {
if let Event::AddStart | Event::RemoveStart | Event::UpgradeStart = kind {
let pos = current + this.offset;
let total = howmany + this.offset;
let progress_name: String = name(kind,pkgname);
let whitespace = whitespace(total, pos);
if let Some(_) = this.current {
this.bar(None, &progress_name, howmany, current, howmany);
}
let progress = match this.progress.as_mut() {
Some(progress) => progress, None => return,
};
progress.set_position(current as u64);
if current != howmany {
progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) {progress_name}", *BOLD, *RESET, *BOLD, *RESET));
} else {
progress.set_message(format!("({}{whitespace}{pos}{}/{}{total}{}) Synchronization complete", *BOLD, *RESET, *BOLD, *RESET));
progress.finish();
}
} else {
event(progress, pkgname, percent, howmany, current, this)
event(kind, pkgname, percent, howmany, current, this)
}
}
fn name(progress: Progress, pkgname: &str) -> String {
match progress {
Progress::KeyringStart => "Loading keyring".into(),
Progress::IntegrityStart => "Checking integrity".into(),
Progress::LoadStart => "Loading packages".into(),
Progress::ConflictsStart => "Checking conflicts".into(),
Progress::DiskspaceStart => "Checking available diskspace".into(),
Progress::UpgradeStart => format!("Upgrading {}", pkgname),
Progress::AddStart => format!("Installing {}", pkgname),
Progress::RemoveStart => format!("Removing {}", pkgname),
Progress::DowngradeStart => format!("Downgrading {}", pkgname),
Progress::ReinstallStart => format!("Reinstalling {}", pkgname)
fn name(progress: Event, pkgname: &str) -> String {
match progress {
Event::UpgradeStart => format!("Upgrading {}", pkgname),
Event::AddStart => format!("Installing {}", pkgname),
Event::RemoveStart => format!("Removing {}", pkgname),
Event::DowngradeStart => format!("Downgrading {}", pkgname),
Event::ReinstallStart => format!("Reinstalling {}", pkgname),
Event::KeyringStart => format!("Loading keyring"),
Event::IntegrityStart => format!("Checking integrity"),
Event::LoadStart => format!("Loading packages"),
Event::ConflictsStart => format!("Checking conflicts"),
Event::DiskspaceStart => format!("Checking available diskspace"),
}
}
fn ident(progress: Progress, pkgname: &str) -> String {
fn ident(progress: Event, pkgname: &str) -> String {
match progress {
Progress::KeyringStart => "keyring",
Progress::IntegrityStart => "integrity",
Progress::LoadStart => "loadstart",
Progress::ConflictsStart => "conflicts",
Event::KeyringStart => "keyring",
Event::IntegrityStart => "integrity",
Event::LoadStart => "loadstart",
Event::ConflictsStart => "conflicts",
_ => pkgname
}.into()
}.to_owned()
}
pub fn callback(state: &TransactionMode) -> for<'a, 'b> fn(Progress, &'a str, i32, usize, usize, &'b mut ProgressEvent) {
match state {
TransactionMode::Local => event,
TransactionMode::Foreign => condensed,
pub fn callback(state: &TransactionMode, kind: &ProgressKind) -> for<'a, 'b> fn(Event, &'a str, i32, usize, usize, &'b mut ProgressEvent) {
match kind {
ProgressKind::Simple => simple,
ProgressKind::CondensedForeign => match state {
TransactionMode::Local => event,
TransactionMode::Foreign => condensed,
},
ProgressKind::CondensedLocal => match state {
TransactionMode::Local => condensed,
TransactionMode::Foreign => event,
},
ProgressKind::Condensed => condensed,
ProgressKind::Verbose => event,
}
}

View file

@ -23,7 +23,7 @@ use alpm::{AnyQuestion, Question::*};
use crate::utils::prompt::prompt;
pub fn questioncb(question: AnyQuestion, _: &mut ()) {
pub fn callback(question: AnyQuestion, _: &mut ()) {
match question.question() {
Conflict(mut x) => {
let pkg_a = x.conflict().package1();
@ -64,9 +64,6 @@ pub fn questioncb(question: AnyQuestion, _: &mut ()) {
x.set_import(true);
}
},
//TODO: Implement these questions.
RemovePkgs(_) => (),
SelectProvider(_) => (),
InstallIgnorepkg(_) => (),
_ => (),
}
}

View file

@ -147,11 +147,8 @@ impl <'a>FileSystemStateSync<'a> {
continue;
}