Added package Creator struct, with create method to process package

entries fully multithreaded including compressing into the archive, as
well as passing messages through an mpsc::channel() interface for use in
a background thread in various frontends. Much better suited to task
than previous `create_package` function. Untested.
This commit is contained in:
Nathan Fisher 2023-03-27 03:18:20 -04:00
parent 33955fa836
commit 39b8664c9e
6 changed files with 132 additions and 6 deletions

View File

@ -1,12 +1,17 @@
use { use {
clap_complete::{generate_to, shells}, clap_complete::{generate_to, shells},
clap_complete_nushell::Nushell, clap_complete_nushell::Nushell,
std::{error::Error, fs, ops::Deref, path::{Path, PathBuf}}, std::{
error::Error,
fs,
ops::Deref,
path::{Path, PathBuf},
},
}; };
static PROGNAME: &str = "hpk"; static PROGNAME: &str = "hpk";
fn gencomp(outdir: PathBuf, gen:&str) -> Result<(), Box<dyn Error>> { fn gencomp(outdir: PathBuf, gen: &str) -> Result<(), Box<dyn Error>> {
let mut cmd = cli::cli(); let mut cmd = cli::cli();
let path = match gen { let path = match gen {
"bash" => generate_to(shells::Bash, &mut cmd, PROGNAME, outdir)?, "bash" => generate_to(shells::Bash, &mut cmd, PROGNAME, outdir)?,

View File

@ -17,10 +17,14 @@ fn main() -> Result<(), Box<dyn Error>> {
Arg::new("output") Arg::new("output")
.help("the output directory for the installation") .help("the output directory for the installation")
.required(true) .required(true)
.num_args(1) .num_args(1),
]) ])
.get_matches(); .get_matches();
let outdir = matches.get_one::<String>("output").unwrap().to_string().into(); let outdir = matches
.get_one::<String>("output")
.unwrap()
.to_string()
.into();
let arch = matches.get_one::<String>("arch").map(|x| x.to_string()); let arch = matches.get_one::<String>("arch").map(|x| x.to_string());
bootstrap::install(outdir, arch)?; bootstrap::install(outdir, arch)?;
Ok(()) Ok(())

115
src/creator/mod.rs Normal file
View File

@ -0,0 +1,115 @@
use std::{
borrow::BorrowMut,
fs::File,
io::Write,
sync::{
atomic::{AtomicUsize, Ordering},
mpsc::{self, Receiver},
Mutex,
},
};
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
use walkdir::WalkDir;
use zstd::Encoder;
use crate::{Entry, Item, Plist, Package};
use {
crate::Specs,
std::{env, error::Error, io, path::Path, sync::mpsc::Sender},
walkdir::DirEntry,
};
pub enum Message {
Success(String),
Failure(String),
}
pub struct Creator {
entries: Vec<Result<DirEntry, walkdir::Error>>,
specs: Specs,
sender: Sender<Message>,
receiver: Receiver<Message>,
}
impl Creator {
pub fn new(path: &Path, specs: Specs) -> Result<Self, io::Error> {
env::set_current_dir(path)?;
let entries = WalkDir::new(".").into_iter().collect::<Vec<_>>();
let (sender, receiver) = mpsc::channel();
Ok(Self {
entries,
specs,
sender,
receiver,
})
}
pub fn create(self, outdir: &Path) -> Result<(), Box<dyn Error>> {
let plist = Mutex::new(Plist::default());
let totalsize: AtomicUsize = 0.into();
let fullname = format!(
"{}-{}_{}",
&self.specs.name, self.specs.version, self.specs.release
);
let mut archive = outdir.to_path_buf();
archive.push(&fullname);
archive.set_extension("tar.zst");
let fd = File::create(&archive)?;
let writer = Mutex::new(Encoder::new(fd, 0)?);
let sender = Mutex::new(self.sender.clone());
self.entries
.par_iter()
.filter(|x| x.is_ok())
.map(|x| x.as_ref().unwrap())
.for_each(|x| {
let sender = sender.lock().unwrap().clone();
if let Ok(item) = Item::try_create(x.path().to_path_buf().as_path()) {
if let Entry::File {
path: _,
sha256sum: _,
mode: _,
size,
} = &item.entry
{
totalsize.fetch_add(*size, Ordering::Release);
}
let path = match item.entry.clone() {
Entry::File {
path,
sha256sum: _,
mode: _,
size: _,
} => path.clone(),
Entry::Link { path, target: _ } => path.clone(),
Entry::Directory { path, mode: _ } => path.clone(),
};
plist.lock().unwrap().borrow_mut().entries.push(item.entry);
match writer.lock().unwrap().borrow_mut().write_all(&item.data) {
Ok(_) => sender
.send(Message::Success(format!(
"{} added to archive",
path.display()
)))
.expect("couldn't send message"),
Err(e) => sender
.send(Message::Failure(format!("{e}")))
.expect("couldn't send message"),
}
} else {
sender
.send(Message::Failure("Could not process DirEntry".to_string()))
.expect("could not send message");
}
});
let mut package: Package = self.specs.into();
package.size = totalsize.into_inner();
let node = package.save_ron_and_create_tar_node(outdir)?;
let mut writer = writer.into_inner()?;
writer.write_all(&node.to_vec()?)?;
let _fd = writer.finish()?;
self.sender.send(Message::Success(format!("{} saved", archive.display())))?;
Ok(())
}
}

View File

@ -1,5 +1,5 @@
use { use {
crate::{Repository, Package, Version}, crate::{Package, Repository, Version},
serde::{Deserialize, Serialize}, serde::{Deserialize, Serialize},
std::{collections::HashMap, error::Error}, std::{collections::HashMap, error::Error},
url::Url, url::Url,

View File

@ -1,4 +1,5 @@
#![warn(clippy::all, clippy::pedantic)] #![warn(clippy::all, clippy::pedantic)]
mod creator;
mod db; mod db;
mod hooks; mod hooks;
mod item; mod item;
@ -8,6 +9,7 @@ mod repository;
mod version; mod version;
pub use { pub use {
creator::Creator,
db::Database, db::Database,
hooks::Hooks, hooks::Hooks,
item::Item, item::Item,

View File

@ -92,7 +92,7 @@ impl Package {
to_string_pretty(self, cfg) to_string_pretty(self, cfg)
} }
fn save_ron_and_create_tar_node(&self, outdir: &Path) -> Result<Node, Box<dyn Error>> { pub(crate) fn save_ron_and_create_tar_node(&self, outdir: &Path) -> Result<Node, Box<dyn Error>> {
if !outdir.exists() { if !outdir.exists() {
fs::create_dir_all(outdir)?; fs::create_dir_all(outdir)?;
} }