feat: add support for abstract packages

Signed-off-by: sisungo <[email protected]>
This commit is contained in:
2026-06-26 13:22:01 +08:00
parent 1c8aba9d56
commit 4f0f8e9320
10 changed files with 261 additions and 91 deletions
+2
View File
@@ -76,7 +76,9 @@ pub fn main(cli: Cli) -> anyhow::Result<()> {
},
)?;
package_files.push(path);
progress_bar.set_position(init_pos + package.repo_pkg.download_size);
}
drop(progress_bar);
let progress_bar = ProgressBar::new(package_files.len() as _);
for package in package_files.iter() {
+23
View File
@@ -0,0 +1,23 @@
#[macro_export]
macro_rules! impl_serde_str {
($t:ty) => {
impl serde::Serialize for $t {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> serde::Deserialize<'de> for $t {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(|err| serde::de::Error::custom(err))
}
}
};
}
+1
View File
@@ -1,5 +1,6 @@
pub mod checksum;
pub mod fs;
pub mod macros;
pub use fs::*;
+7
View File
@@ -128,6 +128,13 @@ impl super::Packie {
.insert_installation(&installation)
.map_err(InstallError::Database)?;
// Record abstract package provision in the database
for i in installation.pkg_manifest.provides.iter() {
self.local_db
.insert_abspkg(&i, &installation.pkg_manifest.pkg_ident())
.map_err(InstallError::Database)?;
}
// Enable default links
for link in pkg_manifest.links.iter() {
if !link.default {
+84 -18
View File
@@ -1,4 +1,7 @@
use crate::package::{PkgIdent, PkgManifest, PkgSpec};
use crate::{
package::{AbsPkgIdent, PkgIdent, PkgManifest, PkgSpec},
version::VersionFilter,
};
use rusqlite::{OpenFlags, Row, params};
use std::path::{Path, PathBuf};
@@ -7,12 +10,25 @@ impl super::Packie {
&mut self,
pkgspec: &PkgSpec,
) -> rusqlite::Result<Vec<Installation>> {
Ok(self
let mut real = self
.local_db
.select_installation_by_pkgname(&pkgspec.name)?
.select_installation_by_name(&pkgspec.name)?
.into_iter()
.filter(|x| pkgspec.matches(&x.pkg_manifest.pkg_ident()))
.collect())
.collect::<Vec<_>>();
if !matches!(pkgspec.version, VersionFilter::Any) {
return Ok(real);
}
let abspkg = AbsPkgIdent {
name: pkgspec.name.clone(),
arch: pkgspec.arch.clone(),
};
let abs = self.local_db.select_abspkg_by_ident(&abspkg)?;
for (_, provider) in abs {
real.push(self.local_db.select_installation_by_ident(&provider)?);
}
real.dedup_by_key(|x| x.pkg_manifest.pkg_ident());
Ok(real)
}
}
@@ -45,17 +61,22 @@ impl LocalDb {
pub const FILENAME: &str = "local.db";
pub const SCHEMA: &str = r#"
CREATE TABLE IF NOT EXISTS "installation"(
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"pkg_name" TEXT NOT NULL,
"pkg_version" TEXT NOT NULL,
"pkg_arch" TEXT NOT NULL,
"pkg_manifest" TEXT NOT NULL,
"install_pkgspec" TEXT NOT NULL,
"install_date" INTEGER NOT NULL,
"update_date" INTEGER NOT NULL,
"installed_size" INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS "installation"(
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"pkg_name" TEXT NOT NULL,
"pkg_version" TEXT NOT NULL,
"pkg_arch" TEXT NOT NULL,
"pkg_manifest" TEXT NOT NULL,
"install_pkgspec" TEXT NOT NULL,
"install_date" INTEGER NOT NULL,
"update_date" INTEGER NOT NULL,
"installed_size" INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS "abspkg"(
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"abspkg_ident" TEXT NOT NULL,
"provider" TEXT NOT NULL
);
"#;
pub fn open_ro<P: AsRef<Path>>(path: P) -> rusqlite::Result<Self> {
@@ -94,7 +115,18 @@ impl LocalDb {
Ok(())
}
pub fn select_installation_by_pkgname(
pub fn insert_abspkg(
&mut self,
abspkg: &AbsPkgIdent,
provider: &PkgIdent,
) -> rusqlite::Result<()> {
self.0
.prepare_cached(r#"INSERT INTO "abspkg"("abspkg_ident", "provider") VALUES(?1, ?2)"#)?
.execute(params![abspkg.to_string(), provider.to_string(),])?;
Ok(())
}
pub fn select_installation_by_name(
&mut self,
pkg_name: &str,
) -> rusqlite::Result<Vec<Installation>> {
@@ -109,7 +141,7 @@ impl LocalDb {
Ok(ret)
}
pub fn select_installation_unique(
pub fn select_installation_by_ident(
&mut self,
pkg_ident: &PkgIdent,
) -> rusqlite::Result<Installation> {
@@ -118,13 +150,35 @@ impl LocalDb {
"#)?.query_one(params![pkg_ident.name, pkg_ident.version.to_string(), pkg_ident.arch], map_installation)
}
pub fn select_abspkg_by_ident(
&mut self,
ident: &AbsPkgIdent,
) -> rusqlite::Result<Vec<(AbsPkgIdent, PkgIdent)>> {
let mut stmt = self
.0
.prepare_cached(r#"SELECT * FROM "abspkg" WHERE "abspkg_ident" = ?1"#)?;
let rows = stmt.query_map(params![ident.to_string()], map_abspkg)?;
let mut abspkg = Vec::new();
for i in rows {
abspkg.push(i?);
}
Ok(abspkg)
}
pub fn remove_installation(&mut self, pkg_ident: &PkgIdent) -> rusqlite::Result<bool> {
self
.0
.prepare_cached(r#"DELETE FROM "installation" WHERE pkg_name = ?1 AND pkg_version = ?2 AND pkg_arch = ?3"#)?
.prepare_cached(r#"DELETE FROM "installation" WHERE "pkg_name" = ?1 AND "pkg_version" = ?2 AND "pkg_arch" = ?3"#)?
.execute(params![pkg_ident.name, pkg_ident.version.to_string(), pkg_ident.arch])
.map(|x| x > 0)
}
pub fn remove_abspkg_by_provider(&mut self, provider: &PkgIdent) -> rusqlite::Result<bool> {
self.0
.prepare_cached(r#"DELETE FROM "abspkg" WHERE "provider" = ?1"#)?
.execute(params![provider.to_string()])
.map(|x| x > 0)
}
}
#[derive(Debug, Clone)]
@@ -150,6 +204,18 @@ fn map_installation(row: &Row) -> rusqlite::Result<Installation> {
})
}
fn map_abspkg(row: &Row) -> rusqlite::Result<(AbsPkgIdent, PkgIdent)> {
let provider = row
.get::<_, String>("provider")?
.parse()
.map_err(from_sql_error)?;
let abspkg_name = row
.get::<_, String>("abspkg_ident")?
.parse()
.map_err(from_sql_error)?;
Ok((abspkg_name, provider))
}
fn from_sql_error<E: std::error::Error + Send + Sync + 'static>(error: E) -> rusqlite::Error {
rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(error))
}
+62 -21
View File
@@ -83,9 +83,9 @@ pub struct PkgManifest {
#[serde(default)]
pub dependencies: Vec<PkgSpec>,
/// Package provisions.
/// Provided abstract packages.
#[serde(default)]
pub provides: Vec<String>,
pub provides: Vec<AbsPkgIdent>,
/// Links.
#[serde(default)]
@@ -205,27 +205,10 @@ impl Display for PkgSpec {
Ok(())
}
}
impl Serialize for PkgSpec {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.to_string().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for PkgSpec {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse::<Self>()
.map_err(|e| serde::de::Error::custom(e))
}
}
crate::impl_serde_str!(PkgSpec);
/// A package identifier that specifies a unique package.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PkgIdent {
pub name: String,
pub version: Version,
@@ -236,3 +219,61 @@ impl Display for PkgIdent {
write!(f, "{}={}@{}", self.name, self.version, self.arch)
}
}
impl FromStr for PkgIdent {
type Err = crate::version::VersionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut name = String::new();
let mut version = String::new();
let mut arch = String::new();
let mut handling = "name";
for c in s.chars() {
if c == '=' {
handling = "version";
} else if c == '@' {
handling = "arch";
} else if handling == "name" {
name.push(c);
} else if handling == "version" {
version.push(c);
} else if handling == "arch" {
arch.push(c);
}
}
Ok(Self {
name: name.trim().into(),
version: version.trim().parse()?,
arch: arch.trim().into(),
})
}
}
crate::impl_serde_str!(PkgIdent);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AbsPkgIdent {
pub name: String,
pub arch: Option<String>,
}
impl Display for AbsPkgIdent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.arch {
Some(arch) => write!(f, "{}@{}", self.name, arch),
None => write!(f, "{}", self.name),
}
}
}
impl FromStr for AbsPkgIdent {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (name, arch) = s
.split_once('@')
.map(|(a, b)| (a, Some(b)))
.unwrap_or((s, None));
Ok(Self {
name: name.into(),
arch: arch.map(Into::into),
})
}
}
crate::impl_serde_str!(AbsPkgIdent);
+3
View File
@@ -32,6 +32,9 @@ impl super::Packie {
self.local_db
.remove_installation(&found.pkg_manifest.pkg_ident())
.map_err(RemoveError::Database)?;
self.local_db
.remove_abspkg_by_provider(&found.pkg_manifest.pkg_ident())
.map_err(RemoveError::Database)?;
Ok(())
}
+72 -26
View File
@@ -10,7 +10,7 @@ pub use serve::RepoServeDir;
use crate::{
Packie,
package::{PkgIdent, PkgManifest, PkgSpec},
package::{AbsPkgIdent, PkgIdent, PkgManifest, PkgSpec},
};
use download::Download;
use rusqlite::{OpenFlags, Row, params};
@@ -43,7 +43,7 @@ impl super::Packie {
let repo_db = repo_cache_dir.join(RepoDb::FILENAME);
let mut repo_db = RepoDb::open_ro(repo_db).map_err(RepoError::Database)?;
let repo_package = repo_db
.select_package_by_pkgident(pkg_ident)
.select_package_by_ident(pkg_ident)
.map_err(|_| RepoError::NoSuchPackage)?;
let standard_filename = format!("{pkg_ident}.pkg");
@@ -96,17 +96,22 @@ impl RepoDb {
const FILENAME: &str = "repo.db";
const SCHEMA: &str = r#"
CREATE TABLE IF NOT EXISTS "package"(
"pkg_name" TEXT NOT NULL,
"pkg_version" TEXT NOT NULL,
"pkg_arch" TEXT NOT NULL,
"pkg_manifest" TEXT NOT NULL,
"download_size" INTEGER NOT NULL,
"installed_size" INTEGER NOT NULL,
"checksum" TEXT NOT NULL,
PRIMARY KEY ("pkg_name", "pkg_version", "pkg_arch")
);
"#;
CREATE TABLE IF NOT EXISTS "package"(
"pkg_name" TEXT NOT NULL,
"pkg_version" TEXT NOT NULL,
"pkg_arch" TEXT NOT NULL,
"pkg_manifest" TEXT NOT NULL,
"download_size" INTEGER NOT NULL,
"installed_size" INTEGER NOT NULL,
"checksum" TEXT NOT NULL,
PRIMARY KEY ("pkg_name", "pkg_version", "pkg_arch")
);
CREATE TABLE IF NOT EXISTS "abspkg"(
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"abspkg_ident" TEXT NOT NULL,
"provider" TEXT NOT NULL
);
"#;
pub fn open_rw<P: AsRef<Path>>(path: P) -> rusqlite::Result<Self> {
let conn = rusqlite::Connection::open(path)?;
@@ -118,7 +123,7 @@ impl RepoDb {
rusqlite::Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_ONLY).map(Self)
}
pub fn select_package_by_pkgident(
pub fn select_package_by_ident(
&mut self,
pkg_ident: &PkgIdent,
) -> rusqlite::Result<RepoPackage> {
@@ -127,10 +132,7 @@ impl RepoDb {
)?.query_one(params![pkg_ident.name, pkg_ident.version.to_string(), pkg_ident.arch], map_repo_package)
}
pub fn select_package_by_pkgname(
&mut self,
pkgname: &str,
) -> rusqlite::Result<Vec<RepoPackage>> {
pub fn select_package_by_name(&mut self, pkgname: &str) -> rusqlite::Result<Vec<RepoPackage>> {
let mut stmt = self
.0
.prepare_cached(r#"SELECT * FROM "package" WHERE "pkg_name" = ?1"#)?;
@@ -142,6 +144,21 @@ impl RepoDb {
Ok(ret)
}
pub fn select_abspkg_by_ident(
&mut self,
ident: &AbsPkgIdent,
) -> rusqlite::Result<Vec<(AbsPkgIdent, PkgIdent)>> {
let mut stmt = self
.0
.prepare_cached(r#"SELECT * FROM "abspkg" WHERE "abspkg_ident" = ?1"#)?;
let rows = stmt.query_map(params![ident.to_string()], map_abspkg)?;
let mut ret = Vec::with_capacity(16);
for row in rows {
ret.push(row?);
}
Ok(ret)
}
pub fn for_each_package<E: From<rusqlite::Error>>(
&mut self,
mut f: impl FnMut(RepoPackage) -> Result<(), E>,
@@ -178,13 +195,18 @@ impl RepoDb {
])
.map(|_| ())
}
pub fn insert_abspkg(&mut self, abs: &AbsPkgIdent, real: &PkgIdent) -> rusqlite::Result<()> {
self.0
.prepare_cached(r#"INSERT INTO "abspkg"("abspkg_ident", "provider") VALUES(?1, ?2)"#)?
.execute(params![abs.to_string(), real.to_string()])?;
Ok(())
}
}
fn map_repo_package(row: &Row) -> rusqlite::Result<RepoPackage> {
let pkg_manifest: String = row.get("pkg_manifest")?;
let pkg_manifest = serde_json::from_str(&pkg_manifest).map_err(|error| {
rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(error))
})?;
let pkg_manifest = serde_json::from_str(&pkg_manifest).map_err(from_sql_error)?;
Ok(RepoPackage {
pkg_manifest,
download_size: row.get::<_, i64>("download_size")? as u64,
@@ -193,6 +215,22 @@ fn map_repo_package(row: &Row) -> rusqlite::Result<RepoPackage> {
})
}
fn map_abspkg(row: &Row) -> rusqlite::Result<(AbsPkgIdent, PkgIdent)> {
let provider = row
.get::<_, String>("provider")?
.parse()
.map_err(from_sql_error)?;
let abspkg_name = row
.get::<_, String>("abspkg_ident")?
.parse()
.map_err(from_sql_error)?;
Ok((abspkg_name, provider))
}
fn from_sql_error<E: std::error::Error + Send + Sync + 'static>(error: E) -> rusqlite::Error {
rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(error))
}
/// A record representing to a package in a repository.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RepoPackage {
@@ -333,17 +371,25 @@ impl QuerySession {
}
pub fn search_package(&mut self, pkgspec: &PkgSpec) -> Vec<(String, RepoPackage)> {
let mut all = Vec::new();
let mut all: Vec<(String, RepoPackage)> = Vec::new();
for (name, db) in self.repos.iter_mut() {
for pkg in db
.select_package_by_pkgname(&pkgspec.name)
.unwrap_or_default()
{
for pkg in db.select_package_by_name(&pkgspec.name).unwrap_or_default() {
if pkgspec.matches(&pkg.pkg_manifest.pkg_ident()) {
all.push((name.into(), pkg));
}
}
let abspkg_ident = AbsPkgIdent {
name: pkgspec.name.clone(),
arch: pkgspec.arch.clone(),
};
for (_, provider) in db.select_abspkg_by_ident(&abspkg_ident).unwrap_or_default() {
let Ok(real) = db.select_package_by_ident(&provider) else {
continue;
};
all.push((name.into(), real));
}
}
all.dedup_by_key(|(repo, pkg)| (repo.to_string(), pkg.pkg_manifest.pkg_ident()));
all
}
}
+4
View File
@@ -85,6 +85,10 @@ impl RepoServeDir {
std::fs::copy(path, self.path.join(standard_filename))?;
self.db.insert_package(&repo_package)?;
for abs in repo_package.pkg_manifest.provides.iter() {
self.db
.insert_abspkg(abs, &repo_package.pkg_manifest.pkg_ident())?;
}
Ok(())
}
}
+3 -26
View File
@@ -2,30 +2,7 @@
use std::{cmp::Ordering, fmt::Display, str::FromStr};
macro_rules! impl_serde_str {
($t:ty) => {
impl serde::Serialize for $t {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> serde::Deserialize<'de> for $t {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(|err| serde::de::Error::custom(err))
}
}
};
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Version {
/// Version namespace. By default, the value is `0`. The value needs to be changed if the versioning
/// rule has an incompatible change.
@@ -121,7 +98,7 @@ impl Display for Version {
Ok(())
}
}
impl_serde_str!(Version);
crate::impl_serde_str!(Version);
impl Version {}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -229,7 +206,7 @@ impl Display for VersionFilter {
}
}
}
impl_serde_str!(VersionFilter);
crate::impl_serde_str!(VersionFilter);
/// An error caused by dealing with version or version filter strings.
#[derive(Debug, Clone, thiserror::Error)]