Adjust package installer to take &mut hooks as a parameter, which will

be passed to each package is it is installed with all hooks being run
after all packages have finished installation. Also fix some ordering
issues with package installation so that post install scripts and
appstream data are not included in the number of archive members for the
sake of progress bars, etc.
This commit is contained in:
Nathan Fisher 2023-04-16 19:15:12 -04:00
parent f479dd28ad
commit 13a97ff1d8
2 changed files with 250 additions and 21 deletions

231
src/hpk.rs.bak Normal file
View File

@ -0,0 +1,231 @@
#![warn(clippy::all, clippy::pedantic)]
use hpk::{tar::Archive, Database};
use zstd::Decoder;
mod cli;
use {
clap::ArgMatches,
cli::cli,
hpk::{CreationError, Creator, Dependency, InstallMessage, Installer, Message, Specs, Version},
indicatif::{ProgressBar, ProgressStyle},
ron::ser::{to_writer_pretty, PrettyConfig},
std::{
env,
error::Error,
ffi::OsStr,
fmt,
fs::File,
io::{self, BufWriter, ErrorKind},
path::{Path, PathBuf},
sync::mpsc,
thread,
},
};
static TEMPLATE: &str = "[ {prefix} ] {wide_bar}{pos:>5.cyan}/{len:5.green}{msg:>30}";
fn main() -> Result<(), Box<dyn Error>> {
let matches = cli().get_matches();
match matches.subcommand() {
Some(("create", matches)) => create(matches)?,
Some(("init", matches)) => {
let specsfile = init(matches)?;
println!("Created specsfile {}", specsfile.display());
if matches.get_flag("edit") {
cli::edit(specsfile.to_str().unwrap())?;
}
}
Some(("search", matches)) => search(matches)?,
Some(("install", matches)) => install(matches)?,
Some(("remove", matches)) => remove(matches)?,
Some(("upgrade", _)) => upgrade()?,
_ => {}
}
Ok(())
}
fn create(matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
let dir = PathBuf::from(matches.get_one::<String>("directory").unwrap().to_string());
let outdir = if let Some(d) = matches.get_one::<String>("output") {
PathBuf::from(&d.to_string())
} else {
env::current_dir()?
};
let mut specs = if let Some(s) = matches.get_one::<String>("specs") {
let path = PathBuf::from(s.to_string());
let fd = File::open(path)?;
ron::de::from_reader(fd)?
} else {
Specs::default()
};
if let Some(n) = matches.get_one::<String>("name") {
specs.name = n.to_string();
}
if let Some(v) = matches.get_one::<String>("package-version") {
specs.version = v.to_string().parse::<Version>()?;
}
if let Some(r) = matches.get_one::<u8>("release") {
specs.release = *r;
}
if let Some(deps) = matches.get_many::<String>("dependencies") {
for d in deps {
let d = Dependency {
name: d.to_string(),
version: (None, None),
};
specs.dependencies.push(d);
}
}
let creator = Creator::new(&dir, specs.clone())?;
let (sender, receiver) = mpsc::channel();
let len = creator.len();
let pb = ProgressBar::new(len as u64);
pb.set_style(ProgressStyle::with_template(TEMPLATE).unwrap());
pb.set_prefix("Adding files");
pb.println(format!(
"Creating package {}-{}_{}.tar.zst",
&specs.name, &specs.version, &specs.release
));
let handle = thread::spawn(move || {
for msg in receiver.iter() {
match msg {
Message::MemberAdded(s) => {
pb.set_message(s.split('/').last().unwrap().to_string());
pb.inc(1);
}
Message::Success(_s) => {
pb.inc(1);
pb.finish_and_clear();
}
}
}
Ok::<(), CreationError>(())
});
creator.create(&outdir, sender)?;
match handle.join() {
Ok(_) => {
println!("Package created successfully");
Ok(())
}
Err(e) => Err(io::Error::new(ErrorKind::Other, format!("{e:?}")).into()),
}
}
fn install_local<P: AsRef<OsStr> + fmt::Display>(
archive: P,
root: P,
) -> Result<(), Box<dyn Error>> {
let (sender, receiver) = mpsc::channel::<InstallMessage>();
let pb = ProgressBar::new(0);
pb.set_style(ProgressStyle::with_template(TEMPLATE).unwrap());
pb.println(format!("Installing package {}", archive,));
let fd = File::open(Path::new(&archive))?;
let decoder = Decoder::new(fd)?;
let ar = Archive::read(decoder)?;
let Some(node) = ar.get("package.ron") else {
return Err(io::Error::new(ErrorKind::Other, "not a valid package").into());
};
let mut package = vec![];
node.write(&mut package)?;
let package = ron::de::from_bytes(&package)?;
let db = Database::from_file(Some(PathBuf::from(&root)))?;
let mut queue = vec![];
package.dependencies.iter().for_each(|dep| {
if let Some(p) = db.packages.get(&dep.name) {
if !dep.satisfied(p) {
}
}
});
let installer = Installer::new_for_file(root, archive)?;
let handle = thread::spawn(move || {
for msg in receiver.iter() {
match msg {
InstallMessage::ArchiveLen(len) => pb.set_length(len.try_into().unwrap()),
InstallMessage::LinkCreated(link) => {
pb.inc(1);
}
InstallMessage::DirectoryCreated(dir) => {
pb.inc(1);
}
InstallMessage::FileInstalled(file) => {
pb.inc(1);
}
_ => {}
}
}
});
installer.install(sender)?;
match handle.join() {
Ok(hooks) => {
println!("hooks: {hooks:?}");
Ok(())
}
Err(_) => Err(io::Error::new(ErrorKind::Other, "Unknown thread error").into()),
}
}
fn init(matches: &ArgMatches) -> Result<PathBuf, Box<dyn Error>> {
let specsfile = PathBuf::from("package.specs");
let cfg = PrettyConfig::new().struct_names(true);
let buf = File::create(&specsfile)?;
let writer = BufWriter::new(buf);
let specs = create_specs(matches)?;
to_writer_pretty(writer, &specs, cfg)?;
Ok(specsfile)
}
fn search(_matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
fn install(_matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
fn remove(_matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
fn upgrade() -> Result<(), Box<dyn Error>> {
unimplemented!();
}
#[allow(clippy::cast_possible_truncation)]
pub fn create_specs(matches: &ArgMatches) -> Result<Specs, Box<dyn Error>> {
let mut specs = Specs::default();
let mut name: Option<String> = None;
let mut version: Option<Version> = None;
let cwd = env::current_dir()?;
if let Some(dir) = cwd.file_name().and_then(OsStr::to_str) {
if let Some((n, v)) = dir.split_once('-') {
name = Some(n.to_string());
if let Ok(v) = v.parse() {
version = Some(v);
}
}
}
if let Some(name) = matches.get_one::<String>("name") {
specs.name = name.to_string();
} else if let Some(n) = name {
specs.name = n;
}
if let Some(version) = matches.get_one::<String>("package-version") {
specs.version = version.parse()?;
} else if let Some(v) = version {
specs.version = v;
}
if let Some(release) = matches.get_one::<usize>("release") {
specs.release = *release as u8;
}
if let Some(deps) = matches.get_many::<String>("dependencies") {
let deps = deps
.map(|d| Dependency {
name: d.to_string(),
version: (None, None),
})
.collect::<Vec<Dependency>>();
specs.dependencies = deps;
}
Ok(specs)
}

View File

@ -77,7 +77,11 @@ impl<T: io::Read> Installer<T> {
Self { root, reader } Self { root, reader }
} }
pub fn install(self, sender: Sender<InstallMessage>) -> Result<Vec<Hooks>, InstallError> { pub fn install(
self,
hooks: &mut Vec<Hooks>,
sender: Sender<InstallMessage>,
) -> Result<Package, InstallError> {
let reader = Decoder::new(self.reader)?; let reader = Decoder::new(self.reader)?;
let mut archive = Archive::read(reader)?; let mut archive = Archive::read(reader)?;
sender.send(InstallMessage::ArchiveRead)?; sender.send(InstallMessage::ArchiveRead)?;
@ -88,16 +92,25 @@ impl<T: io::Read> Installer<T> {
let mut buf = vec![]; let mut buf = vec![];
pr_node.write(&mut buf)?; pr_node.write(&mut buf)?;
let package: Package = ron::de::from_bytes(&buf)?; let package: Package = ron::de::from_bytes(&buf)?;
let mut hooks = init_hooks(&package, &self.root); if let Some(ref users) = package.users {
let len = archive.nodes.len(); users
sender.send(InstallMessage::ArchiveLen(len))?; .iter()
.for_each(|u| hooks.push((u.clone(), Some(self.root.to_path_buf())).into()));
}
if let Some(ref groups) = package.groups {
groups
.iter()
.for_each(|g| hooks.push((g.clone(), Some(self.root.to_path_buf())).into()));
}
let mut db_pkgdir = crate::get_dbdir(Some(self.root.clone())); let mut db_pkgdir = crate::get_dbdir(Some(self.root.clone()));
db_pkgdir.push(&package.name); db_pkgdir.push(&package.name);
if !db_pkgdir.exists() { if !db_pkgdir.exists() {
fs::create_dir_all(&db_pkgdir)?; fs::create_dir_all(&db_pkgdir)?;
} }
pop_appstream(&mut archive, &db_pkgdir)?; pop_appstream(&mut archive, &db_pkgdir)?;
pop_pinstall(&mut archive, &mut hooks, &package.name, &self.root)?; pop_pinstall(&mut archive, hooks, &package.name, &self.root)?;
let len = archive.nodes.len();
sender.send(InstallMessage::ArchiveLen(len))?;
let mut db_file = db_pkgdir; let mut db_file = db_pkgdir;
db_file.push("package.ron"); db_file.push("package.ron");
if db_file.exists() { if db_file.exists() {
@ -166,25 +179,10 @@ impl<T: io::Read> Installer<T> {
sender.send(msg)?; sender.send(msg)?;
Ok::<(), InstallError>(()) Ok::<(), InstallError>(())
})?; })?;
Ok(hooks.into_inner()?) Ok(package)
} }
} }
fn init_hooks(package: &Package, root: &Path) -> Vec<Hooks> {
let mut hooks = Vec::<Hooks>::new();
if let Some(ref users) = package.users {
users
.iter()
.for_each(|u| hooks.push((u.clone(), Some(root.to_path_buf())).into()));
}
if let Some(ref groups) = package.groups {
groups
.iter()
.for_each(|g| hooks.push((g.clone(), Some(root.to_path_buf())).into()));
}
hooks
}
fn extract_entry( fn extract_entry(
entry: &Entry, entry: &Entry,
node: &Node, node: &Node,