diff --git a/src/cli.rs b/src/cli.rs index 797e70e..27bd367 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -178,6 +178,7 @@ pub fn install() -> Command { .help("install packages into a different root") .short('r') .long("root") + .default_value("/") .value_hint(ValueHint::DirPath) .num_args(1), ]) diff --git a/src/hpk.rs b/src/hpk.rs index 741d4c4..43c9257 100644 --- a/src/hpk.rs +++ b/src/hpk.rs @@ -1,16 +1,18 @@ #![warn(clippy::all, clippy::pedantic)] #![allow(clippy::missing_errors_doc)] + mod cli; use { clap::ArgMatches, cli::cli, - hpk::{CreationError, Creator, Dependency, Message, Specs, Version}, + 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::PathBuf, @@ -108,6 +110,43 @@ fn create(matches: &ArgMatches) -> Result<(), Box> { } } +fn install_local + fmt::Display>( + archive: P, + root: P, +) -> Result<(), Box> { + let (sender, receiver) = mpsc::channel(); + let pb = ProgressBar::new(0); + pb.set_style(ProgressStyle::with_template(TEMPLATE).unwrap()); + pb.println(format!("Installing package {}", archive,)); + 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); + } + _ => {} + } + } + }); + let mut hooks = vec![]; + installer.install(&mut hooks, sender)?; + match handle.join() { + Ok(package) => { + println!("hooks: {package:?}"); + Ok(()) + } + Err(_) => Err(io::Error::new(ErrorKind::Other, "Unknown thread error").into()), + } +} + fn init(matches: &ArgMatches) -> Result> { let specsfile = PathBuf::from("package.specs"); let cfg = PrettyConfig::new().struct_names(true); diff --git a/src/hpk.rs.bak b/src/hpk.rs.bak new file mode 100644 index 0000000..17dc354 --- /dev/null +++ b/src/hpk.rs.bak @@ -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> { + 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> { + let dir = PathBuf::from(matches.get_one::("directory").unwrap().to_string()); + let outdir = if let Some(d) = matches.get_one::("output") { + PathBuf::from(&d.to_string()) + } else { + env::current_dir()? + }; + let mut specs = if let Some(s) = matches.get_one::("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::("name") { + specs.name = n.to_string(); + } + if let Some(v) = matches.get_one::("package-version") { + specs.version = v.to_string().parse::()?; + } + if let Some(r) = matches.get_one::("release") { + specs.release = *r; + } + if let Some(deps) = matches.get_many::("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 + fmt::Display>( + archive: P, + root: P, +) -> Result<(), Box> { + let (sender, receiver) = mpsc::channel::(); + 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> { + 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> { + unimplemented!(); +} + +fn install(_matches: &ArgMatches) -> Result<(), Box> { + unimplemented!(); +} + +fn remove(_matches: &ArgMatches) -> Result<(), Box> { + unimplemented!(); +} + +fn upgrade() -> Result<(), Box> { + unimplemented!(); +} + +#[allow(clippy::cast_possible_truncation)] +pub fn create_specs(matches: &ArgMatches) -> Result> { + let mut specs = Specs::default(); + let mut name: Option = None; + let mut version: Option = 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::("name") { + specs.name = name.to_string(); + } else if let Some(n) = name { + specs.name = n; + } + if let Some(version) = matches.get_one::("package-version") { + specs.version = version.parse()?; + } else if let Some(v) = version { + specs.version = v; + } + if let Some(release) = matches.get_one::("release") { + specs.release = *release as u8; + } + if let Some(deps) = matches.get_many::("dependencies") { + let deps = deps + .map(|d| Dependency { + name: d.to_string(), + version: (None, None), + }) + .collect::>(); + specs.dependencies = deps; + } + Ok(specs) +} diff --git a/src/installer/mod.rs b/src/installer/mod.rs index 8aad365..c0c2dec 100644 --- a/src/installer/mod.rs +++ b/src/installer/mod.rs @@ -12,7 +12,7 @@ use { ffi::OsStr, fmt::Write as _, fs::{self, DirBuilder, File}, - io::{self, BufWriter, Write}, + io::{self, BufReader, BufWriter, Write}, os::{ self, unix::{fs::DirBuilderExt, prelude::OpenOptionsExt}, @@ -55,24 +55,32 @@ pub enum InstallMessage { pub struct Installer { /// The filesystem root under which to install this package. /// Normally this is "/". - path: PathBuf, - package: Package, + root: PathBuf, reader: T, } +impl Installer>> { + /// creates a new installer from an archive path + pub fn new_for_file>(root: P, archive: P) -> Result { + let root = Path::new(&root).to_path_buf(); + let fd = File::open(Path::new(&archive))?; + let decoder = Decoder::new(fd)?; + Ok(Self::new(root, decoder)) + } +} + impl Installer { - /// Creates a new installer from an open archive. - pub fn new>(path: P, package: Package, reader: T) -> Self { - let path = Path::new(&path).to_path_buf(); - Self { - path, - package, - reader, - } + /// Creates a new installer from an open archive stream + pub fn new>(root: P, reader: T) -> Self { + let root = Path::new(&root).to_path_buf(); + Self { root, reader } } - pub fn install(mut self, sender: Sender) -> Result, InstallError> { - let mut hooks = self.init_hooks(); + pub fn install( + self, + hooks: &mut Vec, + sender: Sender, + ) -> Result { let reader = Decoder::new(self.reader)?; let mut archive = Archive::read(reader)?; sender.send(InstallMessage::ArchiveRead)?; @@ -80,15 +88,28 @@ impl Installer { Some(node) => node, None => return Err(InstallError::MissingManifest), }; - let len = archive.nodes.len(); - sender.send(InstallMessage::ArchiveLen(len))?; - let mut db_pkgdir = crate::get_dbdir(Some(self.path.clone())); - db_pkgdir.push(&self.package.name); + let mut buf = vec![]; + pr_node.write(&mut buf)?; + let package: Package = ron::de::from_bytes(&buf)?; + if let Some(ref users) = package.users { + users + .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())); + db_pkgdir.push(&package.name); if !db_pkgdir.exists() { fs::create_dir_all(&db_pkgdir)?; } pop_appstream(&mut archive, &db_pkgdir)?; - pop_pinstall(&mut archive, &mut hooks, &self.package.name, &self.path)?; + 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; db_file.push("package.ron"); if db_file.exists() { @@ -97,15 +118,14 @@ impl Installer { let fd = File::create(&db_file)?; let writer = BufWriter::new(fd); pr_node.write(writer)?; - let path = &self.path; - let package = &self.package; + let root = &self.root; let hooks = Mutex::new(hooks); let s = sender.clone(); archive .nodes .par_iter() .try_for_each_with(s, |sender, node| { - let mut path = path.clone(); + let mut path = root.clone(); let fpath = node.header.file_path()?; if let Some(s) = node.header.prefix() { if s.contains("/share/man/") { @@ -158,22 +178,7 @@ impl Installer { sender.send(msg)?; Ok::<(), InstallError>(()) })?; - Ok(hooks.into_inner()?) - } - - fn init_hooks(&mut self) -> Vec { - let mut hooks = Vec::::new(); - if let Some(ref users) = self.package.users { - users - .iter() - .for_each(|u| hooks.push((u.clone(), Some(self.path.clone())).into())); - } - if let Some(ref groups) = self.package.groups { - groups - .iter() - .for_each(|g| hooks.push((g.clone(), Some(self.path.clone())).into())); - } - hooks + Ok(package) } } @@ -256,6 +261,7 @@ fn pop_pinstall( mod error { use super::InstallMessage; use crate::{tar, Hooks}; + use ron::error::SpannedError; use std::{ error::Error, fmt, io, @@ -270,6 +276,7 @@ mod error { pub enum InstallError { Fmt(fmt::Error), IO(io::Error), + RonError(SpannedError), SendError(SendError), Tar(tar::Error), ChecksumMismatch, @@ -291,6 +298,7 @@ mod error { Self::IO(e) => Some(e), Self::Tar(e) => Some(e), Self::SendError(e) => Some(e), + Self::RonError(e) => Some(e), _ => None, } } @@ -326,6 +334,12 @@ mod error { } } + impl From for InstallError { + fn from(value: SpannedError) -> Self { + Self::RonError(value) + } + } + impl From for InstallError { fn from(_value: FromUtf8Error) -> Self { Self::Utf8