pacwrap/pacwrap/src/sync.rs

269 lines
10 KiB
Rust

/*
* pacwrap
*
* Copyright (C) 2023-2024 Xavier Moffett <sapphirus@azorium.net>
* SPDX-License-Identifier: GPL-3.0-only
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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::collections::{HashMap, HashSet};
use indexmap::IndexMap;
use pacwrap_core::{
config::{cache, init::init, ConfigError::AlreadyExists, ContainerCache, ContainerType},
constants::{ARROW_GREEN, BAR_GREEN, BOLD, RESET},
err,
error::*,
lock::Lock,
log::{Level::Info, Logger},
sync::{
instantiate_container,
instantiate_trust,
transaction::{TransactionAggregator, TransactionFlags, TransactionType},
},
utils::{
arguments::{Arguments, InvalidArgument::*, Operand as Op},
check_root,
print_warning,
},
ErrorKind,
};
pub fn synchronize(args: &mut Arguments) -> Result<()> {
check_root()?;
init()?;
let mut logger = Logger::new("pacwrap-sync").init().unwrap();
let mut cache = cache::populate()?;
let (action, create) = action(args);
let lock = Lock::new().lock()?;
let result = engage_aggregator(&mut cache, &mut logger, args, &lock, action, create);
if let Err(error) = lock.unlock() {
error.error();
}
result
}
fn action(args: &mut Arguments) -> (TransactionType, bool) {
let (mut y, mut u, mut i) = (0, 0, false);
if let Op::Value("init") = args[0] {
(y, u, i) = (1, 1, true);
}
for arg in args.by_ref() {
match arg {
Op::Short('y') | Op::Long("refresh") => y += 1,
Op::Short('u') | Op::Long("upgrade") => u += 1,
_ => continue,
}
}
(TransactionType::Upgrade(u > 0, y > 0, y > 1), i)
}
fn instantiate<'a>(
cache: &mut ContainerCache<'a>,
lock: &'a Lock,
logger: &mut Logger,
action_type: &TransactionType,
targets: IndexMap<&'a str, (ContainerType, Vec<&'a str>)>,
) -> Result<()> {
if targets.is_empty() {
err!(OperationUnspecified)?;
}
if let TransactionType::Upgrade(upgrade, refresh, _) = action_type {
if !refresh {
err!(UnsuppliedOperand("--refresh", "Required for container creation."))?
} else if !upgrade {
err!(UnsuppliedOperand("--upgrade", "Required for container creation."))?
}
}
for (container, (container_type, deps)) in targets.iter() {
if let (ContainerType::Base, true) = (container_type, !deps.is_empty()) {
err!(ErrorKind::Message("Dependencies cannot be assigned to base containers."))?
} else if let (ContainerType::Aggregate | ContainerType::Slice, true) = (container_type, deps.is_empty()) {
err!(ErrorKind::Message("Dependencies not specified."))?
} else if cache.get_instance_option(container).is_some() {
err!(AlreadyExists(container.to_string()))?;
}
}
lock.assert()?;
println!("{} {}Instantiating container{}...{}", *BAR_GREEN, *BOLD, if targets.len() > 1 { "s" } else { "" }, *RESET);
for (container, (container_type, deps)) in targets {
cache.add(container, container_type, deps)?;
instantiate_container(cache.get_instance(container)?)?;
logger.log(Info, &format!("Instantiation of {container} complete."))?;
println!("{} Instantiation of {container} complete.", *ARROW_GREEN);
}
Ok(())
}
fn acquire_targets<'a>(
cache: &'a ContainerCache<'a>,
flags: &TransactionFlags,
mut targets: HashSet<&'a str>,
) -> Result<Option<Vec<&'a str>>> {
Ok(if flags.intersects(TransactionFlags::TARGET_ONLY | TransactionFlags::CREATE) {
if flags.contains(TransactionFlags::CREATE) {
for cache in cache.registered_handles().iter().filter(|a| a.is_creation()) {
targets.extend(cache.metadata().dependencies());
}
}
match flags.contains(TransactionFlags::FILESYSTEM_SYNC) {
false => {
if targets.is_empty() {
err!(TargetUnspecified)?;
}
Some(targets.into_iter().collect())
}
true => None,
}
} else {
None
})
}
fn engage_aggregator<'a>(
cache: &mut ContainerCache<'a>,
log: &'a mut Logger,
args: &'a mut Arguments,
lock: &'a Lock,
action_type: TransactionType,
init: bool,
) -> Result<()> {
let mut flags = TransactionFlags::NONE;
let mut create_targets: IndexMap<&'a str, (ContainerType, Vec<&'a str>)> = IndexMap::new();
let mut targets = HashSet::new();
let mut queue = HashMap::new();
let mut current_target = None;
let mut container_type = None;
let mut create = init;
if let Op::Nothing = args.next().unwrap_or_default() {
err!(OperationUnspecified)?
}
while let Some(arg) = args.next() {
match arg {
Op::Short('y') | Op::Short('u') | Op::Long("refresh") | Op::Long("upgrade") => continue,
Op::Long("debug") => flags |= TransactionFlags::DEBUG,
Op::Long("dbonly") => flags |= TransactionFlags::DATABASE_ONLY,
Op::Long("noconfirm") => flags |= TransactionFlags::NO_CONFIRM,
Op::Long("force-foreign") => flags |= TransactionFlags::FORCE_DATABASE,
Op::Long("disable-sandbox") => flags |= TransactionFlags::NO_ALPM_SANDBOX,
Op::Short('l') | Op::Long("lazy-load") => flags |= TransactionFlags::LAZY_LOAD_DB,
Op::Short('o') | Op::Long("target-only") => flags |= TransactionFlags::TARGET_ONLY,
Op::Short('f') | Op::Long("filesystem") => flags |= TransactionFlags::FILESYSTEM_SYNC,
Op::Short('p') | Op::Long("preview") => flags |= TransactionFlags::PREVIEW,
Op::Short('b') | Op::Long("base") => container_type = Some(ContainerType::Base),
Op::Short('s') | Op::Long("slice") => container_type = Some(ContainerType::Slice),
Op::Short('a') | Op::Long("aggregate") => container_type = Some(ContainerType::Aggregate),
Op::Short('c') | Op::Long("create") => {
container_type = None;
create = true;
}
Op::Short('d') | Op::Long("dep") => match args.next() {
Some(arg) => match arg {
Op::ShortPos('d', dep) | Op::LongPos("dep", dep) => match container_type {
Some(_) => {
let current_target = match current_target {
Some(target) => target,
None => err!(TargetUnspecified)?,
};
let (_, deps) = create_targets.get_mut(current_target).unwrap();
if dep.contains(",") {
for dep in dep.split(",").filter(|a| !a.is_empty()) {
deps.push(dep);
}
} else {
deps.push(dep);
}
}
None => err!(ErrorKind::Message("Container type not specified."))?,
},
_ => args.invalid_operand()?,
},
None => err!(ErrorKind::Message("Dependencies not specified."))?,
},
Op::Short('t') | Op::Long("target") => match args.next() {
Some(arg) => match arg {
Op::ShortPos('t', target) | Op::LongPos("target", target) => {
current_target = Some(target);
targets.insert(target);
if let (true, Some(container_type)) = (create, container_type) {
if let ContainerType::Base = container_type {
queue.insert(target, vec!["base"]);
}
create_targets.insert(target, (container_type, vec![]));
create = init;
} else if let (true, None) = (create, container_type) {
err!(ErrorKind::Message("Container type not specified."))?;
} else if let ContainerType::Symbolic = cache.get_instance(target)?.metadata().container_type() {
err!(ErrorKind::Message("Symbolic containers cannot be transacted."))?;
}
}
_ => args.invalid_operand()?,
},
None => err!(TargetUnspecified)?,
},
Op::LongPos(_, package) | Op::ShortPos(_, package) | Op::Value(package) =>
if let Some(current_target) = current_target {
if let Some(vec) = queue.get_mut(current_target) {
vec.push(package);
} else {
queue.insert(current_target, vec![package]);
}
},
_ => args.invalid_operand()?,
}
}
if flags.contains(TransactionFlags::LAZY_LOAD_DB) {
print_warning("Database lazy-loading triggered by `-l/--lazy-load`; this feature is experimental.");
print_warning("In future, manual intervention may be required for missing dependencies.");
print_warning("See `--help sync` or the pacwrap(1) man page for further information.");
}
if !create_targets.is_empty() || init {
if flags.intersects(TransactionFlags::PREVIEW) {
err!(ErrorKind::Message("Container creation cannot be previewed."))?;
}
flags = flags | TransactionFlags::CREATE | TransactionFlags::FORCE_DATABASE;
instantiate_trust()?;
instantiate(cache, lock, log, &action_type, create_targets)?;
}
TransactionAggregator::new(cache, log, action_type)
.assert_lock(lock)?
.target(acquire_targets(cache, &flags, targets)?)
.queue(queue)
.flag(flags)
.progress()
.aggregate()
}