diff --git a/Cargo.lock b/Cargo.lock index 4f1ca0a..4a9767f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -398,6 +398,7 @@ dependencies = [ "chrono", "clap", "cli", + "deku", "rayon", "ron", "serde", diff --git a/Cargo.toml b/Cargo.toml index f670f59..05b2e65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,11 @@ path = "src/hpk.rs" [workspace.dependencies] clap = "4.1" +deku = "0.16" [dependencies] clap = { workspace = true } +deku.workspace = true cli = { path = "cli" } rayon = "1.7" ron = "0.8" diff --git a/src/hooks/mod.rs b/src/hooks/mod.rs index c8196d0..dc60598 100644 --- a/src/hooks/mod.rs +++ b/src/hooks/mod.rs @@ -20,7 +20,7 @@ impl Hooks { match self { Self::Man => makeinfo(), Self::GlibSchema => compile_schemas(), - Self::Info(path) => install_info(&path), + Self::Info(path) => install_info(path), } } } diff --git a/src/item/mod.rs b/src/item/mod.rs index a20d559..c25460c 100644 --- a/src/item/mod.rs +++ b/src/item/mod.rs @@ -2,6 +2,7 @@ use crate::Entry; use sha2::{Digest, Sha256}; use std::{ error::Error, + ffi::OsStr, fmt::Write, fs, io::Read, @@ -13,13 +14,14 @@ use tar::{Node, Owner}; #[derive(Clone, Debug)] pub struct Item { pub entry: Entry, - pub node: Node, + pub data: Vec, } impl Item { pub fn try_create(path: &Path) -> Result> { - let meta = fs::metadata(path)?; - let filename = format!("/{}", path.display()); + let path = fix_path(path); + let meta = fs::metadata(&path)?; + let filename = format!("{}", path.display()); let owner = Some(Owner::default()); if meta.is_file() { let mut data = vec![]; @@ -41,24 +43,44 @@ impl Item { mode, size, }, - node: Node::read_data_to_tar(&data, &filename, &meta, owner)?, + data: Node::read_data_to_tar(&data, &filename, &meta, owner)?.to_vec()?, }) } else if meta.is_dir() { let mode = meta.mode(); let path = PathBuf::from(&filename); Ok(Self { entry: Entry::Directory { path, mode }, - node: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?, + data: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?.to_vec()?, }) } else if meta.is_symlink() { let target = fs::read_link(path)?; let path = PathBuf::from(&filename); Ok(Self { entry: Entry::Link { path, target }, - node: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?, + data: Node::read_data_to_tar(&[0; 0], &filename, &meta, owner)?.to_vec()?, }) } else { unreachable!(); } } } + +fn fix_path(path: &Path) -> PathBuf { + if path.is_file() || path.is_symlink() { + match path.ancestors().last().and_then(Path::to_str) { + Some("etc" | "var") => { + let ext = if let Some(x) = path.extension().and_then(OsStr::to_str) { + format!("{x}.new") + } else { + "new".to_string() + }; + let mut path = path.to_path_buf(); + path.set_extension(ext); + path + } + _ => path.to_path_buf(), + } + } else { + path.to_path_buf() + } +} diff --git a/src/lib.rs b/src/lib.rs index 385cbc5..5bbcaf3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ mod version; pub use { hooks::Hooks, item::Item, - package::{Dependency, Package, Specs}, + package::{create as create_package, Dependency, Package, Specs}, plist::*, repository::Repository, tar, diff --git a/src/package/mod.rs b/src/package/mod.rs index c6a4f82..972064a 100644 --- a/src/package/mod.rs +++ b/src/package/mod.rs @@ -1,9 +1,25 @@ +use crate::Item; +use std::path::PathBuf; +use zstd::Encoder; + mod dependency; mod specs; + use { - crate::{Plist, Version}, + crate::{Entry, Plist, Version}, + rayon::prelude::*, + ron::ser::{to_string_pretty, PrettyConfig}, serde::{Deserialize, Serialize}, + std::{ + error::Error, + fs::File, + io::{BufWriter, Write}, + path::Path, + }, + tar::Node, + walkdir::WalkDir, }; + pub use {dependency::Dependency, specs::Specs}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] @@ -18,7 +34,7 @@ pub struct Group { pub gid: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] pub struct Package { /// The name of the package minus all version information pub name: String, @@ -48,3 +64,83 @@ pub struct Package { /// an optional post installation shell script to be run pub post_install: Option, } + +impl From for Package { + fn from(value: Specs) -> Self { + let mut package = Package::default(); + package.name = value.name; + package.version = value.version; + package.release = value.release; + package.description = value.description; + package.long_description = value.long_description; + package.appstream_data = value.appstream_data; + package.dependencies = value.dependencies; + package.users = value.users; + package.groups = value.groups; + package.post_install = value.post_install; + package + } +} + +impl Package { + fn as_ron(&self) -> Result { + let cfg = PrettyConfig::new().struct_names(true); + to_string_pretty(self, cfg) + } + + pub fn save_ron_and_create_tar_node(&self) -> Result> { + let buf = File::open("package.ron")?; + let meta = buf.metadata()?; + let s = self.as_ron()?; + let mut writer = BufWriter::new(buf); + writer.write_all(s.as_bytes())?; + let node = Node::read_data_to_tar(s.as_bytes(), "package.ron", &meta, None)?; + Ok(node) + } + + pub fn fullname(&self) -> String { + format!("{}-{}_{}", self.name, self.version, self.release) + } +} + +pub fn create(path: &Path, specs: Specs) -> Result<(), Box> { + let mut items = WalkDir::new(path) + .into_iter() + .collect::>() + .par_iter() + .filter(|x| x.is_ok()) + .filter_map(|x| { + Item::try_create(x.as_ref().unwrap().path().to_path_buf().as_path()).ok() + }) + .collect::>(); + let mut plist = Plist::default(); + let mut archive = vec![]; + let mut totalsize = 0; + while let Some(item) = items.pop() { + match &item.entry { + Entry::File { + path: _, + sha256sum: _, + mode: _, + size, + } => totalsize += size, + _ => {} + } + plist.entries.push(item.entry); + archive.extend(item.data); + } + let mut package: Package = specs.into(); + package.plist = plist; + package.size = totalsize; + let node = package.save_ron_and_create_tar_node()?.to_vec()?; + archive.extend(node); + archive.write_all(&[0; 9216])?; + let name = package.fullname(); + let mut path = PathBuf::from(&name); + path.set_extension("tar.zst"); + let fd = File::open(&path)?; + let mut writer = Encoder::new(fd, 0)?; + writer.write_all(&archive)?; + let _fd = writer.finish()?; + Ok(()) +} diff --git a/src/package/specs.rs b/src/package/specs.rs index 45b460d..d3943ea 100644 --- a/src/package/specs.rs +++ b/src/package/specs.rs @@ -3,7 +3,7 @@ use { crate::{Dependency, Version}, clap::ArgMatches, serde::{Deserialize, Serialize}, - std::{env, error::Error}, + std::{env, error::Error, ffi::OsStr}, }; #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] @@ -35,34 +35,29 @@ pub struct Specs { impl TryFrom<&ArgMatches> for Specs { type Error = Box; + #[allow(clippy::cast_possible_truncation)] fn try_from(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() { - if let Some(n) = dir.to_str() { - if let Some((n, v)) = n.split_once('-') { - name = Some(n.to_string()); - if let Ok(v) = v.parse() { - version = Some(v); - } + 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; - } + } 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; - } + } else if let Some(v) = version { + specs.version = v; } if let Some(release) = matches.get_one::("release") { specs.release = *release as u8; diff --git a/src/plist/mod.rs b/src/plist/mod.rs index a7c1fff..3551aca 100644 --- a/src/plist/mod.rs +++ b/src/plist/mod.rs @@ -13,7 +13,7 @@ use { walkdir::WalkDir, }; -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] pub struct Plist { pub entries: Vec, } diff --git a/src/version/mod.rs b/src/version/mod.rs index d86bb07..7d7004d 100644 --- a/src/version/mod.rs +++ b/src/version/mod.rs @@ -33,6 +33,33 @@ impl Default for Version { } } +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Number(n) => write!(f, "{n}"), + Self::SemVer { + major, + minor, + patch, + } => { + let v = SemVer { + major: *major, + minor: *minor, + patch: *patch, + }; + write!(f, "{v}") + } + Self::Git { hash, datetime } => { + let v = GitRev { + hash: hash.to_owned(), + datetime: *datetime, + }; + write!(f, "{v}") + } + } + } +} + impl From for Version { fn from(value: SemVer) -> Self { Self::SemVer { @@ -45,7 +72,10 @@ impl From for Version { impl From for Version { fn from(value: GitRev) -> Self { - Self::Git { hash: value.hash, datetime: value.datetime } + Self::Git { + hash: value.hash, + datetime: value.datetime, + } } } @@ -56,6 +86,7 @@ impl From for Version { } impl PartialOrd for Version { + #[allow(clippy::many_single_char_names)] fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { (Self::Number(s), Self::Number(o)) => s.partial_cmp(o), diff --git a/src/version/semver.rs b/src/version/semver.rs index 8425f03..474bb31 100644 --- a/src/version/semver.rs +++ b/src/version/semver.rs @@ -70,7 +70,7 @@ impl FromStr for SemVer { let split = s.split('.').collect::>(); match split.len() { 3 => { - let major = split.get(0).unwrap().parse::()?; + let major = split.first().unwrap().parse::()?; let minor = split.get(1).unwrap().parse::()?; let patch = split.get(2).unwrap().parse::()?; Ok(Self { diff --git a/tar/Cargo.toml b/tar/Cargo.toml index e031ebb..14443b9 100644 --- a/tar/Cargo.toml +++ b/tar/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" license = "GPL-3.0-only" [dependencies] -deku = "0.16" +deku = { workspace = true } libc = "0.2" thiserror = "1.0.40" diff --git a/tar/src/lib.rs b/tar/src/lib.rs index a8f22eb..d6c2752 100644 --- a/tar/src/lib.rs +++ b/tar/src/lib.rs @@ -1,6 +1,6 @@ use std::{ fs::File, - io::{self, BufReader}, + io::{self, BufReader, Write}, }; mod error; @@ -12,11 +12,21 @@ pub use { node::Node, }; +#[derive(Default)] pub struct Archive { pub nodes: Vec, } impl Archive { + pub fn to_vec(self) -> Result, Error> { + let mut buf = vec![]; + for node in self.nodes { + buf.extend(node.to_vec()?); + } + buf.write_all(&[0; 9216])?; + Ok(buf) + } + /// Write out a vector of `TarNodes` to a file or something that implements ``std::io::Write`` and ``std::io::Copy``. /// /// # Example diff --git a/tar/src/node.rs b/tar/src/node.rs index a4aa2f8..eb1b6c2 100644 --- a/tar/src/node.rs +++ b/tar/src/node.rs @@ -13,6 +13,14 @@ pub struct Node { } impl Node { + pub fn to_vec(self) -> Result, DekuError> { + let mut buf = self.header.to_bytes()?; + for block in self.data { + buf.extend(block.to_vec()); + } + Ok(buf) + } + /// Write out a single file within the tar to a file or something with a ``std::io::Write`` trait. pub fn write(self, mut input: T) -> Result { input.write_all(&self.header.to_bytes()?)?; @@ -26,7 +34,7 @@ impl Node { } /// Read a TarNode in from a file or something with a ``std::io::Read`` trait. - pub fn read(mut input: T) -> Result { + pub fn read(mut input: T) -> Result { let mut h = vec![0u8; 512]; input.read_exact(&mut h)?; @@ -49,7 +57,7 @@ impl Node { } /// Open and read a file from the ``filename`` argument to a TarNode. - pub fn read_file_to_tar(filename: String) -> Result { + pub fn read_file_to_tar(filename: String) -> Result { let header = Header::new(&filename)?; if header.link_indicator[0] != FileType::Normal as u8 { return Ok(Node { @@ -72,7 +80,7 @@ impl Node { filename: &str, meta: &Metadata, owner: Option, - ) -> Result { + ) -> Result { let header = Header::new_from_meta(filename, meta, owner)?; if header.link_indicator[0] != FileType::Normal as u8 { return Ok(Node {