939 lines
28 KiB
Rust
939 lines
28 KiB
Rust
use std::{
|
|
io::Write,
|
|
path::{Path, PathBuf},
|
|
process::{Command, Stdio},
|
|
};
|
|
|
|
use based::get_pg;
|
|
use serde::Serialize;
|
|
use sqlx::FromRow;
|
|
|
|
use super::{Repository, arch::Architecture};
|
|
|
|
/// General Package
|
|
#[derive(Debug, Clone)]
|
|
pub struct Package {
|
|
/// Repository of the package
|
|
pub repo: String,
|
|
/// `Architecture` of the package
|
|
pub arch: Architecture,
|
|
/// Name of the package
|
|
pub name: String,
|
|
pub rel: String,
|
|
/// Version of the package
|
|
pub version: Option<String>,
|
|
/// Compression used
|
|
pub compression: Compression,
|
|
}
|
|
|
|
pub struct PackageFile {
|
|
pub path: PathBuf,
|
|
pub compression: Compression,
|
|
}
|
|
|
|
impl PackageFile {
|
|
pub fn new(path: PathBuf) -> Self {
|
|
Self {
|
|
path: path.clone(),
|
|
compression: Compression::from_filename(path.to_str().unwrap()).unwrap(),
|
|
}
|
|
}
|
|
|
|
pub fn pkginfo(&self) -> PackageInfo {
|
|
let content = read_file_tar(&self.path, ".PKGINFO", self.compression.clone()).unwrap();
|
|
let keys = Package::pkginfo_from_str(&content);
|
|
PackageInfo::new(keys)
|
|
}
|
|
|
|
pub fn pkginfo_raw(&self) -> Vec<(String, String)> {
|
|
let content = read_file_tar(&self.path, ".PKGINFO", self.compression.clone()).unwrap();
|
|
Package::pkginfo_from_str(&content)
|
|
}
|
|
}
|
|
|
|
impl Package {
|
|
/// Create a new package
|
|
pub fn new(
|
|
repo: &str,
|
|
arch: Architecture,
|
|
pkg_name: &str,
|
|
version: &str,
|
|
rel: String,
|
|
compression: Compression,
|
|
) -> Self {
|
|
let pkg = Package {
|
|
repo: repo.to_string(),
|
|
arch,
|
|
name: pkg_name.to_string(),
|
|
rel: rel,
|
|
version: Some(version.to_string()),
|
|
compression,
|
|
};
|
|
|
|
std::fs::create_dir_all(pkg.base_path()).unwrap();
|
|
pkg
|
|
}
|
|
|
|
pub fn version(ver: &str) -> (String, String) {
|
|
let mut splitted = ver.split('-').collect::<Vec<_>>();
|
|
let rel = splitted.pop().unwrap();
|
|
let ver = splitted.join("-");
|
|
(ver, rel.parse().unwrap())
|
|
}
|
|
|
|
pub fn install_script(&self) -> Option<String> {
|
|
let pkg = self.base_path().join(self.file_name());
|
|
read_file_tar(&pkg, ".INSTALL", self.compression.clone())
|
|
}
|
|
|
|
pub fn file_list(&self) -> Vec<String> {
|
|
list_tar_file(&self.base_path().join(self.file_name()))
|
|
.unwrap()
|
|
.into_iter()
|
|
.filter(|x| !x.ends_with("/"))
|
|
.collect()
|
|
}
|
|
|
|
pub fn binaries(&self) -> Vec<String> {
|
|
list_tar_file(&self.base_path().join(self.file_name()))
|
|
.unwrap_or_default()
|
|
.into_iter()
|
|
.filter_map(|x| {
|
|
let mut paths: Vec<_> = x.split("/").collect();
|
|
paths.reverse();
|
|
let parent = paths.get(1)?;
|
|
if (*parent == "bin" || *parent == "sbin") && !x.ends_with("/") {
|
|
return Some(x);
|
|
}
|
|
|
|
None
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn pkginfo_from_str(content: &str) -> Vec<(String, String)> {
|
|
let mut ret: Vec<(String, Vec<String>)> = Vec::new();
|
|
|
|
for line in content.split("\n") {
|
|
if line.starts_with('#') || line.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
let (key, val) = line.split_once(" = ").unwrap();
|
|
|
|
if let Some(e) = ret.iter_mut().find(|x| x.0 == key) {
|
|
e.1.push(val.trim().to_string());
|
|
} else {
|
|
ret.push((key.to_string(), vec![val.trim().to_string()]));
|
|
}
|
|
}
|
|
|
|
let mut ret: Vec<_> = ret.into_iter().map(|x| (x.0, x.1.join(";"))).collect();
|
|
ret.sort_by(|a, b| a.0.cmp(&b.0));
|
|
|
|
ret
|
|
}
|
|
|
|
pub fn pkginfo(&self) -> PackageInfo {
|
|
PackageFile::new(self.base_path().join(self.file_name())).pkginfo()
|
|
}
|
|
|
|
pub fn pkginfo_raw(&self) -> Vec<(String, String)> {
|
|
PackageFile::new(self.base_path().join(self.file_name())).pkginfo_raw()
|
|
}
|
|
|
|
pub fn arch(&self) -> Vec<Architecture> {
|
|
let mut ret = Vec::new();
|
|
for a in [
|
|
Architecture::x86_64,
|
|
Architecture::aarch64,
|
|
Architecture::any,
|
|
] {
|
|
let check_pkg = self.switch_arch(a.clone());
|
|
if check_pkg.exists() {
|
|
ret.push(a);
|
|
}
|
|
}
|
|
|
|
ret
|
|
}
|
|
|
|
/// Extract values from a package filename
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// use pacco::pkg::Package;
|
|
///
|
|
/// let (name, version, rel, arch, compress) = Package::extract_pkg_name("pkg-ver-rel-x86_64.pkg.tar.zst").unwrap();
|
|
/// assert_eq!(name, "pkg");
|
|
/// assert_eq!(version, "ver");
|
|
/// assert_eq!(rel, "rel");
|
|
/// assert_eq!(arch, pacco::pkg::arch::Architecture::x86_64);
|
|
/// assert_eq!(compress, pacco::pkg::package::Compression::Zstd);
|
|
///
|
|
/// let (name, version, rel, arch, compress) = Package::extract_pkg_name("my-pkg-ver-rel-x86_64.pkg.tar.xz").unwrap();
|
|
/// assert_eq!(name, "my-pkg");
|
|
/// assert_eq!(version, "ver");
|
|
/// assert_eq!(rel, "rel");
|
|
/// assert_eq!(arch, pacco::pkg::arch::Architecture::x86_64);
|
|
/// assert_eq!(compress, pacco::pkg::package::Compression::Xz);
|
|
/// ```
|
|
pub fn extract_pkg_name(
|
|
file_name: &str,
|
|
) -> Option<(String, String, String, Architecture, Compression)> {
|
|
// Extract (assuming the filename is "<pkg_name>-<version>-<relation>-<arch>.pkg.tar.zst")
|
|
let file_name = file_name.trim_end_matches(".sig").to_string();
|
|
|
|
let mut splitted = file_name.split('-').collect::<Vec<&str>>();
|
|
|
|
let arch = splitted.pop()?;
|
|
|
|
let compression = Compression::from_filename(arch);
|
|
assert!(compression.is_some());
|
|
let compression = compression.unwrap();
|
|
|
|
let arch = match compression {
|
|
Compression::Zstd => arch.trim_end_matches(".pkg.tar.zst"),
|
|
Compression::Xz => arch.trim_end_matches(".pkg.tar.xz"),
|
|
};
|
|
|
|
let relation = splitted.pop()?;
|
|
let version = splitted.pop()?;
|
|
|
|
let pkg_name = splitted.join("-");
|
|
|
|
Some((
|
|
pkg_name,
|
|
version.to_string(),
|
|
relation.to_string(),
|
|
Architecture::parse(arch)?,
|
|
compression,
|
|
))
|
|
}
|
|
|
|
/// Parse a pkg filename
|
|
pub fn from_filename(repo: &str, file_name: &str) -> Package {
|
|
let (pkg_name, version, rel, arch, compression) =
|
|
Package::extract_pkg_name(file_name).unwrap();
|
|
Self {
|
|
repo: repo.to_string(),
|
|
arch,
|
|
name: pkg_name,
|
|
rel: rel.parse().unwrap(),
|
|
version: Some(version.to_string()),
|
|
compression,
|
|
}
|
|
}
|
|
|
|
/// Find a package with latest version
|
|
pub fn find(repo: &str, arch: Architecture, pkg_name: &str) -> Option<Self> {
|
|
if let Some(pkg) = Self::find_compressed(repo, arch.clone(), pkg_name, Compression::Zstd) {
|
|
return Some(pkg);
|
|
}
|
|
if let Some(pkg) = Self::find_compressed(repo, arch, pkg_name, Compression::Xz) {
|
|
return Some(pkg);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Find a package with latest version
|
|
fn find_compressed(
|
|
repo: &str,
|
|
arch: Architecture,
|
|
pkg_name: &str,
|
|
compression: Compression,
|
|
) -> Option<Self> {
|
|
let mut base = Package {
|
|
repo: repo.to_string(),
|
|
arch,
|
|
name: pkg_name.to_string(),
|
|
rel: 1.to_string(),
|
|
version: None,
|
|
compression,
|
|
};
|
|
|
|
let versions = base.versions();
|
|
let ver = versions.first()?;
|
|
let (ver, rel) = Package::version(&ver);
|
|
|
|
base.version = Some(ver.clone());
|
|
base.rel = rel;
|
|
|
|
Some(base)
|
|
}
|
|
|
|
pub fn man_entries(&self) -> Vec<String> {
|
|
let files = self.file_list();
|
|
files
|
|
.into_iter()
|
|
.filter(|x| x.starts_with("usr/share/man"))
|
|
.map(|x| {
|
|
x.trim_start_matches("usr/share/man/")
|
|
.trim_end_matches(".gz")
|
|
.to_string()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn kernel_modules(&self) -> Vec<String> {
|
|
let files = self.file_list();
|
|
files
|
|
.into_iter()
|
|
.filter(|x| x.starts_with("usr/lib/modules"))
|
|
.map(|x| {
|
|
x.trim_start_matches("usr/lib/modules/")
|
|
.trim_end_matches(".zst")
|
|
.to_string()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn fonts(&self) -> Vec<String> {
|
|
let files = self.file_list();
|
|
files
|
|
.into_iter()
|
|
.filter(|x| x.starts_with("usr/share/fonts"))
|
|
.map(|x| x.trim_start_matches("usr/share/fonts/").to_string())
|
|
.collect()
|
|
}
|
|
|
|
pub fn firmware(&self) -> Vec<String> {
|
|
let files = self.file_list();
|
|
files
|
|
.into_iter()
|
|
.filter(|x| x.starts_with("usr/lib/firmware"))
|
|
.map(|x| {
|
|
x.trim_start_matches("usr/lib/firmware/")
|
|
.trim_end_matches(".zst")
|
|
.to_string()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn keyrings(&self) -> Vec<String> {
|
|
let files = self.file_list();
|
|
files
|
|
.into_iter()
|
|
.filter(|x| x.starts_with("usr/share/pacman/keyrings"))
|
|
.map(|x| {
|
|
x.trim_start_matches("usr/share/pacman/keyrings/")
|
|
.to_string()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn shared_objects(&self) -> Vec<String> {
|
|
list_tar_file(&self.base_path().join(self.file_name()))
|
|
.unwrap_or_default()
|
|
.into_iter()
|
|
.filter(|x| {
|
|
let file_name = x.split("/").last().unwrap();
|
|
file_name.contains(".so.") || file_name.ends_with(".so")
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn icons(&self) -> Vec<String> {
|
|
let files = self.file_list();
|
|
files
|
|
.into_iter()
|
|
.filter(|x| x.starts_with("usr/share/icons"))
|
|
.map(|x| x.trim_start_matches("usr/share/icons/").to_string())
|
|
.collect()
|
|
}
|
|
|
|
pub fn etc_entries(&self) -> Vec<String> {
|
|
let files = self.file_list();
|
|
files.into_iter().filter(|x| x.starts_with("etc")).collect()
|
|
}
|
|
|
|
pub fn systemd_units(&self) -> Vec<String> {
|
|
// TODO : Extract unit infos
|
|
list_tar_file(&self.base_path().join(self.file_name()))
|
|
.unwrap_or_default()
|
|
.into_iter()
|
|
.filter(|x| {
|
|
let ext = x.split(".").last().unwrap();
|
|
ext == "service" || ext == "timer" || ext == "mount" || ext == "socket"
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn readmes(&self) -> Vec<(String, String)> {
|
|
let pkg_file = self.base_path().join(self.file_name());
|
|
let files = self.file_list();
|
|
files
|
|
.into_iter()
|
|
.filter(|x| {
|
|
let file_name = x.split("/").last().unwrap();
|
|
let cleaned = file_name.trim_end_matches(".md").trim_end_matches(".txt");
|
|
cleaned == "README"
|
|
})
|
|
.map(|x| {
|
|
let content = read_file_tar(&pkg_file, &x, self.compression.clone()).unwrap();
|
|
(x, content)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn pacman_hooks(&self) -> Vec<(String, String)> {
|
|
let pkg_file = self.base_path().join(self.file_name());
|
|
let files = self.file_list();
|
|
files
|
|
.into_iter()
|
|
.filter(|x| {
|
|
x.starts_with("etc/pacman.d/hooks/") || x.starts_with("usr/share/libalpm/hooks/")
|
|
})
|
|
.map(|x| {
|
|
let content = read_file_tar(&pkg_file, &x, self.compression.clone()).unwrap();
|
|
(x, content)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
/// Save a new package to repository
|
|
pub fn save(&self, pkg: Vec<u8>, sig: Option<Vec<u8>>) {
|
|
let pkg_file = self.base_path().join(self.file_name());
|
|
let sig_file = self.base_path().join(format!("{}.sig", self.file_name()));
|
|
|
|
std::fs::write(&pkg_file, pkg).unwrap();
|
|
if let Some(sig) = sig {
|
|
std::fs::write(sig_file, sig).unwrap();
|
|
}
|
|
|
|
let db_file = PathBuf::from("./data")
|
|
.join(&self.repo)
|
|
.join(self.arch.to_string())
|
|
.join(format!("{}.db.tar.gz", self.repo));
|
|
|
|
repo_add(db_file.to_str().unwrap(), pkg_file.to_str().unwrap());
|
|
|
|
// Add to any arch repo db if `arch=any`
|
|
if self.arch == Architecture::any {
|
|
let archs = Repository::new(&self.repo).unwrap().arch();
|
|
|
|
for arch in archs {
|
|
if arch == Architecture::any {
|
|
continue;
|
|
}
|
|
|
|
let db_file = PathBuf::from("./data")
|
|
.join(&self.repo)
|
|
.join(arch.to_string())
|
|
.join(format!("{}.db.tar.gz", self.repo));
|
|
|
|
repo_add(db_file.to_str().unwrap(), pkg_file.to_str().unwrap());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn base_path(&self) -> PathBuf {
|
|
// <repo>/<arch>/<pkg>/
|
|
Path::new("./data")
|
|
.join(&self.repo)
|
|
.join(self.arch.to_string())
|
|
.join(&self.name)
|
|
}
|
|
|
|
/// Switch the `Architecture` of the package
|
|
pub fn switch_arch(&self, arch: Architecture) -> Self {
|
|
let mut new = self.clone();
|
|
new.arch = arch;
|
|
new
|
|
}
|
|
|
|
/// Switch to a specific `version` of the package
|
|
pub fn get_version(&self, version: &str) -> Self {
|
|
let mut new_pkg = self.clone();
|
|
new_pkg.version = Some(version.to_string());
|
|
new_pkg
|
|
}
|
|
|
|
/// Check if the specific package exists
|
|
pub fn exists(&self) -> bool {
|
|
let pkg_file = self.base_path().join(self.file_name());
|
|
pkg_file.exists()
|
|
}
|
|
|
|
/// Checks if the package has a signature
|
|
pub fn is_signed(&self) -> bool {
|
|
let signed_file = self.base_path().join(format!("{}.sig", self.file_name()));
|
|
signed_file.exists()
|
|
}
|
|
|
|
/// Build a pkg filename from the packages values
|
|
pub fn file_name(&self) -> String {
|
|
format!(
|
|
"{}-{}-{}-{}.pkg.tar.{}",
|
|
self.name,
|
|
if let Some(ver) = &self.version {
|
|
ver.to_string()
|
|
} else {
|
|
let versions = self.versions();
|
|
Package::version(&versions.first().unwrap().clone()).0
|
|
},
|
|
self.rel,
|
|
self.arch.to_string(),
|
|
self.compression.extension()
|
|
)
|
|
}
|
|
|
|
pub fn has_pkg_ext(filename: &str) -> bool {
|
|
filename.ends_with(".pkg.tar.zst") || filename.ends_with(".pkg.tar.xz")
|
|
}
|
|
|
|
/// Get all versions of the package
|
|
pub fn versions(&self) -> Vec<String> {
|
|
let dir_path = self.base_path();
|
|
let mut versions: Vec<String> = vec![];
|
|
|
|
if let Ok(entries) = std::fs::read_dir(dir_path) {
|
|
for entry in entries.filter_map(Result::ok) {
|
|
let file_name = entry.file_name().into_string().unwrap_or_default();
|
|
if file_name.starts_with(&self.name) && Package::has_pkg_ext(&file_name) {
|
|
let (_, version, rel, _, _) = Package::extract_pkg_name(&file_name).unwrap();
|
|
versions.push(format!("{version}-{rel}"));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort versions in descending order (most recent version first)
|
|
versions.sort_by(|a, b| b.cmp(a));
|
|
versions
|
|
}
|
|
|
|
pub fn pkg_content_path(&self) -> Option<String> {
|
|
if self.exists() {
|
|
return Some(
|
|
self.base_path()
|
|
.join(self.file_name())
|
|
.to_str()
|
|
.unwrap()
|
|
.to_string(),
|
|
);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Get the content of the `.pkg.tar.zst`
|
|
pub fn pkg_content(&self) -> Option<Vec<u8>> {
|
|
if self.exists() {
|
|
return std::fs::read(self.base_path().join(self.file_name())).ok();
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Get the content of the `.pkg.tar.zst.sig`
|
|
pub fn sig_content(&self) -> Option<Vec<u8>> {
|
|
if self.exists() {
|
|
return std::fs::read(self.base_path().join(format!("{}.sig", &self.file_name()))).ok();
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn run_command(cmd: Vec<&str>) {
|
|
std::process::Command::new(cmd.first().unwrap())
|
|
.args(cmd.into_iter().skip(1).collect::<Vec<&str>>())
|
|
.spawn()
|
|
.unwrap()
|
|
.wait()
|
|
.unwrap();
|
|
}
|
|
|
|
/// Add a package file to a repo db
|
|
pub fn repo_add(db_file: &str, pkg_file: &str) {
|
|
run_command(vec!["repo-add", db_file, pkg_file]);
|
|
}
|
|
|
|
pub fn read_file_tar(tar: &Path, file_path: &str, compression: Compression) -> Option<String> {
|
|
let mut output = Command::new("tar");
|
|
let output = match compression {
|
|
Compression::Zstd => output.arg("-xO").arg("--zstd"),
|
|
Compression::Xz => output.arg("-xOJ"),
|
|
};
|
|
|
|
let output = output.arg("-f").arg(tar).arg(file_path).output().ok()?;
|
|
|
|
if output.status.success() {
|
|
Some(String::from_utf8(output.stdout).ok()?.to_string())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn read_file_tar_raw(
|
|
tar_data: &[u8],
|
|
file_path: &str,
|
|
compression: Compression,
|
|
) -> Option<String> {
|
|
let mut output = Command::new("tar");
|
|
let output = match compression {
|
|
Compression::Zstd => output.arg("-xO").arg("--zstd"),
|
|
Compression::Xz => output.arg("xOJ"),
|
|
};
|
|
|
|
let mut output = output
|
|
.arg("-f")
|
|
.arg("-") // Indicate that the file input comes from stdin
|
|
.arg(file_path)
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.spawn()
|
|
.ok()?;
|
|
|
|
if let Some(stdin) = output.stdin.as_mut() {
|
|
stdin.write_all(tar_data).ok()?;
|
|
stdin.flush().ok()?;
|
|
}
|
|
|
|
let output = output.wait_with_output().ok()?;
|
|
|
|
if output.status.success() {
|
|
Some(String::from_utf8(output.stdout).ok()?.to_string())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn list_tar_file(tar: &Path) -> Option<Vec<String>> {
|
|
let output = Command::new("tar")
|
|
.arg("-tf") // List the contents of the tar file
|
|
.arg(tar)
|
|
.output()
|
|
.ok()?;
|
|
|
|
if output.status.success() {
|
|
let output_str = String::from_utf8(output.stdout).ok()?;
|
|
let paths = output_str
|
|
.lines()
|
|
.map(|line| line.to_string())
|
|
.collect::<Vec<String>>();
|
|
|
|
Some(paths)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, FromRow)]
|
|
pub struct PackageMeta {
|
|
pub repo: String,
|
|
pub name: String,
|
|
pub arch: String,
|
|
pub version: String,
|
|
pub rel: String,
|
|
pub download_count: i32,
|
|
}
|
|
|
|
pub trait PackageMetaInfo {
|
|
fn download_amount(&self) -> impl std::future::Future<Output = i32>;
|
|
fn increase_download_count(&self) -> impl std::future::Future<Output = ()>;
|
|
}
|
|
|
|
impl PackageMetaInfo for Package {
|
|
async fn download_amount(&self) -> i32 {
|
|
let res: Option<(i32,)> = sqlx::query_as("SELECT download_count FROM package_meta WHERE repo = $1 AND name = $2 AND version = $3 AND arch = $4 AND rel = $5")
|
|
.bind(&self.repo)
|
|
.bind(&self.name)
|
|
.bind(self.version.as_ref().unwrap())
|
|
.bind(&self.arch.to_string())
|
|
.bind(&self.rel)
|
|
.fetch_optional(get_pg!()).await.unwrap();
|
|
|
|
res.map(|x| x.0).unwrap_or(0)
|
|
}
|
|
|
|
async fn increase_download_count(&self) {
|
|
sqlx::query("INSERT INTO package_meta (repo, name, arch, version, rel) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (repo, name, arch, version, rel) DO UPDATE SET download_count = package_meta.download_count + 1")
|
|
.bind(&self.repo)
|
|
.bind(&self.name)
|
|
.bind(&self.arch.to_string())
|
|
.bind(&self.version.as_ref().map(|x| x.to_string()).unwrap_or_default())
|
|
.bind(&self.rel)
|
|
.execute(get_pg!()).await.unwrap();
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum Compression {
|
|
Zstd,
|
|
Xz,
|
|
}
|
|
|
|
impl Compression {
|
|
pub fn from_filename(name: &str) -> Option<Self> {
|
|
if name.ends_with("zst") {
|
|
Some(Self::Zstd)
|
|
} else if name.ends_with("xz") {
|
|
Some(Self::Xz)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn extension(&self) -> &str {
|
|
match self {
|
|
Compression::Zstd => "zst",
|
|
Compression::Xz => "xz",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Serialize)]
|
|
pub struct PackageInfo {
|
|
/// Architecture
|
|
pub architectures: Vec<Architecture>,
|
|
/// Build Timestamp
|
|
pub build_date: i64,
|
|
/// Licenses
|
|
pub licenses: Vec<String>,
|
|
/// Make Dependencies
|
|
pub makedepends: Vec<String>,
|
|
/// Packager
|
|
pub packager: Option<String>,
|
|
/// Package Base
|
|
pub pkgbase: String,
|
|
/// Description
|
|
pub description: String,
|
|
/// Package Name
|
|
pub name: String,
|
|
/// Version
|
|
pub version: String,
|
|
/// Size
|
|
pub size: i64,
|
|
/// Website
|
|
pub website: Option<String>,
|
|
/// XDATA
|
|
pub xdata: Option<String>,
|
|
/// Dependencies
|
|
pub dependencies: Vec<String>,
|
|
/// Optional Dependencies
|
|
pub optdepends: Vec<OptionalDependency>,
|
|
/// Provides
|
|
pub provides: Vec<String>,
|
|
// Replaces
|
|
pub replaces: Vec<String>,
|
|
/// Backup
|
|
pub backup: Vec<String>,
|
|
/// Conflicts
|
|
pub conflicts: Vec<String>,
|
|
/// Check Dependencies
|
|
pub checkdepends: Vec<String>,
|
|
/// Groups
|
|
pub groups: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Default, Serialize)]
|
|
pub struct OptionalDependency {
|
|
pub pkg: String,
|
|
pub reason: Option<String>,
|
|
}
|
|
|
|
impl OptionalDependency {
|
|
pub fn as_str(&self) -> String {
|
|
if let Some(reason) = &self.reason {
|
|
format!("{}: {}", self.pkg, reason)
|
|
} else {
|
|
self.pkg.clone()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PackageInfo {
|
|
pub fn new(keys: Vec<(String, String)>) -> Self {
|
|
let mut info = Self::default();
|
|
|
|
for (key, val) in keys {
|
|
match key.as_str() {
|
|
"arch" => {
|
|
info.architectures.push(Architecture::parse(&val).unwrap());
|
|
}
|
|
"builddate" => {
|
|
info.build_date = val.parse().unwrap();
|
|
}
|
|
"license" => {
|
|
let elements: Vec<_> = val.split(';').collect();
|
|
for e in elements {
|
|
let licenses: Vec<_> = e.split(" OR ").collect();
|
|
for e in licenses {
|
|
info.licenses.push(e.to_string());
|
|
}
|
|
}
|
|
}
|
|
"makedepend" => {
|
|
let pkgs: Vec<_> = val.split(';').collect();
|
|
for pkg in pkgs {
|
|
info.makedepends.push(pkg.to_string());
|
|
}
|
|
}
|
|
"packager" => {
|
|
info.packager = Some(val);
|
|
}
|
|
"pkgbase" => {
|
|
info.pkgbase = val;
|
|
}
|
|
"pkgdesc" => {
|
|
info.description = val;
|
|
}
|
|
"pkgname" => {
|
|
info.name = val;
|
|
}
|
|
"pkgver" => {
|
|
info.version = val;
|
|
}
|
|
"size" => {
|
|
info.size = val.parse().unwrap();
|
|
}
|
|
"url" => {
|
|
info.website = Some(val);
|
|
}
|
|
"xdata" => {
|
|
info.xdata = Some(val);
|
|
}
|
|
"depend" => {
|
|
let pkgs: Vec<_> = val.split(';').collect();
|
|
for pkg in pkgs {
|
|
info.dependencies.push(pkg.to_string());
|
|
}
|
|
}
|
|
"optdepend" => {
|
|
let pkgs: Vec<_> = val.split(';').collect();
|
|
for pkg in pkgs {
|
|
let (pkg, reason) = if let Some((pkg, reason)) = pkg.split_once(':') {
|
|
let reason = if !reason.is_empty() {
|
|
Some(reason.trim().to_string())
|
|
} else {
|
|
None
|
|
};
|
|
(pkg, reason)
|
|
} else {
|
|
(pkg, None)
|
|
};
|
|
|
|
info.optdepends.push(OptionalDependency {
|
|
pkg: pkg.to_string(),
|
|
reason: reason,
|
|
});
|
|
}
|
|
}
|
|
"provides" => {
|
|
let provides: Vec<_> = val.split(';').collect();
|
|
for p in provides {
|
|
info.provides.push(p.to_string());
|
|
}
|
|
}
|
|
"replaces" => {
|
|
let replaces: Vec<_> = val.split(';').collect();
|
|
for r in replaces {
|
|
info.replaces.push(r.to_string());
|
|
}
|
|
}
|
|
"backup" => {
|
|
let backups: Vec<_> = val.split(';').collect();
|
|
for b in backups {
|
|
info.backup.push(b.to_string());
|
|
}
|
|
}
|
|
"conflict" => {
|
|
let conflicts: Vec<_> = val.split(';').collect();
|
|
for e in conflicts {
|
|
info.conflicts.push(e.to_string());
|
|
}
|
|
}
|
|
"checkdepend" => {
|
|
let pkgs: Vec<_> = val.split(';').collect();
|
|
for pkg in pkgs {
|
|
info.checkdepends.push(pkg.to_string());
|
|
}
|
|
}
|
|
"group" => {
|
|
let groups: Vec<_> = val.split(';').collect();
|
|
for group in groups {
|
|
info.groups.push(group.to_string());
|
|
}
|
|
}
|
|
_ => {
|
|
println!("Unrecognized key {key} -> {val}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
info
|
|
}
|
|
|
|
pub fn gen_array(key: &str, vals: &[String]) -> String {
|
|
if vals.is_empty() {
|
|
return String::new();
|
|
}
|
|
|
|
let mut ret = format!("{key}=(");
|
|
|
|
for (index, val) in vals.iter().enumerate() {
|
|
if index == vals.len() - 1 {
|
|
ret.push_str(&format!("'{val}'"));
|
|
} else {
|
|
ret.push_str(&format!("'{val}' "));
|
|
}
|
|
}
|
|
|
|
ret.push_str(")\n");
|
|
ret
|
|
}
|
|
|
|
/// Generate a basic `PKGBUILD` from `PKGINFO`
|
|
pub fn pkgbuild(&self) -> String {
|
|
#[allow(non_snake_case)]
|
|
let mut PKGBUILD = String::new();
|
|
|
|
if let Some(packager) = &self.packager {
|
|
PKGBUILD.push_str(&format!("# Packager: {packager}\n\n"));
|
|
}
|
|
|
|
PKGBUILD.push_str(&format!("pkgbase={}\n", self.pkgbase));
|
|
PKGBUILD.push_str(&format!("pkgname={}\n", self.name));
|
|
PKGBUILD.push_str(&format!("pkgdesc={}\n", self.description));
|
|
PKGBUILD.push_str(&format!("pkgver={}\n", self.version));
|
|
|
|
let arch: Vec<_> = self.architectures.iter().map(|x| x.to_string()).collect();
|
|
PKGBUILD.push_str(&PackageInfo::gen_array("arch", &arch));
|
|
|
|
PKGBUILD.push_str(&PackageInfo::gen_array("license", &self.licenses));
|
|
|
|
PKGBUILD.push_str(&PackageInfo::gen_array("makedepends", &self.makedepends));
|
|
PKGBUILD.push_str(&PackageInfo::gen_array("depends", &self.dependencies));
|
|
PKGBUILD.push_str(&PackageInfo::gen_array("checkdepends", &self.checkdepends));
|
|
PKGBUILD.push_str(&PackageInfo::gen_array(
|
|
"optdepends",
|
|
&self
|
|
.optdepends
|
|
.iter()
|
|
.map(|x| x.as_str())
|
|
.collect::<Vec<_>>(),
|
|
));
|
|
|
|
if let Some(website) = &self.website {
|
|
PKGBUILD.push_str(&format!("url={}\n", website));
|
|
}
|
|
|
|
PKGBUILD.push_str(&PackageInfo::gen_array("provides", &self.provides));
|
|
|
|
PKGBUILD.push_str(&PackageInfo::gen_array("replaces", &self.replaces));
|
|
|
|
PKGBUILD.push_str(&PackageInfo::gen_array("backup", &self.backup));
|
|
|
|
PKGBUILD.push_str(&PackageInfo::gen_array("conflicts", &self.conflicts));
|
|
|
|
PKGBUILD.push_str(&PackageInfo::gen_array("groups", &self.groups));
|
|
|
|
PKGBUILD.push_str("\npackage() {\n\trsync -avzhruP ../root/ \"$pkdir/\"\n}");
|
|
|
|
PKGBUILD
|
|
}
|
|
}
|